这次实验主要是修改linux V0.11版本的引导程序的代码,使操作系统在启动时能再屏幕上打印一个你自己规定的字符串。因为这次实验仅仅修改了OS的引导部分(bootsect.s),我们只需要让引导部分能正常工作就可以验证你的字符串有没有正常输出了,至于后面的System模块我们不需要让它正常加载,所以,我们还要修改build.c文件,让它执行时不加载System模块,这是本次实验的要点。
赵烔博士的《Linux 内核完全注释》一书第六章中对Linux V0.11操作系统的引导过程有详细的描述,对本次实验有很大帮助。
首先要修改bootsect.s,以达到OS引导时能再屏幕上输出你指定的字符串,样例代码如下:
! Print some inane message mov ah,#0x03 ! read cursor pos xor bh,bh int 0x10 mov cx,#49 !在这里要将原来的“24”改为“24+你指定的字符串的字符数”,我指定了25个字符串,故改为29 mov bx,#0x0007 ! page 0, attribute 7 (normal) mov bp,#msg1 mov ax,#0x1301 ! write string, move cursor int 0x10 ! ok, we've written the message, now
然后在这里(bootsect.s里)加上你的字符串,如下所示:
msg1: .byte 13,10 .ascii "Loading system ..." .byte 13,10,13,10 .ascii "Liushuai'OS is booting..." !这里是你指定的在引导时在屏幕上显示的字符串,13和10好像是回车和空格
然后,如上所述,修改build.c,让其不引导System模块,代码如下:
if (!strcmp("none",argv[3]))//当第三个参数是none时 { fprintf(stderr,"System block have not been wrriten!\n");//提醒用户系统模块并未被写入 return 0;//直接返回 } if ((id=open(argv[3],O_RDONLY,0))<0) die("Unable to open 'system'"); // if (read(id,buf,GCC_HEADER) != GCC_HEADER) // die("Unable to read header of 'system'"); // if (((long *) buf)[5] != 0) // die("Non-GCC header of 'system'"); for (i=0 ; (c=read(id,buf,sizeof buf))>0 ; i+=c ) if (write(1,buf,c)!=c) die("Write call failed"); close(id); fprintf(stderr,"System is %d bytes.\n",i); if (i > SYS_SIZE*16) die("System is too big"); return(0);
原来,build.c从命令行参数得到bootsect、setup和system内核的文件名,将三者做简单的整理后一起写入Image。其中system是第三个参数(argv[3])。当“make all”或者“makeall”的时候,这个参数传过来的是正确的文件名,build.c会打开它,将内容写入Image。而“make BootImage”时,传过来的是字符串"none"。所以,改造build.c的思路就是当argv[3]是"none"的时候,只写bootsect和setup,忽略所有与system有关的工作,或者在该写system的位置都写上“0”,这是基本思想。
修改工作主要集中在build.c的尾部,本文中样例代码的改法只是改法中的一种。
此外,还要对setup.s做简单的删除,删除与System模块有关的部分,否则会出现加载虚拟机时虚拟机里的Linux0.11总是重启的现象,详见实验指导书。
最后是实验的报告部分,笔者的报告仅供参考。
附:
/*1.有时,继承传统意味着别手蹩脚。x86计算机为了向下兼容,导致启动过程比较复杂。请找出x86计算机启动过程中,被硬件强制,软件必须遵守的两个“多此一举”的步骤(多找几个也无妨),说说它们为什么多此一举,并设计更简洁的替代方案。 答:1).BIOS初始化时,会在物理内存开始处放置大小位1KB的中断向量表,供BIOS的中断使用。这就强制了操作系统的引导程序在加载操作系统的主模块时如果要利用BIOS中断获取一些信息,则主模块的加载位置不能将BIOS的向量表覆盖掉,而操作系统的主模块一般运行时要在内存的开始处(这样主模块中的代码地址也就等于实际的物理地址,便于操作),所以操作系统的引导程序会先将主模块(如Linux0.11中的System模块)读到内存中不与BIOS中断向量表冲突的位置,之后在将主模块移动到内存起始处,将BIOS的中断向量表覆盖掉,这是“多此一举”的。 解决方案是BIOS初始化时将BIOS的中断向量表放到内存中其他实模式下BIOS可以访问到的内存处,这样操作系统引导程序就可以直接将操作系统的主模块读到内存的起始处了。 2)PC机加电后,执行BIOS中的代码时,由于BIOS可访问的内存有限,则操作系统最开始的引导程序(如Linux0.11中的bootsect模块)会被读到内存绝对地址0x7C00处开始执行。执行时它会自己把自己移到内存中相对靠后的位置,以便加载系统主模块。这步是“多此一举”的。 解决方案是在保证可靠性的前提下尽量扩大BIOS的可访问的内存的范围,免去这不必要的移动。 2.操作系统的引导程序都完成哪些功能?你知道几个操作系统引导程序?分别是什么? 答:操作系统的引导程序一般完成识别主机的某些特性,确保内核被正确加载;加载内核到内存;将系统控制权交给内核;通知内核所需要的根文件系统的位置;确定根文件的设备号;将CPU由实模式切换为保护模式等功能。我知道的操作系统的引导程序有Windows NT 5.X 和Windows NT 6.X */