Блочные устройства. Таблица операций устройства и организация обмена данными

Comments

В предыдущей статье из цикла "Блочные устройства" мы заполнили в структуре описания диска struct gendisk поле fops— таблицу операций блочного устройства. Таблица функций struct block_device_operations, расположенная в файле linux/blkdev.h, выполняет для блочного устройства ту же роль, что и таблица файловых операций для символьного устройства struct file_operations, рассматривавшаяся ранее в 18-ой статье из цикла "Разработка модулей ядра Linux". Ниже приведен сокращенный фрагмент таблицы block_device_operations из ядра версии 3.5.

struct block_device_operations {
        int (*open) (struct block_device *, fmode_t);
        int (*release) (struct gendisk *, fmode_t);
        int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
        unsigned int (*check_events) (struct gendisk *disk, unsigned int clearing);
        void (*unlock_native_capacity) (struct gendisk *);
        int (*revalidate_disk) (struct gendisk *);
        int (*getgeo)(struct block_device *, struct hd_geometry *);
        struct module *owner;
...
};

В данном фрагменте всё должно быть понятно, так как его содержание во многом напоминает аналогичную таблицу для символьных устройств. Операции open() и release()— это функции, вызываемые при открытии и закрытии устройства, но довольно часто они вызываются неявно, поэтому мы не будем описывать их.

В архиве blkdev.tgz, расположенном в разделе "Материалы для скачивания" можно найти примеры реализации операций getgeo() и ioctl().

Листинг 1. Общие определения (файл common.c)
static struct block_device_operations mybdrv_fops = {
   .owner = THIS_MODULE,
   .ioctl = my_ioctl,
   .getgeo = my_getgeo
};
Листинг 2. Пример реализации операций getgeo() и ioctl() (файл ioctl.c)
static int my_getgeo( struct block_device *bdev, struct hd_geometry *geo ) {
   unsigned long sectors = ( diskmb * 1024 ) * 2;
   DBG( KERN_INFO "getgeo\n" );
   geo->heads = 4;
   geo->sectors = 16;
   geo->cylinders = sectors / geo->heads / geo->sectors;
   geo->start = geo->sectors;
   return 0;
};

static int my_ioctl( struct block_device *bdev, fmode_t mode,
                     unsigned int cmd, unsigned long arg ) {
   DBG( "ioctl cmd=%d\n", cmd );
   switch( cmd ) {
      case HDIO_GETGEO: {
         struct hd_geometry geo;
         LOG( "ioctk HDIO_GETGEO\n" );
         my_getgeo( bdev, &geo );
         if( copy_to_user( (void __user *)arg, &geo, sizeof( geo ) ) )
            return -EFAULT;
         return 0;
      }
      default:
         DBG( "ioctl unknown command\n" );
         return -ENOTTY;
   }
}

Примечание: Используемые макросы ERR(), LOG(), DBG() определены только для компактности кода и скрывают за собой вызов printk(). Их исходный код можно найти в листинге 3.

Современное ядро никак не связано с конфигурацией блочного устройства, которое рассматривается как линейный массив секторов (в наших примерах это массив секторов в памяти). Также для работы системы с блочным устройством не имеет значения, как оно разбито в терминах цилиндров, головок и числа секторов на дорожку. Но многие утилиты GNU для работы с дисковыми устройствами (fdisk, hdparm, ...) предполагают получение информации о геометрии диска. Поэтому, чтобы не создавать препятствий для работы этих утилит, целесообразно реализовать показанные выше операции, так как без них может оказаться невозможным создание разделов на устройстве. Проверим работу функции getgeo() из листинга 2.

# insmod block_mod_s.ko# hdparm -g /dev/xdd
/dev/xdd:
 geometry      = 128/4/16, sectors = 8192, start = 16

В таблице операций struct block_device_operations находит отражение тот факт, что эти операции предназначены именно для блочного устройства. Например, при разработке устройства со сменными носителями для проверки несменяемости носителя при каждом новом открытии устройства в теле функции open() следует вызывать функцию ядраcheck_disk_change( struct block_device* ).

В свою очередь для проверки фактической смены носителя функция check_disk_change() выполняет вызов метода из таблицы операций:

int (*media_changed)( struct gendisk* );

Если носитель сменился, то функция media_changed() возвращает ненулевое значение, и в этом случае ядро вызовет ещё один метод из таблицы операций:

int (*revalidate_disk)( struct gendisk *);

