最近看《linux内核设计的艺术》,主要讲述了linux0.11从启动到进入怠速状态的整个过程,讲解的非常透彻,图文并茂。但是如果没有实验环境,纵使把整本书都读懂了,还是不能入木三分。所以一直想搭建一个跟此书配套的实验环境,搞了好几天,终于完成,现总结一下。
集成盘的代码和根文件系统是在赵炯的两个包的基础上修改的:linux-0.11-060618-gcc4.tar.gz,bootroot-0.11-040928.zip。
一、编译错误有两处,修改比较简单:
①:blk.h的86行 #elif 修改为 #elif 1。
②:fs,kernel和kernel/chr_drv下的Makefile的CFLAGS中添加-fno-stack-protector参数。
二、修改根目录下Makefile文件,最终编译成引导+根文件系统的集成盘:
①:第5行RAMDISK = #-DRAMDISK=512 修改为 RAMDISK = -DRAMDISK=2048
②:第23行ROOT_DEV= #FLOPPY 修改为 ROOT_DEV= FLOPPY
③:第44行后面添加:
dd bs=1024 if=/dev/zero of=bootroot count=1024
dd bs=1024 if=Image of=bootroot conv=notrunc
dd bs=1024 if=bootroot-0.11 of=bootroot conv=notrunc skip=256 seek=256
最后生成的集成盘bootroot前256k是Image的内容。后面的内容需要用到bootroot-0.11-040928.zip中的bootroot-0.11,该文件256k后面的内容可以作为根文件系统。
当然,也可以参考赵炯的《Linux内核完全注释》17.7小结自己制作根文件系统的,里面也主要是从现成的系统里面拷贝。这里我就不麻烦了。不过这里让我很好奇,linux0.11一开始的根文件系统是谁开发的呢?源代码不知道网上有没有?
④:为了后面调试方便加了一些反汇编的语句:
objdump -D tools/system > system.dasm
$(AS86) -l setup.list -o boot/setup.o boot/setup.s
$(AS86) -l bootsect.list -o boot/bootsect.o boot/bootsect.s
三、通过上面的修改,就可以编译生成集成盘bootroot了,但调试花了一些时间,虽然总结起来也并不是太复杂:
①:不停的Loading system。
通过在关键位置打断点,发现运行到head.s开始处时,反汇编代码并不是head.s的。查看System.map文件,发现main函数被编译到了位置0x0处。这是编译问题,尝试修改编译链接的参数,最后把Makefile文件中CFLAGS的参数-O2 修改为 -O就行了。
②:通过上面的修改,系统还是停留在Loading system处。
系统能运行到sys_pause处,但运行不到init处。在sys_pause处打断点,查看系统进程表task的数据状态,发现进程1没有被正常创建,进程0的数据结构没有正常拷贝给进程1。
再次把断点copy_process函数的*p = *current处,发现错误是由于df没有被清除导致的。
那DF是是在哪里被置位的呢?
查看代码发现前面的get_free_page函数中有sti语句,但并没有配套的cld语句。
网上有一篇文章,对这个问题讲解的不错http://blog.chinaunix.net/uid-27062906-id-3380279.html。
谷歌也没有令我失望,我找到了下面这段文字:从gcc4.3开始的,官方文档如下:
GCC no longer places the cld instruction before string operations. Both i386 and x86-64 ABI documents mandate the direction flag to be clear at the entry of a function. It is now invalid to set the flag in asm statement without reseting it afterward.
就是说在string操作之前,编译器不再插入cld指令。因为i386和x86-64的官方文档都明确要求在进入一个函数时要把方向标记(DF)清除,汇编指令设置方向标记(std)而不清除属于非法!
最终,解决这个问题应该在在get _free_page函数第77行后添加"cld"语句。
③:修改后还是不能进入系统,panic退出。但虚拟盘正常加载了。
由于有panic消息,就很容易定位了。添加一些printk消息,再次运行后,发现错误是由于s->s_magic != SUPER_MAGIC导致的。
断点到前面的*((struct d_super_block *) s) =*((struct d_super_block *) bh->b_data)处,发现没有读到虚拟盘超级块的内容, bh->b_data所指内存处内容为空。
这样就可以推断是rd_load拷贝根文件系统进入复制虚拟盘时有问题了,难道也是DF位没清除的原因吗?定位到rd_load函数中memcpy处一看究竟。
DF和地址都正确,但循环一次后edi的值就不正确了。
观察反汇编代码可以再次证明是edi值错误导致的。
1284a: 8b 30 mov (%eax),%esi
1284c: b9 00 04 00 00 mov $0x400,%ecx
12851: fc cld
12852: f3 a4 rep movsb %ds:(%esi),%es:(%edi) 这里edi的值会增加
12854: 89 04 24 mov %eax,(%esp)
12857: e8 07 8e ff ff call b663 <brelse>
1285c: 89 6c 24 04 mov %ebp,0x4(%esp)
12860: c7 04 24 b0 60 01 00movl $0x160b0,(%esp)
12867: e8 40 55 ff ff call 7dac <printk>
1286c: 81 c7 00 04 00 00 add $0x400,%edi 这里edi的值也会增加
最终,修改memcpy的第10行为__asm__ ("pushl %%edi;cld;rep;movsb;popl %%edi" )
再次编译,可以正常进入系统。留图纪念 !
编译:ubuntu12.04 gcc4.6.3
调试:windows7 Bochs-2.4.5 peter-bochs-debugger20120606
相关资料:
bootroot-0.11.7z 已经编译好的集成盘和调试环境