Подсистема I/O
В соответствии с алгоритмом, подсистема I/O выполняет инициализацию драйвера блочного устройства, и в дальнейшем принимает запросы драйвера файловой системы на чтение/запись данных на устройство. Во время инициализации соответствующий драйвер заполняет таблицу блочных устройств, которая представляет собой массив структур:
static struct blkdev_struct blkdev[MAX_BLKDEV],
где MAX_BLKDEV - число элементов в таблице блочных устройств, и, соответственно, количество блочных устройств, которое можно подключить к системе:
#define MAX_BLKDEV 256
Элемент таблицы блочных устройств представляет собой структуру следующего вида:
struct blkdev_struct {
const char name[20];
int (*dev_request)(u32, u8, u32, u32, unsigned char *);
};
Назначение полей структуры struct blkdev_struct:
- const char name[20] - имя драйвера блочного устройства
- int (*dev_request)(u32, u8, u32, u32, unsigned char *) - адрес функции-диспетчера драйвера блочного устройства.
Таблица блочных устройств проиндексирована при помощи старшего номера устройства. Для ATA-устройств старший номер равен 5:
#define MAJOR_ATA 5
Процедура инициализации выполняется путем вызова функции blkdev_init():
int blkdev_init()
{
if(hd_init() != MAJOR_ATA) {
printf("init error\n");
return -1;
}
return 0;
}
Во время инициализации вызывается функция hd_init(), находящаяся в теле драйвера. Эту функцию мы уже практически полностью рассмотрели, за исключением функции reg_blkdev - функции регистрации драйвера устройства в системе:
int reg_blkdev(u32 major,const char *name,
int (*dev_req)(u32, u8, u32, u32, unsigned char *))
{
blkdev[major].name = name;
blkdev[major].dev_request = dev_req;
return major;
}
Параметры вызова функции мы уже рассмотрели. Эта функция заполняет соответствующий элемент таблицы блочных устройств, и, тем самым, у нас появляется возможность обратиться к функции-диспетчеру драйвера ATA-устройства.
Эту возможность реализует функция blkdev_io():
int blkdev_io(u32 major, u32 minor, u8 cmd, u32 start_sect, u32 count, u8 *buff)
{
if(blkdev[major].dev_request(minor, cmd, start_sect, count, buff) < 0) return -1;
return 0;
}
Параметры функции blkdev_io():
- u32 major - старший номер устройства, и, соответственно, индекс в таблице блочных устройств;
- u32 minor - младший номер, определяет номер устройства и номер раздела на устройстве;
- u8 cmd - команда, посылаемая устройству;
- u32 start_sect - адрес стартового сектора для чтения(записи);
- u32 count - число секторов для чтения(записи);
- u8 *buff - указатель на буфер для данных;
Перед выполнением операций чтения/записи данных на раздел устройства сперва необходимо получить характеристики раздела, такие как размер блока на разделе и количество этих блоков. Для этого устройству посылается команда STAT при помощи функции stat_blkdev():
int stat_blkdev(u32 major, u32 minor, u8 *buff)
{
if(blkdev_io(major,minor,STAT,0,0,buff) < 0) return -1;
return 0;
}
Получив характеристики раздела устройства, можно приступать к чтению/записи данных. Функция read_blkdev(), которую мы сейчас рассмотрим, выполняет чтение данных с раздела жесткого диска. Одновременно эта функция является точкой входа для драйвера файловой системы.
int read_blkdev(u32 major, u32 minor, u64 start, u64 count, u8 *buff)
{
Параметрами функции являются старший и младший номер устройства, смещение к данным на разделе в байтах (т.к. драйвер ФС "видит" раздел как последовательность байт), число байт для считывания и указатель на буфер, куда будут помещены считанные данные.
Так как драйвер жесткого диска считывает информацию блоками, то необходимо преобразовать величину смещения в номер блока на устройстве, и при этом нет никаких гарантий, что смещение к данным попадет точно на границу блока. Поэтому алгоритм считывания данных следующий с раздела жесткого диска следующий:
- определяется номер блока, в который "попадает" величина смещения, количество блоков для чтения, и эти блоки считываются в дисковый кеш;
- определяется величина смещения к данным в кеше, и эти данные копируются в область памяти, на которую указывает последний параметр вызова функции read_blkdev().
Весь этот процесс показан на рис. 2.
Определим необходимые переменные:
u32 start_lba, // стартовый сектор для чтения
s_count, // число секторов для чтения
start_block, // стартовый блок для чтения (0,1,2, ...)
end_block, // конечный блок для чтения. Может быть равен стартовому
tail, // смещение к данным в буферном кеше
num_block; // число блоков для считывания
device_info_t dev_i;
u8 *cache_buff; // указатель на начало буферного кешв
Получаем характеристики раздела:
if(stat_blkdev(major,minor,(u8 *)&dev_i) < 0) return -1;
Вычисляем номера стартового и конечного блока, смещение к данным и число блоков для считывания:
start_block = start/dev_i.block_size;
end_block = (start+count)/dev_i.block_size;
tail = start%dev_i.block_size;
num_block = (end_block - start_block) + 1;
Выведем отладочную информацию:
printf("Размер блока - %d байт\n",dev_i.block_size);
printf("Число блоков на устройстве (разделе) - %d\n",dev_i.blocks_num);
printf("Стартовый блок на разделе - %d\n",start_block);
printf("Смещение к данным в буферном кеше, байт - %d\n",tail);
printf("Число блоков для чтения - %d\n\n",num_block);
Выделяем память для буферного кеша:
cache_buff = (u8 *)malloc(num_block * dev_i.block_size);
memset(cache_buff,0,num_block * dev_i.block_size);
Теперь необходимо определить номер стартового сектора на устройстве, с которого начинать считывать данные, и количество секторов для считывания:
start_lba = block_to_lba(start_block,minor);
if(start_lba < 0) return -1;
s_count = num_block * (BLK_SIZE/BYTE_PER_SECT);
printf("Стартовый сектор для чтения на устройстве - %d\n",start_lba);
printf("Число секторов для чтения - %d\n\n",s_count);
И вот теперь вызываем функцию-диспетчер соответствующего блочного устройства (жесткого диска), передав ей команду для выполнения и необходимые параметры:
if(blkdev_io(major,minor,READ,start_lba,s_count,cache_buff) < 0) return -1;
В область памяти, на которую указывает buff (параметр вызова функции read_blkdev()), скопируем данные из буферном кеше.
memcpy(buff, cache_buff+tail, count);
Очищаем кеш и возвращаемся из функции:
free(cache_buff);
return 0;
}
Пересчет номера блока на устройстве в стартовый номер логического сектора выполняет функция block_to_lba():
u32 block_to_lba(u32 start_block, int minor)
{
u32 lba;
u8 dev = GET_DEV(minor);
u16 part = GET_PART(minor);
if((start_block < 0) (part > 4)) return -1;
lba = start_block * (BLK_SIZE/BYTE_PER_SECT);
if(part != 0) lba += DEV_PT(dev,(part-1)).sect_before;
return lba;
}
Перед тем, как приступить к рассмотрению драйвера файловой системы ext2, необходимо познакомиться с самой файловой системой, с её логической структурой. Об этом читайте во второй части статьи.