Jack:hi,淫龙,在Linux内核的源代码里,有几段汇编代码,那几段代码是负责Linux内核引导的。
我:是的。早期的Linux内核引导代码只有bootsect.s、setup.s、head.s这3个文件,这三个文件都是Linus在1991年左右亲手写的。后来的代码虽然进行了加固,但是原型还是这几个。
Jack:我想弄清楚。这几个汇编代码做了些什么事情。
我:我不能在对话中一句代码一句代码地给你进行注释,这是不现实的。我希望能把你最困惑的知识点解释清楚,起到画龙点睛的作用,而不是手把手地教你它是什么。
Jack:那我提一个最困惑的问题吧。Linux的内核是如何开始执行main()函数的?(在1.0之后?的代码中,所有的Linux入口函数已经改名为start_kernel())。
我:我给你看一段head.s的代码吧。
head.s line 135-141
after_page_tables:
pushl $0 # These are the parameters to main :-)
pushl $0
pushl $0
pushl $L6 # return address for main, if it decides to.
pushl $_main # 把main函数的地址压入栈内,等待出栈,指令跳转
jmp setup_paging # 跳转到setup_paging标签
head.s line 210-218
1: stosl /* fill pages backwards - more efficient :-) */
subl $0x1000,%eax
jge 1b
xorl %eax,%eax /* pg_dir is at 0x0000 */
movl %eax,%cr3 /* cr3 - page directory start */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* set paging (PG) bit */
ret /* this also flushes prefetch-queue */#弹栈,调用_main
关键就在这里:入栈函数地址,ret返回顺便调用_main。
Jack:明白了。原来是这样。我再问一个问题。Linux的内核的入口函数为什么可以是start_kernel()——C语言的入口地址不是main()吗?
我:这个问题你可以尝试从汇编的角度来思考。C语言的入口地址不是main()吗?——这个是不一定的。仅仅从C语言的角度来思考,这话是对的。如果从汇编的角度来思考,这话就不对了。
Jack:从汇编的角度来思考C语言?我操。
我:你用gcc -S选项编译一个source file,会生成一个汇编源代码文件。之后才会生成二进制文件。C只是一层皮,真正的骨在于汇编。
Jack:你的意思是,C其实是需要翻译成汇编的,从汇编的层面来思考代码才能把握本质的东西。
我:great。你明白了。