Этот метод, реализуемый программистом, должен выполнить операции, необходимые для подготовки драйвера для работы с новым носителем (если добавляемым устройством является RAM-диск, то вызов должен обнулить область памяти устройства). После осуществления вызова revalidate_disk(), ядро тут же предпримет попытку заново считать таблицу разделов устройства. Именно так обрабатываются сменные носители в блочных устройствах.

Примечание: Нужно различать устройство со сменным носителем и сменное устройство. К устройствам со сменным носителем относятся DVD/СD-дисководы и подобные устройства. А сменные устройства — это внешние жёсткие диски, USB флеш-диски и т.д. Реинициализация драйвера в этих случаях происходит совершенно по разному.

И последние замечания относительно таблицы операций:

  1. При изменении версии ядра в struct block_device_operations также происходят изменения, например, вызов swap_slot_free_notify() появился в таблице только начиная с ядра 2.6.35 как указывается в данной статье;
  2. Также существующие и описанные в публикациях методы могут быть объявлены устаревшими (deprecated, т.е. могут быть удалены в будущих версиях), как, например, метод media_changed() (с версии 2.6.38), вместо которого предложен новый метод check_events():
    ...
    unsigned int (*check_events) (struct gendisk *disk, unsigned int clearing);
        /* ->media_changed() is DEPRECATED, use ->check_events() instead */
       int (*media_changed) (struct gendisk *);
    ...

Обмен данными

К данному моменту мы рассмотрели многие аспекты функционирования блочных устройств за исключением самих операции чтения и записи данных. Так вот сами операции чтения и записи в драйвере блочного устройства отсутствуют! Они выполняются по-другому:

  • получив запрос на чтение или запись ядро помещает его в очередь, связанную с драйвером (которую мы инициализировали ранее);
  • запросы в очереди ядро сортирует так, чтобы оптимизировать выполнение последовательности запросов (например, минимизировать перемещения головок жёсткого диска);
  • очередной (текущий крайний) запрос из очереди передаётся на обработку функции драйвера, которую мы определили для обработки запросов;
  • направление (ввод или вывод) и параметры запроса (начальный сектор, число секторов и др.) указываются в самом теле запроса.

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

К этому времени мы рассмотрели всю информацию, необходимую для создания работающего примера модуля блочного устройства. В архиве blkdev.tgz находятся несколько вариантов подобного модуля, но мы рассмотрим только самый общий из них. Этот модуль реализован в файле block_mod_s.c с некоторым небольшими включениями, которые почти все уже были обсуждены выше. В листинге 3 представлен последний включаемый файл common.h.

Листинг 3. Включаемый файл для модуля блочного устройства (файл common.h)
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/blkdev.h>
#include <linux/genhd.h>
#include <linux/errno.h>
#include <linux/hdreg.h>
#include <linux/version.h>

#define KERNEL_SECTOR_SIZE    512

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,35)
#define blk_fs_request(rq)      ((rq)->cmd_type == REQ_TYPE_FS)
#endif

static int diskmb = 4;
module_param_named( size, diskmb, int, 0 ); // размер диска в Mb, по умолчанию - 4Mb
static int debug = 0;
module_param( debug, int, 0 );              // уровень отладочных сообщений

#define ERR(...) printk( KERN_ERR "! "__VA_ARGS__ )
#define LOG(...) printk( KERN_INFO "+ "__VA_ARGS__ )
#define DBG(...) if( debug > 0 ) printk( KERN_DEBUG "# "__VA_ARGS__ )

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

Листинг 4. Модуль блочного устройства (файл block_mod_s.c)
#include "../common.h"

static int major = 0;
module_param( major, int, 0 );
static int hardsect_size = KERNEL_SECTOR_SIZE;
module_param( hardsect_size, int, 0 );
static int ndevices = 4;
module_param( ndevices, int, 0 );
// различные стратегии обмена данными
enum {	RM_SIMPLE  = 0,	// простой запрос с обработкой очереди
	RM_FULL    = 1,	// запрос с обработкой вектора BIO
	RM_NOQUEUE = 2,	// прямое выполнение запроса без очереди
     };
static int mode = RM_SIMPLE;
module_param( mode, int, 0 );

static int nsectors;

struct disk_dev {               // внутренняя структура нашего устройства
   int size;                    // размер устройства в секторах
   u8 *data;                    // массив с данными
   spinlock_t lock;             // флаг блокировки устройства
   struct request_queue *queue; // очередь запросов к устройству
   struct gendisk *gd;
};

