linux块设备驱动

    和字符驱动相比 ,在对磁盘,flash等设备进行读写时,块设备驱动可以进行优化合并等操作,提高了操作效率。
    以下代码来自linux-2.6.38。LDD3电子书网页版的地址:http://oss.org.cn/kernel-book/ldd3/
一、注册
 

这个任务的函数是 register_blkdev(在 <linux/fs.h> 中定义):

int register_blkdev(unsigned int major, const char *name)

参数是你的设备要使用的主编号和关联的名子(内核将显示它在 /proc/devices). 如果 major 传递为0,

内核分配一个新的主编号并且返回它给调用者. 如常, 自 register_blkdev 的一个负的返回值指示已发生了一个错误.

取消注册的对应函数是:

int unregister_blkdev(unsigned int major, const char *name);

这里, 参数必须匹配传递给 register_blkdev 的那些, 否则这个函数返回 -EINVAL 并且什么都不注销.

在2.6内核, 对 register_blkdev 的调用完全是可选的. 由 register_blkdev 所进行的功能已随时间正在减少;

这个调用唯一的任务是 (1) 如果需要, 分配一个动态主编号, 并且 (2) 在 /proc/devices 创建一个入口. 在将来的内核,

register_blkdev 可能被一起去掉. 同时, 但是, 大部分驱动仍然调用它; 它是惯例.


 
二、块设备的操作

字符设备通过 file_ 操作结构使它们的操作对系统可用. 一个类似的结构用在块设备上; 它是 struct block_device_operations, 定义在 <linux/fs.h>. 下面是一个对这个结构中的成员的简短的概览; 当我们进入 sbull 驱动的细节时详细重新访问它们.

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.

专心的读者可能已注意到这个列表一个有趣的省略: 没有实际读或写数据的函数. 在块 I/O 子系统, 这些操作由请求函数处理, 它们应当有它们自己的一节并且在本章后面讨论. 在我们谈论服务请求之前, 我们必须完成对磁盘注册的讨论.

1 struct block_device_operations {
 2     int (*open) (struct block_device *, fmode_t);
 3     int (*release) (struct gendisk *, fmode_t);
 4     int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
 5     int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
 6     int (*direct_access) (struct block_device *, sector_t,
 7                         void **, unsigned long *);
 8     unsigned int (*check_events) (struct gendisk *disk,
 9                       unsigned int clearing);
10     /* ->media_changed() is DEPRECATED, use ->check_events() instead */
11     int (*media_changed) (struct gendisk *);
12     void (*unlock_native_capacity) (struct gendisk *);
13     int (*revalidate_disk) (struct gendisk *);
14     int (*getgeo)(struct block_device *, struct hd_geometry *);
15     /* this callback is with swap_lock and sometimes page table lock held */
16     void (*swap_slot_free_notify) (struct block_device *, unsigned long);
17     struct module *owner;
18 };

三、gendisk 结构
 

struct gendisk (定义于 <linux/genhd.h>) 是单独一个磁盘驱动器的内核表示。

 1 struct gendisk {
 2     /* major, first_minor and minors are input parameters only,
 3      * don't use directly.  Use disk_devt() and disk_max_parts().
 4      */
 5     int major;            /* major number of driver */
 6     int first_minor;
 7     int minors;                     /* maximum number of minors, =1 for
 8                                          * disks that can't be partitioned. */
 9 
10     char disk_name[DISK_NAME_LEN];    /* name of major driver */
11     char *(*devnode)(struct gendisk *gd, mode_t *mode);
12 
13     unsigned int events;        /* supported events */
14     unsigned int async_events;    /* async events, subset of all */
15 
16     /* Array of pointers to partitions indexed by partno.
17      * Protected with matching bdev lock but stat and other
18      * non-critical accesses use RCU.  Always access through
19      * helpers.
20      */
21     struct disk_part_tbl __rcu *part_tbl;
22     struct hd_struct part0;
23 
24     const struct block_device_operations *fops;
25     struct request_queue *queue;
26     void *private_data;
27 
28     int flags;
29     struct device *driverfs_dev;  // FIXME: remove
30     struct kobject *slave_dir;
31 
32     struct timer_rand_state *random;
33     atomic_t sync_io;        /* RAID */
34     struct disk_events *ev;
35 #ifdef  CONFIG_BLK_DEV_INTEGRITY
36     struct blk_integrity *integrity;
37 #endif
38     int node_id;
39 };
事实上, 内核还使用 gendisk 来表示分区, 但是驱动作者不必知道这点. struct gedisk 中有几个成员, 必须被一个块驱动初始化:
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 直到你的驱动被完全初始化并且准备好响应对那个磁盘的请求.

。。。。。。


版权声明:本文为qiao_yihan原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。