Библиотека сайта rus-linux.net
Драйверы устройств в Linux
Часть 15: Диск в оперативной памяти — экспериментируем с драйверами блочных устройств
Оригинал: "Device Drivers, Part 15: Disk on RAM — Playing with Block Drivers"Автор: Anil Kumar Pugalia
Дата публикации:February 28, 2012
Перевод: Н.Ромоданов
Дата перевода: июнь 2012 г.
В этой статье, которая является частью серии статей о драйверах устройств в Linux, будут проведены эксперименты с фиктивным жестким диском, расположенным в оперативной памяти, с помощью которого будет продемонстрирована работа драйверов блочных устройств.
После вкусного обеда изучение теории тянет слушателей в сон. Поэтому давайте сразу начнем с кода.
Исходный код диска в оперативной памяти
Давайте создадим директорий DiskOnRAM, в котором будут находиться следующие шесть файлов: три с исходными кодами на языке С, два с заголовочными файлами для С и один файл Makefile.
partition.h
#ifndef PARTITION_H #define PARTITION_H #include <linux/types.h> extern void copy_mbr_n_br(u8 *disk); #endif
partition.c
#include <linux/string.h> #include "partition.h" #define ARRAY_SIZE(a) (sizeof(a) / sizeof(*a)) #define SECTOR_SIZE 512 #define MBR_SIZE SECTOR_SIZE #define MBR_DISK_SIGNATURE_OFFSET 440 #define MBR_DISK_SIGNATURE_SIZE 4 #define PARTITION_TABLE_OFFSET 446 #define PARTITION_ENTRY_SIZE 16 // sizeof(PartEntry) #define PARTITION_TABLE_SIZE 64 // sizeof(PartTable) #define MBR_SIGNATURE_OFFSET 510 #define MBR_SIGNATURE_SIZE 2 #define MBR_SIGNATURE 0xAA55 #define BR_SIZE SECTOR_SIZE #define BR_SIGNATURE_OFFSET 510 #define BR_SIGNATURE_SIZE 2 #define BR_SIGNATURE 0xAA55 typedef struct { unsigned char boot_type; // 0x00 - Inactive; 0x80 - Active (Bootable) unsigned char start_head; unsigned char start_sec:6; unsigned char start_cyl_hi:2; unsigned char start_cyl; unsigned char part_type; unsigned char end_head; unsigned char end_sec:6; unsigned char end_cyl_hi:2; unsigned char end_cyl; unsigned long abs_start_sec; unsigned long sec_in_part; } PartEntry; typedef PartEntry PartTable[4]; static PartTable def_part_table = { { boot_type: 0x00, start_head: 0x00, start_sec: 0x2, start_cyl: 0x00, part_type: 0x83, end_head: 0x00, end_sec: 0x20, end_cyl: 0x09, abs_start_sec: 0x00000001, sec_in_part: 0x0000013F }, { boot_type: 0x00, start_head: 0x00, start_sec: 0x1, start_cyl: 0x0A, // extended partition start cylinder (BR location) part_type: 0x05, end_head: 0x00, end_sec: 0x20, end_cyl: 0x13, abs_start_sec: 0x00000140, sec_in_part: 0x00000140 }, { boot_type: 0x00, start_head: 0x00, start_sec: 0x1, start_cyl: 0x14, part_type: 0x83, end_head: 0x00, end_sec: 0x20, end_cyl: 0x1F, abs_start_sec: 0x00000280, sec_in_part: 0x00000180 }, { } }; static unsigned int def_log_part_br_cyl[] = {0x0A, 0x0E, 0x12}; static const PartTable def_log_part_table[] = { { { boot_type: 0x00, start_head: 0x00, start_sec: 0x2, start_cyl: 0x0A, part_type: 0x83, end_head: 0x00, end_sec: 0x20, end_cyl: 0x0D, abs_start_sec: 0x00000001, sec_in_part: 0x0000007F }, { boot_type: 0x00, start_head: 0x00, start_sec: 0x1, start_cyl: 0x0E, part_type: 0x05, end_head: 0x00, end_sec: 0x20, end_cyl: 0x11, abs_start_sec: 0x00000080, sec_in_part: 0x00000080 }, }, { { boot_type: 0x00, start_head: 0x00, start_sec: 0x2, start_cyl: 0x0E, part_type: 0x83, end_head: 0x00, end_sec: 0x20, end_cyl: 0x11, abs_start_sec: 0x00000001, sec_in_part: 0x0000007F }, { boot_type: 0x00, start_head: 0x00, start_sec: 0x1, start_cyl: 0x12, part_type: 0x05, end_head: 0x00, end_sec: 0x20, end_cyl: 0x13, abs_start_sec: 0x00000100, sec_in_part: 0x00000040 }, }, { { boot_type: 0x00, start_head: 0x00, start_sec: 0x2, start_cyl: 0x12, part_type: 0x83, end_head: 0x00, end_sec: 0x20, end_cyl: 0x13, abs_start_sec: 0x00000001, sec_in_part: 0x0000003F }, } }; static void copy_mbr(u8 *disk) { memset(disk, 0x0, MBR_SIZE); *(unsigned long *)(disk + MBR_DISK_SIGNATURE_OFFSET) = 0x36E5756D; memcpy(disk + PARTITION_TABLE_OFFSET, &def_part_table, PARTITION_TABLE_SIZE); *(unsigned short *)(disk + MBR_SIGNATURE_OFFSET) = MBR_SIGNATURE; } static void copy_br(u8 *disk, int start_cylinder, const PartTable *part_table) { disk += (start_cylinder * 32 /* sectors / cyl */ * SECTOR_SIZE); memset(disk, 0x0, BR_SIZE); memcpy(disk + PARTITION_TABLE_OFFSET, part_table, PARTITION_TABLE_SIZE); *(unsigned short *)(disk + BR_SIGNATURE_OFFSET) = BR_SIGNATURE; } void copy_mbr_n_br(u8 *disk) { int i; copy_mbr(disk); for (i = 0; i < ARRAY_SIZE(def_log_part_table); i++) { copy_br(disk, def_log_part_br_cyl[i], &def_log_part_table[i]); } }
ram_device.h
#ifndef RAMDEVICE_H #define RAMDEVICE_H #define RB_SECTOR_SIZE 512 extern int ramdevice_init(void); extern void ramdevice_cleanup(void); extern void ramdevice_write(sector_t sector_off, u8 *buffer, unsigned int sectors); extern void ramdevice_read(sector_t sector_off, u8 *buffer, unsigned int sectors); #endif
ram_device.c
#include <linux/types.h> #include <linux/vmalloc.h> #include <linux/string.h> #include "ram_device.h" #include "partition.h" #define RB_DEVICE_SIZE 1024 /* sectors */ /* So, total device size = 1024 * 512 bytes = 512 KiB */ /* Array where the disk stores its data */ static u8 *dev_data; int ramdevice_init(void) { dev_data = vmalloc(RB_DEVICE_SIZE * RB_SECTOR_SIZE); if (dev_data == NULL) return -ENOMEM; /* Setup its partition table */ copy_mbr_n_br(dev_data); return RB_DEVICE_SIZE; } void ramdevice_cleanup(void) { vfree(dev_data); } void ramdevice_write(sector_t sector_off, u8 *buffer, unsigned int sectors) { memcpy(dev_data + sector_off * RB_SECTOR_SIZE, buffer, sectors * RB_SECTOR_SIZE); } void ramdevice_read(sector_t sector_off, u8 *buffer, unsigned int sectors) { memcpy(buffer, dev_data + sector_off * RB_SECTOR_SIZE, sectors * RB_SECTOR_SIZE); }
ram_block.c
/* Disk on RAM Driver */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/genhd.h> #include <linux/blkdev.h> #include <linux/errno.h> #include "ram_device.h" #define RB_FIRST_MINOR 0 #define RB_MINOR_CNT 16 static u_int rb_major = 0; /* * The internal structure representation of our Device */ static struct rb_device { /* Size is the size of the device (in sectors) */ unsigned int size; /* For exclusive access to our request queue */ spinlock_t lock; /* Our request queue */ struct request_queue *rb_queue; /* This is kernel's representation of an individual disk device */ struct gendisk *rb_disk; } rb_dev; static int rb_open(struct block_device *bdev, fmode_t mode) { unsigned unit = iminor(bdev->bd_inode); printk(KERN_INFO "rb: Device is opened\n"); printk(KERN_INFO "rb: Inode number is %d\n", unit); if (unit > RB_MINOR_CNT) return -ENODEV; return 0; } static int rb_close(struct gendisk *disk, fmode_t mode) { printk(KERN_INFO "rb: Device is closed\n"); return 0; } /* * Actual Data transfer */ static int rb_transfer(struct request *req) { //struct rb_device *dev = (struct rb_device *)(req->rq_disk->private_data); int dir = rq_data_dir(req); sector_t start_sector = blk_rq_pos(req); unsigned int sector_cnt = blk_rq_sectors(req); struct bio_vec *bv; struct req_iterator iter; sector_t sector_offset; unsigned int sectors; u8 *buffer; int ret = 0; //printk(KERN_DEBUG "rb: Dir:%d; Sec:%lld; Cnt:%d\n", dir, start_sector, sector_cnt); sector_offset = 0; rq_for_each_segment(bv, req, iter) { buffer = page_address(bv->bv_page) + bv->bv_offset; if (bv->bv_len % RB_SECTOR_SIZE != 0) { printk(KERN_ERR "rb: Should never happen: " "bio size (%d) is not a multiple of RB_SECTOR_SIZE (%d).\n" "This may lead to data truncation.\n", bv->bv_len, RB_SECTOR_SIZE); ret = -EIO; } sectors = bv->bv_len / RB_SECTOR_SIZE; printk(KERN_DEBUG "rb: Sector Offset: %lld; Buffer: %p; Length: %d sectors\n", sector_offset, buffer, sectors); if (dir == WRITE) /* Write to the device */ { ramdevice_write(start_sector + sector_offset, buffer, sectors); } else /* Read from the device */ { ramdevice_read(start_sector + sector_offset, buffer, sectors); } sector_offset += sectors; } if (sector_offset != sector_cnt) { printk(KERN_ERR "rb: bio info doesn't match with the request info"); ret = -EIO; } return ret; } /* * Represents a block I/O request for us to execute */ static void rb_request(struct request_queue *q) { struct request *req; int ret; /* Gets the current request from the dispatch queue */ while ((req = blk_fetch_request(q)) != NULL) { #if 0 /* * This function tells us whether we are looking at a filesystem request * - one that moves block of data */ if (!blk_fs_request(req)) { printk(KERN_NOTICE "rb: Skip non-fs request\n"); /* We pass 0 to indicate that we successfully completed the request */ __blk_end_request_all(req, 0); //__blk_end_request(req, 0, blk_rq_bytes(req)); continue; } #endif ret = rb_transfer(req); __blk_end_request_all(req, ret); //__blk_end_request(req, ret, blk_rq_bytes(req)); } } /* * These are the file operations that performed on the ram block device */ static struct block_device_operations rb_fops = { .owner = THIS_MODULE, .open = rb_open, .release = rb_close, }; /* * This is the registration and initialization section of the ram block device * driver */ static int __init rb_init(void) { int ret; /* Set up our RAM Device */ if ((ret = ramdevice_init()) < 0) { return ret; } rb_dev.size = ret; /* Get Registered */ rb_major = register_blkdev(rb_major, "rb"); if (rb_major <= 0) { printk(KERN_ERR "rb: Unable to get Major Number\n"); ramdevice_cleanup(); return -EBUSY; } /* Get a request queue (here queue is created) */ spin_lock_init(&rb_dev.lock); rb_dev.rb_queue = blk_init_queue(rb_request, &rb_dev.lock); if (rb_dev.rb_queue == NULL) { printk(KERN_ERR "rb: blk_init_queue failure\n"); unregister_blkdev(rb_major, "rb"); ramdevice_cleanup(); return -ENOMEM; } /* * Add the gendisk structure * By using this memory allocation is involved, * the minor number we need to pass bcz the device * will support this much partitions */ rb_dev.rb_disk = alloc_disk(RB_MINOR_CNT); if (!rb_dev.rb_disk) { printk(KERN_ERR "rb: alloc_disk failure\n"); blk_cleanup_queue(rb_dev.rb_queue); unregister_blkdev(rb_major, "rb"); ramdevice_cleanup(); return -ENOMEM; } /* Setting the major number */ rb_dev.rb_disk->major = rb_major; /* Setting the first mior number */ rb_dev.rb_disk->first_minor = RB_FIRST_MINOR; /* Initializing the device operations */ rb_dev.rb_disk->fops = &rb_fops; /* Driver-specific own internal data */ rb_dev.rb_disk->private_data = &rb_dev; rb_dev.rb_disk->queue = rb_dev.rb_queue; /* * You do not want partition information to show up in * cat /proc/partitions set this flags */ //rb_dev.rb_disk->flags = GENHD_FL_SUPPRESS_PARTITION_INFO; sprintf(rb_dev.rb_disk->disk_name, "rb"); /* Setting the capacity of the device in its gendisk structure */ set_capacity(rb_dev.rb_disk, rb_dev.size); /* Adding the disk to the system */ add_disk(rb_dev.rb_disk); /* Now the disk is "live" */ printk(KERN_INFO "rb: Ram Block driver initialised (%d sectors; %d bytes)\n", rb_dev.size, rb_dev.size * RB_SECTOR_SIZE); return 0; } /* * This is the unregistration and uninitialization section of the ram block * device driver */ static void __exit rb_cleanup(void) { del_gendisk(rb_dev.rb_disk); put_disk(rb_dev.rb_disk); blk_cleanup_queue(rb_dev.rb_queue); unregister_blkdev(rb_major, "rb"); ramdevice_cleanup(); } module_init(rb_init); module_exit(rb_cleanup); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Anil Kumar Pugalia <email@sarika-pugs.com>"); MODULE_DESCRIPTION("Ram Block Driver"); MODULE_ALIAS_BLOCKDEV_MAJOR(rb_major);
Вы также можете загрузить отсюда код, используемый для демонстрации.
Как и обычно, с помощью команды make
соберем драйвер "диска в оперативной памяти"(dor.ko
), объединив вместе три файла на С. Чтобы увидеть, как это делается, смотрите файл Makefile.
Makefile
# If called directly from the command line, invoke the kernel build system. ifeq ($(KERNELRELEASE),) KERNEL_SOURCE := /usr/src/linux PWD := $(shell pwd) default: module module: $(MAKE) -C $(KERNEL_SOURCE) SUBDIRS=$(PWD) modules clean: $(MAKE) -C $(KERNEL_SOURCE) SUBDIRS=$(PWD) clean # Otherwise KERNELRELEASE is defined; we've been invoked from the # kernel build system and can use its language. else obj-m := dor.o dor-y := ram_block.o ram_device.o partition.o endif
Чтобы привести в исходное состояние файлы, используемые при сборке, выполните, как и обычно, команду make clean
.
Как только сборка будет завершена, выполните следующие три эксперимента (смотрите рис.1 — 3).
Рис.1: Экспериментируем с драйвером "диска в оперативной памяти"
Рис.2: xxd показывает первоначальные данные, находящиеся в первом разделе (/dev/rb1)
Рис.3: Форматирование третьего раздела (/dev/rb3)
Пожалуйста, обратите внимание, что все эти действия нужно выполнять с привилегиями пользователя root:
- Загрузите драйвер
dor.ko
с помощью командыinsmod
. В результате будут созданы файлы блочного устройства, соответствующие диску размером в 512 Кбайтов, расположенному в оперативной памяти. - Проверьте файлы автоматически созданного блочного устройства (
/dev/rb*
)./dev/rb
— это весь диск, размер которого равен 512 Кбайтов.rb1
,rb2
иrb3
являются первичными разделами, причемrb2
является расширенным разделом, в котором расположены логические разделыrb5
,rb6
иrb7
. - Прочитайте весь диск с помощью утилиты дампа диска
dd
. - Опять с помощью утилиты
dd
заполните нулями первый сектор первого раздела (/dev/rb1
) - С помощью команды
cat
запишите некоторый текст в первый раздел диска (/dev/rb1
). - С помощью утилиты
xxd
отобразите начальное содержимое первого раздела (/dev/rb1
). - С помощью команды
fdis
k отобразите информацию о разделах диска. Результат работы командыfdisk
показан на рис.3. - С помощью
mkfs.vfat
(рис.3) выполните быстрое форматирование третьего первичного раздела (/dev/rb3
) — создайте файловую систему vfat (точно такую, как у нашего флеш устройства). - Смонтируйте вновь отформатированный раздел с помощью команды
mount
, например, в точке монтирования/mnt
(рис.3). - Утилита
df
, отображающая использования дисков, теперь должна показать, что этот раздел смонтирован в точке/mnt
(рис.3). Вы можете пойти дальше и сохранить на диске данные, но не забудьте, что этот диск находится в оперативной памяти, т. е. после выключения компьютера файлы на нем не сохраняются. - После того, как вы размонтируете раздел с помощью команды
umount /mnt
, выгрузите драйвер с помощью командыrmmod dor
.
Теперь давайте изучим правила
Мы всего лишь попробовали воспользоваться диском, созданным в оперативной памяти (disk on RAM - DOR), но при этом фактически не знаем правил, как это все происходит. Так что давайте попытаемся разобраться во всех подробностях этого процесса. В каждом из трех файлов .c
представлена определенная часть драйвера; в ram_device.c
и ram_device.h
абстрагированы основные операции с памятью, такие vmalloc/vfree
, memcpy
и т. п., с помощью которых реализованы API таких операции, как инициализация/очистка, чтение/запись и т.д.
В partition.c
и partition.h
реализованы функции, эмулирующие в DOR работу с таблицами различных разделов. Чтобы разобраться с особенностями организации разделов, вспомните о том, что рассказывалось на утреннем занятии (т.е. в предыдущей статье).
С помощью этого кода предоставляется информация о разделах, например, номер раздела, его тип, размер и т. п., которая отображается с помощью функции fdisk
. Файл ram_block.c
является основой реализации блочного драйвера, позволяющей отображать DOR в пользовательском пространстве в виде файлов блочного устройства (/dev/rb*
). Другими словами, с помощью четырех из пяти файлов ram_device.*
и partition.*
формируется горизонтальный слой драйвер устройства, а с помощью файла ram_block.c
формируется вертикальный (блочный) слой драйвера устройства. Итак, давайте разберемся в деталях.
Основы драйверов блочных устройств
Концептуально, блочные драйверы очень похожи на драйверы символьных устройств, в частности в отношении следующего:
- Используются файлы устройств
- Есть старшие и младшие номера
- Используются операции с файлами устройств
- Применяется концепция регистрации устройств
Итак, если вы уже знаете, как реализован символьный драйвер, вам будет проще понять реализацию блочных драйверов.
Тем не менее, они, безусловно, не идентичны. Основные различия заключаются в следующем:
- Абстракция блочно-ориентированных устройств отличается от абстракции байт-ориентированных устройств
- Чтобы обеспечить оптимальные производительность, блочные драйверы должны вызываться планировщиками ввода/вывода. Сравните это с символьными драйверами, которые должны использоваться внутри виртуальной файловой системы VFS.
- Чтобы обеспечить эффективный доступ к данным, блочные драйверы проектируются так, что их можно интегрировать с механизмом кэш-буфера Linux. Символьные драйверы являются драйверами непосредственного доступа, которые напрямую получают доступ к аппаратному обеспечению.
И все это является причиной различий в реализации. Давайте проанализируем ключевые фрагменты кода из файла ram_block.c
, и начнем с конструктора драйвера rb_init()
.
Первым шагом будет регистрация 8-битного (блочного) старшего номера (что неявно означает регистрацию всех 256 8-битных младших номеров, связанных с ним). Функция для этого выглядит следующим образом:
int register_blkdev(unsigned int major, const char *name);
Здесь major
является старшим номером, который должен регистроваться, а name является регистрационной меткой, отображаемой в директории /proc/devices
. Интересно, что если в качестве первого параметра major
передается 0, то функция register_blkdev()
пытается выделить и зарегистрировать произвольный свободный старший номер; в случае успеха происходит возврат выделенного старшего номера. Соответствующая функция отмены регистрации выглядит следующим образом:
void unregister_blkdev(unsigned int major, const char *name);
Прототипы обеих этих функций находятся в <linux/fs.h>.
На втором шаге в структуру block_device_operations
(прототип в
<linux/blkdev.h>
) заносятся операции для работы с файлами устройств с зарегистрированными старшими номерами.
Но эти операции мало похожи на операции с файлами символьных устройств; совпадения, как правило, незначительны. Если вдаваться в детали, то нет, что удивительно, даже таких операций, как чтение и запись. Но, поскольку, как мы уже знаем, блочный драйвер должен быть интегрирован с планировщиками ввода/вывода, реализация чтения и записи осуществляется с помощью так называемых очередей запросов. Таким образом, кроме операций для работы файлов устройств, также потребуется предоставить следующее:
- Очередь запросов для запросов на чтение/запись
- Механизм блокировки запросов для защиты от одновременного доступа
- Функция запросов для обработки запросов из очереди запросов
Кроме того, нет отдельного интерфейса для создания файлов блочных устройств, так что также следует предоставить:
- Префикс имени файла устройства, который, как правило, называется именем диска
disk_name
(rb
для драйвераdor
) - Начальный младший номер для файлов устройств, который, как правило,
называется первым младшим номером
first_minor
.
Наконец, нужно также предоставить два специальных значения, необходимые для характеризации блочных устройств, а именно:
- Максимальное количество разделов для данного блочного устройства, указав для этого количество младших номеров.
- Размер устройства, указываемый в единицах 512-байтовых секторов, что нужно абстрактного доступа к логическим блокам.
Все эти операции регистрируются в структуре struct gendisk
с помощью следующей функции:
void add_disk(struct gendisk *disk);
Соответствующая функция удаления delete
выглядит следующим образом:
void del_gendisk(struct gendisk *disk);
Прежде, чем использовать функцию добавления диска add_disk()
, нужно либо непосредственно, либо с помощью различных макросов/функций, таких как set_capacity()
, инициальзировать различные поля структуры struct gendisk
. Как минимум, нужно непосредственно инициализировать следующие поля - major
, first_minor
, fops
, queue
, disk_name
. И даже перед тем, как эти поля будут инициализированы, нужно будет с помощью следующей функции выделить память под структуру struct gendisk
:
struct gendisk *alloc_disk(int minors);
Здесь minors
указывает общее количество разделов, поддерживаемых для этого диска. И соответствующая обратная функция будет выглядеть так:
void put_disk(struct gendisk *disk);
Прототипы всех этих функций имеются в <linux/genhd.h>
.
Очередь запросов и функция запросов
Перед тем, как использовать функцию add_disk()
, нужно также инициализировать очередь запросов и и занести ее в структуру struct gendisk
. Инициализация очереди запросов осуществляется с помощью следующей функции:
struct request_queue *blk_init_queue(request_fn_proc *, spinlock_t *);
В качестве параметров мы указываем функцию обработки запросов и инициализируем механизм зашиты от одновременного доступа. Ниже приведена соответствующая функция работы с очередью:
void blk_cleanup_queue(struct request_queue *);
Функция запроса (обработки) должна быть определена с помощью следующего прототипа:
void request_fn(struct request_queue *q);
Она должна кодироваться так, чтобы для запроса использовался параметр q, например, следующим образом:
struct request *blk_fetch_request(struct request_queue *q);
Затем функция должна либо обработать запрос, либо инициализировать обработку. В любом случае блокировок возникать не должно, поскольку функция запроса вызывается не из контекста процесса обработки. Более того, внутри функции запроса должны использоваться только те функции, из-за которых не возникает блокировок в очереди запросов.
Ниже приведен типичный пример обработки запроса, демонстрируемый на примере функции rb_request()
из файла ram_block.c
:
while ((req = blk_fetch_request(q)) != NULL) /* Fetching a request */ { /* Processing the request: the actual data transfer */ ret = rb_transfer(req); /* Our custom function */ /* Informing that the request has been processed with return of ret */ __blk_end_request_all(req, ret); }
Запросы и их обработка
Нашей основной функцией будет функция rb_transfer()
, в которой происходит анализ структуры struct request
и, в соответствии с ним, выполняется фактическая передача данных. В этой структуре указывается, прежде всего, направление передачи данных, начальный сектор передаваемых данных, общее число секторов передаваемых данных и буфер, используемый для обмена данными. Для доступа к ним в структуре struct request
предоставляются различные макросы:
rq_data_dir(req); /* Operation type: 0 - read from device; otherwise - write to device */ blk_req_pos(req); /* Starting sector to process */ blk_req_sectors(req); /* Total sectors to process */ rq_for_each_segment(bv, req, iter) /* Iterator to extract individual buffers */
Макрос rq_for_each_segment()
является специализированным, в котором с помощью команды iter
происходит обращение к струтуре struct request (req)
и при каждой итерации выполняется извлечение конкретных данных из буфера в структуру struct bio_vec
(bv: basic input/output vector
). А затем, когда на каждой итерации передача данных будет завершена, для выполнения соответствующей передачи данных будет использован, в зависимости от типа операции, один из следующих интерфейсов API из файла ram_device.c
:
void ramdevice_write(sector_t sector_off, u8 *buffer, unsigned int sectors); void ramdevice_read(sector_t sector_off, u8 *buffer, unsigned int sectors);
Код функции rb_transfer()
смотрите полностью в файле ram_block.c
.
Подведем итог
Итак, благодаря тому, что мы рассмотрели методику создания жесткого диска и поэкспериментировали с его разделами, форматированием и другими низкоуровневыми операциями, выполняемыми на жестком диске, мы, на самом деле, изучили интересные драйверы блочных устройств. Спасибо за внимание. Теперь можно задавать вопросы - пожалуйста, не стесняйтесь, задавайте ваши вопросы и комментируйте.
К предыдущей статье | Оглавление | К следующей статье |