操作系统并不是在功能上给予用户的支持,这种支持是体现在机制上。也就是说,单纯的操作系统,用户拿它什么都做不了,用户需要的是某种功能。而操作系统仅仅是个提供支持的平台。
虽然我们是模仿linux来写一个黑屏白字的系统,但如果没有windows的话,估计当今这个世界将会失去70%以上的光芒。由于有了操作系统的支持,我们可以安装一些软件,也就是应用程序,比如安装了QQ或一些其它的即时通讯工具,这样我们就能够给同其他人聊天。
所以,操作系统并不能直接帮大家做什么,但大家想做什么的时候,操作系统能提供最大限度的支持。
任何程序都需要被载入到内存后才能运行,这是cpu等其它硬件的运行机制决定的,我们若在该硬件系统上运行程序,不得不遵守这样那样的约束。应用程序是独立于操作系统的,它不会像操作系统那样,含着金钥匙,一出生就直接在内存中。它们通常是位于磁盘等外存设备中,在使用时,需要从外存中将其调入到内存后才行。
如何去加载用户程序呢?
操作系统是程序、是软件,用户程序也是软件,用一个程序去调用另一个程序一点难度都没有,最最简单的办法,就是用jmp或call指令。我们的bios就是这样调用mbr,我们的mbr就是这样调用loader的。但大家还记得不,bios调用mbr,mbr的地址是0x7c00,mbr调用loader,loader的地址是0x900。这两个地址是写死的,也就是说,我们目前的方法是很不灵活的,调用方需要提前和被调用方约定调用地址。
有没有一种灵活的方法让程序的加载地址不那么固定呢?肯定有,不过突然想起周一要汇报工作,今晚加班,下节再说。
接上节,有没有一种灵活的方法让程序的加载地址不那么固定呢?
显然是有的,由于每个程序是单独存在的,所以程序的入口地址信息需要与程序绑定,最简单的办法就是在程序文件中专门腾出个空间来写入这些程序的入口地址,主调程序在该程序文件的相应空间中将该程序的入口信息读出来,将其加载到相应的入口地址,跳转过去就行了。当然不仅仅只写入程序入口地址,能写的东西很多,比如为了给程序分配内存,至少还得需要知道程序的尺寸大小。但在哪里写入程序的入口地址呢?这便是文件头的由来,在程序文件的开头部分记载这类信息,而程序文件中除文件头外其余的部分则是之前的程序体。这样一来,原先的纯二进制可执行文件加上新的文件头,就形成了一种文件格式。不仅文件是这样,很多其它传输协议也是采用文件头header+文件体body的形式,如邮件传输协议和http传输协议。在现实生活中也有这样的例子,比如咱们坐火车的时候,按理说,只要火车停在能让咱们看到的地方,咱们就能直接上火车了。但现实中不可能让所有火车摆在咱们面前,所以我们在乘坐火车时,都是进站后先要查看大屏幕上的列车时刻表,从中找到在哪个候车室等候上车。其中,列车时刻表就相当于文件头,我们从中找到上火车的入口,而火车则相当于文件体。
在程序中,程序头(也就是文件头)是用来描述程序的布局等信息,它属于信息的信息,也就是元数据。包含程序头的程序文件示意如图:
由于程序文件中包含了程序头,好处是程序的入口地址等信息不需要写死,调用方中的调用代码可以变得通用,根据实际情况加载便可。但不好的地方是,这些元信息不是代码,故不应该将其放在cpu上“执行”,所以程序就不再是纯粹的二进制可执行文件了,不像之前咱们用nasm默认编译的可执行文件(里面全是程序本身的指令和数据)那样纯粹。所以,将这种具有程序头格式的程序文件从外存读入到内存后,从该程序文件的程序头中读出入口地址,需要直接跳进入口地址执行,跨过程序头才行。