三、块设备
1、申请与释放设备编号
块设备驱动程序在使用前,必须向内核注册设备号,内核函数原型:
int register_blkdev(unsigned int major,const char *name);
major是需要注册的主设备号;name为驱动程序在/proc/devices中显示的名字。在驱动程序卸载时,原先注册的主设备号需要归还组内核,
int unregister_blkdev(unsinged int major,const char *name);
2、注册磁盘
驱动程序通过内核函数register_bkldev()注册主设备号后,并不能让系统使用任何磁盘。内核中由gendisk结构表示一个独立的磁盘设备,块设备驱动程序在注册主设备号之后,需要设置gendisk结构,并将该结构添加到内核中。
若磁盘设备是可被分区,每个分区都需要分配一个次设备号。minors成员常取16.。fops是块设备提供给内核的接口 内核通过这些接口完成用户对设备的驱动操作。 queue成员是块设备所用到的请求队列。capacity是以512B为一个扇区时,该设备所包含的扇区数;驱动程序不能直接设置该值而要通过set_capacity()内核函数来设置。
gendisk是一个动态分配的结构它需要内核的特殊处理来进行初始化,驱动程序不能分配该结构 ,须执行内核函数:
struct gendisk *alloc_disk(int minors);
参数minors是该磁盘使用的次设备号的数量。gendisk申请后,其值是不能改变的。用户不再需要使用gendisk时,必须通过del_gendisl()函数将gendisk结构删除。
驱动程序将gendisk设置完后,需要将gendisk注册进内核。:
void add_disk(struct gendisk *gd);
一旦调用add_disk,内核就有可能随时操作gendisk。因此gendisk只有在初始化完毕后,才能调用add_disk进行注册。
在gendisk结构中,fops的类型为block_devic_operations,该结构主要成员:
int(*open)(struct inode*,struct file*);
int (*release)(struct inode*,struct file*);
int(*ioctl)(struct inode*,stuct file*,unsigned,unsigned long);
int(*media_changed)(struct gendisk*);
int(*revalidate_disk)(struct gendisk*);
自v2.5内核版本起,为块设备引入一种新型、灵活的容器bio结构体来实现I/O。该结构体代表正在活动的、以片段链表形式组织的块I/Or操作,一个片段是需要传输的一小块连续的主存缓冲区。这样,就不需要保证单个 缓冲区一定结。
使用bio结构体的目的主要是代表正在现场执行的I/O操作,该结构体中的主要域都是用来管理相关信息的。
bi_io_vec域指向一个bio_vec结构体数组,该结构体链表包含一个特定I/O操作所需要使用的所有的片段。每个bio_vec结构都是一个形式为<page,offset,len>的向量,描述为:片段所在的页框、块在页框中的偏移位置、从给定偏移量开始的块长度。bio_vec 结构描述I/O缓冲区,整个bio_io_vec结构体数组表示一个完整的缓冲区。
在每次启动一个新的I/Or操作时,需要通过内核函数bio_alloc()申请一个新的bio结构。操作bio结构体的内核函数:
bio_alloc()、bio_get()和bio_out()管理bio结构体的分配、引用计数和bio结构体的释放。
4、 请求队列
扶起的块I/O请求保存在请求队列中,该队列由request_queue结构体表示。请求队列表中的每一项都是一个单独的请求,由request结构体表示 。
请求结构体request:
请求队列结构定义:
块的读写操作是通过由内核经调用的generic_file_read()和blkdev_file_write()内核函数完成对设备的读写。
即当用户对设备发出读操作时,generic_file_read()将一个读请求发送给设备驱动程序,并积存在该设备的请求队列中。内核对这些请求进行调度,并将请求交给驱动程序执行。
创建和初始化请求队列的内核函数是:
request_queue_t *blk_init_queue(queue_fn_proc *rfn,spinlock_t*lock);
其中,rfn是驱动程序提供的一个函数指针,该函数是真正用于实现设备的按读写请求;lock是驱动程序提供的一个自旋锁,内核通过该自旋锁实现请求队列的同步保护;函数的返回值是一个请求队列的指针。当请求队列不再使用时,驱动程序需要将请求队列归还:
void blk_cleanup_queue(request_queue_t*q);
请求队列中,的成员queuedata,它是供驱动程序自行使用的,若驱动程序用该成员指向一段动态分配的内存,则在卸载驱动程序时,要负责将该段主存回收。请求队列的每一个读写请求在内核中用request结构定义,request结构与驱动程序相关的成员:
struct request{
sector_t sector;//请求的开始扇区的索引号
unsigned long nr_sectors;//传输的扇区数
/*buffer是要传输或要接受数据的缓冲区指针,该指针在内核 的虚拟地址中,驱动程序可以直接引用它。*/
}
当创建请求队列时,需要绑定一个request()内核函数,用于处理请求队列中请求:
void request(request_queue_t*queue);
块设备的读、写操作都是由request()完成 的。
5、页高速缓存
Linux页高速缓冲由address_space结构体描述:
address_space结构与某些对象相关联。其中a_ops指向地址空间对象中的操作函数表,它由 address_space_operations结构表示: