块设备是指数据传输以块为单位的设备,这里硬件的块一般称为“扇区(sector)”,块设备驱动程序提供了面向数据块的设备的访问,支持随机访问的方式,但不可由用户直接访问。关于设备节点、节点文件和设备文件其实都指的是一个东西。
block_device_operations结构体
在块设备驱动中,有1个类似于字符设备驱动中file_operations结构体的block_device_operations结构体,它是对块设备操作的集合。
struct block_device_operations
{
int(*open)(struct inode *, struct file*); //打开
int(*release)(struct inode *, struct file*); //释放
int(*ioctl)(struct inode *, struct file *, unsigned, unsigned long); //ioctl
int(*media_changed)(struct gendisk*); //介质被改变?
int(*revalidate_disk)(struct gendisk*); //使介质有效
int(*getgeo)(struct block_device *, struct hd_geometry*);//填充驱动器信息
struct module *owner; //模块拥有者
};
打开和释放
int (*open)(struct inode *inode, struct file *filp);
int (*release)(struct inode *inode,struct file *filp);
与字符设备驱动类似,当设备被打开和关闭时将调用它们。
IO控制
int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd,unsigned long arg);
上述函数是ioctl()系统调用的实现,块设备包含大量的标准请求,这些标准请求由Linux块设备层处理,因此大部分块设备驱动的ioctl()函数相当短。
gendisk结构体
在Linux内核中,使用gendisk(通用磁盘)结构体来表示1个独立的磁盘设备(或分区)
struct gendisk
{
int major; /* 主设备号*/
int first_minor; /*第1个次设备号*/
int minors; /* 最大的次设备数,如果不能分区,则为1*/
char disk_name[32]; /* 设备名称*/
struct hd_struct **part; /* 磁盘上的分区信息*/
struct block_device_operations *fops; /*块设备操作结构体*/
struct request_queue *queue; /*请求队列*/
void *private_data; /*私有数据*/
sector_t capacity; /*扇区数,512字节为1个扇区*/
…
}
增加gendisk
gendisk结构体被分配之后,系统还不能使用这磁盘,需要调用如下函数来注册这个磁盘设备。
void add_disk(struct gendisk *gd);
特别要注意的是对add_disk()的调用必须发生在驱动程序的初始化工作完成并能响应磁盘的请求之后。
释放gendisk
当不再需要一个磁盘时,应当使用如下函数释放gendisk。
void del_gendisk(struct gendisk *gd);
request队列结构体
一个块请求队列是一个块I/O请求的队列
struct request_queue
{/* 保护队列结构体的自旋锁*/
spinlock_t *queue_lock;/* 队列设置*/
unsigned long nr_requests; /* 最大的请求数量*/
unsigned short max_sectors; /* 最大的扇区数*/
unsigned short hardsect_size; /* 硬件扇区尺寸*/
…
}
请求队列简介
request函数是块设备驱动程序里面最重要的函数
request函数要完成以下几个功能
测试请求的有效性;执行实际的数据传输;清除已经处理过的请求;返回开头,并且开始处理下一条请求。
操作请求队列
初始化请求队列
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);
该函数的第一个参数是请求处理函数的指针,第二个参数是控制访问队列权限的自旋锁,这个函数会发生内存分配的行为,它可能会失败,因此一定要检查它的返回值。这个函数一般在块设备驱动的模块加载函数中调用。
提取请求
struct request *elv_next_request(request_queue_t *queue);
上述函数用于返回下一个要处理的请求(由I/O调度器决定),如果没有请求则返回NULL。
elv_next_request()不会清除请求。因为elv_next_request()不从队列里清除请求,因此连续调用它两次,两次会返回同一个请求结构体。
启停请求队列
void blk_stop_queue(request_queue_t *queue);
void blk_start_queue(request_queue_t *queue);
如果块设备到达不能处理等候的命令的状态,应调用blk_stop_queue()来告知块设备层。之后,请求函数将不被调用,除非再次调用blk_start_queue()将设备恢复到可处理请求的状态。
块设备驱动注册与注销
驱动注册
vint register_blkdev(unsigned int major,const char *name);
v驱动注销
int unregister_blkdev(unsigned int major, const char *name);
块设备不同于字符设备一个明显特征就是——块设备可以被挂载到文件系统上。在内核挂载块设备时,调用open方法来访问驱动程序。设备打开之后,内核就会调用request方法传输数据块。