Rambler's
Top100

Файловая система EXT2. Часть 2

Автор: uncle Bob
Дата: 22.12.2003
Раздел: Низкоуровневое программирование в Linux

ФАЙЛОВАЯ СИСТЕМА EXT2

ЧАСТЬ 2

6. Структура файловой системы ext2

6.1. Основные компоненты файловой системы ext2

Как и в любой файловой системе UNIX, в составе файловой системы ext2 можно выделить следующие составляющие:
- блоки и группы блоков;
- информационный узел (information node);
- суперблок (superblock);

Блоки и группы блоков

Все пространство раздела диска разбивается на блоки фиксированного размера, кратные размеру сектора - 1024, 2048 и 4096 байт. Размер блока указывается при создании файловой системы на разделе диска. Меньший размер блока позволяет экономить место на жестком диске, но также ограничивает максимальный размер файловой системы. Все блоки имеют порядковые номера. С целью уменьшения фрагментации и количества перемещений головок жесткого диска при чтении больших массивов данных блоки объединяются в группы.

Информационный узел

Базовым понятием файловой системы является информационный узел, information node, или inode. Это специальная структура, которая содержит информацию об атрибутах и физическом расположении файла. Атрибутами файла являются его тип (обычный файл, каталог и т.д.), права доступа к нему, идентификатор владельца, размер, время создания. Информация о физическом расположении представляет собой последовательность абсолютных номеров блоков, содержащих данные файла.

Суперблок

Суперблок - основной элемент файловой системы ext2. Он содержит следующую информацию о файловой системе (список не полный):
- общее число блоков и inode-ов в файловой системе
- число свободных блоков и inode-ов в файловой системе
- размер блока файловой системы
- количество блоков и inode-ов в группе
- размер inode-а
- идентификатор файловой системы
- номер первого блока данных. Другими словами, это номер блока, содержащего суперблок. Этот номер всегда равен 0, если размер блока файловой системы больше 1024 байт, и 1, если размер блока равен 1024 байт

От целосности суперблока напрямую зависит работоспособность файловой системы. Операционная система создает несколько резервных копий суперблока для возможности его восстановления в случае повреждения. Главная копия находится по смещению 1024 байт от начала раздела, на котором создана файловая система (первые 1024 байта зарезервированы для загрузчика операционной системы). Ранние версии файловой системы ext2 создавали копии суперблока в начале каждой группы блоков. Это приводило к большим потерям дискового пространства, поэтому позже количество резервных копий суперблока было уменьшено, и для их размещения были выделены группы блоков 0, 1, 3, 5 и 7.


6.2. Формат группы блоков

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

Битовая карта занятости блоков обычно расположена в первом блоке группы. Если в группе присутствует резервная копия суперблока, битовая карта располагается во втором блоке группы. Размер битовой карты - один блок. Каждый бит этой карты обозначает состояние блока. Если бит установлен (1), то блок занят, если сброшен (0) - блок свободен. Первому блоку группы соответствует нулевой бит карты, второму блоку - первый бит и т.д.

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

Каждая группа блоков описывается при помощи дескриптора группы блоков. Дескриптор группы - это структура, которая содержит информацию о адресах битовой карты занятости блоков, битовой карты занятости inode-ов и таблицы inode-ов соответствующей группы. Все дескрипторы групп собраны в таблицу дескрипторов групп, которая хранится в группе блоков 0. Также, как и для суперблока, операционная система создает резервные копии таблицы дескрипторов групп.


6.3. Алгоритм чтения файла

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

Как было сказано выше, информация о физическом расположении файла содержится в inode. Эта информация представляет собой последовательность 32-х битных номеров блоков, содержащих данные файла. Первые 12 номеров - это прямые ссылки на информационные блоки (direct blocks number). 13-й номер является косвенной ссылкой (indirect blocks number). В нём находится адрес блока, в котором хранятся адреса информационных блоков. 14-й номер - двойная косвенная ссылка (double blocks number), 15-й номер - тройная косвенная ссылка (triple blocks number).

