ANTICHAT.XYZ    VIDEO.ANTICHAT.XYZ    НОВЫЕ СООБЩЕНИЯ    ФОРУМ  
Баннер 1   Баннер 2
Antichat снова доступен.
Форум Antichat (Античат) возвращается и снова открыт для пользователей. Здесь обсуждаются безопасность, программирование, технологии и многое другое. Сообщество снова собирается вместе.
Новый адрес: forum.antichat.xyz
Вернуться   Форум АНТИЧАТ > Программирование > С/С++, C#, Delphi, .NET, Asm
   
Ответ
 
Опции темы Поиск в этой теме Опции просмотра

ОС с нуля
  #1  
Старый 28.04.2008, 01:33
Аватар для z01b
z01b
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме:
2360904

Репутация: 1393


По умолчанию ОС с нуля

Оглавление

[01] - введение / основные сведения о ядре
[02] - организация работы с памятью
[03] - этапы загрузки различных ОС
[04] - создание bootsector'а
[05] - основы защищенного режима
[06] - шлюзы / виртуальный режим процессора 8086
[07] - исключения защищенного режима / микроядерные системы
[08] - файловые системы
[09] - чтение ext2fs
[10] - форматы файлов ELF и PE
[11] - процесс загрузки
[12] - определение количества памяти


***********************************************

Последний раз редактировалось z01b; 29.04.2008 в 14:22..
 
Ответить с цитированием

  #2  
Старый 28.04.2008, 01:34
Аватар для z01b
z01b
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме:
2360904

Репутация: 1393


По умолчанию

[01] - введение / основные сведения о ядре

И начнем мы наше дело с написания ядра. Ядро, которое мы напишем, будет ориентированно на UNIX-подобные операционные системы. Чтобы нам было проще, мы с самого начала будем стремиться к совместимости с существующими системами.

Задача наша будет состоять в следующем:
Сделать, по возможности, компактное, надежное и быстрое ядро, с максимальным эффектом используя возможности процессора. Писать будем в основном на Ассемблере.


Для начала разберемся, как устроены системы.


Ядро состоит из следующих компонентов:

1. "Собственно ядро"
2. Драйвера устройств
3. Системные вызовы

В зависимости от организации внутренних взаимодействий, ядра подразделяются на "микроядра" (microkernel) и монолитные ядра.
Системы с "микроядром" строятся по модульному принципу, имеют обособленное ядро, и механизм взаимодействия между драйверами устройств и процессами. По такому принципу строятся системы реального времени. Примерно так сделан QNX или HURD.
Монолитное ядро имеет более жесткую внутреннюю структуру. Все установленные драйвера жестко связываются между собой, обычно прямыми вызовами. По таким принципам строятся обыкновенные операционные системы типа Linux, FreeBSD.
Естественно, не все так четко, идеального монолитного или "микроядра" нет, наверное, ни в одной системе, просто системы приближаются к тому или иному типу ядра.

Мне бы очень хотелось, чтобы то, что мы будем делать, больше походило на первый тип ядер.


Немного углублюсь в аппаратные возможности компьютеров.

Один, отдельно взятый, процессор, в один момент времени, может исполнять только одну программу. Но к компьютерам предъявляются более широкие требования. Мало кто, в настоящее время, удовлетворился однозадачной операционной системой (к каким относился DOS, например). В связи с этим разработчики процессоров предусмотрели мультизадачные возможности.
Возможность эта заключается в том, что процессор выполняет какую-то одну программу (их еще называют процессами или задачами). Затем, по истечении некоторого времени (обычно это время меряется микросекундами), операционная система переключает процессор на другую программу. При этом все регистры текущей программы сохраняются. Это необходимо для того, чтобы через некоторое время вновь передать управление этой программе. Программа при этом не замечает каких либо изменений, для нее процесс переключения остается незаметен.

Для того чтобы программа не могла, каким либо образом, нарушить работоспособность системы или других программ, разработчики процессоров предусмотрели механизмы защиты.
Процессор предоставляет 4 "кольца защиты" (уровня привилегий), можно было бы использовать все, но это связано со сложностями взаимодействия программ разного уровня защиты. Поэтому в большинстве существующих систем используют два уровня. 0 - привилегированный уровень (ядро) и 3 - непривилегированный (пользовательские программы).

Всем этим обеспечивается надежное функционирование системы и независимость программ друг от друга.


Теперь немного поподробнее про устройство ядра.


На "Собственно ядро" возлагаются функции менеджера памяти и процессов. Переключение процессов - это основной момент нормального функционирования системы. Драйвера не должны "тормозить", а тем более блокировать работу ядра. Windows - наглядный пример того, что этого нельзя допустить!

Теперь о драйверах. Драйвера - это специальные программы, обеспечивающие работу устройств компьютера. В существующих системах (во FreeBSD это точно есть, про Linux не уверен) предусматриваются механизмы прерывания работы драйверов по истечении какого-то времени. Правда, все зависит от того, как написан драйвер. Можно написать драйвер под FreeBSD или Linux, который полностью блокирует работу системы.
Избежать этого при двухуровневой защите не представляется возможным, поэтому драйвера надо будет тщательно программировать. В нашей работе драйверам мы уделим очень много внимания, поскольку от этого в основном зависит общая производительность системы.

Системные вызовы - это интерфейс между процессами и ядром (читайте-железом). Никаких других методов взаимодействия процессов с устройствами компьютера быть не должно. Системных вызовов достаточно много, на Linux их 190, на FreeBSD их порядка 350, причем большей частью они совпадают, соответствуя стандарту POSIX (стандарт, описывающий системные вызовы в UNIX). Разница заключается в передаче параметров, что легко будет предусмотреть. Естественно, мы не сможем сделать ядро, работающее одновременно на Linux и на FreeBSD, но по отдельности совместимость вполне реализуема.

Прикладным программам абсолютно безразлично, как системные вызовы реализуются в ядре. Это облегчает для нас обеспечение совместимости с существующими системами.

В следующем выпуске мы поговорим про защищенный режим процессора, распределение памяти, менеджер задач и рассмотрим, как это сделано в существующих системах.

Последний раз редактировалось z01b; 29.04.2008 в 14:22..
 
Ответить с цитированием

  #3  
Старый 28.04.2008, 01:41
Аватар для z01b
z01b
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме:
2360904

Репутация: 1393


По умолчанию

[02] - организация работы с памятью

Как процессор работает с памятью?

Для начала небольшое предисловие.
В процессорах имеются базовые регистры, которые могут задавать смещение. На 16-битной архитектуре максимальное смещение могло быть до 64 килобайт, что, в общем-то, не много и вызывало определенные трудности (разные модели памяти, разные форматы файлов). Так же, в 16-битной архитектуре присутствовали сегментные регистры, которые указывали адрес сегмента в памяти. В процессорах, начиная с i386, базовые регистры стали 32-х битными, что позволяет адресовать до 4 гигабайт. Сегментные регистры остались 16-битными, и в защищенном режиме они не содержат адреса! они содержат индекс дескриптора. В реальном режиме сегментные регистры работают так же, как и на 16-битных процессорах.

В реальном режиме сегментные регистры непосредственно указывают на адрес начала сегмента в памяти. Это позволяет нам, без каких либо преград, адресовать 1 мегабайт памяти. Но создает определенные трудности для защиты. А защищать нам нужно многое. Например, мы не можем пользовательским программам дать возможность непосредственно обращаться к коду или данным ядра. Так же мы не можем дать возможность пользовательским программам обращаться к коду или данным других пользовательских программ, поскольку это может нарушить их работоспособность.
Для этого был изобретен защищенный режим работы процессора, который появился в процессорах i286.

Защищенность этого режима заключается в следующем:
Сегментный регистр больше не указывает на адрес в памяти. В этом регистре теперь задается индекс в таблице дескрипторов.
Таблица дескрипторов может быть глобальная или локальная (применяется в многозадачных системах для изоляции адресного пространства задач) и представляет собой массив записей, по 8 байт в каждой, где описываются адреса, пределы и права доступа к сегментам.
Про адрес ничего не буду говорить, и так все ясно. Что такое предел? В этом Поле описывается размер сегмента. При обращении за пределы сегмента процессор генерирует исключение (специальное прерывание защищенного режима). Так же исключение генерируется в случае нарушения прав доступа к сегменту. Поле прав доступа описывает возможность чтения/записи сегмента, возможность выполнения кода сегмента, уровень привилегий для доступа к сегменту.

При обращении к сегменту из дескриптора берется базовый адрес сегмента и складывается со смещением сегмента. Так получается линейный 32-х разрядный (в i286 - 24-х разрядный) адрес. Для i286 на этом процесс получения адреса завершается, линейный адрес там равен физическому. Для i386 или выше это справедливо не всегда.


Страничная организация памяти.

В процессорах, начиная с i386, появилась, так называемая, страничная организация памяти. Страница имеет размер 4 килобайта или 4 мегабайта. Большие страницы могут быть только в pentium или выше. Не знаю только, какой толк от таких страниц.
Если возможность страничной адресации не используется, то линейный адрес, как и на i286, равен физическому. Если используется - то линейный адрес разбивается на три части. Первая, 10-битная, часть адреса является индексом в каталоге страниц, который адресуется системным регистром CR3. Запись в каталоге страниц указывает адрес таблицы страниц. Вторая, 10-битная, часть адреса является индексом в таблице страниц. Запись в таблице страниц указывает физический адрес нахождения страницы в памяти. последние 12 бит адреса указывают смещение в этой странице.
В страничных записях, как и в дескрипторных записях, есть служебные биты, описывающие права доступа, и некоторые другие тонкости страниц. Одной из важных тонкостей является бит присутствия страницы в памяти. В случае не присутствия страницы, процессор генерирует исключение, в котором можно считать данную страницу из файла или из swap раздела. Это сильно облегчает реализацию виртуальной памяти. Чуть ниже мы про это поговорим.

