Все выпуски  

Голландия. Информация об эмиграции


  
              Служба рассылок Subscribe.Ru проекта Citycat.Ru
-*--------------------------------------------------------------------------
                    Здравствуйте, уважаемые подписчики. 
 
----------------------------------------------------------------------------


   Прошу меня извинить за задержку с выпуском, все как-то не до
   того... дела...
     _____________________________________________________________
   
                   Выпуск No9
                                    
   Сегодняшний выпуск посвящен чтению файлов с файловой системы
   ext2fs. Эту систему мы, скорее всего, возьмем за базовую для
   начала. Как я уже говорил FAT для наших целей мало подходит. Тот
   boot sector, который я публиковал до этого - можно забыть, от него
   уже почти ничего не осталось. Связано это с тем, что мы отошли от
   linux, наша система будет совсем другой. Я, конечно, предвижу
   трудности связанные с переносом программного обеспечения. Возможно
   продумаем возможность эмуляции существующих операционных систем.
   Время покажет.
   Так же прошу меня простить, что мы занимаемся тут всякой ерундой с
   бутсекторами и файловыми системами, но до сих пор так и не начали
   писать собственно ядро. Задача эта не столь, тривиальна и нужно
   многое продумать, чтобы не было потом горько и обидно за бесцельно
   написанный код.
   
  Чтение 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 байт. А если убрать
   параноидальные функции, то и того меньше. Не стоит пытаться
   откомпилировать этот код, все эти модули вы сможете найти на нашем
   сайте, ссылка на который приведена ниже. Здесь я все это привел
   для того чтобы объяснить как и что. Надеюсь у меня это получается
   хоть как-то. Если кто-то что-то не понимает - пишите мне, мой
   адрес вы всегда можете найти чуть ниже.
   
   В следующем выпуске мы с вами рассмотрим форматы выполняемых
   файлов, используемые в unix. Это нам тоже потребуется на этапе
   загрузки.
   
   И наберитесь немного терпения... скоро мы начнем писать ядро.
Отправлено 2001-09-06 для 4463 подписчиков. 
ведущий рассылки Dron - dron@infosec.ru 
Сайт проекта - http://spawnhole.narod.ru/asmos/asmos.html 
Архив Рассылки - http://subscribe.ru/archive/comp.soft.prog.asmos 
 
При поддержке http://www.Kalashnikoff.ru 
 
---------------------------------------------------------------------------

Подписка на рассылки Subscribe.Ru:

  Операционная система "с нуля" на Ассемблере и Cи
    http://subscribe.ru/catalog/comp.soft.prog.asmos

  Ассемблер? Это просто! Учимся программировать
    http://subscribe.ru/catalog/comp.prog.assembler

  Ассемблер? Это просто! Учимся программировать (FAQ)
    http://subscribe.ru/catalog/comp.soft.prog.faq
 
---------------------------------------------------------------------------

(C)Москва, 2001. Авторское право принадлежит Валяеву А.Ю. Публичное 
размещение материала из рассылки, а также его использование полностью или 
частично в коммерческих или иных подобных целях без письменного согласия 
автора влечет ответственность за нарушение авторских прав. 
 
-*--------------------------------------------------------------------------
Отписаться: http://subscribe.ru/member/unsub?grp=comp.soft.prog.asmos

http://subscribe.ru/                                mailto:ask@subscribe.ru

  

В избранное