Имя файла в состав inode не входит, установление соответствия между именами файлов и порядковыми номерами inode-ов выполняется через каталоги.

Каталоги

Файлы в UNIX- и POSIX-системах хранятся в древовидной иерархической файловой системе. Корень файловой системы - это корневой каталог, обозначенный символом "/". Каждый промежуточный узел в дереве файловой системы - это каталог. Конечные вершины дерева файловой системы являются либо пустыми каталогами, либо файлами. Абсолютное путевое имя файла состоит из имен всех каталогов, ведущих к указанному файлу, начиная с корневого каталога. Так, путевое имя /home/test.file означает, что файл test.file расположен в каталоге home, который, в свою очередь, находится в корневом каталоге "/".

Каталог, также как и файл, описывается при помощи inode. Содержимое каталога представляет собой массив записей, каждая из которых содержит информацию о файле, который находятся "внутри" текущего каталога.
Запись каталога имеет следующий формат:
- порядковый номер inode файла
- длина записи в байтах
- имя файла
- длина имени файла

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

Несколько первых номеров inode зарезервированы файловой системой, их перечень содержится в заголовочном файле <linux/ext2_fs.h>:

/*
* Special inode numbers
*/
#define EXT2_BAD_INO 1 /* Bad blocks inode */
#define EXT2_ROOT_INO 2 /* Root inode */
#define EXT2_ACL_IDX_INO 3 /* ACL inode */
#define EXT2_ACL_DATA_INO 4 /* ACL inode */
#define EXT2_BOOT_LOADER_INO 5 /* Boot loader inode */
#define EXT2_UNDEL_DIR_INO 6 /* Undelete directory inode */

Для записи корневого каталога зарезервирован inode под номером 2 (root inode). Этот inode находится в группе блоков 0 и занимает вторую позицию в таблице inode-ов этой группы. Номер первого незарезервированного inode хранится в суперблоке.

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

Номер группы блоков, в которой расположен inode, вычисляется по формуле:

group = (inode_num - 1) / inodes_per_group,

где group - искомый номер группы блоков
inode_num - порядковый номер inode, определяющего файл
inodes_per_group - число inode-ов в группе (эта информация находится в суперблоке).

Позиция inode-а в таблице inode-ов группы определяется по формуле:

index = (inode_num - 1) % inodes_per_groupe,

где index - позиция inode-а в таблице


Рассмотрим пример получения содержимого файла test.file, находящегося в корневом каталоге. Для чтения файла /test.file необходимо:

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

Этапы 1-6 - чтение корневого каталога:

1. Из группы блоков 0 считывается таблица дескрипторов групп
2. Из таблицы дескрипторов групп извлекается дескриптор группы блоков 0 и из него считывается адрес таблицы inode-ов группы 0
3. Из группы блоков 0 считывается таблица inode-ов
4. Порядковый номер inode корневого каталога фиксирован и равен 2, поэтому из таблицы inode-ов группы 0 считывается второй элемент, который содержит адрес блока с содержимым корневого каталога. Предположим, что этот блок расположен в группе блоков A
5. Из группы блоков A считывается блок, содержащий записи корневого каталога.
6. Выполняется поиск записи с именем "test.file". Если такая запись найдена, из нее извлекается порядковый номер inode файла "test.file".

Определив номер inode, можно получить доступ к информационным блокам файла (этапы 7-11):

7. Вычисляется номер группы, в которой находится данный inode, и его позиция в таблице inode-ов группы (предположим, что номер группы равен B, а позиция в таблице - X)
8. Из таблицы дескрипторов групп извлекаем дескриптор группы блоков B и из него считывается адрес таблицы inode-ов этой группы блоков
9. Из группы блоков B считывается таблица inode-ов
10. Из таблицы inode-ов группы блоков B считывается inode, находящийся в позиции X
11. Из считанного inode извлекается адреса блока с содержимым файла /test.file и выполняется чтение информации из блока с указанным адресом


