Тема: ОС с нуля
Показать сообщение отдельно

  #10  
Старый 28.04.2008, 03:44
z01b
Постоянный
Регистрация: 05.01.2007
Сообщений: 508
С нами: 10182506

Репутация: 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..
 
Ответить с цитированием