存储笔记--块设备驱动的注册

块设备的注册

1。块设备模型示意图

一个块是一个固定大小的数据块, 大小由内核决定. 块常常是 4096 字节, 但是这个值可依赖体系和使用的文件系统而变化. 一个扇区, 相反, 是一个小块, 它的大小常常由底层的硬件决定. 内核期望处理实现 512-字节扇区的设备。

2。注册

  • 2.1 块驱动的注册
    int register_blkdev(unsigned int major, const char *name);
    参数是你的设备要使用的主编号和关联的名子(内核将显示它在 /proc/devices). 如果 major 传递为0, 内核分配一个新的主编号并且返回它给调用者. 如常, 自 register_blkdev 的一个负的返回值指示已发生了一个错误.
    取消注册的对应函数是:
    int unregister_blkdev(unsigned int major, const char *name);

  • 2.2 磁盘注册

    磁盘操作函数 struct block_device_operations。

    int (*open)(struct inode *inode, struct file *filp);
    int (*release)(struct inode *inode, struct file *filp);
    就像它们的字符驱动对等体一样工作的函数; 无论何时设备被打开和关闭都调用它们. 一个字符驱动可能通过启动设备或者锁住门(为可移出的介质)来响应一个 open 调用. 如果你将介质锁入设备, 你当然应当在 release 方法中解锁.

    int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
    实现 ioctl 系统调用的方法. 但是, 块层首先解释大量的标准请求; 因此大部分的块驱动 ioctl 方法相当短.

    int (*media_changed) (struct gendisk *gd);
    被内核调用来检查是否用户已经改变了驱动器中的介质的方法, 如果是这样返回一个非零值. 显然, 这个方法仅适用于支持可移出的介质的驱动器(并且最好给驱动一个”介质被改变”标志); 在其他情况下可被忽略.
    struct gendisk 参数是内核任何表示单个磁盘; 我们将在下一节查看这个结构.

    int (*revalidate_disk) (struct gendisk *gd);
    revalidate_disk 方法被调用来响应一个介质改变; 它给驱动一个机会来进行需要的任何工作使新介质准备好使用. 这个函数返回一个 int 值, 但是值被内核忽略.

    struct module *owner;
    一个指向拥有这个结构的模块的指针; 它应当常常被初始化为 THIS_MODULE.

    磁盘结构体 gendisk
    struct gendisk 是单独一个磁盘驱动器的内核表示. 事实上, 内核还使用 gendisk 来表示分区, 必须被一个块驱动初始化:
    int major;
    int first_minor;
    int minors;
    描述被磁盘使用的设备号的成员. 至少, 一个驱动器必须使用最少一个次编号. 如果你的驱动会是可分区的, 但是(并且大部分应当是), 你要分配一个次编号给每个可能的分区. 次编号的一个普通的值是 16, 它允许”全磁盘”设备盒 15 个分区. 一些磁盘驱动使用 64 个次编号给每个设备.

    char disk_name[32];
    应当被设置为磁盘驱动器名子的成员. 它出现在 /proc/partitions 和 sysfs.

    struct block_device_operations *fops;
    来自前一节的设备操作集合.

    struct request_queue *queue;
    被内核用来管理这个设备的 I/O 请求的结构; 我们在”请求处理”一节中检查它.

    int flags;
    一套标志(很少使用), 描述驱动器的状态. 如果你的设备有可移出的介质, 你应当设置 GENHD_FL_REMOVABLE. CD-ROM 驱动器可设置 GENHD_FL_CD. 如果, 由于某些原因, 你不需要分区信息出现在 /proc/partitions, 设置 GENHD_FL_SUPPRESS_PARTITIONS_INFO.

    sector_t capacity;
    这个驱动器的容量, 以512-字节扇区来计. sector_t 类型可以是 64 位宽. 驱动不应当直接设置这个成员; 相反, 传递扇区数目给 set_capacity.

    void *private_data;
    块驱动可使用这个成员作为一个指向它们自己内部数据的指针.
    内核提供了一小部分函数来使用 gendisk 结构. 我们在这里介绍它们, 接着看 sbull 如何使用它们来使系统可使用它的磁盘驱动器.

    struct gendisk 是一个动态分配的结构, 它需要特别的内核操作来初始化; 驱动不能自己分配这个结构. 相反, 你必须调用:
    struct gendisk *alloc_disk(int minors);
    minors 参数应当是这个磁盘使用的次编号数目; 注意你不能在之后改变 minors 成员并且期望事情可以正确工作. 当不再需要一个磁盘时, 它应当被释放, 使用:

    void del_gendisk(struct gendisk *gd);
    一个 gendisk 是一个被引用计数的结构(它含有一个 kobject). 有 get_disk 和 put_disk 函数用来操作引用计数, 但是驱动应当从不需要做这个. 正常地, 对 del_gendisk 的调用去掉了最一个 gendisk 的最终的引用, 但是不保证这样. 因此, 这个结构可能继续存在(并且你的方法可能被调用)在调用 del_gendisk 之后. 但是, 如果你删除这个结构当没有用户时(即, 在最后的释放之后, 或者在你的模块清理函数), 你可确信你不会再收到它的信息.

    分配一个 gendisk 结构不能使系统可使用这个磁盘. 要做到这点, 你必须初始化这个结构并且调用 add_disk:
    void add_disk(struct gendisk *gd);