Структурные типы, описывающие основные компоненты файловой системы ext2 - суперблок, дескриптор группы блоков, информационный узел, запись каталога - определены в заголовочном файле <linux/ext2_fs.h>. Рассмотрим кратко поля, которые входят в каждую из этих структур.

1. Структура суперблока struct ext2_super_block

__u32 s_inodes_count - общее число inode-ов в файловой системе
__u32 s_blocks_count - общее число блоков в файловой системе
__u32 s_free_blocks_count - количество свободных блоков
__u32 s_free_inodes_count - количество свободных inode-ов
__u32 s_first_data_block - номер первого блока данных (номер блока, в котором находится суперблок)
__u32 s_log_block_size - это значение используется для вычисления размера блока. Размер блока определяется по формуле: block size = 1024 << s_log_block_size
__u32 s_blocks_per_group - количество блоков в группе
__u32 s_inodes_per_group - количество inode-ов в группе
__u16 s_magic - идентификатор файловой системы ext2 (сигнатура 0xEF53)
__u16 s_inode_size - размер информационного узла (inode)
__u32 s_first_ino - номер первого незарезервированного inode


2. Структура дескриптора группы блоков struct ext2_group_desc

__u32 bg_block_bitmap - битовая карта занятости блоков группы
__u32 bg_inode_bitmap - битовая карта занятости inode-ов группы
__u32 bg_inode_table - адрес таблицы inode-ов группы


3. Структура инфрмационного узла struct ext2_inode

__u16 i_mode - тип файла и права доступа к нему. Тип файла определяют биты 12-15 этого поля:
- 0xA000 - символическая ссылка
- 0x8000 - обычный файл
- 0x6000 - файл блочного устройства
- 0x4000 - каталог
- 0x2000 - файл символьного устройства
- 0x1000 - канал FIFO

__u32 i_size - размер в байтах
__u32 i_atime - время последнего доступа к файлу
__u32 i_ctime - время создания файла
__u32 i_mtime - время последней модификации
__u32 i_blocks - количество блоков, занимаемых файлом
__u32 i_block[EXT2_N_BLOCKS] - адреса информационых блоков (включая все косвенные ссылки)


Значение EXT2_N_BLOCKS также определено в файле <linux/ext2_fs.h>:

/*
* Constants relative to the data blocks
*/
#define EXT2_NDIR_BLOCKS 12
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)


4. Структура записи каталога struct ext2_dir_entry_2

#define EXT2_NAME_LEN 255
__u32 inode - номер inode-а файла
__u16 rec_len - длина записи каталога
__u8 name_len - длина имени файла
char name[EXT2_NAME_LEN] - имя файла


Ознакомившись со структурой файловой системы ext2, можно приступить к рассмотрению программной реализации драйвера файловой системы.


7. Драйвер файловой системы

Начнем с описания заголовочных файлов и переменных.

Основным заголовочным файлом является <linux/ext2_fs.h>. С его составом мы только что ознакомились, теперь займемся переменными.

Введем временное ограничение на размер данных, считываемых из файла:
#define TEMP_SIZE_LIMIT 49152 // 12 блоков по 4096 байт

Определим структуру, описывающую суперблок:
struct ext2_super_block sb;

Размер блока файловой системы:
int BLKSIZE;

Буфер для хранения таблицы дескрипторов групп:
unsigned char buff_grp[4096];

Информационный буфер:
unsigned char buff[4096];

u32 major = 0; // старший номер устройства
u32 minor = 0; // младший номер устройства
u64 start = 0; // смещение к данным на разделе устройства
u64 count = 0; // размер блока считываемых данных


Рассмотрим несколько вспомогательных функций, которые нам понадобятся для работы:

