现在的位置: 首页 > 综合 > 正文

Linux块设备驱动

2013年01月13日 ⁄ 综合 ⁄ 共 3798字 ⁄ 字号 评论关闭

文章来源:http://blog.chinaunix.net/u3/94284/showart_1970946.html

块设备驱动(一)

概括:

块设备驱动第一个工作通常是注册自己到内核,是通过register_blkdev完成的,虽然register_blkdev可用来获得一个主设备号,但是它不使磁盘驱动器对系统可用,有一个分开的注册接口你必须使用来管理单独的驱动器,使用这一接口用到block_device_operationsgendisk结构体,在linux内核中,使用gendisk(通用磁盘)结构体来表示1个独立的磁盘设备(或分区)。

块设备驱动的核心是请求处理函数和制造请求函数,所谓请求处理函数或制造请求函数就是我们所说的IO请求处理,请求处理函数的介绍引入了request结构体,请求函数的原型是void requestrequest_queue_t *queue);而不使用请求处理或者制造请求函数,它的的主要参数是bio结构体;

定义:

系统中能够随机(不需要按顺序)访问固定大小数据片(chunks)的设备被称为块设备。

这些数据片就称为块,最常见的是硬盘,还有软盘驱动器,CD-ROM驱动器和闪存等,注意,它们都是以安装文件系统的方式使用的——这也是块设备一般的访问方式。

 

与字符设备的区别:

1、  字符设备按照字符流的方式被有序访问,如串口和键盘就都属于字符设备,如果一个硬件设备是以字符流的方式访问的话,那就应该将它归于字符设备,反过来,如果一个设备是随机(无序的)访问的,那么它就属于块设备。

2、  根本区别是它们能否可以被随机访问,也就是说,能否在访问设备时随意的从一个位置跳转到另一个位置。

3、  块设备只能以块为单位接受输入和返回输出,而字符设备以字节为单位。

4、  块设备对于IO请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设备无须缓冲且被直接读写。

5、  字符设备只能被顺序读写,而块设备可以随机访问。

 

解剖一个块设备

块设备中最小的可寻址单元是扇区,扇区大小一般是2的整数倍,而最常见的大小是512个字节,扇区的大小是设备的物理属性,扇区是所有块设备的基本单元——块设备无法对比它还小的单元进行寻址和操作,不过许多块设备能够一次就传输多个扇区。

虽然各种软件的用途不同,但是它们都会用到自己的最小逻辑可寻址单元——块,块是文件系统一种抽象——只能基于块来访问文件系统,虽然物理磁盘寻址是按扇区级进行的,但是内核执行的所有磁盘操作都是按照块进行的,由于扇区是设备的最小可寻址单元,所以块不能比扇区还小,只能数倍于扇区大小。

扇区对内核的重要性在于所有设备的IO 操作都必须基于扇区来进行,反过来,块是内核使用的较高层概念,它是比扇区高一层的抽象。

 

一、块设备的注册与注销

块驱动, 象字符驱动, 必须使用一套注册接口来使内核可使用它们的设备. 概念是类似的, 但是块设备注册的细节是都不同的.

大部分块驱动采取的第一步是注册它们自己到内核. 这个任务的函数是 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 可能被一起去掉. 同时, 但是, 大部分驱动仍然调用它; 它是惯例.

磁盘注册

虽然 register_blkdev 可用来获得一个主编号, 它不使任何磁盘驱动器对系统可用. 有一个分开的注册接口你必须使用来管理单独的驱动器. 使用这个接口要求熟悉一对新结构, 这就是我们的起点.

块设备操作

字符设备通过 file_ 操作结构使它们的操作对系统可用. 一个类似的结构用在块设备上; 它是 struct block_device_operations, 定义在 <linux/fs.h>.下面是一个对这个结构中的成员的简短的概览;

int (*open)(struct inode *inode, struct file *filp);

int (*release)(struct inode *inode, struct file *filp);

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

 

int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

实现 ioctl 系统调用的方法. 但是, 高层的块设备层代码处理了绝大多数ioctl(),因此大部分的块驱动 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 子系统, 这些操作由请求函数处理, 它们应当有它们自己的一节并且在本章后面讨论. 在我们谈论服务请求之前, 我们必须完成对磁盘注册的讨论.

gendisk 结构

struct gendisk (定义于 <linux/genhd.h>) 是单独一个磁盘驱动器的内核表示. 事实上, 内核还使用 gendisk 来表示分区, 但是驱动作者不必知道这点. struct gedisk 中有几个成员, 必须被一个块驱动初始化:

int major; 主设备号

int first_minor; 第一个次设备号

int minors; 最大的次设备数,如果不能分区,则为1

描述被磁盘使用的设备号的成员. 同一磁盘的各个分区共享一个注设备号,而次设备号则不同,至少, 一个驱动器必须使用最少一个次编号. 如果你的驱动会是可分区的, 但是(并且大部分应当是), 你要分配一个次编号给每个可能的分区. 次编号的一个普通的值是 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);

抱歉!评论已关闭.