资源描述
基于x86的linux2.6.26代码分析
一. 由Makefile分析bzImage的生成过程
内核配置完毕之后使用命令make bzImage生成内核镜像文件,这里使用的make规则默认是源代码目录linux-2.6.26(以后定为SOURCE_HOME).打开SOURCE_HOME/Makefile发现其中的target是vmlinux,没有发现命令中指定的目标bzImage.由于makefile 文件可以包含其他的make规则最后发现SOURCE_HOME/Makefile多处出现下面的一句话(431行)
图<1>
其中$SRCARCH是平台的架构这里是X86,这句话显然包含了SOURCE_HOME/arch/x86/Makefile,打开这个文件继续寻找目标bzImage有如下发现:
图<2>
212行规定生成的bzImage的位置在SOURCE_HOME/arch/x86/boot/下面.
215行发现bzImage生成依赖于vmlinux.
Vmlinux的生成
显然vmlinux是SOURCE_HOME/Makefile中的规则负责生成的.
图<3>
这个vmlinux的生产依赖于后面的几个目标,这些目标如下
图<4>
图<5>
从上面的规则可以清楚看出vmlinux的生成用到了SOURCE_HOME下面的这些目录:kernel/,mm/,fs/,ipc/,security/, crypto/,kblock/,init/,lib/,net/,/drivers/,
Vmlinux到最后直接依赖的是vmlinux.o,kallsyms.o,和上面所说那些目录下的built-in.o.
built-in.o是如何生成的
图<6>
上图主Makefile838行调用了scripts/Makefile.build对保存在vmlinux-dirs目录中的内容进行编译
图<7>
可以看到vmlinux-dirs包括SOURCE_HOME下面的init,drivers,net,lib,kernel,mm,fs,ipc.security,crypto,block这些和体系结构无关的目录内容.
在scripts/Makefile.build83行看到每个目录生成的结构都保存在built-in.o中,这就生成了vmlinux所依赖的部分文件.
有了built-in.o还需要vmlinux.o和kallsyms.o
vmlinux.o的生成方式
vmlinux.o依赖的vmlinux-main上一节讲过就是那些built-in.o.现在缺少的是$(modpost-init)
查找$(modpost-init)发现图<5>822行:modpost-init包括了vmlinux-init中除了init/built-in.o之外的那些文件.
vmlinux-init := $(head-y) $(init-y)
init-y是init/built-in.o,回头看图<5>822行这么做的原因可能是不想多次包含init/built-in.o.
$(head-y)在主Makefile没找到,既然包含了x86/Makefile就去找吧:
在这里又发现其实主Makefile中定义的core-y,lib-y等又加入了和体系结构相关x86目录下的一些东西.而head-y如下:
图<8>
可见head-y包含了x86/kernel/下面的head_32.o,head32.o,init_task.o
至此vmlinux.o的生产条件已经满足了.
图<9>
Kallsyms其实是script下的一个命令,主Makefile在757-759行生成了内核的全部符号表并且把这些符号交给scripts/kallsyms处理.处理的结果可以打开SOURCE_HOME下的..tmp_kallsymsX.o.cmd查看,这里的X代表1/2.
图<10>
从图中看到这个命令生成的是kallsyms.o依赖的.tmp_kallsymsX.o.至此kallsyms.o生成的条件完全满足了.
最后从主Makefile看出又分了三步一步一步修正vmlinux和kallsyms.o最终生成主目录下的vmlinux.
最后查看bzImage是如何生成的,方法是查看编译时的输出信息:
在上图的970行中vmlinux生成了.然而不是最终的bzImage.
从上图983-989,1007-1011行看到vmlinux显然又经过了一些变化,生成的目标都放在compress和boot目录下.到compress目录下查看发现几个命令文件,这些命令文件是编译时的输出,比较详细.
首先是.vmlinux.cmd文件他的目标又是一个vmlinux,目的地是本目录.
cmd_arch/x86/boot/compressed/vmlinux := ld -m elf_i386 -T arch/x86/boot/compressed/vmlinux_32.lds arch/x86/boot/compressed/head_32.o arch/x86/boot/compressed/misc.o arch/x86/boot/compressed/piggy.o -o arch/x86/boot/compressed/vmlinux
可见他的输出文件是一个elf可执行文件,依赖于
vmlinux_32.lds这个文件本来就有;
misc.o,和head_32.o是运行时进行解压缩和重定位的代码.目录中有同名命令生成.而piggy.o还要去找.
打开.piggy.o.cmd
cmd_arch/x86/boot/compressed/piggy.o := ld -m elf_i386 -r --format binary --oformat elf32-i386 -T arch/x86/boot/compressed/vmlinux.scr arch/x86/boot/compressed/vmlinux.bin.gz -o arch/x86/boot/compressed/piggy.o
这里发现piggy.o是吧vmlinux.bin.gz链接而成的.在去找vmlinux.bin.gz的生成.打开.vimlnux.bin.gz.cmd
cmd_arch/x86/boot/compressed/vmlinux.bin.gz := gzip -f -9 < arch/x86/boot/compressed/vmlinux.bin.all > arch/x86/boot/compressed/vmlinux.bin.gz
很显然他使用了gzip命令把vmlinux.bin.all压缩后存放到comprssed/vmlinux.bin.gz
在去找vmlinux.bin.all.同样打开同名文件.vmlinux.bin.all.cmd
cmd_arch/x86/boot/compressed/vmlinux.bin.all := cat arch/x86/boot/compressed/vmlinux.bin arch/x86/boot/compressed/vmlinux.relocs > arch/x86/boot/compressed/vmlinux.bin.all
可见这个文件吧vmlinux.bin和vmlinux.relocs简单的合并成了vmlinux.bin.all
在查看.vmlinux.bin.cmd
cmd_arch/x86/boot/compressed/vmlinux.bin := objcopy -R .comment -S vmlinux arch/x86/boot/compressed/vmlinux.bin
这个文件是用objcopy把原来主Makefile生成的vmlinux拷贝为二进制文件
打开.vmlinux.relocs发现
cmd_arch/x86/boot/compressed/vmlinux.relocs := arch/x86/boot/compressed/relocs vmlinux > arch/x86/boot/compressed/vmlinux.relocs;arch/x86/boot/compressed/relocs --abs-relocs vmlinux
说明vmlinux.relocs是把vmlinux的定位符提取出来,然后又用compressed/relocs把原来的vmlinux进行重定位了.
这样正推过去就找到了这个目录下vmlinux的生成方式
A>objcopy 主Makefile生成的位于SOURCE_HOME下的那个vmlinux生成arch/x86/compressed/vmlinux.bin
B>arch/x86/compressed/relocs提取SOURCE_HOME/vmlinux的重定位信息输出到arch/x86/compressed/vmlinux.relocs.
C>vmlinux.relocs和vmlinux.bin被简单的合并到一块儿.其中vmlinux.bin在头部,重定位信息在尾部.输出为vmlinux.bin.all
D>用gzip命令把vmlinux.bin.all压缩为vmlinux.bin.gz
E>vmlinux.src和vmlinux.bin.gz被链接为piggy.o.
F>head_32.o和misc_32.o,piggy.o链接为vmlinux,格式为elf-i386.
依照创建文件的时间顺序排列位于x86/boot下的那些cmd文件得到
他们的顺序和编译时输出的顺序一样.分别打开他们会发现bzImage生成的后续步骤.
G>boot/.vmlinux.bin.cmd使用objcopy把新生成的这个vmlinux拷贝为二进制文件boot/vmlinux.bin
H>boot/.setup.elf.cmd生成elf文件setup.elf于当前目录
I>boot/.setup.bin.cmd把setup.elf 用objcopy命令拷贝为二进制文件setup.bin于当前目录.
J>cmd_arch/x86/boot/bzImage := arch/x86/boot/tools/build -b arch/x86/boot/setup.bin arch/x86/boot/vmlinux.bin CURRENT > arch/x86/boot/bzImage
上面的命令负责生成boot目录下的bzImage.显然他使用boot/tools/build把boot目录下的vmlinux.bin和setup.bin合成为bzImage
这样bzImage就生成了!!
从上面的生成过程可以了解到bzImage由两部分组成:vmlinux代码段和跟特定体系结构机器启动紧密相关的setup代码段.
Vmlinux包含了可以重定位和自解压的代码.setup 则包含了arch/x86/boot目录下的很多代码,可以断定她和启动的关系非常大.
通过以上的分析bzImage 的代码结构也就一目了然了.
linux 的启动过程分析
一:BIOS启动阶段
Cpu上电时他的指令寄存器cs:ip总是指向一个固定的地址,这个地址保存在主板上的ROM里.这条指令是cpu执行的第一条指令的地址.x86启动之后进入到实模式下,他的地址线只有20位,因此可以寻址1MB的地址空间,段寄存器是16位.第一条指令的地址是0xffff0000.BIOS加载之后会按照自己的设置查找活动设备的可引导记录.以grub引导硬盘为例.grub在安装时会把自己的一部分写入到MBR中,MBR512字节.BIOS会把这块的内容加载到物理地址为0x7c00的地方.然后就到这个地方来执行.这里就是grub的代码.grub大于512B所以分为stage1和stage2.其中stage1放入MBR中,负责载入stage2的内容.stage2的代码会根据配置文件列出操作系统供选择,如果选择了linux则他就把bzImage载入到内存中.这之后grub转到linux的实模式代码setup的入口点_start.
这时内存布局如下图所示:
Setup的入口点_start可以在arch/x86/boot/setup.ld里找到而_start的代码则定义在boot/header.S里
Header.S最后调转到C语言的/boot/main.c里运行
main.c里的main函数会把header.S收集到的参数拷贝一份到文件内的结构体boot_params里.这个结构体定义在include/asm-x86/bootparam.h中.
在boot_params里有个结构体很特别setup_header他里面的数据几乎就是header.S 的.header.S收集到的很多信息都被main函数调用的copy_boot_params拷贝到本文件定义的boot_params里的结构体setup_header里了.grub可以向内核传递参数,grub会把参数放在hdr的cmd_line_ptr里等待copy_boot_params去复制.
Main的最后调用boot/pm.c中go_to_protected_mode函数进入保护模式.
这个函数中realmode_switch_hook运行保存在hdr中realmode_swtch钩子函数,关闭中断.最后setup_gdt在全局描述符表中建立临时的数据段和代码段供保护模式调用.最后跳转到一段汇编代码,两个参数第一个是x86保护模式的入口地址,在boot_params中有保存.第二个参数是传给内核的参数ds()<<4是对地址的修正.
保护模式的启动
从实模式调转之后来到了保护模式的代码,首先应该对代码进行解压缩.解压缩的代码是boot/compressed/head_32.S他把保护模式的内核部分解压缩之后调转到x86/kernel/head_32.S.从head_32.S的注释可以知道他做了以下几件事情:初始化段选择子,加载全局描述符表;把BSS区域初始化为0;拷贝传递给内核的参数;初始化中断;开启分页机制;初始化各个寄存器;这样保护模式的环境就基本建立了.
内核接着运行init/main.c里的init_kernel函数
这个函数又调用了很多其他的函数完成内核的初始化.打印内核版本信息,根据参数收集硬件信息复制参数,调度模块初始化,内存管理初始化,中断初始化,软中断初始化,时钟和控制台的初始化等等.
他的最后调用了rest_init这个函数.rest_init调用kernel_init产生了著名的1号进程init.调用关系如下:
rest_init->kernel_init->init_post->产生init进程
到这里linux就启动成功了.
展开阅读全文