Функция set_perm() запрашивает у системы разрешение доступа к портам ATA-контроллера
void set_perm()
{
ioperm(CH0,8,1);
ioperm(CH1,8,1);
ioperm(CH2,8,1);
ioperm(CH3,8,1);

return;
}

Функция release_perm() забирает у нас эти права:
void release_perm()
{
ioperm(CH0,8,0);
ioperm(CH1,8,0);
ioperm(CH2,8,0);
ioperm(CH3,8,0);

return;
}


Рассмотрим функция, выполняющую чтение суперблока:
void read_sb()
{

Задаем смещение к суперблоку и его размер:
start = 1024;
count = sizeof(sb);

Смещение к суперблоку равно 1024, т.к. первые 1024 байт на разделе с файловой системой ext2 зарезервированы для загрузчика.

memset(&sb,0,1024);

Для считывания данных обращаемся к подсистеме ввода/вывода, задав все необходимые параметры:
if(read_blkdev(major,minor,start,count,(u8 *)&sb) < 0) {
printf("Error read superblock\n");
exit(-1);
}

В результате структура sb будет содержать суперблок.

Проверяем идентификатор файловой системы (MAGIC-номер):
if(sb.s_magic != EXT2_SUPER_MAGIC) {
perror("magic");
exit(-1);
}

Если все в порядке и включен режим отладки, выведем информацию о файловой системе:
#ifdef DEBUG
printf("\nSuperblock info\n-----------\n");
printf("Inodes count\t\t-\t%u\n",sb.s_inodes_count);
printf("Blocks count\t\t-\t%u\n",sb.s_blocks_count);
printf("Block size\t\t-\t%u\n",1024 << sb.s_log_block_size);
printf("First inode\t\t-\t%d\n",sb.s_first_ino);
printf("Magic\t\t\t-\t0x%X\n",sb.s_magic);
printf("Inode size\t\t-\t%d\n",sb.s_inode_size);
printf("Inodes per group\t-\t%u\n",sb.s_inodes_per_group);
printf("Blosks per group\t-\t%u\n",sb.s_blocks_per_group);
printf("First data block\t-\t%u\n\n",sb.s_first_data_block);
#endif

return;
}

Функция read_gd() выполняет чтение дескрипторов групп:
void read_gd()
{

Определяем размер блока файловой системы:
BLKSIZE = 1024 << sb.s_log_block_size;

Вычисляем смещение к блоку, в котором находятся дескрипторы групп:
start = (sb.s_first_data_block + 1) * BLKSIZE;
count = BLKSIZE;

Считываем дескрипторы групп:
if(read_blkdev(major,minor,start,count,buff_grp) < 0) {
printf("Error read group descriptor table\n");
exit(-1);
}

return;
}

Следующая функция получает содержимое inode по его номеру:
void get_inode(int i_num, struct ext2_inode *in)
{

У функции только один параметр - i_num, номер inode файла

Структура дескриптора группы:
struct ext2_group_desc gd;
u64 group, index;

Вычисляем по формуле, приведенной в пункте 6.3, номер группы, в которой находится inode под номером i_num:
group = (i_num - 1) / sb.s_inodes_per_group;

Копируем этот дескриптор в структуру gd:
memset((void *)&gd, 0, sizeof(gd));
memcpy((void *)&gd, buff_grp + group*(sizeof(gd)), sizeof(gd));

Вычисляем по формуле из п. 6.3 позицию inode-а в таблице inode-ов данной группы:
index = (i_num - 1) % sb.s_inodes_per_group;

Вычисляем смещение к искомому inode-у и считываем его в структуру in:
start = (u64)(gd.bg_inode_table) * BLKSIZE + index * sb.s_inode_size;
count = sb.s_inode_size; // размер inode

if(read_blkdev(major,minor,start,count,(u8*)in) < 0) {
printf("Error read inode\n");
exit(-1);
}

return;
}


