重庆邮电大学重庆邮电大学法学院黄永洪15825929935inside_pro@163.comLinux引导启动重庆邮电大学总体介绍PC电源打开后,x86结构的CPU将自动进入实模式,并从地址0xFFFF0开始自动执行程序代码,该地址通常是ROM-BIOS中的地址。BIOS执行系统自检,并在物理地址0处初始化中断向量。然后将启动设备的第一个扇区(512字节)读入内存绝对地址0x7c00(31KB处),并跳转到此处继续执行。该512字节的程序即是bootsect.S。Bootsect.S执行时,会把自动移动到内存绝对地址0x90000(576KB处),并把启动盘的后2KB代码,也就是setup.S读入到内存0x90200,正好紧接着bootsect.S代码。再将内核system模块读入到0x10000(64KB处)。加电后的程序执行顺序如后面的图所示。由于当时的system模块长度不会超过0x80000(512KB),所以从0x10000开始的system模块不会覆盖0x90000开始的bootsect和setup。后面setup会将system再移动到内存绝对地址0处。不直接在bootsect中将system读入到内存绝对地址0而是在setup中移动system模块到绝对地址0,其原因主要是setup开始部分的代码需要利用BIOS中断调用功能获取机器配置参数(如显卡模式、硬盘参数表等)。BIOS初始化时会在绝对地址0放置一个大小为0x400字节(1KB)的中断向量,使用完BIOS中断调用后才能将该区域覆盖掉。重庆邮电大学系统加电开始程序运行顺序重庆邮电大学启动时内核在内存中的变化重庆邮电大学bootsect.Sbootsect.S重庆邮电大学Bootsect各代码段位置#includelinux/config.hSYSSIZE=DEF_SYSSIZE!SYSSIZE为0x3000,是要加载的系统模块长度,单位是节(16字节),也就是0x30000字节,即192KB。……SETUPLEN=4BOOTSEG=0x07c0INITSEG=DEF_INITSEG!0x9000本代码要移动到的目标段SETUPSEG=DEF_SETUPSEG!0x9020setup代码段SYSSEG=DEF_SYSSEG!0x1000system代码段ENDSEG=SYSSEG+SYSSIZE重庆邮电大学Bootsec移动到INITSEGentrystartstart:movax,#BOOTSEG!0x7c0,注意是段寄存器值,不是0x7c00;movds,axmovax,#INITSEG!0x9000moves,axmovcx,#256subsi,sisubdi,direp!重复前缀指令,递减cx的值直到为0;movwjmpigo,INITSEG!段间跳转指令(JumpIntersegment)!注意实模式寻址方式和寻址范围重庆邮电大学对各段寄存器和栈指令赋值go:movax,cs!此时cs代码段寄存器为0x9000movdx,#0xfef4!0xfef4=0xff00-12(12为十六进制c)movds,axmoves,axpushaxmovss,ax!ss=0x9000movsp,dx!sp要大于0x200+0x200*4+堆栈大小!此时ds=es=ss=cs=INITSEG(0x9000)!BIOS加载引导扇区后,ss=0x00,sp=0xfffe!fs=0,gs=参数表所在位置重庆邮电大学复制并修改软驱参数表push#0popfsmovbx,#0x78!BIOS设置的中断0x1E的中断向量值是软驱参数表地址,位于内存0x1E*4=0x78处segfslgssi,(bx)!gs:si就为参数表源地址了movdi,dx!dx前面已经赋值0xfef4=0xff00-12movcx,#6!copy12bytescldrepseggs!复制12字节到0x9000:0xfef4处,ES:DImovw!es在前面已赋值为0x9000,注意用gs作了段寄存器movdi,dx!es:di指向新表,偏移4处为最大扇区数movb4(di),*18!每磁道最大扇区数修改为18segfsmov(bx),di!中断向量0x1E指向新参数表,注意es和di都要保存segfsmov2(bx),esxorah,ah!复位软驱控制器,让其采用新参数;xordl,dlint0x13重庆邮电大学读入setupload_setup:xordx,dx!dh磁头号0,dl驱动器号0;movcx,#0x0002!ch磁道柱面号低8位,cl开始扇区2(0-5位),磁道柱面号高2位(6-7位)movbx,#0x0200!es:bx指向缓冲区,512处,es已赋值。如果出错则CF标志位置位,ah是出错码;movax,#0x0200+SETUPLEN!ah=0x02表示读磁盘扇区到内存,al=需要读入的扇区数量;int0x13!Int0x13读扇区中断调用jncok_load_setup!ok–continuepushax!dumperrorcodecallprint_nlmovbp,spcallprint_hexpopaxxordl,dl!resetFDCxorah,ahint0x13jload_setupok_load_setup:重庆邮电大学获取每磁道扇区数到sectorsok_load_setup:xordl,dlmovah,#0x08!AH=0x08,dl为驱动器号int0x13xorch,ch!ch=磁道柱面号低8位,cl=每磁道最大扇区数(0-5位),磁道柱面号高2位(6-7位)segcsmovsectors,cx!ch=0,dh=最大磁头数,dl=驱动器数量,对软盘来说(dl=0),最大磁道号不会超过256,ch足够表示,因此cl的6-7位为0,cx即是cl,每磁道扇区数;movax,#INITSEGmoves,ax重庆邮电大学显示信息loading+回车+换行movah,#0x03!readcursorposxorbh,bhint0x10movcx,#9movbx,#0x0007!page0,attribute7(normal)movbp,#msg1movax,#0x1301!writestring,movecursorint0x10BIOS中断0x10功能号ah=0x03,读光标位置输入:bh=页号返回:ch=扫描开始线,cl=扫描结束线;dh=行号,dl=列号;BIOS中断0x10功能号ah=0x13,显示字符串;输入:al=放置光标的方式及规定属性。0x01表示使用bl中的属性值,光标停在字符串结尾,es:bp指向要显示的字符串起始位置,cx=要显示的字符数,bh=显示页面号,bl=字符属性,dh=行号,dl=列号。重庆邮电大学加载system模块到SYSSEGmovax,#SYSSEGmoves,ax!segmentof0x010000callread_itcallkill_motorcallprint_nl重庆邮电大学根文件系统设备号root_devsegcsmovax,root_dev!508,509字节处存放根设备号orax,axjneroot_definedsegcsmovbx,sectorsmovax,#0x0208!/dev/ps0-1.2Mbcmpbx,#15jeroot_definedmovax,#0x021c!/dev/PS0-1.44Mbcmpbx,#18jeroot_definedundef_root:jmpundef_rootroot_defined:segcsmovroot_dev,ax检查根文件系统设备是否定义,如果已经指定了根设备(ax!=0),就直接使用给定的设备。否则就需要根据BIOS报告的每磁道扇区数来确定到底使用/dev/PS0(2,28),还是/dev/at0(2,8)。Linux中软驱的主设备号是2次设备号=type*4+nr,nr为0-3分别对应ABCD,type是软驱的类型(2-1.2MB或7-1.44MB)。7*4=28,/dev/PS0指1.44MB的A驱,设备号0x021c;同理/dev/at0指1.2MB的A驱。Makefile里,也就是系统默认是dev/hd6,即0x0306,指的是第2个硬盘的第1个分区。重庆邮电大学跳转到setupseg执行jmpi0,SETUPSEG程序加载完毕,现在从bootsect跳转到setup程序中去,使用段间跳转指令jmpi,跳转到0x9020:0000(setup程序开始处)。还是要注意实模式下的寻址方式,寻址空间是20位,即1M。……………….org506swap_dev:.wordSWAP_DEVroot_dev:.wordROOT_DEVboot_flag:.word0xAA55!引导盘具有有效引导扇区的标志。重庆邮电大学setup.Ssetup.S重庆邮电大学Setup总体功能setup.s是一个操作系统加载程序,它的主要作用是利用ROMBIOS中断读取机器系统数据,并将这些数据保存到0x90000开始的位置(覆盖掉了bootsect程序所在的地方),所取得的参数和保留的内存位置如下表所示。这些参数将被内核中相关程序使用,例如字符设备驱动程序console.c和tty-io.c程序等。然后setup程序将system模块从0x10000-0x8ffff(当时认为内核系统模块system的长度不会超过此值:512KB)整块向下移动到内存绝对地址0x00000处。接下来加载中断描述符表寄存器(idtr)和全局描述符表寄存器(gdtr),开启A20地址线,重新设置两个中断控制芯片8259A,将硬件中断号重新设置为0x20一0x2f。最后设置CPU的控制寄存器CR0(也称机器状态字),从而进入32位保护模式运行,并跳转到位于system模块最前面部分的head.s程序继续运行。为了能让head.s在32位保护模式下运行,在本程序中临时设置了中断描述符表(IDT)和全局描述符表(GDT),并在GDT中设置了当前内核代码段的描述符和数据段的描述符。后面的head.s程序中会根据内核的需要重新设置这些描述符表。重庆邮电大学GDT与LDT段描述符存放在描述符表中。描述符表其实就是内存中描述符项的一个数组。描述符表有两类:全局描述符表(GDT)和局部描述符表(LDT)。处理器是通过使用GDTR和LDTR寄存器来定位GDT表和当前的LDT表。这两个寄存器以线性地址的方式保存了描述符表的基地址和表的长度。指令lgdt和sgdt用于访问GDTR寄存器;指令lldt和sldt用于访问LDTR寄存器。lgdt使用内存中一个6字节操作数来加载GDTR寄存器。头两个字节代表描述符表的长度,后4个字节是描述符表的基地址。然而请注意,访问LDTR寄存器的指令lldt所使用的操作数却是一个2字节的操作数,表示全局描述符表GDT中一个描述符项的选择符。该选择符所对应的GDT表中的描述符项应该对应一个局部描述符表。重庆邮电大学内核代码段与数据段描述符重庆邮电大学内核代码段与数据段gdt:.word0,0,0,0.word0x07FF.word0x0000.word0x9A00.word0x00C0.word0x07FF.word0x0000.word0x9200.word0x00C0代码段描述符的值是0x00C09A00000007FF,表示代码段的限长是8MB(=(0x7FF+1)*4KB,这里加1是因为限长值是从0开始算起的),段在线性地址空间中的基址是0,段类型值0x9A表示该段存在于内存中、段的特权级别为0、段类型是可读可执行的代码段,段代码是32位的并且段的颗粒度是4KB。数据段描述符的值是0x00C09200000007FF,表示数据段的限长是8MB,段在线性地址空间中的基址是0,段类型值0x92表示该段存在于内存中、段的特权级别为0、段类型是可读可写的数据段,段代码是32位的并且