linux块设备驱动
块设备驱动程序
<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块设备驱动的更多相关文章
- linux块设备驱动之实例
1.注册:向内核注册个块设备驱动,其实就是用主设备号告诉内核这个代表块设备驱动 sbull_major = register_blkdev(sbull_major, "sbull&quo ...
- Linux块设备驱动详解
<机械硬盘> a:磁盘结构 -----传统的机械硬盘一般为3.5英寸硬盘,并由多个圆形蝶片组成,每个蝶片拥有独立的机械臂和磁头,每个堞片的圆形平面被划分了不同的同心圆,每一个同心圆称为一个 ...
- Linux 块设备驱动 (一)
1.块设备的I/O操作特点 字符设备与块设备的区别: 块设备只能以块为单位接受输入和返回输出,而字符设备则以字符为单位. 块设备对于I/O请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设 ...
- Linux块设备驱动(一) _驱动模型
块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,本文以3.14为蓝本,探讨内核中的块设备驱动模型 框架 下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一 ...
- Linux块设备驱动(二) _MTD驱动及其用户空间编程
MTD(Memory Technology Device)即常说的Flash等使用存储芯片的存储设备,MTD子系统对应的是块设备驱动框架中的设备驱动层,可以说,MTD就是针对Flash设备设计的标准化 ...
- Linux块设备驱动_WDS
推荐书:<Linux内核源代码情景分析> 1.字符设备驱动和使用中等待某一事件的方法①查询方式②休眠唤醒,但是这种没有超时时间③poll机制,在休眠唤醒基础上加一个超时时间④异步通知,异步 ...
- linux 块设备驱动 (三)块设备驱动开发
一: 块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为:int register_blkdev(unsigne ...
- linux块设备驱动(一)——块设备概念介绍
本文来源于: 1. http://blog.csdn.net/jianchi88/article/details/7212370 2. http://blog.chinaunix.net/uid-27 ...
- linux块设备驱动---程序设计(转)
块设备驱动注册与注销 块设备驱动中的第1个工作通常是注册它们自己到内核,完成这个任务的函数是 register_blkdev(),其原型为:int register_blkdev(unsigned i ...
随机推荐
- Web应用:当文件超过100KB,无法上传,有种原因你想象不到
今天下午2点多,突然发现凡是文件超过100KB的,在上传的时候都会卡住,但低于100KB的文件可以上传成功. 服务器端使用的是asp无组件上传,为什么突然出现这种问题呢? 我们知道,IIS默认上传限制 ...
- apigateway-kong(五)集群搭建部署
kong 集群将使得系统通过增加更多机器,从而实现水平扩展,承接更多的请求流量.它们将共享同样的配置且使用同一个数据库.kong 集群中的的所有节点都连接同一个数据库. 你需要在 kong 集群的上一 ...
- MATLAB:增加噪声,同时多次叠加噪声图和原图以及求平均图像(imnoise,imadd函数)
本次涉及了对原图像增加高斯噪声.多次叠加原图和高斯噪声图以及叠加后的平均图像. close all; %关闭当前所有图形窗口,清空工作空间变量,清除工作空间所有变量 clear all; clc; R ...
- 微软笔记工具OneNote
闲来无事,就在网上找找记笔记的软件工具.发现了微软的OneNote. 门槛还是比较高的:需要微软账户,安装也比较慢.不过用起来体验不错.除了干净的界面,功能也不错,还能同步到云端,这样你随时随地都可以 ...
- docker存储与网络
目录 Docker存储 挂载主机目录 创建一个数据卷 挂载一个宿主机目录作为数据卷 数据卷容器 创建一个数据卷容器 利用数据卷容器迁移数据 删除数据盘 Docker网络 简介 bridge网络 bri ...
- jenkins+gitlab webhooks 实现自动触发打包
说明:实现代码在gitlab上的提交后立马自动进行jenkins的job构建 安装插件: Gitlab Hook Plugin Build Authorization Token Root Plug ...
- Telnet的三种登录方式
Telnet的三种登录方式 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.华为创建telnet的三种验证方式 首先,我们可以简单的看一个拓扑图,让我们可以在亦庄的路由器上对双桥 ...
- ivew实现table的编辑保存追加删除
ivew实现table的编辑 例子1 例子2
- Codeforces 590D Top Secret Task
D. Top Secret Task time limit per test 3 seconds memory limit per test 256 megabytes input standard ...
- Java编程思想 学习笔记9
九.接口 接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法. 1.抽象类和抽象方法 抽象类是普通的类与接口之间的一种中庸之道.创建抽象类是希望通过这个通用接口操纵一系列类. Java提 ...