块设备驱动程序
<1>.块设备和字符设备的区别
1、读取数据的单元不同,块设备读写数据的基本单元是块,字符设备的基本单元是字节。
2、块设备可以随机访问,字符设备只能顺序访问。

块设备的访问:
当多个请求提交给块设备时,执行效率依赖于请求的顺序。如果所有的请求是同一个方向(如:写数据),执行效率是最大的。内核在调用块设备驱动程序例程处理请求之前,先收集I/O请求并将请求排
序,然后,将连续扇区操作的多个请求进行合并以提高执行效率,对I/O请求排序的算法称为电梯算法(elevator algorithm)。
电梯算法在I/O调度层完成。内核提供了不同类型的电梯算法,电梯算法有
   noop(实现简单的FIFO,基本的直接合并与排序),
   anticipatory(延迟I/O请求,进行临界区的优化排序),
 Deadline(针对anticipatory缺点进行改善,降低延迟时间),
 Cfq(均匀分配I/O带宽,公平机制)

电梯调度算法:我们先来说一说电梯是怎样工作的。当电梯上的时候,如果在电梯所在层的上层和下层都有请求的话,电梯会先处理上层的请求,同样当电梯下的时候如果上层下层都有请求的话,电梯会先处理下层的请求。那么我们这里电梯调度算法也是采用如此的策略,比如当读数据的时候,如果有读请求也有写请求,那么要先处理读请求。在写数据的时候,如果有读请求也有写请求,那么先处理写请求。

块设备驱动相关数据结构:
 block_device: 旨在描述一个分区或整个磁盘对内核的一个块设备实例
 gendisk: 旨在描述一个通用硬盘(generic hard disk)对象。
 hd_struct: 旨在描述分区应有的分区信息
 bio: 旨在描述块数据传送时怎样完成填充或读取块给driver
 request: 旨在描述向内核请求一个列表准备做队列处理。
 request_queue: 旨在描述内核申请request资源建立请求链表并填写BIO形成队列。

<2>.通用硬盘结构 gendisk
    结构体gendisk代表了一个通用硬盘(generic hard disk)对象,它存储了一个硬盘的信息,包括请求队列、分区链表和块设备操作函数集等。块设备驱动程序分配结构gendisk实例,装载分区表,分配请求队列并填充结构的其他域。
 支持分区的块驱动程序必须包含 <linux/genhd.h> 头文件,并声明一个结构gendisk,内核还维护该结构实例的一个全局链表gendisk_head,通过函数add_gendisk、del_gendisk和get_gendisk维护该链表。

struct gendisk {
      int major; //主设备号
      int first_minor; //次设备号
      int minors;  //次设备号的最大数量,没有分区的设备,此值为1
      char disk_name[DISK_NAME_LEN]; //驱动名
      struct block_device_operations *fops; //块设备操作函数集
      struct request_queue *queue;  //请求队列
      ………………………………………………
      int node_id;
};

分配gendisk: struct gendisk *alloc_disk(int minors);
增加gendisk: void add_disk(struct gendisk *gd);
释放gendisk: void del_gendisk(struct gendisk *gd);
gendisk引用计数: 通过get_disk()和put_disk()函数可用来操作引用计数
设置gendisk容量: void set_capacity(struct gendisk *disk, sector_t size);

<3>.设备操作 block_device_operations
struct block_device_operations {
      int (*open)(struct block_device *,fmode_t );
      int (*release)(struct gendisk * ,fmode_t);
      int (*ioctl)(struct block_device *,fmode_t ,unsigned ,unsigned long);
      ………………………………………………………………………………
}

块设备注册与注销:
int register_blkdev(unsigned int major, const char *name);
int unregister_blkdev(unsigned int major, const char *name);

<4>.请求结构 request
linux内核中,使用struct request来表示等待处理的块设备IO请求。
struct request
{
      struct list_head queuelist; //链表结构
      sector_t sector; //要操作的首个扇区
      unsigned long nr_sectors; //要操作的扇区个数
      struct bio *bio; //请求的bio结构体的链表
      struct bio *biotail; //请求的bio结构体的链表尾
      …………………………………………………………
}

<5>.请求队列结构 request_queue
每个块设备都有一个请求队列,每个请求队列单独执行I/O调度,请求队列是由请求结构实例链接成的双向链表,链表以及整个队列的信息用结构request_queue描述,称为请求队列对象结构或请求队列结构。
struct request_queue
{
 struct list_head queue_head; /*待处理的请求队列的链表头*/
 struct request *last_merge; /*指向队列中上次合并的请求*/
 elevator_t *elevator; /*指向电梯算法对象*/
   ........................................
}

队列操作函数:
struct request_queue *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock); //初始化请求队列,一般在模块加载函数中被调用。
void blk_cleanup_queue(request_queue *q); //清除请求队列,完成将请求队列返回给系统任务,一般在模块卸载函数中调用。
struct request *elv_next_request(request_queue_t *queue); //返回下一个要处理的请求,如果没有请求则返回NULL。elv_next_request()不会清除请求,仍然把这个请求放到队列上,因此连续调用它两次,会返回同一个请求结构体。
struct request *blk_fetch_request(struct request_queue *q); //提取请求
struct request *blk_put_request(struct request *req); //去除请求
void elv_requeue_request(request_queue_t *queue, struct request *req); //将1个已经出列的请求归还到队列中
void blkdev_dequeue_request(struct request *req); //从队列中删除一个请求结构体。

<6>.bio结构
通常1个bio对应1个I/O请求,IO调度算法可将连续的bio合并成1个请求。所以,1个请求可以包含多个bio。
struct bio
{
    sector_t bi_sector; //第一个要访问的扇区
    unsigned int bi_size; //以字节为单位要传输的数据大小
    struct bio_vec *bi_io_vec; //实际的vector列表
    ...............................
}
内核还提供了一组函数(宏)用于操作bio:
 int bio_data_dir(struct bio *bio); //这个函数可用于获得数据传输的方向是READ还是WRITE。
 struct page *bio_page(struct bio *bio) ; //这个函数可用于获得目前的页指针。
 int bio_offset(struct bio *bio) ; //这个函数返回操作对应的当前页内的偏移,通常块I/O操作本身就是页对齐的。
 int bio_cur_sectors(struct bio *bio) ; //这个函数返回当前bio_vec要传输的扇区数。
 char *bio_data(struct bio *bio) ; //这个函数返回数据缓冲区的内核虚拟地址。

<7>.内存数据段结构 bio_vec
struct bio_vec
{
    struct page *bv_page; //页指针
    unsigned int bv_len; //要传输数据的长度
    unsigned int bv_offset; //偏移量
}
使用bio_for_each_segment()宏来访问bio的bio_vec成员,可以用这个宏循环遍历整个bio中的每个段.

<8>.Request 与 bio关系

<9>.请求处理的另一种实现方法
分配请求队列:request_queue_t *blk_alloc_queue(int fgp_mask);
对于FLASH、RAM盘等完全随机访问的非机械设备,并不需要进行复杂的I/O调度,这个时候,应该使用上述函数分配1个“请求队列”,并使用如下函数来绑定“请求队列”和“制造请求”函数。
void blk_queue_make_request(request_queue_t *q,make_request_fn *fn); //绑定请求队列和制造请求函数
void blk_queue_logical_block_size(struct request_queue *q, unsigned short size); //该函数用于告知内核块设备硬件扇区的大小,所有由内核产生的请求都是这个大小的倍数并且被正确对界。但是,内核块设备层和驱动之间的通信还是以512字节扇区为单位进行。

<10>.块设备驱动测试
#insmod simple-blk.ko
#ls /dev/simp_blkdev
#mkfs.ext3 /dev/simp_blkdev
#mkdir -p /mnt/blk
#mount
#cp /etc/init.d/* /mnt/blk
#ls /mnt/blk
#umount /mnt/blk
#ls /mnt/blk

<11>.块设备驱动总结:
在块设备驱动的模块加载函数中通常需要完成如下工作:
   ① 分配、初始化请求队列,绑定请求队列和请求函数。
   ② 分配、初始化gendisk,给gendisk的major、fops、queue等成员赋值,最后添加gendisk。
   ③ 注册块设备驱动。
在块设备驱动的模块卸载函数中通常需要与模块加载函数相反的工作:
    ① del_gendisk(xxx_disks);//清除请求队列。
 ② blk_cleanup_queue(xxx_queue[i]); //清除请求队列
 ③ unregister_blkdev(xxx_major, "xxx"); //删除对块设备的引用,注销块设备驱动。

参考链接:http://bbs.chinaunix.net/thread-2017377-1-1.html

http://blog.csdn.net/yangdelong/article/details/5499797

参考示例代码链接:http://hi.baidu.com/fighter0425/item/593be8d5b17c8af0795daa87

参考文档下载链接:http://download.csdn.net/detail/klcf0220/5867629

示例代码下载链接:http://download.csdn.net/detail/klcf0220/5869071

linux块设备驱动的更多相关文章

  1. linux块设备驱动之实例

    1.注册:向内核注册个块设备驱动,其实就是用主设备号告诉内核这个代表块设备驱动 sbull_major  =  register_blkdev(sbull_major, "sbull&quo ...

  2. Linux块设备驱动详解

    <机械硬盘> a:磁盘结构 -----传统的机械硬盘一般为3.5英寸硬盘,并由多个圆形蝶片组成,每个蝶片拥有独立的机械臂和磁头,每个堞片的圆形平面被划分了不同的同心圆,每一个同心圆称为一个 ...

  3. Linux 块设备驱动 (一)

    1.块设备的I/O操作特点 字符设备与块设备的区别: 块设备只能以块为单位接受输入和返回输出,而字符设备则以字符为单位. 块设备对于I/O请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设 ...

  4. Linux块设备驱动(一) _驱动模型

    块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,本文以3.14为蓝本,探讨内核中的块设备驱动模型 框架 下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一 ...

  5. Linux块设备驱动(二) _MTD驱动及其用户空间编程

    MTD(Memory Technology Device)即常说的Flash等使用存储芯片的存储设备,MTD子系统对应的是块设备驱动框架中的设备驱动层,可以说,MTD就是针对Flash设备设计的标准化 ...

  6. Linux块设备驱动_WDS

    推荐书:<Linux内核源代码情景分析> 1.字符设备驱动和使用中等待某一事件的方法①查询方式②休眠唤醒,但是这种没有超时时间③poll机制,在休眠唤醒基础上加一个超时时间④异步通知,异步 ...

  7. linux 块设备驱动 (三)块设备驱动开发

    一: 块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为:int register_blkdev(unsigne ...

  8. linux块设备驱动(一)——块设备概念介绍

    本文来源于: 1. http://blog.csdn.net/jianchi88/article/details/7212370 2. http://blog.chinaunix.net/uid-27 ...

  9. linux块设备驱动---程序设计(转)

    块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为:int register_blkdev(unsigned i ...

随机推荐

  1. eclipse --- 新建JSP页面默认模版设置

    设置 在eclipse中新建 jsp时是这样的: 有时候我们不想字符集是ISO_8859-1,想字符集是UTF-8,一个个修改会很麻烦,那么我们可以修改jsp模版的设置: window>Pref ...

  2. 设置 webstorm 对 .vue 高亮

    1. 首先安装vue插件,安装方法: setting  -->  plugin  ,点击plugin,在内容部分的左侧输入框输入vue,会出现两个关于vue的插件,点击安装即可.安装完成后,就可 ...

  3. 反射attr以及模块动态导入

    一.实现自省的四个函数 1.hasattr判断一个对象中有没有一个name字符串对应的方法或属性 class BlackMedium: feture="Ugly" def __in ...

  4. Golang面向API编程-interface(接口)

    Golang面向API编程-interface(接口) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. Golang并不是一种典型的面向对象编程(Object Oriented Pr ...

  5. python---redis缓存页面实现

    import tornado.web from controllers.BaseController import BaseRequestHandler import redis pool = red ...

  6. CM记录-优化配置解决Reduce卡顿问题

    CDH大数据集群问题问题分析与解决方案 问题描述:Hive提交任务,一直卡在Reduce阶段,进度缓慢. 日志分析:NodeManager节点产生的usercache所在分区空间不足,导致进程异常退出 ...

  7. spring tool suite处理 maven项目名称红色感叹号的问题

    今天构建一个springboot项目时,发现项目有个红色感叹号,但是pom.xml跟Build Path 都没问题. 解决方案: 选择 Windows --> show view --> ...

  8. Java POI 读取word文件

    Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能. 1.读取word 2003及word 2007需要 ...

  9. POJ 2970 The lazy programmer

    The lazy programmer Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 2785   Accepted: 70 ...

  10. CodeForces - 327D Block Tower

    D. Block Tower time limit per test 2 seconds memory limit per test 256 megabytes input standard inpu ...