Для начала небольшое предисловие.
В процессорах имеются базовые регистры, которые могут задавать смещение. На 16-битной архитектуре максимальное смещение могло быть до 64 килобайт, что, в общем-то, не много и вызывало определенные трудности (разные модели памяти, разные форматы файлов). Так же, в 16-битной архитектуре присутствовали сегментные регистры, которые указывали адрес сегмента в памяти. В процессорах, начиная с i386, базовые регистры стали 32-х битными, что позволяет адресовать до 4 гигабайт. Сегментные регистры остались 16-битными, и в защищенном режиме они не содержат адреса! они содержат индекс дескриптора. В реальном режиме сегментные регистры работают так же, как и на 16-битных процессорах.

В реальном режиме сегментные регистры непосредственно указывают на адрес начала сегмента в памяти. Это позволяет нам, без каких либо преград, адресовать 1 мегабайт памяти. Но создает определенные трудности для защиты. А защищать нам нужно многое. Например, мы не можем пользовательским программам дать возможность непосредственно обращаться к коду или данным ядра. Так же мы не можем дать возможность пользовательским программам обращаться к коду или данным других пользовательских программ, поскольку это может нарушить их работоспособность.
Для этого был изобретен защищенный режим работы процессора, который появился в процессорах i286.

Защищенность этого режима заключается в следующем:
Сегментный регистр больше не указывает на адрес в памяти. В этом регистре теперь задается индекс в таблице дескрипторов.
Таблица дескрипторов может быть глобальная или локальная (применяется в многозадачных системах для изоляции адресного пространства задач) и представляет собой массив записей, по 8 байт в каждой, где описываются адреса, пределы и права доступа к сегментам.
Про адрес ничего не буду говорить, и так все ясно. Что такое предел? В этом Поле описывается размер сегмента. При обращении за пределы сегмента процессор генерирует исключение (специальное прерывание защищенного режима). Так же исключение генерируется в случае нарушения прав доступа к сегменту. Поле прав доступа описывает возможность чтения/записи сегмента, возможность выполнения кода сегмента, уровень привилегий для доступа к сегменту.

При обращении к сегменту из дескриптора берется базовый адрес сегмента и складывается со смещением сегмента. Так получается линейный 32-х разрядный (в i286 - 24-х разрядный) адрес. Для i286 на этом процесс получения адреса завершается, линейный адрес там равен физическому. Для i386 или выше это справедливо не всегда.


Многозадачность.

Многозадачные возможности в процессорах так же появились в процессорах, начиная с i286. Для реализации этого, процессор для каждой задачи использует, так называемый, "сегмент состояния задачи" ("Task State Segment", сокращенно TSS). В этом сегменте, при переключении задач, сохраняются все базовые регистры процессора, сегменты и указатели стека для трех уровней защиты (для каждого уровня используется свой стек), сегментный адрес локальной таблицы дескрипторов ("Local descriptor table", сокращенно LDT). В процессорах, начиная с i386, там еще хранится адрес каталога страниц (регистр CR3). Так же этот сегмент обеспечивает некоторые другие механизмы защиты, но о них мы пока не будем говорить.

Операционная система может расширить TSS, и использовать его для хранения регистров и состояния сопроцессора. Процессор при переключении задач не сохраняет этого. Так же возможны другие применения.



Что из всего этого следует?


В своей работе мы не будем ориентироваться на процессор i286, поскольку 16-битная архитектура и отсутствие механизма страничного преобразования сильно усложняет программирование операционной системы. К тому же, таких процессоров давно уже никто не использует.

Ориентироваться мы будем на i386 или более старшие модели процессоров, вплоть до последних.

Ядро системы при распределении памяти оперирует 4-х килобайтными страницами.
Страницы могут использоваться самим ядром, для нужд драйверов (кэширование, например), или для процессов.

Программа или процесс состоит из следующих частей:

* Сегмент кода. Может только выполняться, сама программа его не прочитать, не переписать не может! Использовать для этого сегмента swap не нужно, при необходимости код считывается прямо из файла;
* Сегмент данных состоит из трех частей:
o Константные данные, их тоже можно загружать из файла, так как они не меняются при работе программы;
o Инициализированные данные. Участвует в процессе свопинга;
o Не инициализированные данные. Так же участвует в свопинге;
* Сегмент стека. Так же участвует в свопинге.

Но, обычно, системы делят сегмент данных на две части: инициализированные данные и не инициализированные данные.

Все сегменты разбиваются на страницы. Сегмент кода имеет постоянный размер. Сегмент данных может увеличиваться в сторону больших адресов. Сегмент стека, поскольку растет вниз, увеличивается в сторону уменьшения адресов. Страницы памяти для дополнительных данных или стека выделяются системой по мере необходимости.

Очень интересный момент:
При выполнении программы операционная система делает следующие действия:

* Готовит для программы локальную таблицу дескрипторов;
* Готовит для программы каталог страниц, все страницы помечаются как не присутствующие в памяти.
* Все.

При передаче управления этой программе процессор генерирует исключение по отсутствию страницы, в котором нужная страница загружается из файла или инициализируется.

Еще один интересный момент:

Когда в системе загружается две или более одинаковых программы - нет необходимости для каждой из них выделять место для кодового сегмента, они спокойно могут использовать один код на всех.

Последний раз редактировалось z01b; 29.04.2008 в 14:22..
 
Ответить с цитированием

  #4  
Старый 28.04.2008, 01:52
Аватар для z01b
z01b
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме:
2360904

Репутация: 1393


По умолчанию

[03] - этапы загрузки различных ОС

Процесс загрузки, естественно, начинается с BIOS.
При старте процессор находится в реальном режиме, следовательно больше одного мегабайта памяти адресовать не может. Но это и не обязательно.

BIOS проверяет устройства, с которых может производиться загрузка. Порядок проверки в современных BIOS устанавливается. В список устройств могут входить Floppy disk, IDE disk, CDROM, SCSI disk...

Вне зависимости от типа устройства суть загрузки
одна...
На устройстве обнаруживается boot sector. Для CDROM это не совсем справедливо, но про них мы пока не будем говорить. BootSector загружается в память по адресу 0:7с00. Дальнейшее поведение BootSector'а зависит от системы.


Загрузка Linux.


Для Linux свойственно два способа загрузки:

* Загрузка через boot sector ядра;
* Загрузка через boot manager LILO (Linux Loader);

Процесс загрузки через ядро используется обычно на Floppy дисках и происходит в следующем порядке:

1. boot sector переписывает свой код по адресу 9000h:0;
2. Загружает с диска Setup, который записан в нескольких последующих секторах, по адресу: 9000h:0200h;
3. Загружает ядро по адресу 1000h:0. Ядро так же следует в последующих секторах за Setup. Ядро не может быть больше чем 508 килобайт, но так как оно, чаще всего, архивируется - это не страшно;
4. Запускается Setup;
5. Проверяется корректность Setup;
6. Производится проверка оборудования средствами BIOS. Определяется размер памяти, инициализируется клавиатура и видеосистема, наличие жестких дисков, наличие шины MCA (Micro channel bus), PC/2 mouse, APM BIOS (Advanced power management);
7. Производится переход в защищенный режим;
8. Управление передается по адресу 1000h:0 на ядро;
9. Если ядро архивировано, оно разархивируется. иначе просто переписывается по адресу 100000h (за пределы первого мегабайта);
10. Управление передается по этому адресу;
11. Активируется страничная адресация;
12. Инициализируются idt и gdt, при этом в кодовый сегмент и в сегмент данных ядра входит вся виртуальная память;
13. Инициализируются драйвера;
14. Управление передается неуничтожимому процессу init;
15. init запускает все остальные необходимые программы в соответствии с файлами конфигурации;

В случае загрузки через LILO:

1. boot sector LILO переписывает свой код по адресу 9a00h:0;
2. До адреса 9b00h:0 размещает свой стек;
3. Загружает вторичный загрузчик по адресу 9b00h:0 и передает ему управление;
4. Вторичный загрузчик загружает boot sector ядра по адресу 9000h:0;
5. Загружает Setup по адресу 9000h:0200h;
6. Загружает ядро по адресу 1000h:0;
7. Управление передается программе Setup. Зачем загружает boot sector из ядра? не понятно;

В Linux есть такое понятие как "big kernel". Такой kernel сразу загружается по адресу 100000h.


Загрузка FreeBSD.

Принципиальных отличий для FreeBSD, конечно, нет. основное отличие состоит в том, что ядро, как и модули ядра являются перемещаемыми и могут быть загружены или выгружены в процессе загрузки системы.

Порядок загрузки примерно следующий:


1. BootSector загружает вторичный загрузчик;
2. Вторичный загрузчик переводит систему в защищенный режим и запускает loader;
3. loader предоставляет пользователю возможность выбрать необходимые модули или запустить другое ядро;
4. После чего управление передается ядру и начинается инициализация драйверов;

В прошлом выпуске я писал: В следующем выпуске мы рассмотрим процессы загрузки разных операционных систем (Windows не предлагать!). Почему Windows не предлагать? Windows пока что еще никто не отменял Не хотите загружаться как Windows, но тогда расскажите, почему и приведите сравнение, но все равно расскажите, как это делает Windows. Не хотел рассказывать, но придется...
Если что-то я напутаю, уж извините...
Давайте по порядку рассмотрим, как грузятся системы от Microsoft.



Загрузка DOS.


boot sector DOS загружает в память два файла: io.sys и msdos.sys. Названия этих файлов в разных версиях DOS различались, не важно. Файл io.sys содержит в себе функции прерывания int 21h, файл msdos.sys обрабатывает config.sys, и запускает командный интерпретатор command.com, который в свою очередь обрабатывает командный файл autoexec.bat.


Загрузка Windows 9x.

Отличие от DOS заключается в том, что функции msdos.sys взял на себя io.sys. msdos.sys остался ради совместимости как конфигурационный файл. После того как командный интерпретатор command.com обрабатывает autoexec.bat вызывается программа win.com, которая осуществляет перевод системы в защищенный режим, и запускает различные другие программы, обеспечивающие работу системы.