static struct disk_dev *Devices = NULL;
...
// простой запрос с обработкой очереди
static void simple_request( struct request_queue *q ) {
...
}

// запрос с обработкой вектора BIO
static void full_request( struct request_queue *q ) {
...
}

// прямое выполнение запроса без очереди
static void make_request( struct request_queue *q, struct bio *bio ) {
...
}

#include "../ioctl.c"
#include "../common.c"

#define MY_DEVICE_NAME "xd"
#define DEV_MINORS	16

// настройка внутреннего устройства
static void setup_device( struct disk_dev *dev, int which ) {
   memset( dev, 0, sizeof( struct disk_dev ) );
   dev->size = diskmb * 1024 * 1024;
   dev->data = vmalloc( dev->size );
   if( dev->data == NULL ) {
      ERR( "vmalloc failure.\n" );
      return;
   }
   spin_lock_init( &dev->lock );
   switch( mode ) { // выбор режима выполнения операции
      case RM_NOQUEUE:
         dev->queue = blk_alloc_queue( GFP_KERNEL );
         if( dev->queue == NULL ) goto out_vfree;
         blk_queue_make_request( dev->queue, make_request );
         break;
      case RM_FULL:
         dev->queue = blk_init_queue( full_request, &dev->lock );
         if( dev->queue == NULL ) goto out_vfree;
         break;
      default:
         LOG( "bad request mode %d, using simple\n", mode );
      case RM_SIMPLE:
         dev->queue = blk_init_queue( simple_request, &dev->lock );
         if( dev->queue == NULL ) goto out_vfree;
         break;
   }
   // установка раздела аппаратного сектора
   blk_queue_logical_block_size( dev->queue, hardsect_size );
   dev->queue->queuedata = dev;
   dev->gd = alloc_disk( DEV_MINORS );
   if( ! dev->gd ) {
      ERR( "alloc_disk failure\n" );
      goto out_vfree;
   }
   dev->gd->major = major;
   dev->gd->minors = DEV_MINORS;
   dev->gd->first_minor = which * DEV_MINORS;
   dev->gd->fops = &mybdrv_fops;
   dev->gd->queue = dev->queue;
   dev->gd->private_data = dev;
   snprintf( dev->gd->disk_name, 32, MY_DEVICE_NAME"%c", which + 'a' );
   set_capacity( dev->gd, nsectors * ( hardsect_size / KERNEL_SECTOR_SIZE ) );
   add_disk( dev->gd );
   return;
out_vfree:
   if( dev->data ) vfree( dev->data );
}

static int __init blk_init( void ) {
   int i;
   nsectors = diskmb * 1024 * 1024 / hardsect_size;
   major = register_blkdev( major, MY_DEVICE_NAME );
   if( major <= 0 ) {
      ERR( "unable to get major number\n" );
      return -EBUSY;
   }
   // выделение массива для хранения данных устройства
   Devices = kmalloc( ndevices * sizeof( struct disk_dev ), GFP_KERNEL );
   if( Devices == NULL ) goto out_unregister;
   for( i = 0; i < ndevices; i++ ) // инициализировать каждое устройство
      setup_device( Devices + i, i );
   return 0;
out_unregister:
   unregister_blkdev( major, MY_DEVICE_NAME );
   return -ENOMEM;
}

static void blk_exit( void ) {
   int i;
   for( i = 0; i < ndevices; i++ ) {
      struct disk_dev *dev = Devices + i;
      if( dev->gd ) {
         del_gendisk( dev->gd );
         put_disk(dev->gd);
      }
      if( dev->queue ) {
         if( mode == RM_NOQUEUE )
            blk_put_queue( dev->queue );
         else
            blk_cleanup_queue( dev->queue );
      }
      if( dev->data ) vfree( dev->data );
   }
   unregister_blkdev( major, MY_DEVICE_NAME );
   kfree( Devices );
}

MODULE_AUTHOR( "Jonathan Corbet" );
MODULE_AUTHOR( "Oleg Tsiliuric <olej@front.ru>" );

MODULE_LICENSE( "GPL v2" );
MODULE_VERSION( "1.5" );

Заключение

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


Ресурсы для скачивания


Похожие темы


Комментарии

Войдите или зарегистрируйтесь для того чтобы оставлять комментарии или подписаться на них.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=931978
ArticleTitle=Блочные устройства. Таблица операций устройства и организация обмена данными
publish-date=05302013