Наши партнеры

UnixForum





Библиотека сайта 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).
  • С помощью команды fdisk отобразите информацию о разделах диска. Результат работы команды 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.

Подведем итог

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


К предыдущей статье Оглавление К следующей статье