第 12章
+---------------------------------------------------+
| 写一个块设备驱动 |
+---------------------------------------------------+
| 作者:赵磊 |
| email: zhaoleidd@hotmail.com |
+---------------------------------------------------+
| 文章版权归原作者所有。 |
| 大家可以自由转载这篇文章,但原版权信息必须保留。 |
| 如需用于商业用途,请务必与原作者联系,若因未取得 |
----------------------- Page 85-----------------------
| 授权而收起的版权争议,由侵权者自行负责。 |
+---------------------------------------------------+
本章中我们将实现对高端内存的支持
女孩子相处时,和她聊天,逛街,爬山,看电影,下棋中的每一件事情好像都与结婚扯不上太大的关系 ,
但经过天天年年的日积月累后,女孩子在潜意识中可能已经把你看成了她生活的一部分,
最终的结果显得是那么的自然,甚至连求婚都有些多余了
学习也很相似,我们认真学习的的每一样知识,努力寻求的每一个答案就其本身而言,
都不能让自己成为专家,但专家却无一不是经历了长时间的认真学习,
努力钻研和细致思考的结果
正如我们的程序,经历了前几章中的准备工作,离目标功能的距离大概也不算太远了
而现在我们要做得就是实现它
首先改动 alloc_diskmem()函数,给这个函数中申请内存的语句、也就是 alloc_pages()的
gfp_mask中加上__GFP_HIGHMEM标志,
这使得申请块设备的内存块时,会优先考虑使用高端内存
修改过的函数如下:
int alloc_diskmem(void)
{
int ret;
int i;
struct page *page;
INIT_RADIX_TREE(&simp_blkdev_data, GFP_KERNEL);
for (i = 0; i < (simp_blkdev_bytes + SIMP_BLKDEV_DATASEGSIZE - 1)
>> SIMP_BLKDEV_DATASEGSHIFT; i++) {
page = alloc_pages(GFP_KERNEL | __GFP_ZERO | __GFP_HIGHMEM,
SIMP_BLKDEV_DATASEGORDER);
if (!page) {
ret = -ENOMEM;
goto err_alloc;
}
ret = radix_tree_insert(&simp_blkdev_data, i, page);
if (IS_ERR_VALUE(ret))
goto err_radix_tree_insert;
}
----------------------- Page 86-----------------------
return 0;
err_radix_tree_insert:
__free_pages(page, SIMP_BLKDEV_DATASEGORDER);
err_alloc:
free_diskmem();
return ret;
}
不过事情还没有全部做完,拿到了高端内存,我们还要有能力使用它才行
这就如同带回一个身材火爆的 mm仅仅是个开始,更关键的还在于如何不让人家半小时后怒火冲天摔门而
归
因此我们要继续改造使用内存处的代码,也就是 simp_blkdev_trans_oneseg()函数
在此之前这个函数很简单,由于申请的是低端内存,这就保证了这些内存一直是被映射在内核的地址空
间中的
因此只要使用一个 page_address()函数就完成了 page指针到内存指针的转换问题
但对于高端内存就没有这样简单了
首先,高端内存需要在进行访问之前被映射到非线性映射区域,还要在访问之后解除这个映射以免人家
骂我们的程序像公仆欠白条,
我们可以使用 kmap()和 kunmap()函数解决这个问题
然后我们还要考虑另一个边界问题,也就是页面边界
由于我们使用的 kmap()函数一次只能映射一个物理页面,当需要访问的数据在块设备的内存块中跨越页
面边界时,
我们就需要识别这样的情况,并做出相应的处理,也就是多次调用 kmap()和 kunmap()函数对依次每个
页面进行访问
我们可以采用与先前章节中处理被访问数据跨越多个块设备内存块相似的方法来应对这种情况
其实对于这种情况,我们还可以选择另一个方案,就是使用 vmap()函数
我们可以使用它把地址分散的多个物理页面映射到一段地址连续的区域中,
当然对我们正在用作块设备存储空间的这些地址连续的物理页面更没有问题
但问题在于 vmap()函数的内部处理比较复杂,这也意味着vmap()函数需要耗费更多的 CPU时间,
并且使用 vmap()函数时,我们需要一次性映射相当于内存块长度的所有页面,
但我们往往不会访问全部的这些页面,这意味着另一方面的性能损失
因此,我们决定选择使用 kmap()函数,而让程序自己去处理跨页面的访问问题
参照以上的思路,我们写出了新的 simp_blkdev_trans_oneseg()函数:
static int simp_blkdev_trans_oneseg(struct page *start_page,
unsigned long offset, void *buf, unsigned int len, int dir)
{
----------------------- Page 87-----------------------
unsigned int done_cnt;
struct page *this_page;
unsigned int this_off;
unsigned int this_cnt;
void *dsk_mem;
done_cnt = 0;
while (done_cnt < len) {
/* iterate each page */
this_page = start_page + ((offset + done_cnt) >> PAGE_SHIFT);
this_off = (offset + done_cnt) & ~PAGE_MASK;
this_cnt = min(len - done_cnt, (unsigned int)PAGE_SIZE
- this_off);
dsk_mem = kmap(this_page);
if (!dsk_mem) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": map device page failed: %p\n", this_page);
return -ENOMEM;
}
dsk_mem += this_off;
if (!dir)
memcpy(buf + done_cnt, dsk_mem, this_cnt);
else
memcpy(dsk_mem, buf + done_cnt, this_cnt);
kunmap(this_page);
done_cnt += this_cnt;
}
return 0;
}
其核心是使用 kmap()函数将内存页面映射到内核空间然后再进行访问,
以实现对高端内存的操作
到此为止,经历了若干章的问题就这样被解决了
通过这样的改变,我们至少得到了两个好处:
1 :避免了争抢宝贵的低端内存
作为内存消耗大户,霸占低端内存的行为不可容忍,
----------------------- Page 88-----------------------
其理由我们在前些章节中已经论述过
今后我们的程序至少不会在这一方面被人鄙视了
2 :增加了块设备的最大容量
使用原先的程序,在 i386中无论如何也无法建立容量超过 896M的块设备,
实际上更小,这是由于低端内存不可能全部拿来放块设备的数据,
而现在的程序可以使用包括高端内存在内的所有空闲内存,
这无疑大大增加了块设备的最大容量
前些章中没有进行的试验憋到现在终于可以开始了
首先证明这个程序经过了这么多个章节的折腾后仍然是能编译的:
# make
make -C /lib/modules/2.6.18-53.el5/build
SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step12 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686‘
CC [M] /root/test/simp_blkdev/simp_blkdev_step12/simp_blkdev.o
Building modules, stage 2.
MODPOST
CC /root/test/simp_blkdev/simp_blkdev_step12/simp_blkdev.mod.o
LD [M] /root/test/simp_blkdev/simp_blkdev_step12/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686‘
#
然后瞧瞧目前的内存状况:
# cat /proc/meminfo
...
HighTotal: 1146816 kB
HighFree: 509320 kB
LowTotal: 896356 kB
LowFree: 872612 kB
...
#
我们看到高端内存与低端内存分别剩余509M和 872M
然后加载现在的模块,为了让模块吃内存的行为表现得更加显眼一些,
我们使用 size参数指定了更大的块设备容量:
# insmod simp_blkdev.ko size=5 M
#
现在看看内存的变化情况:
# cat /proc/meminfo
...
HighTotal: 1146816 kB
----------------------- Page 89-----------------------
HighFree: 1652 kB
LowTotal: 896356 kB
LowFree: 863696 kB
...
#
结果显示模块如我们所料的吃掉了 5 M左右的高端内存
虽然低端内存看样子也少了一些,我们却不能用模块本身占用的内存空间来解释这一现象,
因为模块的代码和静态数据占用的内存无论如何也到不了 8.9M ,
或许我们解释为用作一些文件操作的缓存了,还有就是基树结构占用的内存,
这个结构占用的内存会随着块设备容量的增大而增加,或者我们可以计算一下 ......
不过现在我们并不打算对这个小问题做过多的关注,因为这是扯淡,
正如闹得沸沸扬扬的周久耕事件的最后调查结果居然仅仅只是公款买烟
因此我们不会纠缠在这 8.9M的问题中,因为很明显大头是在减少的 5 多兆高端内存上,
这减少的 5 M高端内存已经足以证明这几章中的修改结果了
我们再移除这个模块后看看内存的状况:
# rmmod simp_blkdev
# cat /proc/meminfo
...
HighTotal: 1146816 kB
HighFree: 504684 kB
LowTotal: 896356 kB
LowFree: 868480 kB
...
#
刚才被占用的高端内存 回来了,
一切都显得如此的和谐
作为最后一步的测试,我们做一件本章之前做不到的事情,
就是申请大于 896M的内存
刚才我们看到剩余的低端内存和高端内存总共达到了 1.37G ,
好吧,我们就申请 1.3G :
# insmod simp_blkdev.ko size=13 M
#
这时我们惊喜地发现系统没有 DOWN掉
再看看这时的内存情况:
# cat /proc/meminfo
...
HighTotal: 1146816 kB
HighFree: 41204 kB
LowTotal: 896356 kB
LowFree: 48284 kB
----------------------- Page 90-----------------------
...
#
高端内存与低端内存中的大头基本上都被吃掉了,
数量上也差不多是 1.3G ,这符合我们的预期
老让模块占用着这么多的内存也不是什么好主意,
我们放掉:
# rmmod simp_blkdev.
#
随着本章的结束,围绕高端内存的讨论也终于修成正果了
不过我们对这个驱动程序的改进还没有完,因为我们要发扬做精每一样事情的精神,
一个民族的振兴,不是靠对小学生进行填鸭式的政治思想教育,也不是靠官员及家属的出国考察,
更不是靠公仆们身先士卒、前仆后继、以自己的健康为代价大吃大喝以创造9 亿的 GDP ,
而是靠每一个屁民们的诚实、认真、勤劳、勇敢、创造、奉献与精益求精
<未完,待续>
================================================================================
================================================================================
====================================================================
第 13章
+---------------------------------------------------+
| 写一个块设备驱动 |
+---------------------------------------------------+
| 作者:赵磊 |
| email: zhaoleidd@hotmail.com |
+---------------------------------------------------+
| 文章版权归原作者所有。 |
| 大家可以自由转载这篇文章,但原版权信息必须保留。 |
| 如需用于商业用途,请务必与原作者联系,若因未取得 |
| 授权而收起的版权争议,由侵权者自行负责。 |
+---------------------------------------------------+
没有最好的代码,是因为我们总能把代码改得更好
因此我们现在打算做一个小的性能改进,这次我们准备拿free_diskmem()函数下刀
本质上说,这个改进的意义不大,这是因为 free_diskmem()函数仅仅是在模块卸载时被调用,
----------------------- Page 91-----------------------
而对这种执行次数即少 不在关键路径上的函数来说,最好是尽量让他简单以增加可靠性和可读性,
除非它的耗时已经慢到能让人有所感觉,否则0.01秒和 0. 1秒是差不多的,毕竟在现实中尼奥不
太可能用我们的程序
但我们仍然打算继续这一改进,一是为了示范什么是没有意义的改进,二是为了通过这一改进示范使用
radix_tree_gang_lookup()函数和 page->index的技巧
首先我们看看原先的 free_diskmem()函数:
void free_diskmem(void)
{
int i;
struct page *page;
for (i = 0; i < (simp_blkdev_bytes + SIMP_BLKDEV_DATASEGSIZE - 1)
>> SIMP_BLKDEV_DATASEGSHIFT; i++) {
page = radix_tree_lookup(&simp_blkdev_data, i);
radix_tree_delete(&simp_blkdev_data, i);
/* free NULL is safe */
__free_pages(page, SIMP_BLKDEV_DATASEGORDER);
}
}
它遍历所有的内存块索引,在基树中找到这个内存块的 page指针,然后释放内存,顺带着释放掉基数中
的这个节点
考虑到这个函数不仅会在模块卸载时被调用,也会在模块加载时、申请内存中途掉链子时用来擦屁股,
因此也需要考虑内存没有完全申请的情况
所幸的是这种情况下 radix_tree_lookup()函数会返回 NULL指针,而radix_tree_delete()和
__free_pages()函数都能对 NULL指针做出我们最期待的处理:就是什么也不做
这段代码很小很直接,逻辑简单而清晰,性能也差不到哪里去,完全符合设计要求,
不幸的是我们还是打算做一些没必要的优化,借此还可以顺便读一读基树的内核代码
首先看 radix_tree_lookup()函数,它在基数中查找指定索引对应的指针,为了获得这一指针的值,
基本上它需要把基树从上到下找一遍
而对于 free_diskmem()函数而言,我们仅仅是需要遍历基树中的所有节点,使用逐一查找的方法进行
遍历未免代价太大了
就像是我们要给在场的所有同学每人发一个糖果,只需要让他们排好队,每人领一个即可,而不需要按
照名单找出每个人再发
为了实现这一思想,我们跑到 linux/lib/radix-tree.c中找函数,找啊找,找到了
radix_tree_gang_lookup()函数
radix_tree_gang_lookup()函数虽然不是我们理想中的遍历函数,但也有了八九不离十的功能
就像在酒吧里找不到 D Cup ,带回去个 C Cup也总比看A片强
----------------------- Page 92-----------------------
通过 radix_tree_gang_lookup()函数,我们可以一次从基树中获取多个节点的信息:
unsigned int radix_tree_gang_lookup(struct radix_tree_root *root, void
**results, unsigned long first_index, unsigned int max_items);
具体的参数嘛,RTFSC 吧
这是我们注意到使用这个函数时顾此失彼的一面,虽然我们获得了一组需要释放的指针,但却无法获得
这些指针的索引
而执行释放基树中节点的操作时却恰恰需要使用索引作参数
然后就是一个技巧了,我们借用 page结构的 index成员来存储这一索引
之所以可以这样用,是因为 page结构的 index成员在该页用作页高速缓存时存储相对文件起始处的以
页大小为单位的偏移,
而我们所使用的页面不会被同时用作页高速缓存,因此这里可以借用 page.index成员
按照以上思路,我们写出了修改后的代码:
void free_diskmem(void)
{
unsigned long long next_seg;
struct page *seglist[64];
int listcnt;
int i;
next_seg = 0;
do {
listcnt = radix_tree_gang_lookup(&simp_blkdev_data,
(void **)seglist, next_seg, ARRAY_SIZE(seglist));
for (i = 0; i < listcnt; i++) {
next_seg = seglist[i]->index;
radix_tree_delete(&simp_blkdev_data, next_seg);
__free_pages(seglist[i], SIMP_BLKDEV_DATASEGORDER);
}
next_seg++;
} while (listcnt == ARRAY_SIZE(seglist));
}
当然,alloc_diskmem()函数中也需要加上 page->index = i这一行,用于把基树的索引存入
page.index ,修改后的代码如下:
int alloc_diskmem(void)
{
int ret;
int i;
struct page *page;
----------------------- Page 93-----------------------
INIT_RADIX_TREE(&simp_blkdev_data, GFP_KERNEL);
for (i = 0; i < (simp_blkdev_bytes + SIMP_BLKDEV_DATASEGSIZE - 1)
>> SIMP_BLKDEV_DATASEGSHIFT; i++) {
page = alloc_pages(GFP_KERNEL | __GFP_ZERO | __GFP_HIGHMEM,
SIMP_BLKDEV_DATASEGORDER);
if (!page) {
ret = -ENOMEM;
goto err_alloc;
}
page->index = i;
ret = radix_tree_insert(&simp_blkdev_data, i, page);
if (IS_ERR_VALUE(ret))
goto err_radix_tree_insert;
}
return 0;
err_radix_tree_insert:
__free_pages(page, SIMP_BLKDEV_DATASEGORDER);
err_alloc:
free_diskmem();
return ret;
}
现在试验一下修改后的代码,先看看能不能编译:
# make
make -C /lib/modules/2.6.18-53.el5/build
SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step13 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686‘
CC [M] /root/test/simp_blkdev/simp_blkdev_step13/simp_blkdev.o
Building modules, stage 2.
MODPOST
CC /root/test/simp_blkdev/simp_blkdev_step13/simp_blkdev.mod.o
LD [M] /root/test/simp_blkdev/simp_blkdev_step13/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686‘
#
看看当前系统的内存情况:
# cat /proc/meminfo
HighTotal: 1146816 kB
----------------------- Page 94-----------------------
HighFree: 339144 kB
LowTotal: 896356 kB
LowFree: 630920 kB
...
#
这里显示现在剩余339M高端内存和 630M低端内存
然后加载我们的模块,让它吃掉3 M内存:
# insmod simp_blkdev.ko size=3 M
# cat /proc/meminfo
HighTotal: 1146816 kB
HighFree: 137964 kB
LowTotal: 896356 kB
LowFree: 5239 kB
...
#
正如我们的预期,剩余内存减少3 M左右
然后看看卸载模块后的内存情况:
# rmmod simp_blkdev
# cat /proc/meminfo
HighTotal: 1146816 kB
HighFree: 338028 kB
LowTotal: 896356 kB
LowFree: 631044 kB
...
#
我们发现剩余内存增加了 3 M ,这意味着模块已经把吃掉的内存吐回来了,
从而可以推断出我们修改过的 free_diskmem()函数基本上是能够工作的
本章的改动不大,就算是暂作休整,以留住忍耐至今忍无可忍认为无需再忍而开始打包收拾行李准备溜
之大吉的读者们
不过下一章中倒是预备了一个做起来让人比较有成就感的功能
<未完,待续>
================================================================================
================================================================================
====================================================================
----------------------- Page 95-----------------------
第 14章
+---------------------------------------------------+
| 写一个块设备驱动 |
+---------------------------------------------------+
| 作者:赵磊 |
| email: zhaoleidd@hotmail.com |
+---------------------------------------------------+
| 文章版权归原作者所有。 |
| 大家可以自由转载这篇文章,但原版权信息必须保留。 |
| 如需用于商业用途,请务必与原作者联系,若因未取得 |
| 授权而收起的版权争议,由侵权者自行负责。 |
+---------------------------------------------------+
在本章中我们要做一个比较大的改进,就是实现内存的推迟分配
这意味着我们并不是在驱动程序加载时就分配用于容纳数据的全部内存,
而是推迟到真正需要用到某块内存时再进行分配
详细来说,我们将在块设备的某个区域上发生第一次写请求时分配用于容纳被写入数据的内存,
如果读者在之前章节的熏陶下养成了细致的作风和勤于思考的习惯,
应该能发现这里提到的分配内存的时机是第一次写,而不是第一次读写
现在可能有些读者已经悟出了这样做的道理,让我们无视他们,依然解释一下这样做的目的
对块设备而言,只要保证读出的数据是最近一次写进的即可
如果在读数据之前从来没有往块设备的同一块区域中写入数据,那么这时返回任何随机数据都是正确的
这意味着对于第一次读,我们完全可以返回任意的数据给用户,这时并不需要分配某段内存来存储它
对真实的物理设备而言,就像我们买回的新硬盘,出厂时盘片中的数据内容是什么都无所谓
在具体的实现中,我们可以不对用以接收被读出数据的内存进行任何填充,直接告诉上层“已经读好了”,
这样做无疑会更加快速,但这会造成 2个问题:
1 :这块内存原先的内容最终将被传送到用户程序中,这将造成数据安全问题
2 :违背了真实设备的一个潜特性,就是即使这个设备没有写入任何内容,对同一区域的多次读操作返回
的内容相同
因此,我们将向接收数据的内存中写些什么,最简单的就是用全 填充了
实现这一功能的优点在于,块设备不需要在一开始加载时就占用全部的内存,这优化了系统资源的使用
率
让我们假设块设备自始至终没有被全部填满时,通过本章的功能,将占用更少的内存
另外,我们甚至可以创建容量远远大于机器物理内存的块设备,只要在随后的使用中不往这个块设备中
写入过多的内容即可
在 linux中,类似的思想被广泛应用
----------------------- Page 96-----------------------
比如对进程的内存区而言,并不是一开始就为这段内存区申请和映射全部需要的物理内存,
如在不少文件系统中,也不会给没有写入内容的文件部分分配磁盘的
现在我们就实现这一功能
分析代码,我们发现不太容易找到往什么地方加代码
往往在这种情况下,不如首先看看可以剥掉哪部分不需要的代码,
正如初次跟一个 mm时,如果两个人都有些害羞,不知道从哪开始、或者正在期待对方打开局面时,
不如先脱下该脱的东西,然后的事情基本上就比较自然了
现在的代码中,明显可以砍掉的是在驱动程序加载时用于申请容纳数据的内存的代码,
也就是 alloc_diskmem()函数,把它砍了,没错,是全砍了
还有调用它的代码,在 simp_blkdev_init()函数里面的这几行:
ret = alloc_diskmem();
if (IS_ERR_VALUE(ret))
goto err_alloc_diskmem;
是的,也砍了
还没完,既然这个函数的调用都没了,那么调用这个函数失败时的出错处理也没用了,也就是:
err_alloc_diskmem:
put_disk(simp_blkdev_disk);
这两句,不用犹豫了,砍掉
经过刚才的大刀阔斧后,我们发现......刚才由于砍上瘾了,不小心多砍了一条语句,就是对基树的初
始化语句:
INIT_RADIX_TREE(&simp_blkdev_data, GFP_KERNEL);
原来它是在 alloc_diskmem()函数里面的,现在 alloc_diskmem()函数不在了,我们索性把它放到初
始化模块的 simp_blkdev_init()函数中,
放到刚才原来调用 alloc_diskmem()函数的位置就行了
(注:
其实这里不添加 INIT_RADIX_TREE()宏也行,直接在定义基树结构时顺便初始化掉就行了,也就是把
static struct radix_tree_root simp_blkdev_data;
改成
static struct radix_tree_root simp_blkdev_data = RADIX_TREE_INIT(GFP_KERNEL);
就行了,或者改成让人更加撞墙的形式:
static RADIX_TREE(simp_blkdev_data, GFP_KERNEL);
也可以,但我们这里的代码中,依然沿用原先的方式
)
这样一来,simp_blkdev_init()函数变成了这个样子:
static int __init simp_blkdev_init(void)
{
----------------------- Page 97-----------------------
int ret;
ret = getparam();
if (IS_ERR_VALUE(ret))
goto err_getparam;
simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
if (!simp_blkdev_queue) {
ret = -ENOMEM;
goto err_alloc_queue;
}
blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);
simp_blkdev_disk = alloc_disk(SIMP_BLKDEV_MAXPARTITIONS);
if (!simp_blkdev_disk) {
ret = -ENOMEM;
goto err_alloc_disk;
}
INIT_RADIX_TREE(&simp_blkdev_data, GFP_KERNEL);
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 >> SIMP_BLKDEV_SECTORSHIFT);
add_disk(simp_blkdev_disk);
return 0;
err_alloc_disk:
blk_cleanup_queue(simp_blkdev_queue);
err_alloc_queue:
err_getparam:
return ret;
}
淋漓尽致地大砍一番之后,我们发现下一步的工作清晰多了
现在在模块加载时,已经不会申请所需的内存,而我们需要做的就是,
在处理块设备读写操作时,添加不存在相应内存时的处理代码
----------------------- Page 98-----------------------
在程序中,查找基数中的一个内存块是在 simp_blkdev_trans()函数内完成的,目前的处理是:
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: %llu\n",
(dsk_offset + done_cnt)
>> SIMP_BLKDEV_DATASEGSHIFT);
return -ENOENT;
}
也就是找不到内存块时直接看作错误
在以前这是正确的,因为所有的内存块都在初始化驱动程序时申请了,因此除非电脑的脑子进水了,
运行错了指令,或者人脑的脑子进水了,编错了代码,否则不会发生这种情况
但现在情况不同了,这时找不到内存块是正常的,这意味着该位置的数据从未被写入过,
因此我们需要在这里做出合理的动作
也就是在本章开始时所说的,对于读处理返回全 ,对于写处理给块设备的这段空间申请内存,并写入数
据
因此我们把上段代码改成了这个样子:
this_first_page = radix_tree_lookup(&simp_blkdev_data,
(dsk_offset + done_cnt) >> SIMP_BLKDEV_DATASEGSHIFT);
if (!this_first_page) {
if (!dir) {
memset(buf + done_cnt, 0, this_cnt);
goto trans_done;
}
/* prepare new memory segment for write */
this_first_page = alloc_pages(
GFP_KERNEL | __GFP_ZERO | __GFP_HIGHMEM,
SIMP_BLKDEV_DATASEGORDER);
if (!this_first_page) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": allocate page failed\n");
return -ENOMEM;
}
this_first_page->index = (dsk_offset + done_cnt)
>> SIMP_BLKDEV_DATASEGSHIFT;
if (IS_ERR_VALUE(radix_tree_insert(&simp_blkdev_data,
----------------------- Page 99-----------------------
this_first_page->index, this_first_page))) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": insert page to radix_tree failed"
" seg=%lu\n", this_first_page->index);
__free_pages(this_first_page,
SIMP_BLKDEV_DATASEGORDER);
return -EIO;
}
}
对这段代码的流程几乎不要解释了,因为代码本身就是最好的说明
唯一要提一下的就是 goto trans_done这句话,因为前一条语句实质上已经完成了数据读取,
因此需要直接跳转到该段数据处理完成的位置,也就是函数中的 done_cnt += this_cnt语句之前
说到这里猴急的读者可能已经在 done_cnt += this_cnt语句之前添加
trans_done:
这一行了,不错,正是要加这一行
改过的 simp_blkdev_trans()函数变成了这个样子:
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) {
if (!dir) {
memset(buf + done_cnt, 0, this_cnt);
goto trans_done;
}
/* prepare new memory segment for write */
----------------------- Page 100-----------------------
this_first_page = alloc_pages(
GFP_KERNEL | __GFP_ZERO | __GFP_HIGHMEM,
SIMP_BLKDEV_DATASEGORDER);
if (!this_first_page) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": allocate page failed\n");
return -ENOMEM;
}
this_first_page->index = (dsk_offset + done_cnt)
>> SIMP_BLKDEV_DATASEGSHIFT;
if (IS_ERR_VALUE(radix_tree_insert(&simp_blkdev_data,
this_first_page->index, this_first_page))) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": insert page to radix_tree failed"
" seg=%lu\n", this_first_page->index);
__free_pages(this_first_page,
SIMP_BLKDEV_DATASEGORDER);
return -EIO;
}
}
if (IS_ERR_VALUE(simp_blkdev_trans_oneseg(this_first_page,
this_off, buf + done_cnt, this_cnt, dir)))
return -EIO;
trans_done:
done_cnt += this_cnt;
}
return 0;
}
代码就这样被莫名其妙地改完了,感觉这次的改动比预想的少,并且也比较集中,
这其实还是托了前些章的福,正是在此之前对程序结构的规划调整,
在增加可读性的同时,也给随后的维护带来方便
处于良好维护下的程序代码结构应该越维护越让人赏心悦目,而不是越维护越混乱不堪
现在我们来试验一下这次修改的效果:
先编译:
# make
----------------------- Page 101-----------------------
make -C /lib/modules/2.6.18-53.el5/build
SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step14 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686‘
CC [M] /root/test/simp_blkdev/simp_blkdev_step14/simp_blkdev.o
Building modules, stage 2.
MODPOST
CC /root/test/simp_blkdev/simp_blkdev_step14/simp_blkdev.mod.o
LD [M] /root/test/simp_blkdev/simp_blkdev_step14/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686‘
#
没发现问题
然后看看目前的内存状况:
# cat /proc/meminfo
...
HighTotal: 1146816 kB
HighFree: 87920 kB
LowTotal: 896356 kB
LowFree: 791920 kB
...
#
可以看出高端和低端内存分别剩余87M和 791M
然后指定 size=50M加载模块后看看内存变化:
# insmod simp_blkdev.ko size=50M
# cat /proc/meminfo
...
HighTotal: 1146816 kB
HighFree: 86804 kB
LowTotal: 896356 kB
LowFree: 791912 kB
...
#
在这里我们发现剩余内存的变化不大,
这也证明了这次修改的效果,因为加载模块时不会申请用于存储数据的全部内存
而在原先的代码中,这一步骤将使机器减少大约50M的剩余空间
然后我们来验证读取块设备时也不会导致分配内存:
# dd if=/dev/simp_blkdev of=/dev/null
1024 +0 records in
1024 +0 records out
524288 bytes (52 MB) copied, 0.376118 seconds, 139 MB/s
# cat /proc/meminfo
----------------------- Page 102-----------------------
...
HighTotal: 1146816 kB
HighFree: 85440 kB
LowTotal: 896356 kB
LowFree: 791888 kB
...
#
剩余内存几乎没有变化,这证明了我们的设想
然后是写设备的情况:
# dd if=/dev/zero of=/dev/simp_blkdev
dd: writing to `/dev/simp_blkdev‘: No space left on device
102401+0 records in
1024 +0 records out
524288 bytes (52 MB) copied, 0.542117 seconds, 96.7 MB/s
# cat /proc/meminfo
...
HighTotal: 1146816 kB
HighFree: 34116 kB
LowTotal: 896356 kB
LowFree: 791516 kB
...
#
这时剩余内存终于减少了大约50M ,
这意味着驱动程序申请了大约50M的内存用于存储写入的数据
如果向已写入的位置再次写入数据,理论上不应该造成再一次的分配,
让我们试试:
# dd if=/dev/zero of=/dev/simp_blkdev
dd: writing to `/dev/simp_blkdev‘: No space left on device
102401+0 records in
1024 +0 records out
524288 bytes (52 MB) copied, 0.644972 seconds, 81.3 MB/s
# cat /proc/meminfo
...
HighTotal: 1146816 kB
HighFree: 33620 kB
LowTotal: 896356 kB
LowFree: 791516 kB
...
#
结果与预想一致
----------------------- Page 103-----------------------
现在卸载模块:
# rmmod simp_blkdev
# cat /proc/meminfo
...
HighTotal: 1146816 kB
HighFree: 84572 kB
LowTotal: 896356 kB
LowFree: 791640 kB
...
#
我们发现被驱动程序使用的内存被释放回来了
如果以上的实验没有让读者过瘾的话,我们来继续一个过分一些的,
也就是创建空间远远大于机器物理内存的块设备
首先我们看看目前的系统内存状况:
# cat /proc/meminfo
...
HighTotal: 1146816 kB
HighFree: 77688 kB
LowTotal: 896356 kB
LowFree: 783296 kB
...
#
机器的总内存是 2G ,目前剩余的高、低端内存加起来是 860M左右
然后我们加载模块,注意一下 size参数的值:
# insmod simp_blkdev.ko size=1 G
#
命令成功返回,而如果换作原先的代码,
命令出错返回......是不太可能的,
最可能的大概是内核直接panic
这是因为申请光全部内存的操作将导致申请出错时运行的用于释放内存的代码所需要的内存都无法满足
无论我们设置多大的块设备容量,模块加载后只要不执行写操作,
驱动程序都不会申请存储数据的内存。而这个测试:
# cat /proc/meminfo
...
HighTotal: 1146816 kB
HighFree: 75208 kB
LowTotal: 896356 kB
LowFree: 783132 kB
----------------------- Page 104-----------------------
...
#
也证明了这一点
现在我们看看这时的块设备情况:
# fdisk -l /dev/simp_blkdev
Disk /dev/simp_blkdev: 10737.4 GB, 1073741824 bytes
255 heads, 63 sectors/track, 1305416 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Disk /dev/simp_blkdev doesn‘t contain a valid partition table
#
果然是 1 G ,这可以通过换算 1073741824 bytes得到
而 fdisk显示10737.4 GB是因为它是按照1k=1 字节、1M=1 K、1G=1 M来算的,
这种流氓的算法给硬盘厂商的缺斤少两行为提供了极好的借口
这里省略fdisk、mkfs、mount、cp等操作,
直接用 dd往这个 "1 G磁盘"中写入 50M的数据:
# dd if=/dev/zero of=/dev/simp_blkdev bs=1M count=5
50+0 records in
50+0 records out
524288 bytes (52 MB) copied, 0.324054 seconds, 162 MB/s
# cat /proc/meminfo
...
HighTotal: 1146816 kB
HighFree: 23512 kB
LowTotal: 896356 kB
LowFree: 782884 kB
...
#
现在的内存情况证明我们的 "1 G磁盘"为这些数据申请了 50M的内存
实验差不多了,我们卸载模块:
# rmmod simp_blkdev.
#
做完以上的实验,读者可能会有一个疑问,如果我们真的向那个 "1 G磁盘"中写入了 1 G的数据
怎么样呢?
回答可能不太如人意,就是系统很可能会panic
因为这个操作将迫使驱动程序吃掉全部可能获得的物理内存,并且在吃光最后那么一丁点内存之前不会
发生错误,
这也意味着走到出错处理这一步的时候,系统已经几乎无可救药了 其实在此之前系统就会一次进行:
----------------------- Page 105-----------------------
释放缓存、试图把所有的用户进程的内存换出、 死全部能够 死的进程等操作
而我们的驱动程序由于被看作是内核的一部分,却不会被停止,而是在继续不停的吃掉通过上述方式释
放出的可怜的内存
试想,一个已经走到这一步的系统还有什么继续运行的可能呢?
因此,我们的程序确实需要改善以解决这个问题,因为世界上总是有一些疯狂的人在想各种办法虐待电
脑
但我们并不打算在本教程中解决它,因为这个教程中的每一章都企图为读者说明一类知识或一种方法,
而不是仅仅为了这个示例性质的程序的功能本身
所以这一项改善就当作是留给读者的练习了
本章通过改善块设备驱动程序实现了内存的滞后申请,
其目的在于介绍这种方法,以使它在其他的相似程序中也得以实现
不过,这并不意味着作者希望读者把这种方法过分引用,
比如引用成平时不学习,考试前临时抱佛脚
<未完,待续>
================================================================================
================================================================================
====================================================================
第 15章 (最终章)
+---------------------------------------------------+
| 写一个块设备驱动 |
+---------------------------------------------------+
| 作者:赵磊 |
| email: zhaoleidd@hotmail.com |
+---------------------------------------------------+
| 文章版权归原作者所有。 |
| 大家可以自由转载这篇文章,但原版权信息必须保留。 |
| 如需用于商业用途,请务必与原作者联系,若因未取得 |
| 授权而收起的版权争议,由侵权者自行负责。 |
+---------------------------------------------------+
在上一章中我们对这个块设备驱动所作的更改使它具备了动态申请内存的能力,
但实际上同时也埋下一个隐患,就是数据访问冲突
这里我们顺便唠叨一下内核开发中的同步问题
提到数据访问同步,自然而然会使人想到多进程、多线程、加锁、解锁、
----------------------- Page 106-----------------------
信号量、synchronized关键字等东西,然后就很头疼
对于用户态程序,网上大量的解释数据同步概念和方法的文章给人的印象大概是:
同步很危险,编程要谨慎,
处处有机关,问题很难找
对于第一次进行多线程时编程的人来说,感觉可能是以下两种:
一种是觉得程序中处处都会有问题,任何一条访问数据的指令都不安全,
恨不得把程序中所有的数据都加上锁,甚至打算给锁本身的数据再加个锁,
另一种是没觉得有什么困难,根本不去理什么都互斥不互斥,
就按原先的来,编出的程序居然也运行得很顺
然后怀着这两种想法人通过不断的学习和实践掌握了数据同步的知识后认识到,
数据同步其实并不像前一种想法那样危险,也不像后一种想法那样简单
所幸的是对于不少用户态程序来说,倒是可以不用考虑数据同步问题
至少当我们刚开始写 HelloWorld时不用去理这个麻烦
而对于内核态代码而言,很不幸,整个儿几乎都相当于用户态的多线程
其实事情也并非原本就是这么糟的
在很久很久以前,山是青的,草是绿的,牛奶是能喝的,
见到老人摔跤是敢扶的,作者是纯情的,电脑也是单 CPU的
那时的内核环境很静,很美 除了中断会时不时地捣捣乱,其余的都挺诗意
代码独个儿在跑,就像是一辆汽车在荒漠上奔驰,因为没有其他妨碍,
几乎可以毫无顾忌地访问数据,而不用考虑什么万恶的访问冲突
唯一要考虑的从天而降的中断奥特曼,解决的方法倒也不难,禁用了中断看你还能咋的
然后随着作者的成长,目光从书本转向了美眉,计算机也由单 CPU发展成了多 CPU
内核代码的执行环境终于开始热闹起来,由于每个 CPU上都在执行任务,
这些任务进入到对应的内核态时会出现多条内核指令流同时执行,
这些指令流对全局数据的访问很明显就牵涉到了同步问题,这是开端
从那时起编程时要考虑其他CPU上的事情了
然后随着作者的进一步成长,目光从美眉的脸转向了胸,
CPU制造商为了贯彻给程序员找麻烦的精神,搞出了乱序执行
这一创举惊醒了多年来还在梦中的诸多程序员,原来,程序不是按程序执行的啊
正如林高官说的:“我是交通部派来的,级别和你们市长一样高,敢跟我斗,
你们这些人算个屁呀!”原来,无职无权的平民百姓就是屁啊
正当程序员从睡梦中惊醒还没缓过神时,编译器又跟着捣乱,
“你 CPU都能乱序了,凭什么不让我乱序 ?”
然后热闹了,好在我们还有mb()、rmb()、wmb()、barrier()这几根救命稻草,
事情倒是没变得太糟
----------------------- Page 107-----------------------
然后随着作者的进一步成长,目光从美眉的胸转向了臀,
内核也从一开始时被动的为了适应多 CPU而不得已半推半就支持多任务并行,
转向了主动掀起裙角管它一个还是几个 CPU都去多任务了
从技术面解释,这就是大名鼎鼎的内核抢占
内核的程序员从此不仅要考虑其他CPU ,好要提妨自个儿的 CPU ,
因为执行代码的 CPU说不定什么时候就莫名其妙的被调度执行别的任务了
如果以作者的成长历程为主线解释内核的演化还不至于太混乱的话,
我们还可以考虑再介绍一下 spin_lock, mutex_lock, preempt_disable,
atomic_t和 rcu等函数,不过作者忍住了这一冲动,还是让读者去google吧
然后回到我们的代码,现在的代码是有问题的
比如 simp_blkdev_trans()函数中,假设 2个任务同时向块设备的同一区域写数据,
而这块区域在这之前没有被写过,也就是说还没有申请内存,那么如果运气够好的话,
这两个进程可能几乎同时运行到:
this_first_page = radix_tree_lookup(&simp_blkdev_data,
(dsk_offset + done_cnt) >> SIMP_BLKDEV_DATASEGSHIFT);
这句,很明显这两个任务得到的 this_first_page都是 NULL ,然后它们争先恐后的执行
if (!this_first_page)
判断,从而进入之后的 alloc_pages ,随后它们都会为这个块设备区域申请内存,并加入基树结构
如果运气爆发的话,这两个任务 radix_tree_insert()的代码中将有机会近乎同时越过
if (slot != NULL)
return -EEXIST;
的最后防线,先后将新申请的内存指针赋值给基树结点
虽然 x86的多处理器对同一块内存的写操作是原子的,
这样至少不会因为这两个任务同时赋值基树指针造成指针指向莫名其妙的值,
但这仍然也解决不了我们的问题,后一个赋值操作将覆盖前一个操作的结果,
基数节点最终将指向稍后一点执行赋值操作的任务
这两个任务最终将运行到 radix_tree_insert()函数的结尾,而函数的返回值都是漂亮的
剩下的事情扳脚丫子大概也能想出来了,这两个任务都将自欺欺人地认为自己正确而成功地为块设备分
配了内存,
而真相是其中一个任务拿走的内存却再也没有机会拿回来了
至于解决方法嘛,当然是加锁
只要我们让“查找基数中有没有这个节点”到“分配内存并插入这节点”的过程中没有其他任务的打搅,
就自然的解决了这个问题
首先定义一个锁,因为是用来锁simp_blkdev_data的,
就放在 static struct radix_tree_root simp_blkdev_data;后面吧:
DEFINE_MUTEX(simp_blkdev_datalock); /* protects the disk data op */
然后根据刚才的思想给对 simp_blkdev_trans()函数中的 simp_blkdev_datalock的操作加锁,
----------------------- Page 108-----------------------
也就是在
this_first_page = radix_tree_lookup(&simp_blkdev_data,
(dsk_offset + done_cnt) >> SIMP_BLKDEV_DATASEGSHIFT);
语句之前添加:
mutex_lock(&simp_blkdev_datalock);
操作结束后被忘了把锁还回去,否则下次再操作时就成死锁了,因此在
trans_done:
后面加上
mutex_unlock(&simp_blkdev_datalock);
这一行
完成了吗?细心看看就知道还没完
simp_blkdev_trans()函数中有一些判断异常的代码,这些代码大多是扔出一条printk就直接
return的
这样可不行,可千万别让它们临走时把锁也顺回去了
这意味着我们要在 simp_blkdev_trans()函数中的 3个故障时return的代码前完成锁的释放
因此 simp_blkdev_trans()函数最后就成了这样:
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);
mutex_lock(&simp_blkdev_datalock);
this_first_page = radix_tree_lookup(&simp_blkdev_data,
(dsk_offset + done_cnt) >> SIMP_BLKDEV_DATASEGSHIFT);
if (!this_first_page) {
if (!dir) {
memset(buf + done_cnt, 0, this_cnt);
goto trans_done;
}
----------------------- Page 109-----------------------
/* prepare new memory segment for write */
this_first_page = alloc_pages(
GFP_KERNEL | __GFP_ZERO | __GFP_HIGHMEM,
SIMP_BLKDEV_DATASEGORDER);
if (!this_first_page) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": allocate page failed\n");
mutex_unlock(&simp_blkdev_datalock);
return -ENOMEM;
}
this_first_page->index = (dsk_offset + done_cnt)
>> SIMP_BLKDEV_DATASEGSHIFT;
if (IS_ERR_VALUE(radix_tree_insert(&simp_blkdev_data,
this_first_page->index, this_first_page))) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": insert page to radix_tree failed"
" seg=%lu\n", this_first_page->index);
__free_pages(this_first_page,
SIMP_BLKDEV_DATASEGORDER);
mutex_unlock(&simp_blkdev_datalock);
return -EIO;
}
}
if (IS_ERR_VALUE(simp_blkdev_trans_oneseg(this_first_page,
this_off, buf + done_cnt, this_cnt, dir))) {
mutex_unlock(&simp_blkdev_datalock);
return -EIO;
}
trans_done:
mutex_unlock(&simp_blkdev_datalock);
done_cnt += this_cnt;
}
return 0;
}
这个函数差不多了
----------------------- Page 110-----------------------
我们再看看代码中还有什么地方也对 simp_blkdev_data进行操作来着,别漏掉了这些小王八蛋
查找一下代码,我们发现free_diskmem()函数中也进行了操作
其实从理论上说,这里不加锁是不会产生问题的,因为对内核在执行对块设备设备时,
会锁住这个设备对应的模块 (天哪, 是锁,这一章和锁彪上了) ,
其结果是在 simp_blkdev_trans()函数操作 simp_blkdev_data的过程中,
该模块无法卸载,从而无法不会运行到 free_diskmem()函数
那么如果同时卸载这个模块呢,回答是也没有问题,英勇的模块锁也会搞掂这种情况
这一章由于没有进行功能增加,就不列出修改后模块的测试经过了,
不过作为对读者的安慰,我们将列出到目前为止经历了大大小小修改后的全部模块代码
看到这些代码,我们能历历在目的回忆出读这篇教程到现在为止所经受的全部折磨和苦难
当然也能感受到坚持到现在所得到的知识和领悟
对于 Linux而言,甚至仅仅对于块设备驱动程序而言,这部教程揭开的也仅仅是冰山一角
而更多的知识其实离我们很近,在google上,在代码中,在心中
学习,是要用心,不断地去想,同时要有恒心、耐心、要细心,
人应该越学越谦虚,问题应该越学越多,这大概就是作者通过这部教程最想告诉读者的
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
#include <linux/version.h>
/*
* A simple block device driver based on memory
*
* Copyright 2 8 -
* Zhaolei <zhaolei@cn.fujitsu.com>
*
* Sample for using:
* Create device file (first time only):
* Note: If your system have udev, it can create device file for you in time
* of lsmod and fdisk automatically.
* Otherwise you need to create them yourself by following steps.
* mknod /dev/simp_blkdev b 72
* mknod /dev/simp_blkdev1 b 72 1
* mknod /dev/simp_blkdev2 b 72 2
*
* Create dirs for test (first time only):
* mkdir /mnt/temp1/ # first time only
* mkdir /mnt/temp2/ # first time only
----------------------- Page 111-----------------------
*
* Run it:
* make
* insmod simp_blkdev.ko
* # or insmod simp_blkdev.ko size=numK/M/G/T
* fdisk /dev/simp_blkdev # create 2 patitions
* mkfs.ext3 /dev/simp_blkdev1
* mkfs.ext3 /dev/simp_blkdev2
* mount /dev/simp_blkdev1 /mnt/temp1/
* mount /dev/simp_blkdev2 /mnt/temp2/
* # play in /mnt/temp1/ and /mnt/temp2/
* umount /mnt/temp1/
* umount /mnt/temp2/
* rmmod simp_blkdev.ko
*
*/
#define SIMP_BLKDEV_DEVICEMAJOR COMPAQ_SMART2_MAJOR
#define SIMP_BLKDEV_DISKNAME "simp_blkdev"
#define SIMP_BLKDEV_SECTORSHIFT (9)
#define SIMP_BLKDEV_SECTORSIZE (1ULL<<SIMP_BLKDEV_SECTORSHIFT)
#define SIMP_BLKDEV_SECTORMASK (~(SIMP_BLKDEV_SECTORSIZE-1))
/* usable partitions is SIMP_BLKDEV_MAXPARTITIONS - 1 */
#define SIMP_BLKDEV_MAXPARTITIONS (64)
#define SIMP_BLKDEV_DATASEGORDER (2)
#define SIMP_BLKDEV_DATASEGSHIFT (PAGE_SHIFT + SIMP_BLKDEV_DATASEGORDER)
#define SIMP_BLKDEV_DATASEGSIZE (PAGE_SIZE <<
SIMP_BLKDEV_DATASEGORDER)
#define SIMP_BLKDEV_DATASEGMASK (~(SIMP_BLKDEV_DATASEGSIZE-1))
static struct request_queue *simp_blkdev_queue;
static struct gendisk *simp_blkdev_disk;
static struct radix_tree_root simp_blkdev_data;
DEFINE_MUTEX(simp_blkdev_datalock); /* protects the disk data op */
static char *simp_blkdev_param_size = "16M";
module_param_named(size, simp_blkdev_param_size, charp, S_IRUGO);
static unsigned long long simp_blkdev_bytes;
----------------------- Page 112-----------------------
static int simp_blkdev_trans_oneseg(struct page *start_page,
unsigned long offset, void *buf, unsigned int len, int dir)
{
unsigned int done_cnt;
struct page *this_page;
unsigned int this_off;
unsigned int this_cnt;
void *dsk_mem;
done_cnt = 0;
while (done_cnt < len) {
/* iterate each page */
this_page = start_page + ((offset + done_cnt) >> PAGE_SHIFT);
this_off = (offset + done_cnt) & ~PAGE_MASK;
this_cnt = min(len - done_cnt, (unsigned int)PAGE_SIZE
- this_off);
dsk_mem = kmap(this_page);
if (!dsk_mem) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": map device page failed: %p\n", this_page);
return -ENOMEM;
}
dsk_mem += this_off;
if (!dir)
memcpy(buf + done_cnt, dsk_mem, this_cnt);
else
memcpy(dsk_mem, buf + done_cnt, this_cnt);
kunmap(this_page);
done_cnt += this_cnt;
}
return 0;
}
static int simp_blkdev_trans(unsigned long long dsk_offset, void *buf,
unsigned int len, int dir)
{
unsigned int done_cnt;
----------------------- Page 113-----------------------
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);
mutex_lock(&simp_blkdev_datalock);
this_first_page = radix_tree_lookup(&simp_blkdev_data,
(dsk_offset + done_cnt) >> SIMP_BLKDEV_DATASEGSHIFT);
if (!this_first_page) {
if (!dir) {
memset(buf + done_cnt, 0, this_cnt);
goto trans_done;
}
/* prepare new memory segment for write */
this_first_page = alloc_pages(
GFP_KERNEL | __GFP_ZERO | __GFP_HIGHMEM,
SIMP_BLKDEV_DATASEGORDER);
if (!this_first_page) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": allocate page failed\n");
mutex_unlock(&simp_blkdev_datalock);
return -ENOMEM;
}
this_first_page->index = (dsk_offset + done_cnt)
>> SIMP_BLKDEV_DATASEGSHIFT;
if (IS_ERR_VALUE(radix_tree_insert(&simp_blkdev_data,
this_first_page->index, this_first_page))) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": insert page to radix_tree failed"
" seg=%lu\n", this_first_page->index);
__free_pages(this_first_page,
----------------------- Page 114-----------------------
SIMP_BLKDEV_DATASEGORDER);
mutex_unlock(&simp_blkdev_datalock);
return -EIO;
}
}
if (IS_ERR_VALUE(simp_blkdev_trans_oneseg(this_first_page,
this_off, buf + done_cnt, this_cnt, dir))) {
mutex_unlock(&simp_blkdev_datalock);
return -EIO;
}
trans_done:
mutex_unlock(&simp_blkdev_datalock);
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: %lu\n", bio_rw(bio));
goto bio_err;
}
if ((bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT) + bio->bi_size
----------------------- Page 115-----------------------
> simp_blkdev_bytes) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": bad request: block=%llu, count=%u\n",
(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: %p\n", 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;
}
----------------------- Page 116-----------------------
static int simp_blkdev_getgeo(struct block_device *bdev,
struct hd_geometry *geo)
{
/*
* capacity heads sectors cylinders
* 0~16M 1 1 0~32768
* 16M~512M 1 32 1024~32768
* 512M~16G 32 32 1024~32768
* 16G~... 255 63 2088~...
*/
if (simp_blkdev_bytes < 16 * 1024 * 1024) {
geo->heads = 1;
geo->sectors = 1;
} else if (simp_blkdev_bytes < 512 * 1024 * 1024) {
geo->heads = 1;
geo->sectors = 32;
} else if (simp_blkdev_bytes < 16ULL * 1024 * 1024 * 1024) {
geo->heads = 32;
geo->sectors = 32;
} else {
geo->heads = 255;
geo->sectors = 63;
}
geo->cylinders = simp_blkdev_bytes >> SIMP_BLKDEV_SECTORSHIFT
/ geo->heads / geo->sectors;
return 0;
}
struct block_device_operations simp_blkdev_fops = {
.owner = THIS_MODULE,
.getgeo = simp_blkdev_getgeo,
};
void free_diskmem(void)
{
unsigned long long next_seg;
struct page *seglist[64];
int listcnt;
----------------------- Page 117-----------------------
int i;
next_seg = 0;
do {
listcnt = radix_tree_gang_lookup(&simp_blkdev_data,
(void **)seglist, next_seg, ARRAY_SIZE(seglist));
for (i = 0; i < listcnt; i++) {
next_seg = seglist[i]->index;
radix_tree_delete(&simp_blkdev_data, next_seg);
__free_pages(seglist[i], SIMP_BLKDEV_DATASEGORDER);
}
next_seg++;
} while (listcnt == ARRAY_SIZE(seglist));
}
int getparam(void)
{
char unit;
char tailc;
if (sscanf(simp_blkdev_param_size, "%llu%c%c", &simp_blkdev_bytes,
&unit, &tailc) != 2) {
return -EINVAL;
}
if (!simp_blkdev_bytes)
return -EINVAL;
switch (unit) {
case ‘g‘:
case ‘G‘:
simp_blkdev_bytes <<= 30;
break;
case ‘m‘:
case ‘M‘:
simp_blkdev_bytes <<= 20;
break;
case ‘k‘:
case ‘K‘:
simp_blkdev_bytes <<= 10;
----------------------- Page 118-----------------------
break;
case ‘b‘:
case ‘B‘:
break;
default:
return -EINVAL;
}
/* make simp_blkdev_bytes fits sector‘s size */
simp_blkdev_bytes = (simp_blkdev_bytes + SIMP_BLKDEV_SECTORSIZE - 1)
& SIMP_BLKDEV_SECTORMASK;
return 0;
}
static int __init simp_blkdev_init(void)
{
int ret;
ret = getparam();
if (IS_ERR_VALUE(ret))
goto err_getparam;
simp_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
if (!simp_blkdev_queue) {
ret = -ENOMEM;
goto err_alloc_queue;
}
blk_queue_make_request(simp_blkdev_queue, simp_blkdev_make_request);
simp_blkdev_disk = alloc_disk(SIMP_BLKDEV_MAXPARTITIONS);
if (!simp_blkdev_disk) {
ret = -ENOMEM;
goto err_alloc_disk;
}
INIT_RADIX_TREE(&simp_blkdev_data, GFP_KERNEL);
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;
----------------------- Page 119-----------------------
simp_blkdev_disk->queue = simp_blkdev_queue;
set_capacity(simp_blkdev_disk,
simp_blkdev_bytes >> SIMP_BLKDEV_SECTORSHIFT);
add_disk(simp_blkdev_disk);
return 0;
err_alloc_disk:
blk_cleanup_queue(simp_blkdev_queue);
err_alloc_queue:
err_getparam:
return ret;
}
static void __exit simp_blkdev_exit(void)
{
del_gendisk(simp_blkdev_disk);
free_diskmem();
put_disk(simp_blkdev_disk);
blk_cleanup_queue(simp_blkdev_queue);
}
module_init(simp_blkdev_init);
module_exit(simp_blkdev_exit);
MODULE_LICENSE("GPL");
追记:偶然看到刚才的代码首部注释,Copyright后面还是 2 8年
大概是从第一章开始一直这样拷贝过来的
这部教程从2 8年11月断断续续的写到了 2 9年3月,终于功德圆满了
作为作者写的第一个如此长度篇幅的教程,炸一眼瞟过来,倒也还像个样子,
看来写教程并不是太难高攀的事情,因此如果读者也时不时地有一些写起来的冲动,
就不妨开始吧 : )
本章以块设备驱动程序的代码为例,说明了内核中的同步概念,
当然,在不少情况下,程序员遇到的同步问题比这里的要复杂的多,
内核中也采用了很多方法和技巧来处理同步,了解和学习这些知识,
收获的不仅是数据同步本身的解决方法,更是一种思路,
这对于更一般的程序设计都是有很大帮助的,因此有空时google一下,
总能找到自己想了解的知识
<--全文完,赵磊出品,必属精品-->
----------------------- Page 120-----------------------
LINUX块设备驱动<12/13/14/15>,布布扣,bubuko.com
原文地址:http://www.cnblogs.com/mmcmmc/p/3800269.html