前面用无请求队列实现的ramdisk的驱动程序虽然申请了请求队列,但实际上没用上,因为ramdisk不像实际的磁盘访问速度慢需要缓存,ramdisk之间使用内存空间,所以就没用请求队列了。本文将介绍使用请求队列的ramdisk驱动,虽然对于ramdisk使用请求队列用处不大,但对于基于磁盘的块设备驱动来说却是必须要用的。
在LDD3书中,其中的有些块设备操作函数在当前的linux版本中有了很大的变动,需要自己重新根据新定义的一些函数进行适当的移植,以解决编译时报出的各种错误,主要是在请求处理函数中修改。
在前面用无请求队列实现的ramdisk的驱动程序中直接用blk_alloc_queue来分配请求队列,这样分配的请求队列是没有请求处理函数的,之所以可以这样是因为当时就没有使用请求队列,来自上层的请求都被自定义的请求提交函数处理了。现在如果要使用请求队列,那么就必须要对请求队列初始化其请求处理函数,内核提供了函数blk_init_queue,该函数以自定义的请求函数地址作为参数传来被调用,它既实现了blk_alloc_queue的功能,还初始化了队列的请求处理函数和请求提交函数,其中请求提交函数设置为通用函数blk_queue_bio。这样来自上层的请求通过请求队列被分发到我们自定义的请求处理函数来处理。
1 //分配一个请求队列 2 simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL); 3 if(!simp_blkdev_queue) 4 { 5 ret = -ENOMEM; 6 goto blk_init_queue; 7 }
上面的simp_blkdev_do_request为自定义的请求处理函数。
请求处理函数实现类似于在无请求队列ramdisk驱动中提交请求函数,说白了它们都是对上层请求的处理,只不过前者处理的是多个bio链表(每个请求时一个bio链表),后者仅仅处理一个bio而已。我们只需要遍历每个bio链表中的每个bio,并根据当前请求的类型来做相应的处理就可以了。值得注意的是自2.6.31内核开始,一些函数发生变化(见linux/include/blkdev.h)。在2.6.32内核中,
request -> sectors 变为 blk_rq_pos(request)
request -> nr_sectors 变为 blk_rq_nr_sectors(request)
elv_next_request(request) 变为 blk_fetch_request(request)
end_request(request, error) 变为 __blk_end_request_all(req, err))
1 static void simp_blkdev_do_request(struct request_queue *q) 2 { 3 struct request *req; 4 struct req_iterator ri; 5 struct bio_vec *bvec; 6 char *disk_mem; 7 char *buffer; 8 9 //依次从队列中获取request 10 while ((req = blk_fetch_request(q)) != NULL) { 11 //判断当前request是否合法 12 if ((blk_rq_pos(req) << 9) + blk_rq_cur_bytes(req) 13 > SIMP_BLKDEV_BYTES) { 14 printk(KERN_ERR SIMP_BLKDEV_DISKNAME 15 ": bad request: block=%llu, count=%u\n", 16 (unsigned long long)blk_rq_pos(req), 17 blk_rq_cur_bytes(req)); 18 blk_end_request_all(req, -EIO); 19 continue; 20 } 21 //获取需要操作的内存位置 22 disk_mem = simp_blkdev_data + (blk_rq_pos(req) << 9); 23 switch (rq_data_dir(req)) { //判断请求的类型 24 case READ: 25 rq_for_each_segment(bvec, req, ri) 26 { 27 buffer = kmap(bvec->bv_page) + bvec->bv_offset; 28 memcpy(buffer, disk_mem, bvec->bv_len); 29 kunmap(bvec->bv_page); 30 disk_mem += bvec->bv_len; 31 } 32 33 /*memcpy(req->buffer, 34 simp_blkdev_data + (blk_rq_pos(req) << 9), 35 blk_rq_cur_bytes(req));*/ 36 __blk_end_request_all(req, 0); 37 break; 38 case WRITE: 39 rq_for_each_segment(bvec, req, ri) 40 { 41 buffer = kmap(bvec->bv_page) + bvec->bv_offset; 42 memcpy(disk_mem, buffer, bvec->bv_len); 43 kunmap(bvec->bv_page); 44 disk_mem += bvec->bv_len; 45 } 46 /*memcpy(simp_blkdev_data + (blk_rq_pos(req) << 9), 47 req->buffer, blk_rq_cur_bytes(req));*/ 48 __blk_end_request_all(req, 0); 49 break; 50 default: 51 /* No default because rq_data_dir(req) is 1 bit */ 52 break; 53 } 54 } 55 }
需要注意的是结束请求需要用__blk_end_request_all函数,不能使用blk_end_request_all,这两个函数的唯一区别就是前者不会去获取队列锁,后者会尝试获取队列锁,用后者会导致系统死锁,并是系统崩溃(反正开始我是崩溃了几次)。上面代码中的rq_for_each_segment是用来遍历一个请求中的所有segment,和bio_for_each_segment类似。