Загрузка Windows NT.


boot sector NT - зависти от формата FS, для FAT устанавливается один, для NTFS - другой, в нем содержиться код чтения FS, без обработки подкаталогов.

1. boot sector загружает NTLDR из корневой директории, который запускается в real mode;
2. NTLDR певодит систему в защищенный режим;
3. Создаются необходимые таблицы страниц для доступа к первому мегабайту памяти;
4. Активируется механизм страничного преобразования;
5. Далее NTLDR читает файл boot.ini, для этого он использует встроенный read only код FS. В отличии от кода бутсектора он может читать подкаталоги;
6. На экране выводится меню выбора вида загрузки;
7. После выбора, или по истечении таймаута, NTLDR из файла boot.ini определяет нахождение системной директории Windows, она может находиться в другом разделе, но обязательно должна быть корневой;
8. Если в boot.ini указана загрузка DOS (или Win9x), то файл bootsect.dos загружается в память и выполняется горячая перезагрузка;
9. Далее обрабатывается boot.ini;
10. Загружается ntdetect.com, который выводит сообщение "NTDETECT V4.0 Checking Hardware", и детектит различные устройства... Вся информация собирается во внешней структуре данных, которая в дальнейшем становиться ключем реестра "HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION";
11. NTLDR выводит сообщение "OSLOADER V4.0";
12. Из директории winnt\system32 загружается ntoskrnl.exe, содержащий в себе ядро и подсистемы выполнения (менеджер памяти, кэш менеджер, менеджер объектов), и файл hal.dll, который содержит в себе интерфейс с аппаратным обеспечением;
13. Далее NTLDR предоставляет возможность выбрать "последние известные хорошие" конфигурации. В зависимости от выбора выбираются копии реестра используемые для запуска;
14. Загружает все драйвера и другие необходимые для загрузки файлы;
15. В завершение он запускает функцию main из ntoskrnl.exe и завершает свою работу;

__________________________________________________ __________________________________________________ _______

Не могу гарантировать полную достоверность представленной информации, NT я знаю плохо, тем более не знаю что у нее внутри. Так же не могу что-либо более конкретного сказать про распределение памяти в процессе загрузки Windows NT. некоторые неточности могут быть связаны с моим плохим знанием английского, желающие могут посмотреть на оригинал по адресу: Inside the Boot Proccess, Part 1
Ну вот, мы узнали как загружаются системы. В своей системе мы не будем слепо следовать какому либо из представленных здесь путей. Ради совместимости обеспечим формат ядра, аналогичный Linux. Мне кажется, в этой системе все сделано достаточно понятно и просто.

Последний раз редактировалось z01b; 29.04.2008 в 14:23..
 
Ответить с цитированием

  #5  
Старый 28.04.2008, 02:12
Аватар для z01b
z01b
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме:
2360904

Репутация: 1393


По умолчанию

[04] - создание bootsector'а

Как я уже упоминал, boot sector загружается в память по адресу 0:7c00h и имеет длину 512 байт. Это не слишком много, поэтому возможности boot sector'a ограничиваются загрузкой какого либо вторичного загрузчика.

Наш boot sector, по образу и подобию linux, будет загружать в память два блока. Первым является тот самый вторичный загрузчик, у нас он, как и в linux, называется setup. Вторым является собственно ядро.

Этот boot sector служит для загрузки ядра с дискет, поэтому, на первых порах, он жестко привязан к диску "a:".

BIOS предоставляет возможность читать по нескольку секторов сразу, но не более чем до границы дорожки. Такая возможность, конечно, ускоряет чтение с диска, но представляет собой большие сложности в программировании, так как надо учитывать границы сегментов (в реальном режиме сегмент может быть не больше, чем 64к) и границы дорожек, получается достаточно хитрый алгоритм.

Я пошел немного другим путем. Я читаю с диска по секторам. Это, конечно, медленнее, но я думаю, что здесь скорость не очень критична. За то это гораздо проще и компактнее реализуется.


А теперь давайте разбираться, как это все работает.
Код:
%define SETUP_SEG 0x07e0
%define SETUP_SECTS 10

%define KERNEL_SEG      0x1000
%define KERNEL_SECTS 1000
Для начала описываем место и размер для каждого загружаемого блока.
Размеры пока произвольные, поскольку все остальное еще предстоит написать.
Код:
section .text
        BITS    16

        org     0x7c00
Как я уже говорил, boot sector загружается и запускается по адресу 0:7c00h Содержимое регистров при старте таково:

* cs содержит 0
* ip содержит 7с00h

Прерывания запрещены! Про содержание остальных регистров мне ничего не известно, если кто-то, что-то знает, напишите мне. Остальные регистры мы будем инициализировать самостоятельно.
Код:
entry_point:
        mov     ax, cs

        cli
        mov     ss, ax
        mov     sp, entry_point
        sti

        mov     ds, ax
Стек у нас будет располагаться перед программой, до служебной области BIOS еще остается порядка 30 килобайт, для стека больше чем достаточно. Прерывания изначально запрещены, но я все равно сделаю это самостоятельно, на всякий случай. и разрешу после установки стека. Никаких проблем это вызвать, по-моему, не должно.
Так же, нулевым значением, инициализируем сегментный регистр ds.
Код:
        ; Сохpаняем фоpму куpсоpа
        mov     ah, 3
        xor     bh, bh
        int     0x10

        push    cx

        ; отключаем куpсоp
        mov     ah, 1
        mov     ch, 0x20
        int     10h
Чтобы все было красиво и радовало глаз, мы на время чтения отключим курсор. Иначе он будет мелькать на экране. Чтобы его потом восстановить, как и был, мы сохраняем его форму в стеке.
Код:
        ; Загpужаем setup
        mov     ax, SETUP_SEG
        mov     es, ax

        mov     ax, 1
        mov     cx, SETUP_SECTS

        mov     si, load_setup_msg
        call    load_block

        call    outstring

        mov     si, complete_msg
        call    outstring
Загружаем первый блок (setup). Процедуру загрузки блока мы рассмотрим немного позже. А в остальном здесь, по-моему, все понятно.
Код:
        ; загpужаем ядpо.
        mov     ax, KERNEL_SEG
        mov     es, ax

        mov     ax, 1 + SETUP_SECTS
        mov     cx, KERNEL_SECTS

        mov     si, load_kernel_msg
        call    load_block

        call    outstring

        mov     si, complete_msg
        call    outstring
Загружаем второй блок (kernel). Здесь все в точности аналогично первому блоку.
Код:
        ; Восстанавливаем куpсоp
        pop     cx
        mov     ah, 1
        int     0x10
Восстанавливаем форму курсора.
Код:
        ; Пеpедаем упpавление на setup
        jmp     SETUP_SEG:0
На этом работа boot sector'а заканчивается. Дальним переходом мы передаем управление программе setup.

Далее располагаются функции.

; Загрузка блока
; cx - количество сектоpов
; ax - начальный сектоp
; es - указатедь на память
; si - loading message

Функция загрузки блока. Она же занимается выводом на экран процентного счетчика.
Код:
load_block:
        mov     di, cx ; сохpаняем количество блоков

 .loading:
        xor     bx, bx
        call    load_sector
        inc     ax
        mov     bx, es
        add     bx, 0x20
        mov     es, bx

        ; Выводим сообщение о загpузке.
        call    outstring

        push    ax

        ; Выводим пpоценты
        ; ((di - cx) / di) * 100
        mov     ax, di
        sub     ax, cx
        mov     bx, 100
        mul     bx
        div     di

        call    outdec

        push    si
        mov     si, persent_msg
        call    outstring
        pop     si

        pop     ax

        loop    .loading

        ret
В этой функции, по-моему, ничего сложного нет. Обыкновенный цикл.

А вот следующая функция загружает с диска отдельный сектор, при этом оперируя его линейным адресом.
Есть так называемое int13 extension, разработанное совместно фирмами MicroSoft и Intel. Это расширение BIOS работает почти аналогичным образом, Считывая сектора по их линейным адресам, но оно поддерживается не всеми BIOS, имеет несколько разновидностей и работает в основном для жестких дисков. Поэтому нам не подходит.

В своей работе мы пока ориентируемся только на чтение с floppy диска, размером 1,4 мегабайта. Поэтому будем использовать старомодную функцию, которой в качестве параметров задается номер дорожки, головки и сектора.
[/code]
; Загрузка сектора
; ax - номеp сектоpа (0...max (2880))
; es:bx - адpес для pазмещения сектоpа.
[/code]
Абсолютный номеp сектоpа вычисляется по фоpмуле: AbsSectNo = (CylNo * SectPerTrack * Heads) + (HeadNo * SectPerTrack) + (SectNo - 1) Значит обpатное спpаведливо так: CylNo = AbsSectNo / (SectPerTrack * Heads) HeadNo = остаток / SectorPerTrack SectNo = остаток + 1

load_sector: push ax push cx cwd mov cx, 18 ; SectPerTrack div cx mov cx, dx inc cx ; количество сектоpов

Поделив номер сектора на количество секторов на дорожке, мы в остатке получаем номер сектора на дорожке. Это значение хранится в 6 младших битах регистра cl.
Код:
        xor     dx, dx  ; dl - диск - 0!
