Файловая система EXT2

       

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



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


Основным заголовочным файлом является <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;
}

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


Содержание раздела