Чтение информационных блоков файла выполняет функция read_iblock().
void read_iblock(struct ext2_inode *i, int blknum)
{

Параметры функции:
- struct ext2_inode *i - структура, содержащая информацию inode-а соответствующего файла
- int blknum - номер блока из последовательности номеров, содержащихся в этом inode, который мы хотим прочитать

Вычисляем смещение к информационному блоку файла и считываем его:
start = (u64)(i->i_block[blknum])*BLKSIZE;
count = BLKSIZE;

if(read_blkdev(major,minor,start,count,buff) < 0) {
printf("Error read info block\n");
exit(-1);
}

return;
}


Функция get_root_dentry() читает корневой каталог
void get_root_dentry()
{
struct ext2_inode in;

Для записи корневого каталога зарезервирован inode под номером 2 (см. п. 6.3):

get_inode(EXT2_ROOT_INO, &in);
read_iblock(&in, 0);

return;
}


Получить номер inode по имени файла можно при помощи функции get_i_num:
int get_i_num(char *name)
{

Параметр функции - имя файла. Возвращаемое значение - номер inode файла.

int rec_len = 0;

Структура, описывающая формат записи корневого каталога:
struct ext2_dir_entry_2 dent;

В буфере buff находится массив записей каталога. Для определения порядкового номера inode файла необходимо найти в этом массиве запись с именем этого файла. Для этого организуем цикл:

for(;;) {

Копируем в структуру dent записи каталога:

memcpy((void *)&dent, (buff + rec_len), sizeof(dent));

#ifdef DEBUG
printf("dent.name_len - %d\n", dent.name_len);
printf("rec_len - %d\n", dent.rec_len);
printf("name - %s\n", dent.name);
#endif

Длина имени файла равная нулю означает, что мы перебрали все записи в буфере buff и записи с именем нашего файла не нашли. Значит, пора возвращаться:
if(!dent.name_len) return -1;

Поиск выполняется путем сравнения имен файлов. Если имена совпадают - выходим из цикла
if(!memcmp(dent.name, name, strlen(name))) break;

Если имена не совпали - смещаемся к следующей записи:
rec_len += dent.rec_len;
}

В случае успеха возвращаем номер inode файла:
return dent.inode;
}


Функция ata_init() выполняет запрос к подсистеме ввода/вывода с просьбой выполнить процедуру инициализации драйвера блочного устройства:
int ata_init()
{
int i = 0;
u8 dev = GET_DEV(minor); /* номер канала (0,1,2,3) */

if(blkdev_init() < 0) return -1;

if(DEV_STAT(dev) != ATA) {
perror("device type");
exit(-1);
}

#ifdef DEBUG
/* Информация об основных разделах на устройстве 0 */
for(;i < 4; i++) {
printf("\nТип %d раздела - 0x%x\n",i,DEV_PT(dev,i).type_part);
printf("Признак загрузки - 0x%x\n",DEV_PT(dev,i).bootable);
printf("Секторов в разделе %d - %d\n",i,DEV_PT(dev,i).sect_total);
printf("Размер раздела %d в блоках - %d\n",i,DEV_PT(dev,i).sect_total/(BLK_SIZE/512));
printf("Секторов перед разделом %d - %d\n\n",i,DEV_PT(dev,i).sect_before);
}
#endif

return 0;
}



"Главными воротами" драйвера файловой системы является функция ext2_read_file(). Через эту функцию драйвер взаимодействует с пользовательский приложением:

int ext2_read_file(u32 maj_num, u32 min_num, u8 *full_path, u8 *data_buff, u32 *num, u32 seek)
{

Параметры функции:
- maj_num - старший номер устройства
- min_num - младший номер устройства
- full_path - абсолютное путевое имя файла
- data_buff - буфер для данных
- num - сколько байт считывать из файла
- seek - смещение в файле

Переменные и структуры:

Структура информационного узла (inode):
struct ext2_inode in;

Буфер для временного хранения имени файла (будет нужен при разборе абсолютного путевого имени):
unsigned char tmp_buff[EXT2_NAME_LEN];

static int i = 1;
int n, i_num, outf, type;

Первым символом в абсолютном путевом имени файла должен быть прямой слэш (/). Проверяем это:

if(full_path[0] != '/') {
perror("slash");
exit(-1);
}

major = maj_num;
minor = min_num;

Запрашиваем у системы права доступа к портам ATA-контроллера:
set_perm();

Вызываем функцию инициализации драйвера ATA-устройства:
if(ata_init() < 0) {
perror("ata_init");
exit(-1);
}

Считываем суперблок и таблицу дескрипторов групп:
read_sb();
read_gd();

Получаем содержимое корневого каталога:
get_root_dentry();

#ifdef DEBUG
outf = open("root.dent",O_CREAT|O_RDWR,0600);
write(outf,buff,BLKSIZE);
close(outf);
#endif

Сейчас в буфере buff находятся все записи корневого каталога (для контроля сохраним их в отдельном файле по имени root.dent). Теперь, имея записи корневого каталога, мы можем добраться до содержимого файла test.file, используя приведенный в пункте 6.3 алгоритм чтения файла. С этой целью организуем цикл. В теле цикла проведем разбор абсолютного путевого имени файла, выделяя его элементы - подкаталоги (он у нас одни, home) и имя искомого файла (test.file). Для каждого элемента определим порядковый номер inode-а, считаем этот inode и затем получим содержимое нулевого блока (из последовательности адресных блоков, находящихся в inode-е):

while(1) {

memset(tmp_buff, 0, sizeof(tmp_buff));

for(n = 0 ; n < EXT2_NAME_LEN; n++, i++) {
tmp_buff[n] = full_path[i];
if((tmp_buff[n] == '/') || (tmp_buff[n] == '\0')) {
i++;
break;
}
}
tmp_buff[n] = '\0';

Для каждого элемента абсолютного путевого имени файла определяем порядковый номер inode-а и считываем этот inode в память:

i_num = get_i_num(tmp_buff);
if(i_num < 0) {
printf("No such file!\n");
exit(-1);
}
get_inode(i_num, &in);

Отобразим информацию о файле (имя, порядковый номер inode-а, размер файла и его тип):

#ifdef DEBUG
printf("Inode number - %u\n", i_num);
printf("File name - %s\n", tmp_buff);
printf("File size - %u\n",in.i_size);
#endif

Тип файла определяют старшие четыре бита поля i_mode структуры struct ext2_inode:

type = ((in.i_mode & 0xF000) >> 12);

Проверяем тип файла. Если это обычный файл - прерываем цикл:

Если это каталог - считываем его содержимое и продолжаем цикл:

if(type & 0x04) {
read_iblock(&in,0);
continue;
}

Если это обычный файл - считываем из файла блок данных и прерываем цикл:

if(type & 0x08) {
if(read_file_blocks(&in, data_buff, num, seek) < 0) return -1;
break;
}

}

release_perm();
return 0;
}