这里记住一件重要的事情:一旦你调用add_disk, 这个磁盘是”活的”并且它的方法可被在任何时间被调用. 实际上, 这样的第一个调用将可能发生, 即便在 add_disk 返回之前; 内核将读前几个字节以试图找到一个分区表. 因此你不应当调用 add_disk 直到你的驱动被完全初始化并且准备好响应对那个磁盘的请求.

3。初始化

  • 虚拟块设备
    struct sbull_dev {
    int size; /* Device size in sectors */
    u8 data; / The data array */
    short users; /* How many users */
    short media_change; /* Flag a media change? */
    spinlock_t lock; /* For mutual exclusion */
    struct request_queue queue; / The device request queue */
    struct gendisk gd; / The gendisk structure */
    struct timer_list timer; /* For simulated media changes */
    };
  • 注册
    sbull_major = register_blkdev(sbull_major, “sbull”);
    if (sbull_major <= 0)
    {
    printk(KERN_WARNING “sbull: unable to get major number\n”);
    return -EBUSY;
    }

  • 分配内存
    memset (dev, 0, sizeof (struct sbull_dev));
    dev->size = nsectors*hardsect_size;
    dev->data = vmalloc(dev->size);
    if (dev->data == NULL)
    {
    printk (KERN_NOTICE “vmalloc failure.\n”);
    return;
    }

  • 分配请求队列
    spin_lock_init(&dev->lock);
    dev->queue = blk_init_queue(sbull_request, &dev->lock);
    这里, sbull_request 是我们的请求函数 – 实际进行块读和写请求的函数. 当我们分配一个请求队列时, 我们必须提供一个自旋锁来控制对那个队列的存取. 这个锁由驱动提供而不是内核通常的部分, 因为, 常常, 请求队列和其他的驱动数据结构在相同的临界区; 它们可能被同时存取. 如同任何分配内存的函数, blk_init_queue 可能失败, 因此你必须在继续之前检查返回值.
  • 并且安装对应的 gendisk
    dev->gd = alloc_disk(SBULL_MINORS);
    if (! dev->gd)
    {
    printk (KERN_NOTICE “alloc_disk failure\n”);
    goto out_vfree;
    }
    dev->gd->major = sbull_major;
    dev->gd->first_minor = which*SBULL_MINORS;
    dev->gd->fops = &sbull_ops;
    dev->gd->queue = dev->queue;
    dev->gd->private_data = dev;
    snprintf (dev->gd->disk_name, 32, “sbull%c”, which + ‘a’);
    set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
    add_disk(dev->gd);

这里, SBULL_MINORS 是每个 sbull 设备所支持的次编号的数目. 当我们设置第一个次编号给每个设备, 我们必须考虑被之前的设备所用的全部编号. 磁盘的名子被设置, 这样第一个是 sbulla, 第二个是 sbullb, 等等. 用户空间可接着添加分区号以便它们在第 2 个设备上的分区可能是 /dev/sbull3.


你可能感兴趣的:(linux,块设备驱动,linux)