学过C语言的人都知道,用C语言设计的程序都有一个main函数,而且是从main函数开始执行的。linux0.11的代码是用C语言编写的。奇怪的是,为什么在操作系统启动时先执行的是三个由汇编语言写成的程序,然后才开始执行main函数;为什么不是像我们熟知的C语言程序那样,从main函数开始执行呢?
通常,我们用C语言编写的程序都是用户应用程序。这类程序的执行有一个很重要的特征,就是必须在操作系统的平台上执行,也就是说,要由操作系统为应用程序创建进程,并把应用程序的可执行代码从硬盘加载到内存。现在我们讨论的是操作系统,不是普通的应用程序,这样就出现了一个问题:应用程序是由操作系统加载的,操作系统该由谁加载呢?
从前面的只是中我们知道,加载操作系统的时候,计算机刚刚加电,只有BIOS程序在运行,而且此时的计算机处在16位实模式状态,通过BIOS程序自身的代码形成的16位的中断向量表及相关的16位的中断服务程序,将操作系统在软盘上的第一扇区(512字节)的代码加载到内存,BIOS能主动操作的内容也就到此为止了。准确的说,这是一个约定。对于第一扇区代码的加载,不论是什么操作系统都是一样的;从第二扇区开始,就要由第一扇区中的代码来完成后续的代码加载工作。
当加载工作完成后,好像仍然没有立即执行main函数,而是打开A20,打开pe、pg,建立IDT、GDT......然后才开始执行main函数,这是什么道理?
原因是,Linux0.11是一个32位的实时多任务的现代操作系统,main函数肯定要执行的事32位的代码。编译操作系统代码时,是由16位和32位不同的编译选项的。如果选择了16位,C语言编译出来的代码是16位模式的,结果可能是一个int型变量,只有2字节,而不是32位的4字节......这不是Linux0.11想要的。Linux0.11要的是32位的编译结果。只有这样才能成为32位的操作系统代码。这样的代码才能用到32位总线(打开A20后的总线),才能用到保护模式和分页,才能成为32位的实时多任务的现代操作系统。
开机时的16位实模式与main函数执行需要的32位保护模式之间有很大的差距,这个差距谁来填补?head.s做的就是这项工作。这期间,head程序打开A20,打开pe、pg。废弃旧的、16位的中断响应机制,建立新的32位的IDT......这些工作都做完了,计算机已经处在32位的保护模式状态了,调用32位main函数的一切条件已经准备完毕,这时顺理成章地调用main函数。后面的操作系统就可以用32位编译的main函数完成。