Функция read_file_blocks выполняет чтение информационных блоков файла:
int read_file_blocks(struct ext2_inode *in, u8 *data_buff, u32 *num, u32 seek)
{

Параметры функции:
- struct ext2_inode *in - структура, содержащая inode файла
- u8 *data_buff - буфер, куда будут считаны данные
- u32 *num - сколько байт считывать из файла. Этот параметр передается по ссылке, т.к. нам придется его подкорректировать при необходимости, и вернуть вызывающей функции измененным
- u32 seek - смещение к данным в файле

int i = 0, n = 0;
u16 start_block, end_block, num_block, tail;
u8 *cache_buff;

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

if(seek >= (in->i_size)) return -1;
if(((*num) + seek) > (in->i_size)) (*num) = (in->i_size) - seek;
if(((*num) + seek) > TEMP_SIZE_LIMIT) (*num) = TEMP_SIZE_LIMIT - seek;

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

start_block = seek/BLKSIZE;
end_block = (seek + (*num))/BLKSIZE;
tail = seek % BLKSIZE;
num_block = (end_block - start_block) + 1;

cache_buff = (u8 *)malloc(num_block * BLKSIZE);
memset(cache_buff, 0, num_block * BLKSIZE);

Считываем информационные блоки файла в буферный кеш:

i = start_block;

for(; n < num_block; n++) {
read_iblock(in,i);
memcpy((cache_buff + (n * BLKSIZE)), buff, BLKSIZE);
i += 1;
}

Копируем данные из буферного кеша:

memcpy(data_buff, (cache_buff + tail), (*num));

free(cache_buff);
return 0;
}


Итак, мы закончили рассмотрение драйвера файловой системы. Остался последний элемент схемы - приложение пользователя.


8. Приложение пользователя

Роль приложения пользователя у нас играет функция main:

int main()
{
u32 major_num = MAJOR_ATA; // старший номер устройства
u32 minor_num = 3; // младший номер устройства
u32 seek = 0; // смещение в файле
u32 num = 300; // сколько байт считываеть
u8 *data_buff; // буфер для данных
u8 *full_path = "/home/test.file"; // абсолютное путевое имя файла
int outf;

data_buff = (u8 *)malloc(num);
memset(data_buff, 0, num);

Младший номер равен 3. Это означает, что считывание информации производится с третьего основного раздела жесткого диска, который подключен как Primary Master.

Вызываем функцию драйвера файловой системы ext2 для чтения файла:
if(ext2_read_file(major_num, minor_num, full_path, data_buff, &num, seek) < 0) {
perror("ext2_read_file");
exit(-1);
}

Сохраним результат в файле:
outf = open("out",O_CREAT|O_RDWR,0600);
if(outf < 0) {
perror("open");
exit(-1);
}
if(write(outf, data_buff, num) < 0) perror("write");

close(outf);
free(data_buff);
return 0;
}


9. Сборка приложения

Каталог с исходными текстами содержит следующие файлы:
- ./include - каталог с заголовочными файлами
- blkdev_io.c - подсистема ввода/вывода
- fs.c - драйвер файловой системы
- hdd.c - драйвер жесткого диска
- read_file.c - приложение пользователя (функция main)

Также в каталоге с исходными текстами находится Makefile следующего содержания:

#####################################
INCDIR = include
.PHONY = clean
hdd: hdd.o blkdev_io.o hdd.o fs.o read_file.o
gcc -I$(INCDIR) $^ -o $@

%.o: %.c
gcc -I$(INCDIR) -DDEBUG -c $^
# gcc -I$(INCDIR) -c $^

clean:
rm -f *.o
rm -f ./hdd
rm -f ./out
######################################

Для сборки приложения введите команду make. В результате в текущем каталоге появиться исполняемый файл hdd. Далее, в каталоге /home создайте файл test.file любого содержания и введите команду sync. После этого запустите на выполнение файл hdd. В результате в текущем каталоге появиться файл out, в котором будет продублирована часть информации из файла /home/test.file, в соответствии с параметрами seek и num, заданными в функции main.


Литература:
1. "Работа с жестким диском на программном уровне", http://www.lowlevel.ru/articles/hdd_io.htm
2. А.В.Гордеев, А.Ю.Молчанов, "Системное программное обеспечение", издательский дом "Питер".
3. В.Мешков, "Архитектура файловой системы ext2", журнал "Системный администратор", ¦ 11(12), 2003 г., ЗОА "Издательский дом "Учительская газета".
Исходники к статье


[Главная] [Другие статьи] [Обсудить в форуме]
©2003-2004 Lowlevel.RU