Номер диска храниться в dl и устанавливается в 0 (это диск a
Код:
        shr     ax, 1
        rcl     dh, 1 ; номер головки
Младший бит частного определяет для нас номер головки. (0 или 1)
Код:
        mov     ch, al
        shl     ah, 4
        or      cl, ah ; количество доpожек
Оставшиеся биты частного определяют для нас номер цилиндра (или дорожки).
восемь младших бит номера хранятся в регистре ch, два старших бита номера хранятся в двух старших битах регистра cl.
Код:
 .rept:
        mov     ax, 0x201
        int     0x13

        jnc     .read_ok

        push    si
        mov     si, read_error
        call    outstring

        movzx   ax, ah
        call    outdec

        mov     si, crlf
        call    outstring

        xor     dl, dl
        xor     ah, ah
        int     0x13

        pop     si

        jmp     short .rept
В случае ошибки чтения мы не будем возвращать из функции какие-либо результаты, а будем повторять чтение, пока оно не окажется успешным. Ведь в случае неуспешного чтения у нас все равно ничего не будет работать! Для верности мы, в случае сбоя, производим сброс устройства.
Код:
 .read_ok:

        pop     cx
        pop     ax
        ret
Далее идет две интерфейсные функции, обеспечивающие вывод на экран строк и десятичных цифр. Ничего особенного они из себя не представляют а для вывода пользуются телетайпным прерыванием BIOS (ah = 0eh, int 10h), которое обеспечивает вывод одного символа с обработкой некоторых служебных кодов.
Код:
; Вывод стpоки.
; ds:si - стpока.

outstring:
        push    ax
        push    si

        mov     ah, 0eh

        jmp     short .out
 .loop:
        int     10h
 .out:
        lodsb
        or      al, al
        jnz     .loop

        pop     si
        pop     ax
        ret
Эта функция ограничена выводом чисел до 99 включительно, случай с большим числом обрабатывается как переполнение и отображается как '##'.
Код:
; Вывод десятичных чисел от 0 до 99
; ax - число!
outdec:
        push    ax
        push    si

        mov     bl, 10
        div     bl
        cmp     al, 10

        jnc     .overflow

        add     ax, '00'
        push    ax
        mov     ah, 0eh
        int     0x10
        pop     ax
        mov     al, ah
        mov     ah, 0eh
        int     0x10

        jmp     short .exit

 .overflow:
        mov     si, overflow_msg
        call    outstring

 .exit:
        pop     si
        pop     ax
        ret
Далее располагаются несколько служебных сообщений.
Код:
load_setup_msg:
        db      'Setup loading: ', 0

load_kernel_msg:
        db      'Kernel loading: ', 0

complete_msg:
        db      'complete.'

crlf:
        db      0ah, 0dh, 0

persent_msg:
        db      '%', 0dh, 0

overflow_msg:
        db      '##', 0

read_error:
        db      0ah, 0dh
        db      'Read error #', 0

        TIMES   510-($-$$) db 0
Эта комбинация заполняет оставшееся место в секторе нулями. А остается у нас еще около 200 байт.
Код:
        dw      0aa55h
Последние два байта называются "Partition table signature", что не совсем корректно. Фактически эта сигнатура говорит BIOS'у о том, что этот сектор является загрузочным.

Этот boot sector, помимо того, что читает по секторам, отличается от линуксового еще и размещением в памяти. После загрузки он не перемещает себя в памяти, и работает по тому же адресу, по которому его загрузил BIOS. Так же setup загружается непосредственно следом за boot sector'ом, с адреса 7e00h, что в принципе не помешает ему работать в других адресах, если мы будем загружать наше ядро через LILO, например.

Скомпилированную версию boot sector'а вы можете найти в файловом архиве (секция "наработки").

Надеюсь, что я достаточно доходчиво объясняю, если кому-то что-то не понятно - пишите.

В следующем выпуске мы перейдем к программе setup и рассмотрим порядок перехода в защищенный режим. А заодно я более подробно расскажу про этот режим процессора.

Последний раз редактировалось z01b; 29.04.2008 в 14:23..
 
Ответить с цитированием

  #6  
Старый 28.04.2008, 02:27
Аватар для z01b
z01b
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме:
2360904

Репутация: 1393


По умолчанию

[05] - основы защищенного режима

История организации памяти.

Ранние модели процессоров от Intel имели 16 бит шины данных и 20 бит шины адреса. Это налагало определенные ограничения на адресацию памяти, ибо 16-бинтный регистр невозможно было использовать для адресации более чем 64 килобайт памяти. Чтобы обойти это препятствие разработчики предусмотрели сегментные регистры. Сегментный регистр хранит в себе старшие 16 бит адреса и для получения полного адреса к сегментному адресу прибавляется смещение в сегменте.



Таким образом, стало возможным адресовать до 1 мегабайта памяти. Это же позволило делать программы, не настолько привязанными к памяти и упростило адресацию. Сегменты могут начинаться с любого адреса, кратного 16 байтам, эти 16-байтные блоки памяти получили название параграфов. Но это и создает определенные неудобства. Первое неудобство состоит в том, что на один адрес памяти указывает 4096 различных комбинаций сегмент/смещение. Второе неудобство состоит в том, что нет возможности ограничить программам доступ к тем или иным частям памяти, что в некоторых случаях может быть существенно!
Введение защищенного режима решило эти проблемы, но ради совместимости любой из современных процессоров может работать в реальном или виртуальном режиме процессора i8086.


Защита.

Для обеспечения надежной работы операционных систем и прикладных программ разработчики процессоров предусмотрели в них механизмы защиты. В процессорах фирмы Intel предусмотрено четыре уровня привилегий для программ и данных. Нулевой уровень считается наиболее привилегированным, третий уровень - наименее.

Так же в защищенном режиме совсем иначе работает механизм преобразования адресов. в сегментном регистре теперь хранится не старшие биты адреса, а селектор. селектор представляет из себя индекс в таблице дескрипторов. И кроме этого содержит в себе несколько служебных бит. Формат селектора такой:



Поле Index определяет индекс в дескрипторной таблице.

В процессорах Intel одновременно в системе может существовать две дескрипторных таблицы: Глобальная (Global descriptor table или GDT) и Локальная (Local descriptor table или LDT).

GDT существует в единственном экземпляре. Адрес и предел GDT хранятся в специальном системном регистре (GDTR) в 48 бит длиной (6 байт).
LDT может быть индивидуальная для каждой задачи, или общая для системы, или же ее вообще может не быть. Адрес и размер LDT определяется в GDT, для обращения к LDT в процессоре существует специальный регистр (LDTR), но в отличии от GDTR он имеет размер 16 бит и содержит в себе селектор из GDT.

Поле TI (Table indicator) селектора определяет принадлежность селектора GDT (0) или LDT (1).

Поле RPL (Requested privilege level) определяет запрашиваемые привилегии... об этом мы поговорим чуть позже.


Дескрипторы сегментов.


Дескрипторные таблицы состоят из записей по 64 бита (8 байт) в каждой. Формат дескриптора таков:



Сразу бросается в глаза очень странная организация дескриптора, но это связано с совместимостью с процессором i286, формат дескриптора в котором был таков:



Что же содержится в дескрипторе:

Базовый адрес - 32 бита (24 бита для i286). Определяет линейный адрес памяти, с которого начинается сегмент. В отличие от реального режима этот адрес может быть указан с точностью до байта.

Предел - 20 бит (16 бит для i286). Определяет размер сегмента (максимальный адрес, по которому может быть произведено обращение, это справедливо не всегда но об этом чуть позже). 20-битное поле может показаться не очень то большим для 32-х битного процессора, но это не так. Оно не всегда показывает размер в байтах. Но и об этом чуть позже.

Байт прав доступа:



Бит P (present) - Указывает на присутствие сегмента в памяти. обращение к отсутствующему сегменту вызывает особый случай не присутствия сегмента в памяти.

Двух битное поле DPL определяет уровень привилегий сегмента. Про Уровни привилегий мы поговорим чуть позже.

Бит S (Segment)- Будучи установленным в 1, определяет сегмент памяти, к которому может быть получен доступ на чтение (запись) или выполнение.

Три бита Type - в зависимости от бита S определяет либо возможности чтения/записи, выполнения сегмента или определяет тип системных данных, хранимых в селекторе. Подробнее это выглядит так:

Если бит S установлен в 1, о поле Type делится на биты:



Если сегмент расширяется вниз (это используется для стековых сегментов) то поле предела показывает адрес, выше которого допустима запись. ниже запись недопустима и вызовет нарушение пределов сегмента.

Бит А (Accessed) устанавливается в 1, если к сегменту производилось обращение.

Если бит S установлен в 0, то в сегменте находится служебная информация определяемая полем Typе и битом A.

TYPE A Описание
000 1 TSS для i286
001 0 LDT
001 1 Занятый TSS для i286
010 0 Шлюз вызова i286
010 1 Шлюз задачи
011 0 Шлюз прерывания i286
011 1 Шлюз исключения i286
100 1 TSS для i386
101 1 Занятый TSS i386
110 0 Шлюз вызова i386
111 0 Шлюз прерывания i386
111 1 Шлюз ловушки i386

Остальные комбинации либо недопустимы, либо зарезервированы.

TSS - это сегмент состояния задачи (Task state segment) о них мы поговорим позже, возможно в следующем выпуске.

Шестой байт дескриптора, помимо старших бит предела, содержит в себе несколько битовых полей.



Бит G (Granularity) - определяет размер элементов, в которых измеряется предел. если 0 - предел в байтах, если 1 - размер в страницах.

Бит D (Default size) - размер операндов в сегменте. Если 0 - 16 бит. если 1 - 32 бита.

Бит U (User) - доступен для пользователя (вернее для программиста операционной системы)


И снова защита.

Немного терминологии:


Уровень привилегий может быть от 0(высший) до 3(низший). Следовательно повышение уровня привилегий соответствует его уменьшению в численном эквиваленте, понижение - наоборот.

В дескрипторе содержатся биты DPL, которые определяют максимальный уровень привелегий для доступа к сегменту.

В селекторе содержится RPL - то есть запрашиваемый уровень привилегий.

RPL секущего кодового сегмента (хранится в регистре cs) является уровнем привилегий данного процесса и называется текущим уровнем привилегий (CPL)

Прямые обращения к сегментам возможны при соблюдении следующих условий:

* В случае если запрашиваемый уровень привилегий больше текущего, то запрашиваемый уровень понижается до текущего.
* При обращении к сегменту данных RPL селектора должен быть не ниже DPL сегмента.
* При обращении к сегменту кода возможно только при равенстве CPL, RPL и DPL.
* Если сегмент кода помечен как подчиненный, то для обращения к нему необходимо иметь уровень привилегий не ниже уровня сегмента. При этом выполнение сегмента происходит с текущим уровнем привилегий.

Косвенные вызовы возможны только через шлюзы при соблюдении следующих условий:


* DPL шлюза должен быть не выше, чем CPL сегмента, из которого производится вызов шлюза.
* DPL сегмента, на который указывает шлюз, должно быть не ниже чем DPL шлюза.

Последний раз редактировалось z01b; 29.04.2008 в 14:26..
 
Ответить с цитированием

  #7  
Старый 28.04.2008, 03:12
Аватар для z01b
z01b
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме:
2360904

Репутация: 1393


По умолчанию

[06] - шлюзы / виртуальный режим процессора 8086

Шлюзы

В прошлом выпуске, когда я говорил о дескрипторах и дескрипторных таблицах я ни слова не упомянул о дескрипторной таблице прерываний (Interrupt description table или IDT). Эта таблица так же состоит из дескрипторов, но в отличии от LDT и GDT в этой таблице могут размечаться только шлюзы. В защищенном режиме все прерывания происходят через IDT. Традиционная таблица векторов прерываний здесь не используется.

Формат дескрипторов шлюзов отличается от дескриптора сегмента.
Для начала рассмотрим шлюз вызова.



В поле прав доступа задается уровень привилегий, который должен быть ниже CPL текущего процесса, бит присутствия и соответствующий тип в остальных полях.
Селектор и смещение задают адрес вызываемой функции, при этом селектор должен присутствовать либо в GDT либо в активной LDT.
Параметр "Количество слов стека" служит для передачи аргументов в вызываемую функцию, при этом соответствующее количество слов копируется из стека текущего уровня привилегий в стек уровня привилегий вызываемой функции. Это поле использует только младшие 5 бит четвертого байта. Остальные биты должны быть нулевыми.
Обращаться к такому шлюзу, если дескриптор не расположен в IDT, можно только командой call far, при этом указываемое в команде смещение игнорируется. А селектор должен указывать на дескриптор шлюза вызова.

Шлюз прерывания и шлюз ловушки имеют одинаковый формат, отличаются между собой типами в байте прав доступа. В отличии от шлюза вызова эти шлюзы не содержат в себе Количества слов стека, поскольку прерывания бывают аппаратными и передача в них параметров через стек - бессмысленна. Эти шлюзы используются обычно только в IDT.

Шлюз задачи содержит в себе значительно меньше информации.
Во втором и третьем байте дескриптора записывается селектор TSS (Сегмента состояния задачи). Поле прав доступа заполняется аналогично другим шлюзам, но с соответствующим типом. Остальные поля дескриптора не используются.
При вызове такого шлюза происходит переключение контекста задачи. При этом вызывающая задача блокируется и не может быть вызвана до тех пор, пока вызванная задача не вернет ей управление командой iret.

Про правила доступа к шлюзам я говорил в прошлом выпуске, и в этот раз я закончу на этом. в следующем выпуске расскажу про прерывания более подробно.


Виртуальный режим процессора 8086.


Для возможности запуска из защищенного режима программ, предназначенных для реального, существует так называемый "Виртуальный режим процессора 8086". При этом полноценно работают механизмы преобразования адресов защищенного режима. А так же многозадачные системы, которые могут одновременно выполнять как защищенные задачи, так и виртуальные. При этом адресация в виртуальной задаче осуществляется традиционным для 8086 методом - сегмент/смещение.

Обращение к прерываниям осуществляется через IDT, но таблица прерываний реального режима может быть обработана из функций, шлюзы которых размещаются в IDT.
Обращение виртуальной задачи к портам так же может быть отслежено через прерывания защищенного режима. При обращении к запрещенным портам происходит исключение.
При желании может быть обеспечена абсолютно прозрачная работа нескольких виртуальных задач в одной мультизадачной среде. Но мы этой возможностью не будем пользоваться, и в своей работе будем рассчитывать на программы исключительно защищенного режима.

Последний раз редактировалось z01b; 29.04.2008 в 14:26..
 
Ответить с цитированием

  #8  
Старый 28.04.2008, 03:17
Аватар для z01b
z01b
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме:
2360904

Репутация: 1393


По умолчанию

[07] - исключения защищенного режима / микроядерные системы

Исключения защищенного режима.

Я уже неоднократно упоминал это слово в предидущих выпусках. Но думаю что не всем было понятно что это такое. Сейчас мы рассмотрим это поподробнее.

Исключения или системные прерывания существовали еще в самых первых моделях процессоров от Intel. Вот их список:
0. Division by zero (деление на ноль или переполнение при делении);
1. Single step (пошаговая отладка);
3. Breakpoint;
4. Overflow (срабатывает при команде into в случае установленного флага overflow в регистре flags);
6. Invalid opcode (i286+);
7. No math chip;

Исключения располагаются в начале таблицы прерываний. В реальном режиме занимают 8 первых векторов прерываний.

Введение защищенного режима потребовало введения дополнительных исключений. В защищенном режиме первые 32 вектора прерываний зарезервированы для исключений. Не все они используются в существующих процессорах, в будующем возможно их будет больше. Системные прерывания в защищенном режиме делятся на три типа: нарушения (fault), ловушки (trap) и аварии (abort).
Итак в защищенном режиме у нас существуют следующие исключения:
0. Divide error (fault);
1. Debug (fault/trap);
3. Breakpoint (trap);
4. Overflow (trap);
5. Bounds check (fault);
6. Invalid opcode (fault);
7. Coprocessor not available (fault);
8. Double fault (abort);
9. Coprocessor segment overrun (fault);
10. Invalid tss (fault);
11. Segment not present (fault);
12. Stack fault (fault);
13. General protection fault (fault);
14. Page fault (fault);
16. Coprocessor error (fault);
17. Alignument check (fault) (i486+);
18. Hardware check (abort) (Pentium+);
19. SIMD (fault) (Pentium III+).

Нарушения возникают вследствии несанкционированных или неправильных действий программы, предполагается, что ошибки можно исправить и продолжить выполнение программы с инструкции, которая вызвала ошибку.

Ловушки возникают после выполнения инструкции, но тоже подразумевают исправление ошибочной ситуации и дальнейшую работу программы.

Аварии возникают в случае критических нарушений, после этого программа уже не может быть перезапущена и должна быть закрыта.

Но иногда в случае ошибки или ловушки программа тем не менее не может продолжить свое выполнение. Это зависит от тяжести нарушения и от организации операционной системы, которая обрабатывает исключения. И если ошибка или ловушка не может быть исправлена, программу так же следует закрыть.

При возникновении исключения процессор иногда помещает в стек код ошибки, по которому обработчик исключения может проанализировать и, возможно, исправить возникшую ошибку.

Все исключения обрабатываются операционной системой. В случае микроядерных систем этим занимается микроядро. Вот о микроядрах мы и поведем наш дальнейший разговор.


Микроядерные системы.

В первых выпусках я уже касался этой темы, но тогда я ограничился буквально несколькими словами. Но теперь мы решили двигаться именно в сторону микроядерности, значит стоит поподробнее рассказать, что это такое.

Принцип микроядерности заключается в том, что ядро практически не выполняет операций, связанных с обслуживанием внешних устройств. Эту функцию выполняют специальные программы-сервера. Ядро лишь предоставляет им возможность обращаться к устройствам. Помимо этого ядро обеспечивает многозадачность (параллельное выполнение программных потоков), межпроцессное взаимодействие и менеджмент памяти.

Приложения (как и сервера) у нас работают на третьем, непривилегированном кольце и не могут свободно обращаться к портам ввода/вывода или dma памяти. Тем более не могут сами устанавливать свои обработчики прерываний. Для использования ресурсов процессы обращаются к ядру с просьбой выделить необходимые ресурсы в их распоряжение. Осуществляется это следующим образом:

Для обеспечения доступа к портам ввода/вывода используются возможности процессоров, впервые появившиеся intel 80386. У каждой задачи (в сегменте состояния задачи (TSS)) существует карта доступности портов ввода/вывода. Приложение обращается к ядру с "просьбой" зарегистрировать для нее диапазон портов. Если эти порты до тех пор никем не были заняты, то ядро предоставляет их в распоряжение процесса, помечая их как доступные в карте доступности ввода/вывода этого процесса.

DMA память, опять таки после запроса у ядра, с помощью страничного преобразования подключается к адресному пространству процесса. Настройка каналов осуществляется ядром по "просьбе" процесса.

Доступ к аппаратным прерываниям (IRQ) осуществляется сложнее. Для этого процесс порождает в себе поток (thread), и сообщает ядру, что этот поток будет обрабатывать какое-то IRQ. При возникновении аппаратного прерывания, которое обрабатывает всетаки ядро, данный процесс выходит из состояния спячки, в котором он находился в ожидании прерывания, и ставится в очередь к менеджеру процессов. Такие потоки должны иметь более высокий приоритет, чем все остальные, дабы вызываться как можно скорее.

Но, как я говорил, ядро выполняет еще некоторые функции, немаловажная из которых - это межпроцессное взаимодействие. Оно представляет из себя возможность процессов обмениваться сообщениями между собой. В отличии от монолитных систем в микроядерных системах межпроцессное взаимодействие (Inter Process Communication или IPC) это едва ли не основное средство общения между процессами, и поскольку все драйвера у нас такие же процессы, микроядерное IPC должно быть очень быстрым. Быстродействие IPC достигается за счет передачи сообщений без промежуточного буферизирования в ядре. Либо непосредственным переписыванием процессу-получателю, либо с помощью маппинга страниц (если сообщения большого размера).

Менеджер памяти имеет как бы две стороны. Первая сторона - внутренняя, распределение памяти между приложениями, организация свопинга (который тоже осуществляется не ядром непосредственно, а специальной программой-сервером) никаким образом не интересует остальные программы. Но другая сторона - внешняя служит именно для них. Программы могут запросить у ядра во временное пользование некоторое количество памяти, которое ядро им обязательно предоставит (в разумных пределах... гигабайта два... не больше... . Или же программы могут запросить у ядра какой-то определенный участок памяти. Это бывает необходимо программам-серверам. И это требование ядром также вполне может быть удовлетворено при условии, что никакая другая программа до того не забронировала этот участок памяти для себя.
 
Ответить с цитированием

  #9  
Старый 28.04.2008, 03:23
Аватар для z01b
z01b
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме:
2360904

Репутация: 1393


По умолчанию

[08] - файловые системы

Есть много файловых систем, которые нам, в принципе, подойдут (EXT2FS, FFS, NTFS, RaiserFS и много других), есть так же файловые системы, которые нам вообще не подойдут (FAT). В процессе развития нашей операционной системы мы создадим поддержку и для них, но для начала надо остановиться на чем-то одном. Этой одной файловой системой будет EXT2FS.

В этом выпуске я достаточно подробно рассмотрю файловые системы FAT, и более подробно файловую систему Linux (ext2). Поскольку наша операционная система будет юниксоподобная, то файловые системы FAT нам никак не подходят, поскольку они не обеспечивают мер ограничения доступа, и по сути своей не являются многопользовательскими. Про остальные файловые системы я ограничусь лишь основными моментами.

Так же я не стану затрагивать тему разделов диска. Обсудим это в другой раз.


Основные принципы файловых систем.

Все устройства блочного доступа (к которым относятся жесткие или гибкие диски, компакт диски) при чтении/записи информации оперируют секторами. Для жестких или гибких дисков размер сектора равен 512 байт, в компакт-дисках размер сектора равен 2048 байт. Сектора являются физической единицей информации для носителя.

Для файловых систем такое распределение часто бывает не очень удобно, и в них вводится понятие кластера. Кластеры часто бывают больше по размеру, чем сектора носителя. Кластеры являются логической единицей файловых систем. Правда, не всегда они называются кластерами. В ext2 кластеры называются просто блоками, но это не столь важно. Для организации кластеров файловые системы хранят таблицы кластеров. Таблицы кластеров, естественно, расходуют дисковое пространство. Помимо этого, дополнительное дисковое пространство расходуется под каталоги файлов. Эти неизбежные расходы в разных файловых системах имеют разную величину. Но об этом мы поговорим ниже.


Файловые системы на базе FAT (File Allocation Table).

Этот тип файловых систем разработала фирма Microsoft достаточно давно. Вместе с первыми DOS... С тех пор неоднократно натыкались на различные препятствия и дорабатывались в соответствии с требованиями времени.

Теперь пойдет небольшой экскурс в историю.

* В 1977 году Биллом Гейтсом и Марком МакДональдом была разработана первая файловая система FAT. Ради совместимости с CP/M в ней было ограничено имя файла. Максимальная длина имени составляла 8 символов, и 3 символа можно было использовать для расширения файла. Регистр букв не различался и не сохранялся. Размер кластера не превышал 4 килобайта. Размер диска не мог превышать 16 мегабайт.
* В 1981 году вышла первая версия MSDOS, которая базировалась на FAT.
* Начиная с MSDOS версии 3.0, в файловой системе появилось понятие каталога.
* Для поддержки разделов более 16 мегабайт размер элемента FAT был увеличен до 16 бит, (первая версия была 12-битная) а максимальный размер кластера увеличен до 32 килобайт. Это позволило создавать разделы до 2 гигабайт.
* В таком состоянии FAT просуществовал до появления VFAT, появившегося вместе с выходом Windows'95, в которой появилась поддержка длинных имен файлов. Теперь имя файлов могло иметь длину до 255 символов, но ради совместимости старый формат имен так же остался существовать.
* Немного позже FAT был еще расширен, размер элемента FAT стал 32 бита, при этом максимальный размер кластера вновь уменьшился до 4 килобайт, но это позволило создавать разделы до 2 терабайт. Кроме того, была расширена информация о файлах. Теперь она позволяли хранить помимо времени создания файла время модификации и время последнего обращения к файлу.

Ну а теперь подробнее рассмотрим структуру этой файловой системы.

Общий формат файловой системы на базе FAT таков:

* Boot sector (в нем так же содержится "Блок параметров FS")
* Reserved Sectors (могут отсутствовать)
* FAT (Таблица размещения файлов)
* FAT (вторая копия таблицы размещения файлов, может отсутствовать)
* Root directory (корневая директория)
* Область файлов. (Кластеры файловой системы)

Boot sector имеет размер 512 байт, как мы уже знаем, может содержать в себе загрузчик системы, но помимо этого для FAT он содержит Блок параметров. Блок параметров размещается в boot sector'е по смещению 0x0b и содержит в себе следующую информацию:
Код:
struct FAT_Parameter_block {
  u_int16       Sector_Size;
  u_int8        Sectors_Per_Cluster;
  u_int16       Reserved_Sectors;
  u_int8        FAT_Count;
  u_int16       Root_Entries;
  u_int16       Total_Sectors;
  u_int8        Media_Descriptor;
  u_int16       Sectors_Per_FAT;
  u_int16       Sectors_Per_Track;
  u_int16       Heads;
  u_int32       Hidden_sectors;
  u_int32       Big_Total_Sectors;
};
Размер кластера можно вычислить, умножив Sector_Size на Sectors_Per_Cluster.

Общий размер диска определяется следующим образом: Если значение Total_Sectors равно 0, то раздел более 32 мегабайт и его длина в секторах храниться в Big_Total_Sectors. Иначе размер раздела показан в Total_Sectors.

Таблица FAT начинается с сектора, номер которого храниться в Reserved_Sectors и имеет длину Sectors_Per_FAT; при 16-битном FAT размер таблицы может составлять до 132 килобайт (или 256 секторов) (в FAT12 до 12 килобайт).
Вторая копия FAT служит для надежности системы... но может отсутствовать.

После таблицы FAT следует корневая директория диска. Размер этой директории ограничен Root_Entries записями. Формат записи в директории таков:
Код:
struct FAT_Directory_entry {
  char          Name[8];
  char          Extension[3];
  u_int16       File_Attribute;
  char          Reserved[10];
  u_int16       Time;
  u_int16       Date;
  u_int16       Cluster_No;
  u_int32       Size;
};
Размер записи - 32 байта, следовательно, общий размер корневой директории можно вычислить, умножив Root_Entries на 32.

Далее на диске следуют кластеры файловой системы. Из записи в директории берется первый номер кластера, с него начинается файл. В FAT под этим номером может содержаться либо код последнего кластера (0xffff или 0xfff для FAT12) либо номер кластера, следующего за этим.

При записи файла из FAT выбираются свободные кластеры по порядку от начала. В результате возникает фрагментация файловой системы, и существенно замедляется ее работа. Но это уже выходит за тему рассылки.

Все выше сказанное про FAT справедливо для FAT12 и FAT16. FAT32 более существенно отличается, но общие принципы организации для нее примерно такие же. VFAT ничем не отличается от FAT16, для хранения длинных имен там используется однеа запись в директории для хранения короткого имени файла и несколько записей для хранения длинного. Длинное имя храниться в unicode, и на запись в директории приходится 13 символов длинного имени, причем они разбросаны по некоторым полям записи, остальные поля заполняются с таким расчетом, чтобы старые программы не реагировали на такую запись.

С первого взгляда видна не высокая производительность таких файловых систем. Не буду поливать грязью Microsoft, у них и без меня достаточно проблем... К тому же и у них есть другие разработки, которые не столь плохи. Но о них мы поговорим ниже... А сейчас давайте посмотрим на ext2fs. Правда, эта файловая система несколько другого уровня, и сравнивать ее с FAT - нельзя. Но обо всем по порядку.


Ext2fs (Расширенная файловая система версия 2)


Linux разрабатывался на операционной системе Minix. В ней была (да и есть) файловая система minixfs. Система не очень гибкая и достаточно ограниченная. После появления Linux была разработана (на базе minixfs) файловая система extfs, которую в скором времени заменила ext2fs, которая и используется в большинстве Linux, по сей день.

Для начала давайте рассмотрим основное устройство этой файловой системы:

* Boot sector (1 сектор)
* Свободно (1 сектор, может быть использован для расширения Boot sector'а до килобайта)
* Super block (2 сектора или 1024 байта длиной)
* Group descriptors (2 сектора максимум)
* Group 1
* Group 2
* ... и так далее... до Group 32 если необходимо.

Если ext2fs находится на каком ни будь разделе жесткого диска, или является не загрузочной, то boot sector'а там может вообще не быть.

Super block содержит в себе информацию о файловой системе и имеет следующий формат:
Код:
struct ext2_super_block {
  u_int32  s_inodes_count;
  u_int32  s_blocks_count;
  u_int32  s_r_blocks_count;
  u_int32  s_free_blocks_count;
  u_int32  s_free_inodes_count;
  u_int32  s_first_data_block;
  u_int32  s_log_block_size;
  int32    s_log_frag_size;
  u_int32  s_blocks_per_group;
  u_int32  s_frags_per_group;
  u_int32  s_inodes_per_group;
  u_int32  s_mtime;
  u_int32  s_wtime;
  u_int16  s_mnt_count;
  u_int16  s_max_mnt_count;
  u_int16  s_magic;
  u_int16  s_state;
  u_int16  s_errors;
  u_int16  s_pad;
  u_int32  s_lastcheck;
  u_int32  s_checkinterval;
  u_int32  s_reserved[238];
};
Не буду описывать значение всех полей этой структуры, ограничусь основными. Размер блока файловой системы можно вычислить так: 1024 * s_log_block_size. Размер блока может быть 1, 2 или 4 килобайта размером.

Об остальных полях чуть попозже.
А теперь рассмотрим группы дескрипторов файловой системы.
Формат дескриптора группы таков:
Код:
struct ext2_group_desc {
  u_int32  bg_block_bitmap;
  u_int32  bg_inode_bitmap;
  u_int32  bg_inode_table;
  u_int16  bg_free_blocks_count;
  u_int16  bg_free_inodes_count;
  u_int16  bg_used_dirs_count;
  u_int16  bg_pad;
  u_int32  bg_reserved[3];
};
Содержимое группы таково:

* Block bitmap (Битовая карта занятости блоков)
* Inode bitmap (Битовая карта занятости inode)
* Inode table (Таблица inode)
* Available blocks (блоки, доступные для размещения файлов)

Блоки в файловой системе отсчитываются с начала раздела. В дескрипторе группы содержаться номер блока с битовой картой блоков группы, номер блока с битовой картой инодов, и номер блока с которого начинается таблица inode. Про inode мы поговорим чуть попозже, а сперва разберемся с битовыми картами.

В суперблоке храниться количество блоков в группе (s_blocks_per_group). Битовая карта имеет соответствующий размер в битах (занимает она не более блока). и в зависимости от размера блока может содержать информацию об использовании 8, 32 или 132 мегабайт максимум. Дисковое пространство раздела разбивается на группы в соответствии с этими значениями. А групп, как я уже упоминал, может быть до 32... что позволяет создавать разделы, в зависимости от размера блока, 256, 1024 или 4096 мегабайт соответственно.

В битовую карту блоков группы входят так же те блоки, которые используются под саму карту, под карту inode и под таблицу inode. Они сразу помечаются как занятые.

Теперь давайте разберемся, что такое inode. В отличии от FAT информация о файле здесь храниться не в директории, а в специальной структуре, которая носит название inode (информационный узел). В записи директории содержится только адрес inode и имя файла. При этом на один inode могут ссылаться несколько записей директории. Это называется hard link.

Формат inode таков:
Код:
struct ext2_inode {
  u_int16  i_mode;
  u_int16  i_uid;
  u_int32  i_size;
  u_int32  i_atime;
  u_int32  i_ctime;
  u_int32  i_mtime;
  u_int32  i_dtime;
  u_int16  i_gid;
  u_int16  i_links_count;
  u_int32  i_blocks;
  u_int32  i_flags;
  u_int32  i_reserved1;
  u_int32  i_block[14];
  u_int32  i_version;
  u_int32  i_file_acl;
  u_int32  i_dir_acl;
  u_int32  i_faddr;
  u_int8   i_frag;
  u_int8   i_fsize;
  u_int16  i_pad1;
  u_int32  i_reserved2[2];
};
Как видно из приведенной выше структуры в inode содержится следующая информация:

* Тип и права доступа файла (i_mode)
* идентификатор хозяина файла (i_uid)
* Размер (i_size)
* Время доступа, создания, модификации и удаления файла (после удаления inode не удаляется, а просто перестает занимать блоки файловой системы)
* Идентификатор группы
* Количество записей в директориях, указывающих на этот inode...
* Количество занимаемых блоков fs
* дополнительные флаги ext2fs
* таблица занимаемых блоков
* Ну и другая, не столь существенная в данных момент информация.

Остановимся поподробнее на таблице занимаемых блоков. Как видите там всего 14 записей. Но 14 блоков - это мало для одного файла. Дело в том, что не все записи содержат номера блоков. 13-я запись содержит косвенный блок, то есть блок, в котором содержится таблица блоков. А 14-я запись содержит номер блока в котором содержится таблица номеров блоков, в которых содержаться таблицы блоков занимаемых файлом... так что размер файла практически ничто не ограничивает.

Первые 10 inode зарезервированы для специфического использования.

Для корневой директории в этой файловой системе не отводится заранее отведенного места. Любая, в том числе и корневая директория в этой файловой системе является по сути своей обыкновенным файлом. Но для облегчения поиска корневой директории для нее зарезервирован inode номер 2.

В этой файловой системе в отличие от FAT существуют методы защиты файлов, которые обеспечиваются указанием идентификаторов пользователя и группы, а так же правами доступа, которые указываются в inode в поле i_mode.

За счет нескольких групп блоков уменьшается перемещение головки носителя при обращении к файлам, что увеличивает скорость обращения и уменьшает износ носителя. Да и сама файловая система организована так, что для чтения файлов не требуется загрузка больших объемов служебной информации, Что тоже не может не сказаться на производительности.

Примерно так же устроены файловые системы FFS, HPFS, NTFS. Но в их устройство я не буду вдаваться. И так уже выпуск очень большой получается.

Но в недавнее время появился еще один тип файловых систем. Эти системы унаследовали некоторые черты от баз данных и получили общее название "Журналируемые файловые системы". Особенность их заключается в том что все действия, производимые в файловой системе фиксируются в журнале, который правда съедает некоторый объем диска, но это позволяет значительно повысит надежность систем. В случае сбоя проверяется состояние файловой системы и сверяется с записями в журнале. В случае обнаружения несоответствий довести операцию до конца не составляет проблем, и отпадает необходимость в ремонте файловой системы. К таким файловым системам относятся ext3fs, RaiserFS и еще некоторые.

Последний раз редактировалось z01b; 29.04.2008 в 14:27..
 
Ответить с цитированием

  #10  
Старый 28.04.2008, 03:44
Аватар для z01b
z01b
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
Провел на форуме:
2360904

Репутация: 1393


По умолчанию

[09] - чтение ext2fs

Чтение ext2fs

В прошлом выпуске я описывал структуру этой файловой системы. Как вы поняли, (я надеюсь) в файловой системе присутствует Super Block и дескрипторы групп. Эта информация хранится в начале раздела. Super Block во 2-м килобайте, дескрипторы групп - в третьем.
Стоит заметить, что первый килобайт для нужд файловой системы не используется и может быть целиком использован для boot sector'а (правда он уже будет не сектор, а килобайт . Но для этого следует подгрузить второй сектор boot'а.
А для инициализации файловой системы нам нужно загрузить super block и дескрипторы групп, они же понадобятся нам для работы с файловой системой.
Это все можно загрузить одновременно, как мы и сделаем.
Код:
        mov     ax, 0x7e0
        mov     es, ax
        mov     ax, 1
        mov     cx, 5
        call    load_block
Для этого мы используем уже знакомую процедуру загрузки блока, но эта процедура станет значительно короче, потому что никаких процентов мы больше не будем выводить.
В es засылается адрес, следующий за загруженным загрузочным сектором (Загружается он, как мы помним, по адресу 7c00h, и имеет длину 200h байт, следовательно свободная память начинается с адреса 7e00h, а сегмент для этого адреса равен 7e0h). В ax засылается номер сектора с которого начинается блок (в нашем случае это первый сектор, загрузочный сектор является нулевым). в cx засылается длина загружаемых данных в секторах (1 - дополнительная часть boot sector'а, 2 - Super Block ext2, 2 - дескрипторы групп. Всего 5 секторов).

Теперь вызовем процедуру инициализации файловой системы. Эта процедура достаточно проста, и проверяет только соответствие magic номера файловой системы и вычисляет размеры блока для работы.
Код:
sb      equ     0x8000

ext2_init:
        pusha
        cmp     word [sb + ext2_sb.magic], 0xef53
        jz      short .right

        mov     si, bad_sb
        call    outstring

        popa
        stc
        ret

bad_sb: db 'Bad ext2 super block!', 0ah, 0dh, 0
В случае несоответствия magic номера происходит вывод сообщения об ошибке и выход из подпрограммы. Чтобы сигнализировать об ошибке используется бит C регистра flags.
Код:
 .right:
        mov     ax, 1024
        mov     cl, [sb + ext2_sb.log_block_size]
        shl     ax, cl
        mov     [block_size], al        ; Размер блока в байтах
        shr     ax, 2
        mov     [block_dword_size], ax  ; Размер блока в dword
        shr     ax, 2
        mov     [block_seg_size], ax    ; Размер блока в параграфах
        shr     ax, 5
        mov     [block_sect_size], ax   ; Размер блока в секторах
        popa
        clc
        ret

block_size:             dw 1024
block_dword_size:       dw  256
block_seg_size:         dw 64
block_sect_size:        dw 2
Все эти значения нам понадобятся для работы. А теперь рассмотрим процедуру загрузки одного блока файловой системы.
Код:
ext2_load_block:
        pusha

        mov     cx, [block_sect_size]
        mul     cx
        call    load_block

        mov     ax, es
        add     ax, [block_seg_size]
        mov     es, ax ; смещаем es

        popa
        ret
При входе в эту процедуру ax содержит номер блока (блоки нумеруются с нуля), es содержит адрес памяти для загрузки содержимого блока.
Номер блока нам надо преобразовать в номер сектора, для этого мы умножаем его на длину блока в секторах. А в cx у нас уже записана длина блока в секторах, то есть все готово для вызова процедуры load_block.
После считывания блока мы модифицируем регистр es, чтобы последующие блоки грузить следом за этим... в принципе модифицирование указателя можно перенести в другое место, в процедуру загрузки файла, это будет наверное даже проще и компактнее, но сразу я об этом не подумал.

Но пошли дальше... основной структурой описывающей файл в ext2fs является inode. Inode храняться в таблицах, по одной таблице на каждую группу. Количество inode в группе зафиксировано в супер блоке. Итак, процедура загрузки inode:
Код:
ext2_get_inode:
        pusha
        push    es

        dec     ax
        xor     dx, dx
        div     word [sb + ext2_sb.inodes_per_group]
Поделив номер inode на количество inode в группе, в ax мы получаем номер группы, в которой находится inode, в dx получаем номер inode в группе.
Код:
        shl     ax, gd_bit_size
        mov     bx, ax
        mov     bx, [gd + bx + ext2_gd.inode_table]
ax умножаем на размер записи о группе (делается это сдвигом, но, по сути, то же самое умножение) и получаем смещение группы в таблице дескрипторов групп. gd - базовый адрес таблицы групп. Последняя операция извлекает из дескриптора группы адрес таблицы inode этой группы (адрес задается в блоках файловой системы) который у нас пока будет храниться в bx.
Код:
        mov     ax, dx
        shl     ax, inode_bit_size
Теперь разберемся с inode. Определим его смещение в таблице inode группы.
Код:
        xor     dx, dx
        div     word [block_size]
        add     ax, bx
Поделив это значение на размер блока мы получим номер блока относительно начала таблицы inode (ax), и смещение inode в блоке (dx). К номеру блока (bx) прибавим блок, в котором находится inode.
Код:
        mov     bx, tmp_block >> 4
        mov     es, bx
        call    ext2_load_block
Загрузим этот блок в память.
Код:
        push    ds
        pop     es

        mov     si, dx
        add     si, tmp_block
        mov     di, inode
        mov     cx, ext2_i_size >> 1
        rep     movsw
Восстановим содержимое сегментного регистра es и перепишем inode из блока в отведенное для него место.
Код:
        pop     es
        popa
        ret
Inode загружен. Теперь по нему можно загружать файл. Здесь все не столь однозначно. Процедура загрузки файла состоит из нескольких модулей. Потому что помимо прямых ссылок inode может содержать косвенные ссылки на блоки. В принципе можно ограничить возможности считывающей подпрограммы необходимым минимумом, полная поддержка обеспечивает загрузку файлов до 4 гигабайт размером. Естественно в реальном режиме мы такими файлами оперировать не сможем, да это и не нужно. Но сейчас мы рассмотрим полную поддержку:
Код:
ext2_load_inode:
        pusha

        xor     ax, ax
        mov     si, inode + ext2_i.block

        mov     cx, EXT2_NDIR_BLOCKS
        call    dir_blocks

        cmp     ax, [inode + ext2_i.blocks]
        jz      short .exit
В inode храняться прямые ссылки на 12 блоков файловой системы. Такие блоки мы загружаем с помощью процедуры dir_blocks (она будет описана ниже). Данный этап может загрузить максимум 12/24/48 килобайт файла (в зависимости от размера блока fs 1/2/4 килобайта). После окончания работы процедуры проверяем, все ли содержимое файла уже загружено или еще нет. Если нет, то загрузка продолжается по косвенной таблице блоков. Косвенная таблица - это отдельный блок в файловой системе, который содержит в себе таблицу блоков.
Код:
        mov     cx, 1
        call    idir_blocks

        cmp     ax, [inode + ext2_i.blocks]
        jz      short .exit
В inode только одна косвенная таблица первого уровня (cx=1). Для загрузки блоков из такой таблицы мы используем процедуру idir_blocks. Это позволяет нам, в зависимости от размера блока загрузить 268/1048/4144 килобайта файла. Если файл еще не загружен до конца, то используется косвенная таблица второго уровня.
Код:
        mov     cx, 1
        call    ddir_blocks

        cmp     ax, [inode + ext2_i.blocks]
        jz      short .exit
В inode также только одна косвенная таблица второго уровня (cx=1). Для загрузки блоков из такой таблицы мы используем процедуру ddir_blocks. Это позволяет нам, в зависимости от размера блока загрузить 64.2/513/4100 мегабайт файла. Если файл опять не загружен до конца (где же столько памяти взять??), то используется косвенная таблица третьего уровня. Ради этого мы уже не будем вызывать подпрограмм, а обработаем ее в этой процедуре.
Код:
        push    ax
        push    es

        mov     ax, tmp3_block >> 4
        mov     es, ax
        lodsw
        call    ext2_load_block

        pop     es
        pop     ax

        mov     si, tmp3_block
        mov     cx, [block_dword_size]
        call    ddir_blocks
В inode и эта таблица присутствует только в одном экземпляре (куда же больше?). Это, крайняя возможность, позволяет нам, в зависимости от размера блока, загрузить 16/256.5/4100 гигабайт файла. Что уже является пределом даже для размера файловой системы (4 терабайта).
Код:
 .exit:
        popa
        ret
Конечно, такие крайности нам при старте будут не к чему, с учетом, что мы находимся в реальном режиме и не можем адресовать больше ~600к памяти.
Кратко рассмотрю вспомогательные функции:
Код:
dir_blocks:
 .repeat:
        push    ax
        lodsw
        call    ext2_load_block
        add     si, 2
        pop     ax

        inc     ax
        cmp     ax, [inode + ext2_i.blocks]
        jz      short .exit

        loop    .repeat
 .exit:
 ret
Эта функция загружает прямые блоки. Ради простоты я пока не обрабатывал блоки номер которых превышает 16 бит. Это создает ограничение на размер файловой системы в 65 мегабайт, а реально еще меньше, поскольку load_block у нас тоже не оперирует с секторами, номер которых больше 16 бит, ограничение по размеру уменьшается до 32 мегабайт. В дальнейшем эти ограничения мы конечно обойдем, а пока достаточно.
В этой функции стоит проверка количества загруженных блоков, для того чтобы вовремя выйти из процедуры считывания.
Код:
idir_blocks:
 .repeat:
        push    ax
        push    es

        mov     ax, tmp_block >> 4
        mov     es, ax
        lodsw
        call    ext2_load_block

        add     si, 2
        pop     es
        pop     ax

        push    si
        push    cx

        mov     si, tmp_block
        mov     cx, [block_dword_size]
        call    dir_blocks

        pop     cx
        pop     si

        cmp     ax, [inode + ext2_i.blocks]
        jz      short .exit

        loop    .repeat
 .exit:
        ret
Эта функция обращается в свою очередь к функции dir_blocks, предварительно загрузив в память содержимое косвенного блока. так же имеет контроль длины файла.
Функция ddir_blocks в точности аналогична этой, только для считывания вызывает не dir_blocks, а idir_blocks, поскольку адреса блоков в ней дважды косвенны.

Но мы еще не рассмотрели самого главного. Процедуры, которая по пути файла может загрузить его с диска. Начнем.
Код:
ext2_load_file:
        pusha

        cmp     byte [si], '/'
        jnz     short .error_exit
Если путь файла не начинается со слэш, то это в данном случае является ошибкой. Мы не оперируем понятием текущий каталог!
Код:
        mov     ax, INODE_ROOT ; root_inode
        call    ext2_get_inode
Загружаем корневой inode - он имеет номер 2.
Код:
 .cut_slash:
        cmp     byte [si], '/'
        jnz     short .by_inode

        inc     si
        jmp     short .cut_slash
Уберем лидирующий слэш... или несколько слэшей, такое не является ошибкой.
Код:
 .by_inode:
        push    es
        call    ext2_load_inode
        pop     es
Загрузим содержимое файла. Директории, в том числе и корневая, являются такими же файлами, как и все остальные, только содержат в себе записи о находящихся в директории файлах.
Код:
        mov     ax, [inode + ext2_i.mode]
        and     ax, IMODE_MASK
        cmp     ax, IMODE_REG
        jnz     short .noreg_file
По inode установим тип файла.
Если файл не регулярный, то это может быть директорией. Это проконтролируем ниже.
Код:
        cmp     byte [si], 0
        jnz     short .error_exit
Если это файл, который нам надлежит скачать - то в [si] будет содержаться 0, означающий что мы обработали весь путь.
Код:
 .ok_exit:
        clc
        jmp     short .exit
А поскольку содержимое файла уже загружено, то можем со спокойной совестью вернуть управление. Битом C сообщив, что все закончилось хорошо.
Код:
 .noreg_file:
        cmp     ax, IMODE_DIR
        jnz     short .error_exit
Если этот inode не является директорией, то это или не поддерживаемый тип файла или ошибка в пути.
Код:
        mov     dx, [inode + ext2_i.size]
        xor     bx, bx
Если то, что мы загрузили, является директорией, то со смещения 0 (bx) в этом файле содержится список записей о файлах. Нам нужно выбрать среди них нужную. В dx сохраним длину файла, по ней будем определять коней директории.
Код:
 .walk_dir:
        lea     di, [es:bx + ext2_de.name]
        mov     cx, [es:bx + ext2_de.name_len]  ; длина имени

        push    si
        repe    cmpsb

        mov     al, [si]
        pop     si

        test    cx, cx
        jnz     short .notfind
Сравниваем имена из директории с именем, на которое указывает si. Если не совпадает - перейдем на следующую запись (чуть ниже)
Код:
        cmp     al, '/'
        jz      short .normal_path

        test    al, al
        jnz     short .notfind
Если совпал, то в пути после имени должно содержаться либо '/' либо 0 - символ конца строки. Если это не так, значит это не подходящий файл.
Код:
 .normal_path:
        mov     ax, [es:bx + ext2_de.inode]
        call    ext2_get_inode
Загружаем очередной inode.
Код:
        add     si, [es:bx + ext2_de.name_len]
        cmp     byte [si], '/'
        jz      short .cut_slash
        jmp     short .by_inode
И переходим к его обработке. Это продолжается до тех пор, пока не пройдем весь путь.
Код:
 .notfind:
        sub     dx, [es:bx + ext2_de.rec_len]
        add     bx, [es:bx + ext2_de.rec_len]

        test    dx, dx
        jnz     short .walk_dir
Если путь не совпадает, и если в директории еще есть записи - продолжаем проверку.
Код:
 .error_exit:
        mov     si, bad_dir
        call    outstring
        stc
Иначе выводим сообщение об ошибке
Код:
 .exit:
        popa
        ret
Вот и весь алгоритм. Не смотря на большой размер этого повествования, код занимает всего около 450 байт. А если убрать параноидальные функции, то и того меньше.

Последний раз редактировалось z01b; 29.04.2008 в 14:27..
 
Ответить с цитированием
Ответ



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
Объясните мне пожалуйста с нуля LoneWolf666 Уязвимости 12 12.02.2006 22:56



Здесь присутствуют: 1 (пользователей: 0 , гостей: 1)
 


Быстрый переход




ANTICHAT.XYZ