block_device_operations
块设备的ioctl()系统调用包含大量的标准请求,这些标准请求由Linux块设备层处理,因此大部分块设备的ioctl()函数相当短。
介质改变media_change,检测驱动器中的介质是否改变。这个函数仅适用于支持可移动介质的驱动器,通常需要在驱动中增加一个表示介质状态是否改变的标志变量。
使介质有效revalidate_disk()被调用来响应一个介质改变,它给驱动一个机会来进行必要的工作以使新介质准备好。
获得驱动器信息getgeo(),根据驱动器的几何信息填充hd_geometry结构体,该结构体包含磁头等信息。
gendisk结构体(通用磁盘)表示一个独立的磁盘设备。
其中的queue成员用来管理这个设备的I/O请求队列的指针,capacity表明设备的容量,以512个字节为单位。
gendisk的操作函数:
分配 struct gendisk *alloc_disk(int minor), minor是次设备号的数量,也就是磁盘分区的数量,此后不能再修改。
增加 add_disk().分配之后用此函数向内核注册。对它的调用须初始化完成并能响应之后。
释放 del_gendisk()。
应用计数。 gendisk包含一个kobject成员,因此,它是一个可被引用计数的结构体。通过get_disk()和put_disk()操作引用计数,这个工作不需要驱动亲自来做。
设置容量 set_capacity()。最小可寻址单元是扇区,一般是2的整数倍,常见的是512字节。不管物理设备的真实扇区大小多少,内核与块设备驱动交互的扇区都以512字节为单位,因此本函数也以512字节为单位。
用request结构体表征等待进行的I/O请求。
request中主要的成员有sector_t hard_sector; unsigned long hard_nr_sectors; unsigned int hard_cur_sectors. 这些只用于内核块设备层,驱动不使用。驱动使用sector, nr_sectors, current_nr_sectors,他们是相对应的副本。
驱动中经常要与这些成员打交道。他们在内核和驱动交互中发挥着重大作用。以512字节为一个扇区。
I/O调度器类似电梯,提高访问效率。2.6内核包含4个I/O调度器:
No-op I/O scheduler是个简化的调度程序,只做最基本的合并与排序。
Anticipatory I/O scheduler是当前内核默认的调度器,它有非常好的性能。缺点是非常庞大复杂,在数据吞吐量非常大的数据库系统中它会变得比较缓慢。
Deadline I/O scheduler是针对anticipatory的缺点改进的,更小巧。
CFQ I/Q scheduler为系统内所有的任务分配相同的带宽,提供一个公平的工作环境,他比较适合桌面环境。
通过给kernel添加启动参数,选择使用的IO调度算法:kernel elevator=deadline
初始化请求队列 blk_init_queue()
清除请求队列 blk_cleanup_queue()
分配请求队列 blk_alloc_queue()
绑定请求队列和“制造请求”函数 blk_queue_make_request()
提取请求 elv_next_request(),仍在队列上
去除请求 blkdev_dequeue_request()
将已经出列的请求归还队列中 elv_requeue_request()
启停请求队列 blk_stop_queue(), blk_start_queue()
参数设置函数 blk_queue_max_sectors()等一系列函数
通告内核参数: blk_queue_segment_boundary()告知内核设备无法处理跨越的内存边界; blk_queue_dma_aligment() 告知内核块设备施加于DMA传送的内存对齐限制; blk_queue_hardsect_size() 告知内核块设备硬件扇区的大小,所有由内核产生的请求都是这个大小的倍数并且被正确对齐,但是内核块设备层和驱动之间的通信还是512字节扇区为单位。
块I/O。通常一个bio对应一个I/O请求。I/O调度算法可以将连续的bio合并成一个请求,所有一个请求可以包含多个bio。
bio_sectors()可以获得以扇区为单位的被传送数据大小。
bio_for_each_segement()可以遍历循环整个bio中的每个段。
bio_data_dir()获得数据传输方向,是read还是write。
bio_page()获得目前的页指针
bio_offset()返回操作对应的当前页内的偏移
bio_cur_sectors()返回当前bio_vec要传输的扇区数
bio_data()返回数据缓冲区的内核虚拟指针。
...
块设备注册函数 register_blkdev()。它只完成两件事:如果需要,分配一个动态主设备号;在/proc/devices中创建一个入口。
块设备模块加载函数通常完成如下工作:
1,分配、初始化请求队列,绑定请求队列和请求函数 blk_alloc_queue(), blk_queue_make_request()
2,分配、初始化gendisk,给gendisk的major,fops,queue等成员赋值,最后添加gendisk
3,注册块设备驱动,request_blkdev
卸载
if (bdev)
{
invalidate_bdev();
blkdev_put();
}
del_gendisk();
put_disk();
blk_cleanup_queue();
unregister_blkdev();
一个请求队列内request, bio, 以及bio中segment层层遍历关系如下:
请求含 elv_next_request() 含rq_for_each_bio(), 含boi_for_each_segment()
使用请求队列对于一个机械设备而言有助于提高系统的性能,但对于许多块设备,如数码相机存储卡, ram磁盘等完全可真正随机访问的设备而言,无法从这些高级请求队列中获益。对于这些设备,块层支持“无队列”的操作模式,为使用这个模式,驱动必须提供一个“制造请求”函数,而不是一个请求函数。
在整个I/O操作中,贯穿始终的就是请求。驱动的任务是处理请求,对请求的排队和整合由I/O调度算法解决,因此,块设备驱动的核心就是请求处理函数或制造请求函数。
bio_vec中的内存地址是使用page *描述的,这也意味着内存页面有可能处于高端内存中而无法直接访问。这种情况下,常规的处理方法是用kmap映射到非线性映射区域进行访问,当然,访问完后要记得把映射的区域还回去。
因为当时我们是使用__get_free_pages()来申请内存,__get_free_pages()函数只能用来申请低端内存,
因为这个函数返回的是申请到的内存的指针,而上文中说过,高端内存是不能用这样的指针表示的。
要申请高端内存,明显不能使用这样的函数,因此我们隆重介绍它的代替者出场:
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);
这个函数的参数与__get_free_pages()相同,但区别在于,它返回指向struct page的指针,
这个我们在上文中介绍过的指针赋予了alloc_pages()函数申请高端内存的能力。
其实申请一块高端内存并不难,只要使用__GFP_HIGHMEM参数调用alloc_pages()函数,
就可能返回一块高端内存,之所以说是“可能”,使因为在某些情况下,比如高端内存不够或不存在时,也会但会低端内存充数。
阅读(474) | 评论(0) | 转发(0) |