linux下的块设备驱动(一)
块设备的驱动比字符设备的难,这是因为块设备的驱动和内核的联系进一步增大,但是同时块设备的访问的几个基本结构和字符还是有相似之处的。
有一句话必须记住:对于存储设备(硬盘~~带有机械的操作)而言,调整读写的顺序作用巨大,因为读写连续的扇区比分离的扇区快。
但是同时:SD卡和U盘这类设备没有机械上的限制,所以像上面说的进行连续扇区的调整显得就没有必要了。
先说一下对于硬盘这类设备的简单的驱动。
在linux的内核中,使用gendisk结构来表示一个独立的磁盘设备或者分区。这个结构中包含了磁盘的主设备号,次设备号以及设备名称。
在国嵌给的历程中,对gendisk这个结构体的填充是在simp_blkdev_init函数中完成的。在对gendisk这个结构填充之前要对其进行分配空间。具体代码如下:
simp_blkdev_disk = alloc_disk(1);
if (!simp_blkdev_disk) {
ret = -ENOMEM;
goto err_alloc_disk;
}
这里的alloc_disk函数是在内核中实现的,它后面的参数1代表的是使用次设备号的数量,这个数量是不能被修改的。
在分配好了关于gendisk的空间以后就开始对gendisk里面的成员进行填充。具体代码如下:
strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME); //宏定义simp_blkdev
simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR; //主设备号
simp_blkdev_disk->first_minor = 0; //次设备号
simp_blkdev_disk->fops = &simp_blkdev_fops; //主要结构
simp_blkdev_disk->queue = simp_blkdev_queue;
set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9); //宏定义(16*1024*1024),实际上就是这个结构体。
在填充好gendisk这个结构以后向内核中 注册这个磁盘设备。具体代码如下:
add_disk(simp_blkdev_disk);
在LDD中说,想内核中注册设备的必须在gendisk这个结构体已经填充好了以后,我们以前的字符设备的时候也是这么做的,不知道为什么LDD在这里强调了这个。
当不需要一个磁盘的时候要释放gendisk,释放部分的代码在函数simp_blkdev_exit中实现的。具体的释放代码如下:
del_gendisk(simp_blkdev_disk);
在simp_blkdev_exit中同时还有put_disk(simp_blkdev_disk),这个是用来进行操作gendisk的引用计数。simp_blkdev_exit还实现了blk_cleanup_queue清除请求队列的这个函数。终于说到请求队列了。
在说等待队列之前先要明确几个概念:
①用户希望对硬盘数据做的事情叫做请求,这个请求和IO请求是一样的,所以IO请求来自于上层。
②每一个IO请求对应内核中的一个bio结构。
③IO调度算法可以将连续的bio(也就是用户的对硬盘数据的相邻簇的请求)合并成一个request。
④多个request就是一个请求队列,这个请求队列的作用就是驱动程序响应用户的需求的队列。
请求队列在国嵌的程序中的simp_blkdev_queue
下面先说一下硬盘这类带有机械的存储设备的驱动。
这类驱动中用户的IO请求对应于硬盘上的簇可能是连续的,可能是不连续的,连续的当然好,如果要是不连续的,那么IO调度器就会对这些BIO进行排序(例如老谢说的电梯调度算法),合并成一个request,然后再接收请求,再合并成一个request,多个request之后那么我们的请求队列就形成了,然后就可以向驱动程序提交了。
在硬盘这类的存储设备中,请求队列的初始化代码如下:
simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
老谢说,在这种情况下首先调用的是内核中的make_requst函数,然后再调用自己定义的simp_blkdev_do_request。追了一下内核代码,会发现make_requst内核代码如下所示:
static int make_request(struct request_queue *q, struct bio * bio)
具体的就不贴了,不过可以知道这个make_request的作用就是使用IO调度器对多个bio的访问顺序进行了优化调整合并为一个request。也就是在执行完成了这个函数之后才去正式的执行内核的请求队列。
合并后的request其实还是一个结构,这个结构用来表征IO的请求,这个结构在内核中有具体的定义。
请求是一个结构,同时请求队列也是一个结构,这个请求队列在内核中的结构定义如下:
struct request_queue
{
/*
* Together with queue_head for cacheline sharing
*/
struct list_head queue_head;
struct request *last_merge;
struct elevator_queue *elevator; /*
* the queue request freelist, one for reads and one for writes
*/
struct request_list rq; request_fn_proc *request_fn;
make_request_fn *make_request_fn;
prep_rq_fn *prep_rq_fn;
unplug_fn *unplug_fn;
merge_bvec_fn *merge_bvec_fn;
prepare_flush_fn *prepare_flush_fn;
softirq_done_fn *softirq_done_fn;
rq_timed_out_fn *rq_timed_out_fn;
dma_drain_needed_fn *dma_drain_needed;
lld_busy_fn *lld_busy_fn; /*
* Dispatch queue sorting
*/
sector_t end_sector;
struct request *boundary_rq; /*
* Auto-unplugging state
*/
struct timer_list unplug_timer;
int unplug_thresh; /* After this many requests */
unsigned long unplug_delay; /* After this many jiffies */
struct work_struct unplug_work; struct backing_dev_info backing_dev_info; /*
* The queue owner gets to use this for whatever they like.
* ll_rw_blk doesn't touch it.
*/
void *queuedata; /*
* queue needs bounce pages for pages above this limit
*/
gfp_t bounce_gfp; /*
* various queue flags, see QUEUE_* below
*/
unsigned long queue_flags; /*
* protects queue structures from reentrancy. ->__queue_lock should
* _never_ be used directly, it is queue private. always use
* ->queue_lock.
*/
spinlock_t __queue_lock;
spinlock_t *queue_lock; /*
* queue kobject
*/
struct kobject kobj; /*
* queue settings
*/
unsigned long nr_requests; /* Max # of requests */
unsigned int nr_congestion_on;
unsigned int nr_congestion_off;
unsigned int nr_batching; void *dma_drain_buffer;
unsigned int dma_drain_size;
unsigned int dma_pad_mask;
unsigned int dma_alignment; struct blk_queue_tag *queue_tags;
struct list_head tag_busy_list; unsigned int nr_sorted;
unsigned int in_flight[2]; unsigned int rq_timeout;
struct timer_list timeout;
struct list_head timeout_list; struct queue_limits limits; /*
* sg stuff
*/
unsigned int sg_timeout;
unsigned int sg_reserved_size;
int node;
#ifdef CONFIG_BLK_DEV_IO_TRACE
struct blk_trace *blk_trace;
#endif
/*
* reserved for flush operations
*/
unsigned int ordered, next_ordered, ordseq;
int orderr, ordcolor;
struct request pre_flush_rq, bar_rq, post_flush_rq;
struct request *orig_bar_rq; struct mutex sysfs_lock; #if defined(CONFIG_BLK_DEV_BSG)
struct bsg_class_device bsg_dev;
#endif
};
LDD说,请求队列实现了一个插入接口,这个接口允许使用多个IO调度器,大部分IO调度器批量累计IO请求,并将它们排列为递增或者递减的顺序提交给驱动。
多个连续的bio会合并成为一个request,多个request就成为了一个请求队列,这样bio的是直接的也是最基本的请求,bio这个结构的定义如下:
struct bio { sector_t bi_sector;
struct bio *bi_next; /* request queue link */
struct block_device *bi_bdev; /* target device */
unsigned long bi_flags; /* status, command, etc */ unsigned long bi_rw; /* low bits: r/w, high: priority */
unsigned int bi_vcnt; /* how may bio_vec's */
unsigned int bi_idx; /* current index into bio_vec array */
unsigned int bi_size; /* total size in bytes */
unsigned short bi_phys_segments; /* segments after physaddr coalesce*/ unsigned short bi_hw_segments; /* segments after DMA remapping */ unsigned int bi_max; /* max bio_vecs we can hold used as index into pool */ struct bio_vec *bi_io_vec; /* the actual vec list */
bio_end_io_t *bi_end_io; /* bi_end_io (bio) */
atomic_t bi_cnt; /* pin count: free when it hits zero */ void *bi_private;
bio_destructor_t *bi_destructor; /* bi_destructor (bio) */ };
需要注意的是,在bio这个结构中最重要的是bio.vec这个结构。同时还有许多操作bio的宏,这些都是内核给实现好了的。
请求队列的实现:
首先使用 while ((req = elv_next_request(q)) != NULL)进行循环检测,看看到底传来的IO请求是个什么。
然后进行读写区域的判定:
if ((req->sector + req->current_nr_sectors) << 9
> SIMP_BLKDEV_BYTES) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": bad request: block=%llu, count=%u\n",
(unsigned long long)req->sector,
req->current_nr_sectors);
//结束本次请求。
end_request(req, 0);
continue;
}
在进行读写区域的判定的时候涉及到了很多linux的编程习惯。
sector表示要访问的第一个扇区。
current_nr_sectors表示预计访问扇区的数目。
这里的左移九位其实就是乘以512。
这样((req->sector + req->current_nr_sectors) << 9就计算出可以预计要访问的扇区的大小。进行了一次判断。
如果上面的判断没有超出范围,那么就可以对请求的这一部分块设备进行操作了。
simp_blkdev_disk = alloc_disk(1);
if (!simp_blkdev_disk) {
ret = -ENOMEM;
goto err_alloc_disk;
} strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
simp_blkdev_disk->first_minor = 0;
simp_blkdev_disk->fops = &simp_blkdev_fops;
simp_blkdev_disk->queue = simp_blkdev_queue;
set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
add_disk(simp_blkdev_disk); return 0;
关于gendisk结构的内存分配和成员的填充和硬盘类块设备是一样的。
由于SD卡和U盘属于一类非机械类的设备,所以我们不需要那么复杂的调度算法,也就是不需要把io请求进行排序,所以我们需要自己为自己分配一个请求队列。具体代码如下:
simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
需要注意一下的是在硬盘类块设备的驱动中这个函数的原型是blk_init_queue(simp_blkdev_do_request, NULL);
在这种情况下其实并没有调用内核的make_request(这个函数的功能上文说过),也就是说我们接下来要绑定的simp_blkdev_make_request这个函数和make_request是同一级别的函数。这里就没有涉及到算法调度的问题了。
然后需要进行的工作是:绑定制造请求函数和请求队列。具体代码如下:
blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);
把gendisk的结构成员都添加好了以后就可以执行add_disk(simp_blkdev_disk);这个函数把这块分区添加进内核了。
整体列出制造请求部分的函数。
//这个条件是在判断当前正在运行的内核版本。
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
} dsk_mem = simp_blkdev_data + (bio->bi_sector << 9); //遍历
bio_for_each_segment(bvec, bio, i) {
void *iovec_mem; switch (bio_rw(bio)) {
case READ:
case READA:
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
memcpy(iovec_mem, dsk_mem, bvec->bv_len);
kunmap(bvec->bv_page);
break;
case WRITE:
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
memcpy(dsk_mem, iovec_mem, bvec->bv_len);
kunmap(bvec->bv_page);
break;
default:
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": unknown value of bio_rw: %lu\n",
bio_rw(bio));
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
}
dsk_mem += bvec->bv_len;
} #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, bio->bi_size, 0);
#else
bio_endio(bio, 0);
#endif return 0;
}
linux下的块设备驱动(一)的更多相关文章
- linux下的块设备驱动(二)
上一章主要讲了请求队列的一系列问题.下面主要说一下请求函数.首先来说一下硬盘类块设备的请求函数. 请求函数可以在没有完成请求队列的中的所有请求的情况下就返回,也可以在一个请求都不完成的情况下就返回. ...
- linux下获得块设备大小
运行结果如下 jackie@Ubuntu:~/work/0602$ sudo ./a.out /dev/sda/dev/sda3907029168,2000398934016 //BLKGETSIZE ...
- Linux 块设备驱动 (一)
1.块设备的I/O操作特点 字符设备与块设备的区别: 块设备只能以块为单位接受输入和返回输出,而字符设备则以字符为单位. 块设备对于I/O请求有对应的缓冲区,因此它们可以选择以什么顺序进行响应,字符设 ...
- Linux块设备驱动(一) _驱动模型
块设备是Linux三大设备之一,其驱动模型主要针对磁盘,Flash等存储类设备,本文以3.14为蓝本,探讨内核中的块设备驱动模型 框架 下图是Linux中的块设备模型示意图,应用层程序有两种方式访问一 ...
- Linux块设备驱动(二) _MTD驱动及其用户空间编程
MTD(Memory Technology Device)即常说的Flash等使用存储芯片的存储设备,MTD子系统对应的是块设备驱动框架中的设备驱动层,可以说,MTD就是针对Flash设备设计的标准化 ...
- 乾坤合一~Linux设备驱动之块设备驱动
1. 题外话 在蜕变成蝶的一系列学习当中,我们已经掌握了大部分Linux驱动的知识,在乾坤合一的分享当中,以综合实例为主要讲解,在一个月的蜕茧成蝶的学习探索当中,觉得数据结构,指针,链表等等占据了代码 ...
- linux块设备驱动
块设备驱动程序<1>.块设备和字符设备的区别1.读取数据的单元不同,块设备读写数据的基本单元是块,字符设备的基本单元是字节.2.块设备可以随机访问,字符设备只能顺序访问. 块设备的访问:当 ...
- Linux块设备驱动详解
<机械硬盘> a:磁盘结构 -----传统的机械硬盘一般为3.5英寸硬盘,并由多个圆形蝶片组成,每个蝶片拥有独立的机械臂和磁头,每个堞片的圆形平面被划分了不同的同心圆,每一个同心圆称为一个 ...
- Linux块设备驱动_WDS
推荐书:<Linux内核源代码情景分析> 1.字符设备驱动和使用中等待某一事件的方法①查询方式②休眠唤醒,但是这种没有超时时间③poll机制,在休眠唤醒基础上加一个超时时间④异步通知,异步 ...
随机推荐
- DzzOffice结合office web Apps私有部署的实例
很多朋友都想在自己使用的DzzOffice中,调用本地部署的office web Apps server. 下面我就直接从头到尾的将部署全部过程分享给大家. 准备条件:两台服务器,配置稍高点,以免卡顿 ...
- python中的静态方法和类方法
在python中,各种方法的定义如下所示: class MyClass(object): #在类中定义普通方法,在定义普通方法的时候,必须添加self def foo(self,x): print & ...
- [质疑]编程之美求N!的二进制最低位1的位置的问题
引子:编程之美给出了求N!的二进制最低位1的位置的二种思路,但是呢?但是呢?不信你仔细听我道来. 1.编程之美一书给出的解决思路 问题的目标是N!的二进制表示中最低位1的位置.给定一个整数N,求N!二 ...
- bzoj 3270 博物馆(高斯消元)
[题意] 两人起始在s,t点,一人pi概率选择留在i点或等概率移动,问两人在每个房间相遇的概率. [思路] 把两个合并为一个状态,(a,b)表示两人所处的状态,设f[i]为两人处于i状态的概率.则有转 ...
- (转)Spring中Bean的命名问题(id和name区别)及ref和idref之间的区别
Spring中Bean的命名 1.每个Bean可以有一个id属性,并可以根据该id在IoC容器中查找该Bean,该id属性值必须在IoC容器中唯一: 2.可以不指定id属性,只指定全限定类名,如: & ...
- spring依赖注入方法
依赖注入就是在程序运行时期,由外部容器动态的将依赖对象注入到组件中,通俗的理解是:平常我们new一个实例,这个实例的控制权是我们程序员,而控制反转是指new实例工作不由我们程序员来做而是交给sprin ...
- Mysql报错:1172 - Result consisted of more than one row
执行mysql函数时报错:1172 - Result consisted of more than one row 函数语句中select into语句中WHERE account = userNam ...
- Shell script之if...then
1 Variable in shell If you want to print variable, then use echo command. In front of the variable ...
- python中基于descriptor的一些概念
python中基于descriptor的一些概念(上) 1. 前言 2. 新式类与经典类 2.1 内置的object对象 2.2 类的方法 2.2.1 静态方法 2.2.2 类方法 2.3 新式类(n ...
- 桶排序-C-结构体排序
struct TS { int index; ]; }; ] = {{,,,,,"s8"}}; ]; int i; int length = sizeof(a) / sizeof ...