迁移的理由
由于 863 系统软件工作分为若干模块,而最后这些模块要统筹在一起,运行在一个原型机上,因此各个模块的工作需要基于相同的 Linux 内核。不巧的是,我们 DDST 工作组负责的两个部分:文件系统和内存管理,刚好就选用了不同的内核。我是内存管理组的,持久内存系统的开发基于 linux 3.12.49,而文件系统组则用的是3.11.0。更不巧的是,最后我们的老师,也是项目最终负责人,经过一系列会议的讨论,决定选用 3.11 内核作为最终集成的内核版本。
于是,我的工作便是将基于 Linux 3.12 的 Daisy 持久内存系统迁移到 Linux 3.11 上,使之仍能正确运行。
如何迁移
关于迁移方法,最初的的想法是:3.12 和 3.11 只差一个版本,迁移起来必定 so easy。只要我把基于 Linux 3.12 内核新增的代码都放到 3.11 上,不就大功告成了。然而,这是多么天真多么幼稚的理解。而今天一整天的工作就是将内核迁移给做出来。我将如实汇报一路上所遇到的困难与挑战。
第一个挑战
我需要将哪些文件和代码添加到 Linux 3.11 内核中呢?
这涉及到我必须清楚地知晓我们的工作对内核进行了哪些修改。然而事实上我并非对此了然于胸。幸好,预见力极高的师兄为我们准备好了后路:一份 difference 文件拯救我于水火之中:git diff 是一个相当睿智犀利的武器,可以将代码和文件的变更清楚地展示出来。凭着这份利器,我小心翼翼地对照着将 Linux 3.11 内核的代码打开并逐一修改。
2000 多行的代码(为了测试方便,用户层我并没有加上全部的代码),耗费了大概 6 个小时。
第二个挑战
代码修改完,但这只是万水千山的第一步。因为在代码修改的过程中,我意识到了,Linux 3.11 和 3.12 内核的差异,比我想象地更大。也许,编译没法一次通过。
果然,亟待征服的第二个困难不出意外地发生了:make 返回错误信息:
没有规则可以创建 mm/built-in.o 需要的目标 mm/list_lru.o
我的第一反应是谷歌大法好,根据网上查到的结果,敲了一行命令:
make distclean
重新编译后仍然是相同的错误,于是我开始思考:如果无法创建 .o 文件,那是不是说这个目录下根本就连相应的 .c 文件也不存在呢?果然,细查之下,mm目录下根本就没有 list_lru.c 文件,include/linux 中也没有 list_lru.h 文件。既然问题的源头暴露了,一切也就好办了。
我将原来部署在 Linux 3.12 内核中的两份文件拷贝过来,并检查了这两份文件的引用有无疏漏,万幸,list_lru 没有引用其他更稀奇古怪的头文件。但我原来没想到的是,仅仅是一个版本的差别,就已经涉及到如此重要的文件/数据结构的有无之别了。
于是,我怀着忐忑的心情继续开始编译。
第三个挑战
编译仍然失败,返回的错误信息是:
VM_SOFTDIRTY undeclared。经过刚才那次困难的恫吓,这点小事已经吓不到到。稍一思考,我便得出结论:Linux 3.11 内核中缺少对 VM_SOFTDIRTY 这个宏的定义,那么我只要仿照 3.12 内核中的模板去定义不就好了吗!
我用 grep 命令找到 VM_SOFTDIRTY 的定义应该在 mm.h 中,然后如法炮制,迁移到 3.11 内核中去。
然后这次编译,终于~成~功~了!
但转眼就遇到了下一个困难。
第四个挑战
没错,这个问题是系统启动不了。
我尝试敲了如下命令:
make mrproper
make localmodconfig
make
sudo make install
sudo reboot
安装内核成功,进入重启阶段,选择 3.11 内核启动,然后,就没有然后了。
对,系统卡住了,接下来就是显示黑屏。
这还怎么玩儿?
我细想了一下,有两种可能:第一是我拿的这个 3.11 版本本来就有问题(毕竟在修改代码前我并没有进行启动测试),第二是我在编译的时候落了某个关键的步骤(sudo make modules_install)。我希望是后者,因为如果这份内核本身就有问题那也太坑了吧!
还好,3.11 的原始内核没有问题,但我开始害怕答案是我想不到的第三种情形。
重新编译内核时我小心地将命令改为如下顺序组合:
make mrproper
make localmodconfig
make -j4
sudo make modules_install
sudo make install
sudo reboot
然后,重启,选择内核,啊,终于成功了。
这回,我看到了熟悉的桌面,打开了熟悉的终端。走到了万水千山的最后也是最难的一步。
最后的挑战
test 程序无法正常运行。系统调用返回 error,且这一 error 信息没有提供任何有价值的线索,因为对于一个完全未经修改的 3.11 内核来说,它也会显示这样的错误。
虽然目前情况很糟糕,但我至少还能做两件事:第一,确定系统调用是否真正被调用;第二,根据函数调用逻辑规划处可能的程序故障点并一一检查。
第一件事是为了确定现在启用的内核是否为我修改代码后的内核,以及编译后的模块是否已经开始工作;第二点是根据调用逻辑找到故障点从而进一步分析失败的原理并找出解决方案。
当然了,我也将此事跟我的组内伙伴说明了,想必经验丰富的他也许能看出比我更多的东西吧。
第一步:确认系统调用的工作情况
test 程序的系统调用逻辑为:
p_init -> p_get_small_region(id) -> (int)syscall(__NR_p_get_small_region) -> search_heap_region_node(id) + [(get_free_page + insert_heap_region_node) XOR (alloc_pages + insert_heap_region_node)]
我在 p_init 所必经的调用 critical path 上增加了一个 printk 标记,如果 p_get_small_region 函数运行,就会打印出一条预设的特定信息。
然而,返回的结果令我十分诧异。daisy_printk 的信息并没有被打印出来。
在求助薛栋梁师兄后,我发现了 daisy_printk 不能正产工作的缘由。这是因为内核态的“打印”不会直接将信息返回给用户,而是以日志的方式进行记录,如果要查看,需要做这样的操作:
- dmesg > one_file
- vim one_file
再根据 printk 中预设的 keyword 进行查找,以查看打印的信息。而 Daisy 所使用的内核打印函数 daisy_printk 自身包含了 [Daisy] 作为 keyword,因此检索起来十分方便。这就极大地提高了内核调试的效率。
根据返回的结果,现在可以确认系统调用被执行。错误信息如下:
Cannot find heap region per program
Error: alloc_pages
错误发生在 alloc_pages 内部,接下来需要进一步检查 alloc_pages 的工作流程以及程序故障点的位置。
第二步:根据调用逻辑进一步探索故障点位置
虽然不是很确定,但可以先把这一个要素放在这里作为提点。
VM_SOFTDIRTY 是一个根据条件选择决定数值的变量,这是 3.11 版本原来所没有的。
也许它是问题的一个关键也说不定。
总之,现在还是根据 daisy_printk 的逻辑来检查我的程序吧。
alloc_pages 的函数调用逻辑为 alloc_pages -> alloc_pages_current -> __alloc_pages_nodemask -> get_page_from_freelist。
经过和海鑫的讨论检查,我们最终确认了问题所在:SCM地址段没有正确初始化,也就是说我在进行代码移植的过程中缺少了一个关键性的语句:
if (gfp_mask & __GFP_SCM) {
goto try_this_zone;
}
最后修改完这里及做好相应的 printk 机关设置,重新编译内核,安装,重启,选择当前内核,使用 dmesg | grep "Daisy"
查看开机信息,此时 SCM 的地址段和预期一般。最后执行测试程序,p_init 正常执行,Oh yeah!