写一个块设备驱动9,10
Posted ztguang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了写一个块设备驱动9,10相关的知识,希望对你有一定的参考价值。
http://blogold.chinaunix.net/u3/108239/showart.php?id=2144632
第9章
+---------------------------------------------------+
| 写一个块设备驱动 |
+---------------------------------------------------+
| 作者:赵磊 |
| email: [email protected] |
+---------------------------------------------------+
| 文章版权归原作者所有。 |
| 大家可以自由转载这篇文章,但原版权信息必须保留。 |
| 如需用于商业用途,请务必与原作者联系,若因未取得 |
| 授权而收起的版权争议,由侵权者自行负责。 |
+---------------------------------------------------+
在本章中我们来讨论一下这个驱动程序的数据安全,
因为最近的一些事情让作者愈发地感觉到数据泄漏对当事人来说是麻烦的。
我们开门见山的解释一下数据安全问题:
内核常常会向用户态传递数据,而作为内核程序的开发者,我们必须意识到不能把包含意料内容之外的数据随便透露给用户态,
因为如果这些数据不巧被别有用心者利用,就会带来不少麻烦。
比如陈冠希就犯了这样的错误。新余市出国考察团也没有在陈冠希身上吸取教训,把单据也不当回事儿。
单据对于考察团而言并不是什么重要的玩意儿,但一旦落到“别有用心”的人手中被加以利用,就不得不当一回事了。
由此我们发现了单据的商业价值。
今后在旅游公司干过的员工拿着手头攒到的大量单据,可能会比KIRA更有前途。
因此公务员确实属于高风险职业,加薪也是情理当中的了。
对于内核而言,其中的数据也是如此。
即使一些数据对内核而言没有价值,但也不能随意地向用户态传递,因为这段内存中可能不巧包含了不能随意让用户获取的数据,
比如用户A使用linux整理他女友的裸照文件,裸照的数据很可能存在于用户A的进程的虚存中,也可能还存在于文件缓存中,
A的进程结束后,系统回收了进程的内存,这时内存中的数据被系统认定为无效数据,但系统并没有清空这段数据。
A打开的文件的缓存也类似,缓存被系统回收后,内存中的数据并没有被清除。
随后用户B使用了我们的块设备驱动程序。驱动程序初始化时需要获取足够的内存以存储块设备中的数据,
系统很可能将用户A使用过的那段包含裸照数据的内存分配给我们的块设备驱动程序。
这时如果用户B老老实实分区、创建文件系统、写入文件,这当然没事,
但如果用户B别有用心的上来就直接去读块设备中的数据,那么他可能很幸运的看到不该看的东西。
因此我们咬牙切齿,嫉妒心促使我们修改这个块设备驱动,我们都没遇到的好事儿,也决不允许用户B遇到。
修改的方法很简单,我们申请内存时使用了__get_free_pages()函数,
这个函数的第一个参数是gfp_mask,原先我们传递的是GFP_KERNEL,表示用于内核中的一般情况。
现在我们只要向gfp_mask中添加__GFP_ZERO标志,以提示需要申请清0后的内存。
这样驱动程序加载后,块设备中数据的初始值全为0,这就避免了上文中提到的安全问题。
详细来说,就是把alloc_diskmem()函数中的
p = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
这一行改成
p = (void *)__get_free_pages(GFP_KERNEL,
安全方面的改动已经完成了,但为了避免读者认为本章偷工减料,我们再多改一些代码。
块设备中每扇区的数据长度为512字节,我们在驱动程序经常遇到与此相关的转换。
为了快速运算,我们经常用到9这个常数,比如:
乘以512就是左移9、除以512就是右移9、除以512的余数就是& ((1ULL<<9) - 1)、
向上对齐到512的倍数就是加上(1<<9) - 1再& ~((1ULL<<9) - 1)。
不过现在我们决定通过定义几个宏来吧这些操作写得好看一些。
先定义:
#define SIMP_BLKDEV_SECTORSHIFT (9)
#define SIMP_BLKDEV_SECTORSIZE (1ULL<#define SIMP_BLKDEV_SECTORMASK (~(SIMP_BLKDEV_SECTORSIZE-1))
然后使用这几个宏来进行扇区相关的转换工作。
详细来说,就是把simp_blkdev_make_request()函数中的:
if ((bio->bi_sector << 9) + bio->bi_size > simp_blkdev_bytes) {
改成
if ((bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT) + bio->bi_size
> simp_blkdev_bytes) {
dsk_offset = bio->bi_sector << 9;
改成
dsk_offset = bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT;
把simp_blkdev_getgeo()函数中的:
geo->cylinders = simp_blkdev_bytes>>9/geo->heads/geo->sectors;
改成
geo->cylinders = simp_blkdev_bytes >> SIMP_BLKDEV_SECTORSHIFT
/ geo->heads / geo->sectors;
把getparam()函数中的:
simp_blkdev_bytes = (simp_blkdev_bytes + (1<<9) - 1) & ~((1ULL<<9) - 1);
改成
simp_blkdev_bytes = (simp_blkdev_bytes + SIMP_BLKDEV_SECTORSIZE - 1)
& SIMP_BLKDEV_SECTORMASK;
把simp_blkdev_init()函数中的:
set_capacity(simp_blkdev_disk, simp_blkdev_bytes>>9);
改成
set_capacity(simp_blkdev_disk,
simp_blkdev_bytes >> SIMP_BLKDEV_SECTORSHIFT);
如果运气不算太背的话,程序应该是能够运行的,让我们试试:
# make
make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step09 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686‘
CC [M] /root/test/simp_blkdev/simp_blkdev_step09/simp_blkdev.o
Building modules, stage 2.
MODPOST
CC /root/test/simp_blkdev/simp_blkdev_step09/simp_blkdev.mod.o
LD [M] /root/test/simp_blkdev/simp_blkdev_step09/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686‘
# insmod simp_blkdev.ko
#
看一看驱动程序刚刚加载时里面的数据:
# hexdump /dev/simp_blkdev -vn512
0000000 0000 0000 0000 0000 0000 0000 0000 0000
0000010 0000 0000 0000 0000 0000 0000 0000 0000
0000020 0000 0000 0000 0000 0000 0000 0000 0000
0000030 0000 0000 0000 0000 0000 0000 0000 0000
0000040 0000 0000 0000 0000 0000 0000 0000 0000
0000050 0000 0000 0000 0000 0000 0000 0000 0000
0000060 0000 0000 0000 0000 0000 0000 0000 0000
0000070 0000 0000 0000 0000 0000 0000 0000 0000
0000080 0000 0000 0000 0000 0000 0000 0000 0000
0000090 0000 0000 0000 0000 0000 0000 0000 0000
00000a0 0000 0000 0000 0000 0000 0000 0000 0000
00000b0 0000 0000 0000 0000 0000 0000 0000 0000
00000c0 0000 0000 0000 0000 0000 0000 0000 0000
00000d0 0000 0000 0000 0000 0000 0000 0000 0000
00000e0 0000 0000 0000 0000 0000 0000 0000 0000
00000f0 0000 0000 0000 0000 0000 0000 0000 0000
0000100 0000 0000 0000 0000 0000 0000 0000 0000
0000110 0000 0000 0000 0000 0000 0000 0000 0000
0000120 0000 0000 0000 0000 0000 0000 0000 0000
0000130 0000 0000 0000 0000 0000 0000 0000 0000
0000140 0000 0000 0000 0000 0000 0000 0000 0000
0000150 0000 0000 0000 0000 0000 0000 0000 0000
0000160 0000 0000 0000 0000 0000 0000 0000 0000
0000170 0000 0000 0000 0000 0000 0000 0000 0000
0000180 0000 0000 0000 0000 0000 0000 0000 0000
0000190 0000 0000 0000 0000 0000 0000 0000 0000
00001a0 0000 0000 0000 0000 0000 0000 0000 0000
00001b0 0000 0000 0000 0000 0000 0000 0000 0000
00001c0 0000 0000 0000 0000 0000 0000 0000 0000
00001d0 0000 0000 0000 0000 0000 0000 0000 0000
00001e0 0000 0000 0000 0000 0000 0000 0000 0000
00001f0 0000 0000 0000 0000 0000 0000 0000 0000
0000200
#
对比一下修改前的效果:
# hexdump /dev/simp_blkdev -vn512
0000000 f300 0800 1200 0000 b804 1200 0000 0500
0000010 501a 6930 1806 246a bf0a 7700 256a bf0b
0000020 1f80 256b bf0b 47a0 266b bf0b 0ff0 246a
0000030 bf0a 1708 ffff 00ff 5028 256b bf0b 00a8
0000040 ffff 00ff 04b8 ffff 00ff 10c8 256b bf0b
0000050 00e8 246a bf0a 0229 ffff 00ff 1339 ffff
0000060 00ff 0059 246a bf0a 1669 ffff 00ff 12a9
0000070 256b bf0b 02c9 ffff 00ff 12d9 246a bf0a
0000080 215a ffff 00ff 302c 256b bf0b 03ac ffff
0000090 00ff 10cc 256b bf0b 03ec 246a bf0a 522d
00000a0 256b bf0b 32bd 2318 266b bf0c 2700 266c
00000b0 bf0c 2730 276c bf0c 1f60 276c bf0d 3580
00000c0 276d bf0d 1bc0 286d bf0d 05e0 286d bf0e
00000d0 04f0 ffff 00ff 07f5 276c bf0d 0186 ffff
00000e0 00ff 1596 276c bf0d 01b6 ffff 00ff 15e6
00000f0 266b bf0c 0708 266b bf0c 0018 ffff 00ff
0000100 0428 ffff 00ff 1038 266c bf0c 0058 ffff
0000110 00ff 3088 ffff 00ff 1219 266c bf0c 0239
0000120 ffff 00ff 1249 276c bf0d 0689 276c bf0d
0000130 02b9 266b bf0c 031c ffff 00ff 103c 266c
0000140 bf0c 035c 276c bf0d 039c ffff 00ff 20ac
0000150 276d bf0d 03dc 286d bf0d 03ec 266b bf0c
0000160 022d 266c bf0c 223d 276c bf0d 12ad 276d
0000170 bf0d 12cd 286d bf0e 02fd 2b18 286d bf0e
0000180 4400 296e bf0e 1450 296e bf0f 4470 2a6e
0000190 bf0f 14c0 2a6f bf0f 04e0 2a6f bf10 04f0
00001a0 ffff 00ff 2005 286d bf0e 1035 ffff 00ff
00001b0 5055 296e bf0f 0ab5 ffff 00ff 30c5 286d
00001c0 bf0e 1006 ffff 00ff 1426 286d bf0e 0946
00001d0 ffff 00ff 1056 296e bf0f 0176 ffff 00ff
00001e0 1186 296e bf0f 14a6 2a6e bf0f 05c6 ffff
00001f0 00ff 16d6 2a6f bf10 05f6 286d bf0e 0007
0000200
#
本章到此结束,读者是不是感觉我们的教程越来越简单了?
<未完,待续>
第10章
+---------------------------------------------------+
| 写一个块设备驱动 |
+---------------------------------------------------+
| 作者:赵磊 |
| email: [email protected] |
+---------------------------------------------------+
| 文章版权归原作者所有。 |
| 大家可以自由转载这篇文章,但原版权信息必须保留。 |
| 如需用于商业用途,请务必与原作者联系,若因未取得 |
| 授权而收起的版权争议,由侵权者自行负责。 |
+---------------------------------------------------+
如果你的linux系统是x86平台,并且内存大于896M,那么恭喜你,我们大概可以在这个实验中搞坏你的系统。
反之如果你的系统不符合这些条件,也不用为无法搞坏系统而感到失望,本章的内容同样适合你。
这时作者自然也要申明一下对读者产生的任何损失概不负责,
因为这年头一不小心就可能差点成了被告,比如南京的彭宇和镇江花山湾的小许姑娘。
在实验看到的情况会因为系统的实际状况不同而稍有区别,但我们需要说明的问题倒是相似的。
但希望读者不要把这种相似理解成了ATM机取款17.5万和贪污2.6亿在判决上的那种相似。
首先我们来看看目前系统的内存状况:
# cat /proc/meminfo
MemTotal: 1552532 kB
MemFree: 1529236 kB
Buffers: 2716 kB
Cached: 10124 kB
SwapCached: 0 kB
Active: 8608 kB
Inactive: 7664 kB
HighTotal: 655296 kB
HighFree: 640836 kB
LowTotal: 897236 kB
LowFree: 888400 kB
SwapTotal: 522104 kB
SwapFree: 522104 kB
Dirty: 44 kB
Writeback: 0 kB
AnonPages: 3440 kB
Mapped: 3324 kB
Slab: 2916 kB
SReclaimable: 888 kB
SUnreclaim: 2028 kB
PageTables: 272 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 1298368 kB
Committed_AS: 10580 kB
VmallocTotal: 114680 kB
VmallocUsed: 392 kB
VmallocChunk: 114288 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 4096 kB
DirectMap4k: 12288 kB
DirectMap4M: 905216 kB
#
输出很多,但我们只关心这几行:
MemFree: 1529236 kB --这说明系统中有接近1.5G的空闲内存
HighFree: 640836 kB --这说明空闲内存中,处在高端的有600M左右
LowFree: 888400 kB --这说明空闲内存中,处在低端的有800M左右
现在加载上一章完成的模块,我们指定创建800M的块设备:
# insmod simp_blkdev.ko size=800M
#
成功了,我们再看看内存状况:
# cat /proc/meminfo
MemFree: 708812 kB
HighFree: 640464 kB
LowFree: 68348 kB
...
#
我们发现高端内存没怎变,低端内存却已经被耗得差不多了。
我们一不做二不休,继续加大块设备的容量,看看极限能到多少:
# rmmod simp_blkdev
# insmod simp_blkdev.ko size=860M
# cat /proc/meminfo
MemFree: 651184 kB
HighFree: 641972 kB
LowFree: 9212 kB
...
#
系统居然还没事,这时虽然高端内存还是没怎么变,但低端内存剩下的得已经很可怜了。
然后进一步加大块设备的容量:
# rmmod simp_blkdev
# insmod simp_blkdev.ko size=870M
...
这里不用再cat /proc/meminfo了,因为系统已经完蛋了。
如果有些读者嗜好独特,对出错信息情有独钟的话,在这里也满足一下:
kernel: [ 3588.769050] insmod invoked oom-killer: gfp_mask=0x80d0, order=2, oomkilladj=0
kernel: [ 3588.769516] Pid: 4236, comm: insmod Tainted: G W 2.6.27.4 #53
kernel: [ 3588.769868] [] oom_kill_process+0x42/0x183
kernel: [ 3588.771041] [] out_of_memory+0x157/0x188
kernel: [ 3588.771306] [] __alloc_pages_internal+0x2ab/0x360
kernel: [ 3588.771500] [] __get_free_pages+0x14/0x24
kernel: [ 3588.771679] [] alloc_diskmem+0x45/0xb5 [simp_blkdev]
kernel: [ 3588.771899] [] simp_blkdev_init+0x54/0xc6 [simp_blkdev]
kernel: [ 3588.772217] [] _stext+0x3d/0xff
kernel: [ 3588.772393] [] ? simp_blkdev_init+0x0/0xc6 [simp_blkdev]
kernel: [ 3588.772599] [] ? __blocking_notifier_call_chain+0x40/0x4c
kernel: [ 3588.772845] [] sys_init_module+0x87/0x19d
kernel: [ 3588.773250] [] sysenter_do_call+0x12/0x21
kernel: [ 3588.773884] =======================
kernel: [ 3588.774237] Mem-Info:
kernel: [ 3588.774241] DMA per-cpu:
kernel: [ 3588.774404] CPU 0: hi: 0, btch: 1 usd: 0
kernel: [ 3588.774582] Normal per-cpu:
kernel: [ 3588.774689] CPU 0: hi: 186, btch: 31 usd: 0
kernel: [ 3588.774870] HighMem per-cpu:
kernel: [ 3588.778602] CPU 0: hi: 186, btch: 31 usd: 0
...
搞坏系统就当是交学费了,但交完学费我们总要学到些东西。
虽然公款出国考察似乎已经斯通见惯,但至少在我们的理解中,学费不是旅游费,更不是家属的旅游费。
我们通过细心观察、周密推理后得出的结论是:
目前的块设备驱动程序会一根筋地使用低端内存,即使系统中低端内存很紧缺的时候,
也会直道把系统搞死却不去动半点的高端内存,这未免也太挑食了,
因此在本章和接下来的几章中,我们将帮助驱动程序戒掉对低端内存的瘾。
相对高端内存而言,低端内存是比较宝贵的,这是因为它不需要影射就能直接被内核访问的特性。
而内核中的不少功能都直接使用低端内存,以保证访问的速度和简便,
但换句话来说,如果低端内存告急,那么系统可能离Panic也不远了。
因此总的来说,对低端内存的使用方法大概应该是:除非有足够理由,否则就别乱占着。
详细来说,就是:
1:不需要使用低端内存的“在内核中不需要映射就能直接访问”这个特性的功能,应该优先使用高端内存
如:分配给用户态进程的内存,和vmalloc的内存
2:需要占用大量内存的功能,并且也可以通过高端内存实现的,应该优先使用高端内存
如:我们的程序
与内存有关的知识我们在以前的章节中已经谈到,因此这里不再重复了,
但需要说明的是在高端内存被映射之前,我们是无法通过指针来指向它的。
因为它不在内核空间的地址范围以内。
虽然如此,我们却无论如何都需要找出一种方法来指定一个没有被映射的高端内存,
这是由于至少在进行映射操作时,我们需要指定去映射谁。
这就像为一群猴子取名的时候,如何来说明是正在给哪只猴子取名一样。
虽然给猴子取名的问题可能比较容易解决,比如我们可以说,
给哪只红屁股的公猴取名叫齐天大圣、给那只瘦瘦的母猴取名叫白晶晶,
但可惜一块高端内存即没有红屁股,又没有胖瘦之分,
它们唯一有的就是地址,因此我们也必须通过地址来指定这段高端内存。
刚才说过,在高端内存被映射之前,他在内核的地址空间中是不存在的,
但虽然如此,它至少存在其物理地址,而我们正是可以通过它的物理地址来指定它。
是的,本质上是这样的,但在linux中,我们还需要再绕那么一丁点:
linux在启动阶段为全部物理内存按页为单位建立了的对应的struct page结构,用来管理这些物理内存,
也就是,每个页的物理内存,都有着1对1的struct page结构,而这些struct page结构是位于低端内存中的,
我们只要使用指向某个struct page结构的指针,就能指定物理内存中的一个页。
因此,对于没有被映射到内核空间中的高端内存,我们可以通过对应的struct page结构来指定它。
(如果读者希望了解更详细的知识,可以考虑从virt_to_page函数一路google下去)
我们在这里大肆谈论高端内存的表示方法,因为这是让我们的模块使用高端内存的前提。
我们的驱动程序使用多段内存来存储块设备中的数据。
原先的程序中,我们使用指向这些内存段的指针来指定这些数据的位置,这是没有问题的,
因为当时我们是使用__get_free_pages()来申请内存,__get_free_pages()函数只能用来申请低端内存,
因为这个函数返回的是申请到的内存的指针,而上文中说过,高端内存是不能用这样的指针表示的。
要申请高端内存,明显不能使用这样的函数,因此我们隆重介绍它的代替者出场:
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);
这个函数的参数与__get_free_pages()相同,但区别在于,它返回指向struct page的指针,
这个我们在上文中介绍过的指针赋予了alloc_pages()函数申请高端内存的能力。
其实申请一块高端内存并不难,只要使用__GFP_HIGHMEM参数调用alloc_pages()函数,
就可能返回一块高端内存,之所以说是“可能”,使因为在某些情况下,比如高端内存不够或不存在时,也会但会低端内存充数。
我们的现在的目标是让驱动程序使用高端内存,这需要:
1:让驱动程序申请高端内存
2:让驱动程序使用高端内存
但在这一章中,我们要做的即不是1,也不是2,而是1之前的准备工作。
因为1和2必须一气呵成地改完,而为了让一气呵成的时候不要再面临其他插曲,
我们需要做好充足的准备工作,就像ml前尿尿一样。
对应到程序的修改工作上,我们打算先让程序使用struct page *来指定申请到的内存。
要实现这个目的,我们先要改申请内存的函数,也就是alloc_diskmem()。
刚才我们介绍过alloc_pages(),现在就要用它了:
首先把函数中定义的
void *p;
改成
struct page *page;
因为我们要使用struct page *来指定申请到的内存,而不是地址了。
然后把
p = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, SIMP_BLKDEV_DATASEGORDER);
改成
page = alloc_pages(GFP_KERNEL | __GFP_ZERO, SIMP_BLKDEV_DATASEGORDER);
这一行改动的原因大概已经说得很详细了。
还有那个if(!p)改成if (!page)
然后就是把指针加入基树的那一行:
ret = radix_tree_insert(&simp_blkdev_data, i, p);
改成
ret = radix_tree_insert(&simp_blkdev_data, i, page);
由于我们使用了struct page *来指定申请到的内存,因此错误处理部分也要小改一下:
free_pages((unsigned long)p, SIMP_BLKDEV_DATASEGORDER);
改成
__free_pages(page, SIMP_BLKDEV_DATASEGORDER);
这里补充介绍一下__free_pages()函数,可能大家已经猜到其作用了,
其实与我们原先使用的free_pages()函数相似,都是用来释放一段内存,
但__free_pages()使用struct page *来指定要释放的内存,这也意味着它能够用来释放高端内存。
大家应该已经发现我们虽然改用alloc_pages()函数来申请内存,但并没有指定__GFP_HIGHMEM参数,
这时申请到的仍然是低端内存,因此避免了在这一章中对访问内存那部分代码的大肆改动。
改动过的alloc_pages()函数是这样的:
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,
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;
}
相应的,释放内存用的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);
}
}
随后是simp_blkdev_make_request()函数:
首先我们不是把void *dsk_mem改成struct page *dsk_page,而是增加一个
struct page *dsk_page;
变量,因为在访问内存时,我们还是需要用到dsk_mem变量的。
然后是从基数中获取指针的代码,把原先的
dsk_mem = radix_tree_lookup(&simp_blkdev_data, (dsk_offset + count_done) >> SIMP_BLKDEV_DATASEGSHIFT);
改成
dsk_page = radix_tree_lookup(&simp_blkdev_data, (dsk_offset + count_done) >> SIMP_BLKDEV_DATASEGSHIFT);
虽然看起来没什么太大变化,但我们需要知道,这时基树返回的指针已经不是直接指向数据所在的内存了。
还有那个判断是否从基树中获取成功的
if (!dsk_mem) {
用脚丫子也能想得出应该改成这样:
if (!dsk_page) {
还有就是我们需要首先将struct page *dsk_page地址转换成内存的地址后,才能对这块内存进行访问。
这里我们使用了page_address()函数。
这个函数可以获得struct page数据结构所对应内存的地址。
这时可能有读者要问了,如果这个struct page对应的是高端内存,那么如何返回地址呢?
实际上,这种情况下如果高端内存中的页面已经被映射到内核的地址空间,那么函数会返回映射到内核空间中的地址,
而如果没有映射的话,函数将返回0。
对于我们目前的程序而言,由于使用的是低端内存,因此struct page对应的内存总是处于内核地址空间中的。
对应到代码中,我们需要在使用dsk_mem之前,也就是
dsk_mem += (dsk_offset + count_done) & ~SIMP_BLKDEV_DATASEGMASK;
这条语句之前,让dsk_mem指向struct page *dsk_page对应的内存的实际地址。
这是通过如下代码实现的:
dsk_mem = page_address(dsk_page);
if (!dsk_mem) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": get page‘s address failed: %p\n",
dsk_page);
kunmap(bvec->bv_page);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
总的来说,修改后的simp_blkdev_make_request()函数是这样的:
static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio)
{
struct bio_vec *bvec;
int i;
unsigned long long dsk_offset;
if ((bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT) + bio->bi_size
> 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);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
}
dsk_offset = bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT;
bio_for_each_segment(bvec, bio, i) {
unsigned int count_done, count_current;
void *iovec_mem;
struct page *dsk_page;
void *dsk_mem;
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
count_done = 0;
while (count_done < bvec->bv_len) {
count_current = min(bvec->bv_len - count_done,
(unsigned int)(SIMP_BLKDEV_DATASEGSIZE
- ((dsk_offset + count_done) &
~SIMP_BLKDEV_DATASEGMASK)));
dsk_page = radix_tree_lookup(&simp_blkdev_data,
(dsk_offset + count_done)
>> SIMP_BLKDEV_DATASEGSHIFT);
if (!dsk_page) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": search memory failed: %llu\n",
(dsk_offset + count_done)
>> SIMP_BLKDEV_DATASEGSHIFT);
kunmap(bvec->bv_page);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
}
dsk_mem = page_address(dsk_page);
if (!dsk_mem) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": get page‘s address failed: %p\n",
dsk_page);
kunmap(bvec->bv_page);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
}
dsk_mem += (dsk_offset + count_done)
& ~SIMP_BLKDEV_DATASEGMASK;
switch (bio_rw(bio)) {
case READ:
case READA:
memcpy(iovec_mem + count_done, dsk_mem,
count_current);
break;
case WRITE:
memcpy(dsk_mem, iovec_mem + count_done,
count_current);
break;
default:
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": unknown value of bio_rw: %lu\n",
bio_rw(bio));
kunmap(bvec->bv_page);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
}
count_done += count_current;
}
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;
}
通过对这3个函数的更改,代码可以使用struct page *来定位存储块设备数据的内存了。
这也为将来使用高端内存做了一部分准备。
因为本章修改的代码在外部功能上没有发生变动,所以我们就不在这里尝试编译了运行代码了。
不过感兴趣的读者不妨试一试这段代码能不能进行编译和会不会引起死机。
<未完,待续>
第9章
+---------------------------------------------------+
| 写一个块设备驱动 |
+---------------------------------------------------+
| 作者:赵磊 |
| email: [email protected] |
+---------------------------------------------------+
| 文章版权归原作者所有。 |
| 大家可以自由转载这篇文章,但原版权信息必须保留。 |
| 如需用于商业用途,请务必与原作者联系,若因未取得 |
| 授权而收起的版权争议,由侵权者自行负责。 |
+---------------------------------------------------+
在本章中我们来讨论一下这个驱动程序的数据安全,
因为最近的一些事情让作者愈发地感觉到数据泄漏对当事人来说是麻烦的。
我们开门见山的解释一下数据安全问题:
内核常常会向用户态传递数据,而作为内核程序的开发者,我们必须意识到不能把包含意料内容之外的数据随便透露给用户态,
因为如果这些数据不巧被别有用心者利用,就会带来不少麻烦。
比如陈冠希就犯了这样的错误。新余市出国考察团也没有在陈冠希身上吸取教训,把单据也不当回事儿。
单据对于考察团而言并不是什么重要的玩意儿,但一旦落到“别有用心”的人手中被加以利用,就不得不当一回事了。
由此我们发现了单据的商业价值。
今后在旅游公司干过的员工拿着手头攒到的大量单据,可能会比KIRA更有前途。
因此公务员确实属于高风险职业,加薪也是情理当中的了。
对于内核而言,其中的数据也是如此。
即使一些数据对内核而言没有价值,但也不能随意地向用户态传递,因为这段内存中可能不巧包含了不能随意让用户获取的数据,
比如用户A使用linux整理他女友的裸照文件,裸照的数据很可能存在于用户A的进程的虚存中,也可能还存在于文件缓存中,
A的进程结束后,系统回收了进程的内存,这时内存中的数据被系统认定为无效数据,但系统并没有清空这段数据。
A打开的文件的缓存也类似,缓存被系统回收后,内存中的数据并没有被清除。
随后用户B使用了我们的块设备驱动程序。驱动程序初始化时需要获取足够的内存以存储块设备中的数据,
系统很可能将用户A使用过的那段包含裸照数据的内存分配给我们的块设备驱动程序。
这时如果用户B老老实实分区、创建文件系统、写入文件,这当然没事,
但如果用户B别有用心的上来就直接去读块设备中的数据,那么他可能很幸运的看到不该看的东西。
因此我们咬牙切齿,嫉妒心促使我们修改这个块设备驱动,我们都没遇到的好事儿,也决不允许用户B遇到。
修改的方法很简单,我们申请内存时使用了__get_free_pages()函数,
这个函数的第一个参数是gfp_mask,原先我们传递的是GFP_KERNEL,表示用于内核中的一般情况。
现在我们只要向gfp_mask中添加__GFP_ZERO标志,以提示需要申请清0后的内存。
这样驱动程序加载后,块设备中数据的初始值全为0,这就避免了上文中提到的安全问题。
详细来说,就是把alloc_diskmem()函数中的
p = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO,
这一行改成
p = (void *)__get_free_pages(GFP_KERNEL,
安全方面的改动已经完成了,但为了避免读者认为本章偷工减料,我们再多改一些代码。
块设备中每扇区的数据长度为512字节,我们在驱动程序经常遇到与此相关的转换。
为了快速运算,我们经常用到9这个常数,比如:
乘以512就是左移9、除以512就是右移9、除以512的余数就是& ((1ULL<<9) - 1)、
向上对齐到512的倍数就是加上(1<<9) - 1再& ~((1ULL<<9) - 1)。
不过现在我们决定通过定义几个宏来吧这些操作写得好看一些。
先定义:
#define SIMP_BLKDEV_SECTORSHIFT (9)
#define SIMP_BLKDEV_SECTORSIZE (1ULL<
然后使用这几个宏来进行扇区相关的转换工作。
详细来说,就是把simp_blkdev_make_request()函数中的:
if ((bio->bi_sector << 9) + bio->bi_size > simp_blkdev_bytes) {
改成
if ((bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT) + bio->bi_size
> simp_blkdev_bytes) {
dsk_offset = bio->bi_sector << 9;
改成
dsk_offset = bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT;
把simp_blkdev_getgeo()函数中的:
geo->cylinders = simp_blkdev_bytes>>9/geo->heads/geo->sectors;
改成
geo->cylinders = simp_blkdev_bytes >> SIMP_BLKDEV_SECTORSHIFT
/ geo->heads / geo->sectors;
把getparam()函数中的:
simp_blkdev_bytes = (simp_blkdev_bytes + (1<<9) - 1) & ~((1ULL<<9) - 1);
改成
simp_blkdev_bytes = (simp_blkdev_bytes + SIMP_BLKDEV_SECTORSIZE - 1)
& SIMP_BLKDEV_SECTORMASK;
把simp_blkdev_init()函数中的:
set_capacity(simp_blkdev_disk, simp_blkdev_bytes>>9);
改成
set_capacity(simp_blkdev_disk,
simp_blkdev_bytes >> SIMP_BLKDEV_SECTORSHIFT);
如果运气不算太背的话,程序应该是能够运行的,让我们试试:
# make
make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step09 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686‘
CC [M] /root/test/simp_blkdev/simp_blkdev_step09/simp_blkdev.o
Building modules, stage 2.
MODPOST
CC /root/test/simp_blkdev/simp_blkdev_step09/simp_blkdev.mod.o
LD [M] /root/test/simp_blkdev/simp_blkdev_step09/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686‘
# insmod simp_blkdev.ko
#
看一看驱动程序刚刚加载时里面的数据:
# hexdump /dev/simp_blkdev -vn512
0000000 0000 0000 0000 0000 0000 0000 0000 0000
0000010 0000 0000 0000 0000 0000 0000 0000 0000
0000020 0000 0000 0000 0000 0000 0000 0000 0000
0000030 0000 0000 0000 0000 0000 0000 0000 0000
0000040 0000 0000 0000 0000 0000 0000 0000 0000
0000050 0000 0000 0000 0000 0000 0000 0000 0000
0000060 0000 0000 0000 0000 0000 0000 0000 0000
0000070 0000 0000 0000 0000 0000 0000 0000 0000
0000080 0000 0000 0000 0000 0000 0000 0000 0000
0000090 0000 0000 0000 0000 0000 0000 0000 0000
00000a0 0000 0000 0000 0000 0000 0000 0000 0000
00000b0 0000 0000 0000 0000 0000 0000 0000 0000
00000c0 0000 0000 0000 0000 0000 0000 0000 0000
00000d0 0000 0000 0000 0000 0000 0000 0000 0000
00000e0 0000 0000 0000 0000 0000 0000 0000 0000
00000f0 0000 0000 0000 0000 0000 0000 0000 0000
0000100 0000 0000 0000 0000 0000 0000 0000 0000
0000110 0000 0000 0000 0000 0000 0000 0000 0000
0000120 0000 0000 0000 0000 0000 0000 0000 0000
0000130 0000 0000 0000 0000 0000 0000 0000 0000
0000140 0000 0000 0000 0000 0000 0000 0000 0000
0000150 0000 0000 0000 0000 0000 0000 0000 0000
0000160 0000 0000 0000 0000 0000 0000 0000 0000
0000170 0000 0000 0000 0000 0000 0000 0000 0000
0000180 0000 0000 0000 0000 0000 0000 0000 0000
0000190 0000 0000 0000 0000 0000 0000 0000 0000
00001a0 0000 0000 0000 0000 0000 0000 0000 0000
00001b0 0000 0000 0000 0000 0000 0000 0000 0000
00001c0 0000 0000 0000 0000 0000 0000 0000 0000
00001d0 0000 0000 0000 0000 0000 0000 0000 0000
00001e0 0000 0000 0000 0000 0000 0000 0000 0000
00001f0 0000 0000 0000 0000 0000 0000 0000 0000
0000200
#
对比一下修改前的效果:
# hexdump /dev/simp_blkdev -vn512
0000000 f300 0800 1200 0000 b804 1200 0000 0500
0000010 501a 6930 1806 246a bf0a 7700 256a bf0b
0000020 1f80 256b bf0b 47a0 266b bf0b 0ff0 246a
0000030 bf0a 1708 ffff 00ff 5028 256b bf0b 00a8
0000040 ffff 00ff 04b8 ffff 00ff 10c8 256b bf0b
0000050 00e8 246a bf0a 0229 ffff 00ff 1339 ffff
0000060 00ff 0059 246a bf0a 1669 ffff 00ff 12a9
0000070 256b bf0b 02c9 ffff 00ff 12d9 246a bf0a
0000080 215a ffff 00ff 302c 256b bf0b 03ac ffff
0000090 00ff 10cc 256b bf0b 03ec 246a bf0a 522d
00000a0 256b bf0b 32bd 2318 266b bf0c 2700 266c
00000b0 bf0c 2730 276c bf0c 1f60 276c bf0d 3580
00000c0 276d bf0d 1bc0 286d bf0d 05e0 286d bf0e
00000d0 04f0 ffff 00ff 07f5 276c bf0d 0186 ffff
00000e0 00ff 1596 276c bf0d 01b6 ffff 00ff 15e6
00000f0 266b bf0c 0708 266b bf0c 0018 ffff 00ff
0000100 0428 ffff 00ff 1038 266c bf0c 0058 ffff
0000110 00ff 3088 ffff 00ff 1219 266c bf0c 0239
0000120 ffff 00ff 1249 276c bf0d 0689 276c bf0d
0000130 02b9 266b bf0c 031c ffff 00ff 103c 266c
0000140 bf0c 035c 276c bf0d 039c ffff 00ff 20ac
0000150 276d bf0d 03dc 286d bf0d 03ec 266b bf0c
0000160 022d 266c bf0c 223d 276c bf0d 12ad 276d
0000170 bf0d 12cd 286d bf0e 02fd 2b18 286d bf0e
0000180 4400 296e bf0e 1450 296e bf0f 4470 2a6e
0000190 bf0f 14c0 2a6f bf0f 04e0 2a6f bf10 04f0
00001a0 ffff 00ff 2005 286d bf0e 1035 ffff 00ff
00001b0 5055 296e bf0f 0ab5 ffff 00ff 30c5 286d
00001c0 bf0e 1006 ffff 00ff 1426 286d bf0e 0946
00001d0 ffff 00ff 1056 296e bf0f 0176 ffff 00ff
00001e0 1186 296e bf0f 14a6 2a6e bf0f 05c6 ffff
00001f0 00ff 16d6 2a6f bf10 05f6 286d bf0e 0007
0000200
#
本章到此结束,读者是不是感觉我们的教程越来越简单了?
<未完,待续>
第10章
+---------------------------------------------------+
| 写一个块设备驱动 |
+---------------------------------------------------+
| 作者:赵磊 |
| email: [email protected] |
+---------------------------------------------------+
| 文章版权归原作者所有。 |
| 大家可以自由转载这篇文章,但原版权信息必须保留。 |
| 如需用于商业用途,请务必与原作者联系,若因未取得 |
| 授权而收起的版权争议,由侵权者自行负责。 |
+---------------------------------------------------+
如果你的linux系统是x86平台,并且内存大于896M,那么恭喜你,我们大概可以在这个实验中搞坏你的系统。
反之如果你的系统不符合这些条件,也不用为无法搞坏系统而感到失望,本章的内容同样适合你。
这时作者自然也要申明一下对读者产生的任何损失概不负责,
因为这年头一不小心就可能差点成了被告,比如南京的彭宇和镇江花山湾的小许姑娘。
在实验看到的情况会因为系统的实际状况不同而稍有区别,但我们需要说明的问题倒是相似的。
但希望读者不要把这种相似理解成了ATM机取款17.5万和贪污2.6亿在判决上的那种相似。
首先我们来看看目前系统的内存状况:
# cat /proc/meminfo
MemTotal: 1552532 kB
MemFree: 1529236 kB
Buffers: 2716 kB
Cached: 10124 kB
SwapCached: 0 kB
Active: 8608 kB
Inactive: 7664 kB
HighTotal: 655296 kB
HighFree: 640836 kB
LowTotal: 897236 kB
LowFree: 888400 kB
SwapTotal: 522104 kB
SwapFree: 522104 kB
Dirty: 44 kB
Writeback: 0 kB
AnonPages: 3440 kB
Mapped: 3324 kB
Slab: 2916 kB
SReclaimable: 888 kB
SUnreclaim: 2028 kB
PageTables: 272 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 1298368 kB
Committed_AS: 10580 kB
VmallocTotal: 114680 kB
VmallocUsed: 392 kB
VmallocChunk: 114288 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 4096 kB
DirectMap4k: 12288 kB
DirectMap4M: 905216 kB
#
输出很多,但我们只关心这几行:
MemFree: 1529236 kB --这说明系统中有接近1.5G的空闲内存
HighFree: 640836 kB --这说明空闲内存中,处在高端的有600M左右
LowFree: 888400 kB --这说明空闲内存中,处在低端的有800M左右
现在加载上一章完成的模块,我们指定创建800M的块设备:
# insmod simp_blkdev.ko size=800M
#
成功了,我们再看看内存状况:
# cat /proc/meminfo
MemFree: 708812 kB
HighFree: 640464 kB
LowFree: 68348 kB
...
#
我们发现高端内存没怎变,低端内存却已经被耗得差不多了。
我们一不做二不休,继续加大块设备的容量,看看极限能到多少:
# rmmod simp_blkdev
# insmod simp_blkdev.ko size=860M
# cat /proc/meminfo
MemFree: 651184 kB
HighFree: 641972 kB
LowFree: 9212 kB
...
#
系统居然还没事,这时虽然高端内存还是没怎么变,但低端内存剩下的得已经很可怜了。
然后进一步加大块设备的容量:
# rmmod simp_blkdev
# insmod simp_blkdev.ko size=870M
...
这里不用再cat /proc/meminfo了,因为系统已经完蛋了。
如果有些读者嗜好独特,对出错信息情有独钟的话,在这里也满足一下:
kernel: [ 3588.769050] insmod invoked oom-killer: gfp_mask=0x80d0, order=2, oomkilladj=0
kernel: [ 3588.769516] Pid: 4236, comm: insmod Tainted: G W 2.6.27.4 #53
kernel: [ 3588.769868] [
kernel: [ 3588.771041] [
kernel: [ 3588.771306] [
kernel: [ 3588.771500] [
kernel: [ 3588.771679] [
kernel: [ 3588.771899] [
kernel: [ 3588.772217] [
kernel: [ 3588.772393] [
kernel: [ 3588.772599] [
kernel: [ 3588.772845] [
kernel: [ 3588.773250] [
kernel: [ 3588.773884] =======================
kernel: [ 3588.774237] Mem-Info:
kernel: [ 3588.774241] DMA per-cpu:
kernel: [ 3588.774404] CPU 0: hi: 0, btch: 1 usd: 0
kernel: [ 3588.774582] Normal per-cpu:
kernel: [ 3588.774689] CPU 0: hi: 186, btch: 31 usd: 0
kernel: [ 3588.774870] HighMem per-cpu:
kernel: [ 3588.778602] CPU 0: hi: 186, btch: 31 usd: 0
...
搞坏系统就当是交学费了,但交完学费我们总要学到些东西。
虽然公款出国考察似乎已经斯通见惯,但至少在我们的理解中,学费不是旅游费,更不是家属的旅游费。
我们通过细心观察、周密推理后得出的结论是:
目前的块设备驱动程序会一根筋地使用低端内存,即使系统中低端内存很紧缺的时候,
也会直道把系统搞死却不去动半点的高端内存,这未免也太挑食了,
因此在本章和接下来的几章中,我们将帮助驱动程序戒掉对低端内存的瘾。
相对高端内存而言,低端内存是比较宝贵的,这是因为它不需要影射就能直接被内核访问的特性。
而内核中的不少功能都直接使用低端内存,以保证访问的速度和简便,
但换句话来说,如果低端内存告急,那么系统可能离Panic也不远了。
因此总的来说,对低端内存的使用方法大概应该是:除非有足够理由,否则就别乱占着。
详细来说,就是:
1:不需要使用低端内存的“在内核中不需要映射就能直接访问”这个特性的功能,应该优先使用高端内存
如:分配给用户态进程的内存,和vmalloc的内存
2:需要占用大量内存的功能,并且也可以通过高端内存实现的,应该优先使用高端内存
如:我们的程序
与内存有关的知识我们在以前的章节中已经谈到,因此这里不再重复了,
但需要说明的是在高端内存被映射之前,我们是无法通过指针来指向它的。
因为它不在内核空间的地址范围以内。
虽然如此,我们却无论如何都需要找出一种方法来指定一个没有被映射的高端内存,
这是由于至少在进行映射操作时,我们需要指定去映射谁。
这就像为一群猴子取名的时候,如何来说明是正在给哪只猴子取名一样。
虽然给猴子取名的问题可能比较容易解决,比如我们可以说,
给哪只红屁股的公猴取名叫齐天大圣、给那只瘦瘦的母猴取名叫白晶晶,
但可惜一块高端内存即没有红屁股,又没有胖瘦之分,
它们唯一有的就是地址,因此我们也必须通过地址来指定这段高端内存。
刚才说过,在高端内存被映射之前,他在内核的地址空间中是不存在的,
但虽然如此,它至少存在其物理地址,而我们正是可以通过它的物理地址来指定它。
是的,本质上是这样的,但在linux中,我们还需要再绕那么一丁点:
linux在启动阶段为全部物理内存按页为单位建立了的对应的struct page结构,用来管理这些物理内存,
也就是,每个页的物理内存,都有着1对1的struct page结构,而这些struct page结构是位于低端内存中的,
我们只要使用指向某个struct page结构的指针,就能指定物理内存中的一个页。
因此,对于没有被映射到内核空间中的高端内存,我们可以通过对应的struct page结构来指定它。
(如果读者希望了解更详细的知识,可以考虑从virt_to_page函数一路google下去)
我们在这里大肆谈论高端内存的表示方法,因为这是让我们的模块使用高端内存的前提。
我们的驱动程序使用多段内存来存储块设备中的数据。
原先的程序中,我们使用指向这些内存段的指针来指定这些数据的位置,这是没有问题的,
因为当时我们是使用__get_free_pages()来申请内存,__get_free_pages()函数只能用来申请低端内存,
因为这个函数返回的是申请到的内存的指针,而上文中说过,高端内存是不能用这样的指针表示的。
要申请高端内存,明显不能使用这样的函数,因此我们隆重介绍它的代替者出场:
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order);
这个函数的参数与__get_free_pages()相同,但区别在于,它返回指向struct page的指针,
这个我们在上文中介绍过的指针赋予了alloc_pages()函数申请高端内存的能力。
其实申请一块高端内存并不难,只要使用__GFP_HIGHMEM参数调用alloc_pages()函数,
就可能返回一块高端内存,之所以说是“可能”,使因为在某些情况下,比如高端内存不够或不存在时,也会但会低端内存充数。
我们的现在的目标是让驱动程序使用高端内存,这需要:
1:让驱动程序申请高端内存
2:让驱动程序使用高端内存
但在这一章中,我们要做的即不是1,也不是2,而是1之前的准备工作。
因为1和2必须一气呵成地改完,而为了让一气呵成的时候不要再面临其他插曲,
我们需要做好充足的准备工作,就像ml前尿尿一样。
对应到程序的修改工作上,我们打算先让程序使用struct page *来指定申请到的内存。
要实现这个目的,我们先要改申请内存的函数,也就是alloc_diskmem()。
刚才我们介绍过alloc_pages(),现在就要用它了:
首先把函数中定义的
void *p;
改成
struct page *page;
因为我们要使用struct page *来指定申请到的内存,而不是地址了。
然后把
p = (void *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, SIMP_BLKDEV_DATASEGORDER);
改成
page = alloc_pages(GFP_KERNEL | __GFP_ZERO, SIMP_BLKDEV_DATASEGORDER);
这一行改动的原因大概已经说得很详细了。
还有那个if(!p)改成if (!page)
然后就是把指针加入基树的那一行:
ret = radix_tree_insert(&simp_blkdev_data, i, p);
改成
ret = radix_tree_insert(&simp_blkdev_data, i, page);
由于我们使用了struct page *来指定申请到的内存,因此错误处理部分也要小改一下:
free_pages((unsigned long)p, SIMP_BLKDEV_DATASEGORDER);
改成
__free_pages(page, SIMP_BLKDEV_DATASEGORDER);
这里补充介绍一下__free_pages()函数,可能大家已经猜到其作用了,
其实与我们原先使用的free_pages()函数相似,都是用来释放一段内存,
但__free_pages()使用struct page *来指定要释放的内存,这也意味着它能够用来释放高端内存。
大家应该已经发现我们虽然改用alloc_pages()函数来申请内存,但并没有指定__GFP_HIGHMEM参数,
这时申请到的仍然是低端内存,因此避免了在这一章中对访问内存那部分代码的大肆改动。
改动过的alloc_pages()函数是这样的:
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,
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;
}
相应的,释放内存用的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);
}
}
随后是simp_blkdev_make_request()函数:
首先我们不是把void *dsk_mem改成struct page *dsk_page,而是增加一个
struct page *dsk_page;
变量,因为在访问内存时,我们还是需要用到dsk_mem变量的。
然后是从基数中获取指针的代码,把原先的
dsk_mem = radix_tree_lookup(&simp_blkdev_data, (dsk_offset + count_done) >> SIMP_BLKDEV_DATASEGSHIFT);
改成
dsk_page = radix_tree_lookup(&simp_blkdev_data, (dsk_offset + count_done) >> SIMP_BLKDEV_DATASEGSHIFT);
虽然看起来没什么太大变化,但我们需要知道,这时基树返回的指针已经不是直接指向数据所在的内存了。
还有那个判断是否从基树中获取成功的
if (!dsk_mem) {
用脚丫子也能想得出应该改成这样:
if (!dsk_page) {
还有就是我们需要首先将struct page *dsk_page地址转换成内存的地址后,才能对这块内存进行访问。
这里我们使用了page_address()函数。
这个函数可以获得struct page数据结构所对应内存的地址。
这时可能有读者要问了,如果这个struct page对应的是高端内存,那么如何返回地址呢?
实际上,这种情况下如果高端内存中的页面已经被映射到内核的地址空间,那么函数会返回映射到内核空间中的地址,
而如果没有映射的话,函数将返回0。
对于我们目前的程序而言,由于使用的是低端内存,因此struct page对应的内存总是处于内核地址空间中的。
对应到代码中,我们需要在使用dsk_mem之前,也就是
dsk_mem += (dsk_offset + count_done) & ~SIMP_BLKDEV_DATASEGMASK;
这条语句之前,让dsk_mem指向struct page *dsk_page对应的内存的实际地址。
这是通过如下代码实现的:
dsk_mem = page_address(dsk_page);
if (!dsk_mem) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": get page‘s address failed: %p\n",
dsk_page);
kunmap(bvec->bv_page);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
总的来说,修改后的simp_blkdev_make_request()函数是这样的:
static int simp_blkdev_make_request(struct request_queue *q, struct bio *bio)
{
struct bio_vec *bvec;
int i;
unsigned long long dsk_offset;
if ((bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT) + bio->bi_size
> 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);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
}
dsk_offset = bio->bi_sector << SIMP_BLKDEV_SECTORSHIFT;
bio_for_each_segment(bvec, bio, i) {
unsigned int count_done, count_current;
void *iovec_mem;
struct page *dsk_page;
void *dsk_mem;
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
count_done = 0;
while (count_done < bvec->bv_len) {
count_current = min(bvec->bv_len - count_done,
(unsigned int)(SIMP_BLKDEV_DATASEGSIZE
- ((dsk_offset + count_done) &
~SIMP_BLKDEV_DATASEGMASK)));
dsk_page = radix_tree_lookup(&simp_blkdev_data,
(dsk_offset + count_done)
>> SIMP_BLKDEV_DATASEGSHIFT);
if (!dsk_page) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": search memory failed: %llu\n",
(dsk_offset + count_done)
>> SIMP_BLKDEV_DATASEGSHIFT);
kunmap(bvec->bv_page);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
}
dsk_mem = page_address(dsk_page);
if (!dsk_mem) {
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": get page‘s address failed: %p\n",
dsk_page);
kunmap(bvec->bv_page);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
}
dsk_mem += (dsk_offset + count_done)
& ~SIMP_BLKDEV_DATASEGMASK;
switch (bio_rw(bio)) {
case READ:
case READA:
memcpy(iovec_mem + count_done, dsk_mem,
count_current);
break;
case WRITE:
memcpy(dsk_mem, iovec_mem + count_done,
count_current);
break;
default:
printk(KERN_ERR SIMP_BLKDEV_DISKNAME
": unknown value of bio_rw: %lu\n",
bio_rw(bio));
kunmap(bvec->bv_page);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 24)
bio_endio(bio, 0, -EIO);
#else
bio_endio(bio, -EIO);
#endif
return 0;
}
count_done += count_current;
}
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;
}
通过对这3个函数的更改,代码可以使用struct page *来定位存储块设备数据的内存了。
这也为将来使用高端内存做了一部分准备。
因为本章修改的代码在外部功能上没有发生变动,所以我们就不在这里尝试编译了运行代码了。
不过感兴趣的读者不妨试一试这段代码能不能进行编译和会不会引起死机。
<未完,待续>
给主人留下些什么吧!~~
评论热议
以上是关于写一个块设备驱动9,10的主要内容,如果未能解决你的问题,请参考以下文章