再把上节代码贴出来,
1 //int main(void) {
2 int _start(void) {
3 while(1);
4 return 0;
5 }
有没有同学想过,这里写一个_start函数,让其调用main函数如何?其实这是可以的,main函数并不是第一个函数,它实际上也是被别人调用的,不过这是编译器背后的策略啦,好奇心大的同学自己尝试下吧。
虽然把函数名改成_start可以解决问题,但我们习惯于main函数做为主函数,不习惯函数用_start,于是用了-e来指定起始的函数名为main,所以代码才链接正常。
也许有同学想过,哎?我平时写的程序也没有_start啊,直接用gcc编译后就能运行,没出过问题啊。是啊,确实如您所说,由于我也没深入研究过,但咱们通过比较的方式,让您自己悟出这里面的秘密。还是用上述代码为例,gcc –o /tmp/test.bin kernel/main.c编译链接,由于未加-c参数,生成的test.bin不再是目标文件而是可执行文件。然后再用先编译成目标文件再链接成可执行文件的方式,对比这两个文件的区别。见图
您看,test.bin是gcc直接生成的可执行文件,它的大小是4586字节。而kernel.bin是经过手动编译、链接这两个步骤完成的,其文件大小是1777字节。这两个文件的体积可是差了几乎2倍呢。再看看这两个文件中的符号信息,还是用nm命令,如图
test.bin中共有34个符号(wc –l命令是用来统计输出的行数,一个符号占用一行,故34个符号),由于输出太长了,我们只截取了关键的部分,不过您看那些frame_dummy、data_start等,这并不是咱们代码中存在的符号,这说明在编译器在编译过程中为咱们引用了别的代码,这就是c运行库的功劳,目的是在调用main函数前做初始化环境等工作。您看,用白色方框圈出来的_start,这就是默认的入口符号,链接器还是用到了它,它不是咱们提供的代码,依然是运行库提供的,这也说明main函数不是第一个执行的代码,它一定是被其它代码调用的,main函数在运行库代码初始化完环境后才被调用。
咱们继续看kernel.bin中的符号,一共就4行,尽管其中也包含了咱们不认识的符号,但毕竟少得多,我们的程序更短小精干,而且确实没有_start函数。这里添加了3个类型为A的符号,这表示它们的值是不变的。T表示是该符号是位于代码段中,更多符号的意义请参考man nm。
其实上述代码中要是换成汇编代码的话,就是个jmp $,其大小不过是2字节的机器码ebfe。除了编译器自动添加的代码外,一般情况下c语言编译出来的程序也比汇编语言生成的程序体积大。可见,人们常说的汇编语言比c语言快,并不是汇编语言本身有多快(它也要变成机器指令后才能上cpu运行),而是汇编语言对应的机器指令是一对一,简单直接可依赖,而c语言生成的机器指令是一对多,复杂间接略冗余。
好啦,关于内核的部分咱们就此先打住,其实说这话我有点不好意思,您也看到啦,内核代码中就一个死循环而已,我们的内核还没有开始。咱们的内核虽然离真正的内核差得十万八千里,但它目的是两个:
- 是为了演示加载内核,
- 是为了演示elf格式的文件解析。
后面我们将结合此简单至极的c程序来学习有关elf方面的知识。