// 主题:Linux0.12引导启动程序学习笔记(i386)
// 作者:[email protected]
// 版权:kevinjz原创
// 平台:80386
// 发布日期:2011-09-03
// 最后修改:2011-09-03
// 注意事项:欢迎转载,但不得在转载的时候擅自修改、删除文章的任何部分
//-------------------------------------------------------------------------------------------------
一、启动程序执行顺序
ROMBIOS->bootsect.S->setup.S->head.s->main.c,其中head.s以及main.c共同称为system模块。其中bootsect.S位于磁盘的第一扇区,setup.S位于随后的4个扇区,system模块则占据随后的约260扇区。
二、bootsect.S:磁盘引导块程序
该程序驻留在磁盘的第一扇区中。
完成功能及顺序:
1、bootsect.S将被BIOS程序加载到内存的0X7C00处开始执行;
2、bootsect.S将自己移动到0X90000处执行,通过movw指令,移动了512个字节;
3、将es,ds,ss地址都设置为代码段所在地址0x90000,并将堆栈设置在:0x90000:0xfef4(因为setup.S将要放置在0x90200处,且占据4个扇区几512*4个字节,所以SP至少要被安排在大于0x200+0x200*4+堆栈大小-12(参数表长度)的地方即至少大于0x90A00+堆栈大小-12处。此处SP设置为0x9ff00-12即0x9fef4,满足要求);
4、修改最大扇区数,方法是通过BIOS的0X1E中断将地址存于0x78处的原软驱参数(12字节)复制到0X90000:0XFEF4处,然后修改成最大18个扇区。最后再将新的软驱参数地址存入0x78(偏移)0x80(基地址);
5、移动setup.S代码至0X90200处。方法是利用BIOS中断0X13,将第二扇区的setup.S程序移至0X90200开始处,共读取四个扇区的数据,读取出错则重启驱动器重试,没有退路。
6、显示”Loading+回车+换行”共九个字节,方法是使用BIOS的0X10中断,功能好ah=0x03来读取光标位置,在使用功能号ah=0X13来显示字符串;
7、将system模块加载至0X10000处;
8、确定使用哪个根文件系统(根设备);
9、跳转至0X90200处执行setup.S程序;
三、setup.S程序
该程序是一个操作系统加载程序。其主要功能是利用BIOS中断来读取及其系统数据,并将这些数据保存至0X90000处,将会覆盖bootsect.S的程序,这些参数将被内核相关程序使用。(包括光标位置、扩展内存数、硬盘参数表、根设备号等数据)。然后移动system模块至0x00000处,并加载IDT以及GDT,并进入开起保护模式,接着执行system模块的head.s程序。
完成功能及顺序:
1、使用BIOS中断,获取及其的系统参数,方法都类似,并将参数数据保存至0X90000。其中读取显卡信息的程序比较复杂,需要了解很多品牌显卡的参数才能明白;
2、加载完系统参数后,不再需要BIOS中断功能,所以可以将system模块移动到0X00000处,会覆盖掉BIOS设置的中断向量表。Linux0.12的system模块不会超过512kb,所以此处不会覆盖掉0X90000处保存的系统参数;
3、使用lidt以及lgdt指令从IDTR寄存器以及GDTR寄存器加载IDT以及GDT的基地址。其中IDT基地址为0X00000,限长也是0,所以现在的IDT是一个空表。GTD则设置为限长为0x800(256个表项,每个表项8字节),基地址则位于0x90200+GDT表在本程序中的偏移(暂时不明)。GDT的内容则为第一个表项空,第二个表项偏移为0x08,内容为00c09a00000007ff,此段内核代码段;第二个表项的偏移同为0x08,内容为00c09200000007ff,此段为内核数据段;
4、开启A20地址线,以访问1M以上的内存。并需要对两个8259A进行编程,重新设置中断
5、置位CR0中的PE位,开起保护模式;
6、跳转至0X00000处执行system模块中的head.s程序;
四、 head.s程序
该程序位于system最前面的开始部分,从磁盘的第六个扇区开始放置。该程序目前位于内存的0地址处。
完成功能和顺序:
1、将所有段指向GDT中的第二段:内核数据段,然后设置堆栈段;
2、重新设置IDT。此处将IDT设置成256项,每一项都指向ignore_int中断门,之后执行lidt;
3、重新设置GDT,此处仅仅将限长从8M改为16M。并重新加载全局描述符寄存器;
4、测试A20地址线是否正确启用。方法是在0X000000处写一个值,然后在0X100000处读取,看是否是同一个值,如果是则就一直比较,进入死循环,表示A20未开启;
5、检测数字协处理器是否存在。方法是修改CR0,假设数字协处理器存在,并执行一条协处理器指令,如果出错则表示不存在。则置位PP仿真位,复位PP存在位;
6、将main()函数的参数以及地址压入堆栈,以便设置完页表后使用RET指令跳转入main函数;
7、设置页目录、4个页表,一一对应物理内存。就是将页表项都填好,内容是地址+7,从最后一项开始填,每填一项填入地址减去0x1000;
8、将页目录地址0X0000填入CR3。
9、置位PG位,开启页保护;
10、执行RET指令,弹出之前最后PUSH入栈的main()地址,并跳转执行。