加载内核
加载内核
_start函数
一个没有包含头文件的c程序
1 | int main(){ |
利用gcc -c -o kernel/main.o kernel/main.c
编译获得mian.o文件,这里的main文件实际上是一个半成品,并不是直接打开就能运行的可执行文件,使用命令file main.o
也可以看出这是个重定向文件,此时变量、地址和函数名等都还未确定
ld main.o -Ttext 0xc0001500 -o kernel.bin
将文件链接,组成真正的可执行文件,但是这里报了个warning缺少_start,默认地址为00000000c0001500
一个程序需要一个入口地址来表示程序从哪里开始执行,这里的入口程序并不是指平时常见的main函数,也不是程序中第一个字节的起始地址,在一个文件的开头往往会定义相当多的数据(就像之前的loader.S),而是一个名为**_start**的函数,编译器默认将 _start函数所在的地址作为入口地址,在上面中就缺少 _start函数,将main函数名改为 _start即可完成编译
1 | int _start(){ |
在平时利用gcc编译时,我们并没有这么麻烦,直接使用gcc -o
的指令就能成功编译出对应的可执行文件。直接编译出的文件和经过链接的文件有着些许不同
这里分别利用两种方法将相同的源程序编译为kernel.bin和test,使用nm
命令查看其中的符号
kernel.bin:
test:这里只截了一部分
从大小上也可看出两者不同,通过手动编译、链接所生成的可执行文件比直接编译生成的可执行文件也要小得多,且自动编译的文件中自动包含了_start函数
头文件
操作系统也是程序的一种,它经过mbr和loader加载到内存之中才能够执行对应的任务。其他的一般程序也一样,必须先加载到内存之中才能执行其中的指令,这些程序的mbr和loader就是操作系统,是操作系统将它们井然有序的调入到内存之中运行,但是想要运行一个程序只有知道对应程序的入口地址才能调用。在操作系统之中直接写死了程序的入口地址,但是一般的程序是动态的,为了方便加载它们,需要在程序文件之中专门腾出个空间来写入这些程序的入口地址,主调程序在该程序文件的相应空间中将该程序的入口信息读出来,将其加载到相应的入口地址,跳转过去就行了,当然不仅仅只写入程序入口地址,能写的东西很多,比如为了给程序分配内存,至少还得需要知道程序的尺寸大小。但在哪里写入程序的入口地址呢?这便是文件头的由来,在程序文件的开头部分记载这类信息,而程序文件中除文件头外其余的部分则是之前的程序体。
因为文件头之中存放的并不是代码,所以程序不再是纯粹的可执行文件,所以需要从头文件中读出程序的入口地址,进而直接跳到程序入口地址所在位置执行即可。程序头可以自定义,只要我们按照自己定义的格式去解析就行。