首页
登录 | 注册

第12章

第12章

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

本章中我们将实现对高端内存的支持。

女孩子相处时,和她聊天,逛街,爬山,看电影,下棋中的每一件事情好像都与结婚扯不上太大的关系,
但经过天天年年的日积月累后,女孩子在潜意识中可能已经把你看成了她生活的一部分,
最终的结果显得是那么的自然,甚至连求婚都有些多余了。

学习也很相似,我们认真学习的的每一样知识,努力寻求的每一个答案就其本身而言,
都不能让自己成为专家,但专家却无一不是经历了长时间的认真学习,
努力钻研和细致思考的结果。

正如我们的程序,经历了前几章中的准备工作,离目标功能的距离大概也不算太远了。
而现在我们要做得就是实现它。

首先改动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;
        }
        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)
{
        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:避免了争抢宝贵的低端内存
   作为内存消耗大户,霸占低端内存的行为不可容忍,
   其理由我们在前些章节中已经论述过。
   今后我们的程序至少不会在这一方面被人鄙视了。
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=500M
#

现在看看内存的变化情况:
# cat /proc/meminfo
...
HighTotal:     1146816 kB
HighFree:         1652 kB
LowTotal:       896356 kB
LowFree:        863696 kB
...
#
结果显示模块如我们所料的吃掉了500M左右的高端内存。
虽然低端内存看样子也少了一些,我们却不能用模块本身占用的内存空间来解释这一现象,
因为模块的代码和静态数据占用的内存无论如何也到不了8.9M,
或许我们解释为用作一些文件操作的缓存了,还有就是基树结构占用的内存,
这个结构占用的内存会随着块设备容量的增大而增加,或者我们可以计算一下......
不过现在我们并不打算对这个小问题做过多的关注,因为这是扯淡,
正如闹得沸沸扬扬的周久耕事件的最后调查结果居然仅仅只是公款买烟。
因此我们不会纠缠在这8.9M的问题中,因为很明显大头是在减少的500多兆高端内存上,
这减少的500M高端内存已经足以证明这几章中的修改结果了。

我们再移除这个模块后看看内存的状况:
# 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=1300M
#
这时我们惊喜地发现系统没有DOWN掉。

再看看这时的内存情况:
# cat /proc/meminfo
...
HighTotal:     1146816 kB
HighFree:        41204 kB
LowTotal:       896356 kB
LowFree:         48284 kB
...
#
高端内存与低端内存中的大头基本上都被吃掉了,
数量上也差不多是1.3G,这符合我们的预期。

老让模块占用着这么多的内存也不是什么好主意,
我们放掉:
# rmmod simp_blkdev.
#

随着本章的结束,围绕高端内存的讨论也终于修成正果了。
不过我们对这个驱动程序的改进还没有完,因为我们要发扬做精每一样事情的精神,
一个民族的振兴,不是靠对小学生进行填鸭式的政治思想教育,也不是靠官员及家属的出国考察,
更不是靠公仆们身先士卒、前仆后继、以自己的健康为代价大吃大喝以创造9000亿的GDP,
而是靠每一个屁民们的诚实、认真、勤劳、勇敢、创造、奉献与精益求精。

相关文章

  • 《循序渐进Linux》第二版即将出版发行(附封面)
    从<循序渐进Linux>第一版发布,到现在已经近6年了,6年的时间,技术发生了很大的变化,Linux系统的内核版本从2.6.9(RHEL4.x)已经更新到了现在的3.10(Centos7.x),第一版中的部分内容已经陈旧,Lin ...
  • 前几天终于从出版社拿到样书了,我当年对论坛上版友的承诺也终于要兑现了.曾经说过,要写一本Sybase ASE的书.因为Sybase的图书实在太少了.  购买地址: http://book.jd.com/11228378.html (京东) ...
  • 亲爱的各位博主:  7月12日星期四中午12点,博客将停库,上线新版本,持续1个小时左右,届时请大家先暂行停止更博文.以及编辑等操作,以防信息丢失,给大家带来的不便敬请谅解.
  • 在Ubuntu 12.04 LTS上安装Python3.3.x-a
    在Ubuntu 12.04 LTS上安装Python3.3.x By Harrison Feng Python3.3.2是Python3最新的发行版,在Ubuntu 12.04上我们无法通过APT-GET来安装它.因为12.04 LTS的源 ...
  • Ubuntu 12.04 Freeradius 安装实际过程
             这里介绍下详细的Freeradius2.0在Ubuntu 12.04 x64 操作系统上的安装过程,包括使用MySQL数据作为用户名.密码来源的配置操作,经过下面的步骤可以直接将Ubuntu系统变成AAA认证的生产服务器, ...
  • iOS 12.2带来隐藏惊喜:电信用户可开通VoLTE
    中国电信官方给出了开通VoLTE的三步走指南,首先是升级苹果手机系统至最新的iOS 12.2版本,然后通过营业厅或者编辑短信为手机号开通VoLTE服务,最后在手机设置中打开对应开关,那么用户就可以在当前苹果手机上使用VoLTE服务了. 至于 ...

2020 unjeep.com webmaster#unjeep.com
12 q. 0.013 s.
京ICP备10005923号