最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【转】写一个块设备驱动 – 第11章

Driver crifan 1801浏览 0评论

第11章

+—————————————————+
|                 写一个块设备驱动                |
+—————————————————+
| 作者:赵磊                                     |
| email: [email protected]                   |
+—————————————————+
| 文章版权归原作者所有。                         |
| 大家可以自由转载这篇文章,但原版权信息必须保留。   |
| 如需用于商业用途,请务必与原作者联系,若因未取得   |
| 授权而收起的版权争议,由侵权者自行负责。       |
+—————————————————+

本章中我们仍然为块设备驱动程序使用高端内存做准备工作。
这里要进行的准备工作并不意味着要增加或改变什么功能,
而是要收拾一部分代码,因为它们看起来已经有点复杂了。

有编程经验的读者大概能够意识到,编程时最常做的往往不是输入程序,而是拷贝-粘贴。
这是由于我们在编程时可能会不断地发现设计上的问题,或意识到还可以采用更好的结构,然后当然是实现它。
当然,更理想的情况大概是在一开始规划时就确定一个最佳的结构,以避免将来的更改,
但事实往往会与理想背道而驰,但关键是我们发现这种苗头时要及时纠正,而不是像某些部门一样去得过且过大事化小来掩盖问题。
要知道,酒是越陈越香,而垃圾却是越捂越臭,如果我们无法在最初做出完美的设计,至少我们还拥有纠正的勇气。

这里读者可能已经感觉到了,这里我们将要修改simp_blkdev_make_request()函数,因为它显得有些大了,
以至于在前几章中对其进行修改时,不得不列出大段的代码来展示修改结果。
不过这不是主要原因,相对于缩短函数长度来说,我们分割函数时可能更加在意的是提高代码的可读性。

其实这里分割simp_blkdev_make_request()也是为了将来实现对高端内存的支持,
因为访问高端内存无疑将牵涉到页面映射问题,而页面映射的处理又牵涉到了这个函数,
因此我们也希望把这部分功能独立出来,以免动戳就改动这个大函数,
也可能是为了作者的偏好,因为作者作者哪怕是改动函数中的一个字符,也会把整个函数从头到尾检查一番,
以确定这次改动不会产生其他影响,这就解释了作者为什么更加偏爱简单一些的函数了。
当然这种偏好也不一定完全是好事,比如前两天选择液晶电视时,作者就趋向于显示器+机顶盒…

对于一直坚持到这一章的读者而言,应该对simp_blkdev_make_request()函数的功能烂熟于心了,
因此我们直接列出修改后的代码:
static int simp_blkdev_trans_oneseg(struct page *start_page,
unsigned long offset, void *buf, unsigned int len, int dir)
{
void *dsk_mem;

dsk_mem = page_address(start_page);
if (!dsk_mem) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": get page’s address failed: %pn", start_page);
return -ENOMEM;
}
dsk_mem += offset;

if (!dir)
memcpy(buf, dsk_mem, len);
else
memcpy(dsk_mem, buf, len);

return 0;
}

static int simp_blkdev_trans(unsigned long long dsk_offset, void *buf,
unsigned int len, int dir)
{
unsigned int done_cnt;
struct page *this_first_page;
unsigned int this_off;
unsigned int this_cnt;

done_cnt = 0;
while (done_cnt < len) {
/* iterate each data segment */
this_off = (dsk_offset + done_cnt) & ~SIMP_BLKDEV_DATASEGMASK;
this_cnt = min(len – done_cnt,
(unsigned int)SIMP_BLKDEV_DATASEGSIZE – this_off);

this_first_page = radix_tree_lookup(&simp_blkdev_data,
(dsk_offset + done_cnt) >> SIMP_BLKDEV_DATASEGSHIFT);
if (!this_first_page) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": search memory failed: %llun",
(dsk_offset + done_cnt)
>> SIMP_BLKDEV_DATASEGSHIFT);
return -ENOENT;
}

if (IS_ERR_VALUE(simp_blkdev_trans_oneseg(this_first_page,
this_off, buf + done_cnt, this_cnt, dir)))
return -EIO;

done_cnt += this_cnt;
}

return 0;
}

static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio)
{
int dir;
unsigned long long dsk_offset;
struct bio_vec *bvec;
int i;
void *iovec_mem;

switch (bio_rw(bio)) {
case READ:
case READA:
dir = 0;
break;
case WRITE:
dir = 1;
break;
default:
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": unknown value of bio_rw: %lun", bio_rw(bio));
goto bio_err;
}

if ((bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT) + bio->bi_size
> simp_blkdev_bytes) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": bad request: block=%llu, count=%un",
(unsigned long long)bio->bi_sector, bio->bi_size);
goto bio_err;
}

dsk_offset = bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT;

bio_for_each_segment(bvec, bio, i) {
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
if (!iovec_mem) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": map iovec page failed: %pn", bvec->bv_page);
goto bio_err;
}

if (IS_ERR_VALUE(simp_blkdev_trans(dsk_offset, iovec_mem,
bvec->bv_len, dir)))
goto bio_err;

kunmap(bvec->bv_page);

dsk_offset += 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;

bio_err:
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
}

代码在功能上与原先没什么不同,
我们只是从中抽象出处理块设备与一段连续内存之间数据传输的simp_blkdev_trans()函数,
和同样功能的、但数据长度符合块设备数据块长度限制的simp_blkdev_trans_oneseg()函数。

这样一来,程序的结构就比较明显了:
simp_blkdev_make_request()负责决定数据传输方向、检查bio请求是否合法、遍历bio中的每个bvec、映射bvec中的内存页,
然后把剩余的工作扔给simp_blkdev_trans(),
而simp_blkdev_trans()函数通过分割请求数据搞定了数据跨越多个块设备数据块的问题,并且顺便把块设备数据块的第一个page给找了出来,
然后邀请simp_blkdev_trans_oneseg()函数出场。
simp_blkdev_trans_oneseg()函数是幸运的,因为前期的大多数铺垫工作已经做完了,而它只要像领导种树一样装模作样的添上最后一铲土,
就可以迎来开热烈的掌声。实际上,simp_blkdev_trans_oneseg()拿到page指针对应的内存,然后根据给定的数据方向执行指定长度的数据传输。
simp_blkdev_trans_oneseg()不需要关心数据长度是否超出块设备数据块边界的问题,正如领导也不会去管那棵树的死活一样。

本章的代码也同样不做实验,因为我们确实也没什么好做的。
至于能不能通过编译,作者已经试过了,有兴趣的读者大概可以验证一下前一句话是不是真的。

作为支持高端内存的前奏,前一章和本章中做了一些可能让人觉得莫名其妙的改动。
不过到此为止,准备工作已经做得差不多了,我们的程序已经为支持高端内存打下坚实的基础。
下一章将进入正题,我们将实现这一期盼已久的功能。

转载请注明:在路上 » 【转】写一个块设备驱动 – 第11章

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
82 queries in 0.154 seconds, using 22.13MB memory