跳转到主内容
思享编程网:思考分享,玩转编程世界!

Linux开机过程的分析(关于bootsect.S)

本文的目的,在于将linux kernel的boot部分做一个介绍,因为笔者觉得很少有这样的 文章来介绍一个操作系统最最开始的一步----把kernel本身载入至内存中,同时进行一些 机器相关(machine dependent)的初始化工作,由于linux刚好使用的是大家最熟悉的386, 486系列PC,所以在说明其程序流程时,也刚好可以对其相关的PC硬体架构做探讨,可以 说是一举两得。

不过,我必须假设读者对于汇编语言及PC最基础的架构,如寄存器,分段, 分页,中断服务等有大概的认识。

读者可在linux source code的linux/arch/i386/boot子目录下(本文以2.2.5版本为例)找到几个以.S作为副档名的组合语言档, 本文要说明的即是其中的bootsect.S及setup.S两个档案,及尽量简单地说明其所牵涉的 相关硬件部份。

bootsect.S这个程序是linux kernel的第一个程序,包括了linux自己的bootstrap程序,但是 在说明这个程序前,必须先说明一般IBM PC开机时的动作(此处的开机是指"打开PC的电源"):一般PC在电源打开时,是由内存中地址FFFF:0000开始执行(这个地址一定在ROMBIOS 中,ROMBIOS一般是在FE000h到FFFFFh中),而此处的内容则是一个jump指令,jump到另 一个位于ROMBIOS中的位置,开始执行一系列的动作,包括了检查RAM,keyboard,显示 器,软硬磁盘等等,这些动作是由系统测试码(system test code)来执行的,随着制作 BIOS厂商的不同而会有些许差异,但都是大同小异,读者可自行观察自家机器开机时, 屏幕上所显示的检查讯息。

紧接着系统测试码之后,控制权会转移给ROM中的启动程序(ROM bootstrap routine), 这个程序会将磁盘上的零道零扇区读入内存中(这就是一般所谓的bootsect,如果你曾 接触过电脑病毒,就大概听过它的大名),至于被读到内存的哪里呢?----绝对位置07C0 :0000(即07C00h处),这是IBM系列PC的特性。

而位在linux开机磁盘的bootsect上的正 是linux的bootsect程序,也就是说,bootsect是第一个被读入内存中并执行的程序。

现在,我们可以开始来看看到底bootsect做了什么。

第一步 首先,bootsect将它"自己"从被ROMBIOS载入的绝对地址0x7C00处搬到0x90000处, 然后利用一个jmpi(jumpindirectly)的指令,跳到新位置的jmpi的下一行去执行,关键 的汇编代码如下:! ld86 requires an entry symbol. This may as well be the usual one..globl_main_main:#if 0int3#endifmovax,#BOOTSEG//BOOTSEG=0x07C0movds,axmovax,#INITSEG//INITSEG=0x9000moves,axmovcx,#256// cx寄存器用作计数器subsi,sisubdi,dicld//CLD(CLear Direction flag)则是清方向标志位,也就是使DF的值为0,//在执行串操作时,使地址按递增的方式变化,这 样便于调整相关段的的当前指针。

//这条指令与STD(SeT Direction flag)的执行结果相反,即置DF的值为1。

rep//MOVSB(MOVe String Byte):即字符串传送指令,这条指令按字节传送数据。

//通过SI和DI这两个寄存器控制字符串的源地址和目标地址,比如DS:SI这段地址的N个字节//复制到ES:DI指向的地址,复制后DS:SI的内容保持不变。

而REP(REPeat)指令就是“重复”的意思,//术语叫做“重复前缀指令”,因为既然是传递字符串,则不可能一个字(节)一个字(节)地传送,//所以需要有一个寄存器来控制串长度。

这个寄存器就是CX,指令每次执行前都会//判断CX的值是否为0(为0结束重复,不为0,CX的值减1),以此来设定重复执行的次数。

//因此设置好CX的值之后就可以用REP MOVSB了。

movswjmpigo,INITSEG! ax and es already contain INITSEGjmpi go,INITSEGgo:...表示将跳到CS为0x9000,IP为offset"go"的位置(CS:IP=0x9000:offsetgo),其中 INITSEG=0x9000定义于程序开头的部分,而go这个label则恰好是下一行指令所在的位置。

第二步接着,将其它segment registers包括DS,ES,SS都指向0x9000这个位置,与CS看齐。

另外将SP及DX指向一任意位移地址(offset),这个地址等一下会用来存放磁盘参数表 (disk parameter table)。

提到磁盘参数表,就必须提到BIOS中断1Eh。

先简单地介绍一下BIOS的中断服务: 80x86将内存最低的256*4bytes保留给256个中断向量(每个interrupt vector大小为4bytes, 所以一共有256*4=1024bytes),而其中的第1Eh个向量指向"磁盘参数表",这个表会告诉 电脑如何去读取磁盘机,而我们所要做的事是搬移磁盘参数表到刚才所设定的任意地址。

接着,改变搬移来的参数表的参数,以符合我们的需要。

再将中断向量1Eh指向我们 所修改过的磁盘参数表,然后呼叫BIOSinterrupt的int13h(function0,即AH=0)重置磁 盘控制卡及磁盘驱动器,之后磁盘机就会照我们的意思动作了。

如果你曾trace过DOS的 kernel,你会发现,上述的动作在DOS中也有类似的对应流程。

现在让我们来看看关键的程序码:...push #0pop fsmov bx,#0x78.(使GS:SI=FS:BX,指向磁盘参数表,再将GS:SI所指地址的内容搬移6个word至ES:DI所指的地址)..此段程序是将FS:BX调整成0000:0078,接着再将GS:SI的内容设成与FS:BX相同,此 处0x78h即为int1Eh的起始位置(7*16+8=120,(1*16+14)*4=120)。

调整ES:DI为刚才所设 定的任意地址,从GS:SI搬移6个word(即12byte)到ES:DI所指的位置,显然磁盘参数表的 长度就是6个word,(不过事实上,磁盘参数表的确实长度是11个byte)。

关于磁盘参数表, 有兴趣的读者可自行参阅讲述BIOSinterruptservices的技术手册,会有详细的说明。

读者可以用debug自行观察自家机器上dos的磁盘参数表的起始位置(即int1Eh的内容)。

以下是笔者机器的情形(笔者使用的操作系统是msdos6.2):C:>debug-d0000:00000000:0000 8A101601F4067000-1600CB04F4067000......p.......p.0000:0010 F40670000301790E-43EB00F0EBEA00F0..p...y.C.......0000:0020 04108E340C118E34-5700CB046F00CB04...4...4W...o...0000:0030 8700CB0408079433-B700CB04F4067000.......3......p.0000:0040 0C01790E4DF800F0-41F800F0BA165F06..y.M...A....._.0000:0050 39E700F01B01790E-70118E341201790E9.....y.p..4..y.0000:0060 00E000F085175F06-6EFE00F0EE067000......_.n.....p.0000:0070 53FF00F0A4F000F0-220500003E4600C0S......."...>F..^^^^^^^^由上图中可知,在DOS中磁盘参数表的起始位置(int1Eh的内容)为0000:0522。

接着观 察dos中位置0000:0522开始的11个byte,也就是磁盘参数表的内容C:>debug-d0000:0520l100000:0520 4D53DF022502121B-FF54F60F08000000MS..%....T......^^^^^^^^^^^^^^^^^^^^^^此11byte即为磁盘参数表的内容(分别是byte00h到0Ah)在程序中我们所更动的是第五个byte(byte04h),改为18h(在上图例子中为12h),这 个byte的功能是定义磁轨上一个磁区的资料笔数。

关键的程序码如下:.movb 4(di),*18.第三步接着利用BIOS中断服务int13h的第0号功能,重置磁盘控制器,使得刚才的设定发挥 功能。

..xor ah,ahxor dl,dlint 0x13..第四步完成重置磁盘控制器之后,bootsect就从磁盘上读入紧邻着bootsect的setup程序, 也就是以后将会介绍的setup.S,此读入动作是利用BIOS中断服务int13h的第2号功能。

setup的image将会读入至程序所指定的内存绝对地址0x90200处,也就是在内存中紧邻着 bootsect所在的位置。

待setup的image读入内存后,利用BIOS中断服务int13h的第8号功 能读取目前磁盘机的参数。

第五步再来,就要读入真正linux的kernel了,也就是你可以在linux的根目录下看到的 vmlinuz。

在读入前,将会先呼叫BIOS中断服务int10h的第3号功能,读取游标位置,之 后再呼叫BIOS中断服务int10h的第13h号功能,在萤幕上输出字符串"Loading",这个字 符串在boot linux时都会首先被看到,相信大家应该觉得很眼熟吧。

linux的kernel将会被读入至内存绝对地址0x10000处,关键的程序码如下:..mov ax,#SYSSEGmov es,axcall read_itcall kill_motor..其中SYSSEG于程序开头时定义为0x1000,先将ES内容设为0x1000,接着在read_it这 个子程序,便以ES为目的地的节地址,将kernel读入内存中,至于read_it子程序的详细内 容笔者并不想一一介绍,不过聪明的读者们应该已经猜到,read_it一定又利用了BIOS int13h与磁盘有关的I/O中断服务了。

至于kill_motor子程序,它的功能在于停止软盘机的马达(各位聪明的读者会不会觉 得这个子程序的名称取得颇为传神呢?),其程序码如下:..kill_motor:push dxmov dx,#0x3f2xor al,aloutbpop dxret..首先利用DX指定要输出的port,而03f2这个port则是代表了软盘控制器(floppy disk controller)的所在,再利用outb将资料送出,而我们送出的资料,当然就是归零过的 AL了。

如此一来,软盘的马达就停止了。

第六步接下来做的事是检查root device,之后就仿照一开始的方法,利用indirect jump跳 到刚刚已读入的setup部份,程序码如下:..jmpi 0,SETUPSEG其中SETUPSEG已在先前定义为0x9020,所以CS:IP会设定为9020:0000,即跳到绝对 地址为0x90200,也就是setup的起点,而bootsect也大功告成了。

到此为止,内存的内容应该如下图所示:比较把大家所熟知的msdos与linux的开机部份做个粗浅的比较,msdos由位于磁盘上 bootsect的boot程序负责把io.sys载入内存中,而io.sys则负有把dos的kernel-- msdos.sys载入内存的重大责任。

而linux则是由位于bootsect的bootsect程序负责 把setup及linux的kernel载入内存中,再将控制权交给setup。

至于setup.S,就留到下一次再来讨论了。

bootsect.S的代码注释:! bootsect.s (c) 1991, 1992 Linus Torvalds 版权所有! Drew Eckhardt修改过! Bruce Evans (bde)修改过!! bootsect.s 被bios-启动子程序加载至0x7c00 (31k)处,并将自己! 移到了地址0x90000 (576k)处,并跳转至那里。

!! bde - 不能盲目地跳转,有些系统可能只有512k的低! 内存。

使用中断0x12来获得(系统的)最高内存、等。

!! 它然后使用BIOS中断将setup直接加载到自己的后面(0x90200)(576.5k),! 并将系统加载到地址0x10000处。

!! 注意! 目前的内核系统最大长度限制为(8*65536-4096)(508k)字节长,即使是在! 将来这也是没有问题的。

我想让它保持简单明了。

这样508k的最大内核长度应该! 是足够了,尤其是这里没有象minix中一样包含缓冲区高速缓冲(而且尤其是现在! 内核是压缩的 :-)!! 加载程序已经做的尽量地简单了,所以持续的读出错将导致死循环。

只能手工重启。

! 只要可能,通过一次取得整个磁道,加载过程可以做的很快的。

#include!! config.h中(即autoconf.h中)没有CONFIG_ROOT_RDONLY定义!!!?#include.textSETUPSECS = 4 ! 默认的setup程序扇区数(setup-sectors)的默认值;BOOTSEG = 0x7C0 ! bootsect的原始地址;INITSEG = DEF_INITSEG ! 将bootsect程序移到这个段处(0x9000) - 避开;SETUPSEG = DEF_SETUPSEG ! 设置程序(setup)从这里开始(0x9020);SYSSEG = DEF_SYSSEG ! 系统加载至0x1000(65536)(64k)段处;SYSSIZE = DEF_SYSSIZE ! 系统的大小(0x7F00): 要加载的16字节为一节的数;!! 以上4个DEF_参数定义在boot.h中:!! DEF_INITSEG 0x9000!! DEF_SYSSEG 0x1000!! DEF_SETUPSEG 0x9020!! DEF_SYSSIZE 0x7F00 (=32512=31.75k)*16=508k! ROOT_DEV & SWAP_DEV 现在是由"build"中编制的;ROOT_DEV = 0SWAP_DEV = 0#ifndef SVGA_MODE#define SVGA_MODE ASK_VGA#endif#ifndef RAMDISK#define RAMDISK 0#endif#ifndef CONFIG_ROOT_RDONLY#define CONFIG_ROOT_RDONLY 1#endif! ld86 需要一个入口标识符,这和通常的一样;.globl _main_main:#if 0int 3#endifmov ax,#BOOTSEG !! 将ds段寄存器置为0x7C0;mov ds,axmov ax,#INITSEG !! 将es段寄存器置为0x9000;mov es,axmov cx,#256 !! 将cx计数器置为256(要移动256个字, 512字节);sub si,si !! 源地址 ds:si=0x07C0:0x0000;sub di,di !! 目的地址es:di=0x9000:0x0000;cld !! 清方向标志;rep !! 将这段程序从0x7C0:0(31k)移至0x9000:0(576k)处;movsw !! 共256个字(512字节)(0x200长);jmpi go,INITSEG !! 间接跳转至移动后的本程序go处;! ax和es现在已经含有INITSEG的值(0x9000);go: mov di,#0x4000-12 ! 0x4000(16k)是>;=bootsect + setup 的长度 +! + 堆栈的长度 的任意的值;! 12 是磁盘参数块的大小 es:di=0x94000-12=592k-12;! bde - 将0xff00改成了0x4000以从0x6400处使用调试程序(bde)。

如果! 我们检测过最高内存的话就不用担心这事了,还有,我的BIOS可以被配置为将wini驱动表! 放在内存高端而不是放在向量表中。

老式的堆栈区可能会搞乱驱动表;mov ds,ax ! 置ds数据段为0x9000;mov ss,ax ! 置堆栈段为0x9000;mov sp,di ! 置堆栈指针INITSEG:0x4000-12处;! 上面执行重复操作(rep)以后,cx为0;mov fs,cx !! 置fs段寄存器=0;mov bx,#0x78 ! fs:bx是磁盘参数表的地址;push dsseg fslds si,(bx) ! ds:si是源地址;!! 将fs:bx地址所指的指针值放入ds:si中;mov cl,#6 ! 拷贝12个字节到0x9000:0x4000-12开始处;cldpush di !! 指针0x9000:0x4000-12处;repmovswpop di !! di仍指向0x9000:0x4000-12处(参数表开始处);pop si !! ds =>; si=INITSEG(=0X9000);movb 4(di),*36 ! 修正扇区计数值;seg fsmov (bx),di !! 修改fs:bx(0000:0x0078)处磁盘参数表的地址为0x9000:0x4000-12;seg fsmov 2(bx),es! 将setup程序所在的扇区(setup-sectors)直接加载到boot块的后面。

!! 0x90200开始处;! 注意,es已经设置好了。

! 同样经过rep循环后cx为0load_setup:xor ah,ah ! 复位软驱(FDC);xor dl,dlint 0x13xor dx,dx ! 驱动器0, 磁头0;mov cl,#0x02 ! 从扇区2开始,磁道0;mov bx,#0x0200 ! 置数据缓冲区地址=es:bx=0x9000:0x200;! 在INITSEG段中,即0x90200处;mov ah,#0x02 ! 要调用功能号2(读操作);mov al,setup_sects ! 要读入的扇区数SETUPSECS=4;! (假释所有数据都在磁头0、磁道0);int 0x13 ! 读操作;jnc ok_load_setup ! ok则继续;push ax ! 否则显示出错信息。

保存ah的值(功能号2);call print_nl !! 打印换行;mov bp,sp !! bp将作为调用print_hex的参数;call print_hex !! 打印bp所指的数据;pop axjmp load_setup !! 重试!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!INT 13 - DISK - READ SECTOR(S) INTO MEMORY!! AH = 02h!! AL = number of sectors to read (must be nonzero)!! CH = low eight bits of cylinder number!! CL = sector number 1-63 (bits 0-5)!! high two bits of cylinder (bits 6-7, hard disk only)!! DH = head number!! DL = drive number (bit 7 set for hard disk)!! ES:BX ->; data buffer!! Return: CF set on error!! if AH = 11h (corrected ECC error), AL = burst length!! CF clear if successful!! AH = status (see #00234)!! AL = number of sectors transferred (only valid if CF set for some!! BIOSes)!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ok_load_setup:! 取得磁盘驱动器参数,特别是每磁道扇区数(nr of sectors/track);#if 0! bde - Phoenix BIOS手册中提到功能0x08只对硬盘起作用。

! 但它对于我的一个BIOS(1987 Award)不起作用。

! 不检查错误码是致命的错误。

xor dl,dlmov ah,#0x08 ! AH=8用于取得驱动器参数;int 0x13xor ch,ch!!!!!!!!!!!!!!!!!!!!!!!!!!!!! INT 13 - DISK - GET DRIVE PARAMETERS (PC,XT286,CONV,PS,ESDI,SCSI)!! AH = 08h!! DL = drive (bit 7 set for hard disk)!!Return: CF set on error!! AH = status (07h) (see #00234)!! CF clear if successful!! AH = 00h!! AL = 00h on at least some BIOSes!! BL = drive type (AT/PS2 floppies only) (see #00242)!! CH = low eight bits of maximum cylinder number!! CL = maximum sector number (bits 5-0)!! high two bits of maximum cylinder number (bits 7-6)!! DH = maximum head number!! DL = number of drives!! ESI ->; drive parameter table (floppies only)!!!!!!!!!!!!!!!!!!!!!!!!!!!!#else! 好象没有BIOS调用可取得扇区数。

如果扇区36可以读就推测是36个扇区,! 如果扇区18可读就推测是18个扇区,如果扇区15可读就推测是15个扇区,! 否则推测是9. [36, 18, 15, 9]mov si,#disksizes ! ds:si->;要测试扇区数大小的表;probe_loop:lodsb !! ds:si所指的字节 =>;al, si=si+1;cbw ! 扩展为字(word);mov sectors, ax ! 第一个值是36,最后一个是9;cmp si,#disksizes+4jae got_sectors ! 如果所有测试都失败了,就试9;xchg ax,cx ! cx = 磁道和扇区(第一次是36=0x0024);xor dx,dx ! 驱动器0,磁头0;xor bl,bl !! 设置缓冲区es:bx = 0x9000:0x0a00(578.5k);mov bh,setup_sects !! setup_sects = 4 (共2k);inc bhshl bh,#1 ! setup后面的地址(es=cs);mov ax,#0x0201 ! 功能2(读),1个扇区;int 0x13jc probe_loop ! 如果不对,就试用下一个值;#endifgot_sectors:! 恢复esmov ax,#INITSEGmov es,ax ! es = 0x9000;! 打印一些无用的信息(换行后,显示Loading)mov ah,#0x03 ! 读光标位置;xor bh,bhint 0x10mov cx,#9mov bx,#0x0007 ! 页0,属性7 (normal);mov bp,#msg1mov ax,#0x1301 ! 写字符串,移动光标;int 0x10! ok, 我们已经显示出了信息,现在! 我们要加载系统了(到0x10000处)(64k处)mov ax,#SYSSEGmov es,ax ! es=0x01000的段;call read_it !! 读system,es为输入参数;call kill_motor !! 关闭驱动器马达;call print_nl !! 打印回车换行;! 这以后,我们来检查要使用哪个根设备(root-device)。

如果已指定了设备(!=0)! 则不做任何事而使用给定的设备。

否则的话,使用/dev/fd0H2880 (2,32)或/dev/PS0(2,28)! 或者是/dev/at0 (2,8)之一,这取决于我们假设我们知道的扇区数而定。

!! |__ ps0?? (x,y)--表示主、次设备号?

seg csmov ax,root_devor ax,axjne root_definedseg csmov bx,sectors !! sectors = 每磁道扇区数;mov ax,#0x0208 ! /dev/ps0 - 1.2Mb;cmp bx,#15je root_definedmov al,#0x1c ! /dev/PS0 - 1.44Mb !! 0x1C = 28;cmp bx,#18je root_definedmov al,0x20 ! /dev/fd0H2880 - 2.88Mb;cmp bx,#36je root_definedmov al,#0 ! /dev/fd0 - autodetect;root_defined:seg csmov root_dev,ax !! 其中保存由设备的主、次设备号;! 这以后(所有程序都加载了),我们就跳转至! 被直接加载到boot块后面的setup程序去:jmpi 0,SETUPSEG !! 跳转到0x9020:0000(setup程序的开始位置);! 这段程序将系统(system)加载到0x10000(64k)处,! 注意不要跨越64kb边界。

我们试图以最快的速度! 来加载,只要可能就整个磁道一起读入。

!! 输入(in): es - 开始地址段(通常是0x1000)!sread: .word 0 ! 当前磁道已读的扇区数;head: .word 0 ! 当前磁头;track: .word 0 ! 当前磁道;read_it:mov al,setup_sectsinc almov sread,al !! 当前sread=5;mov ax,es !! es=0x1000;test ax,#0x0fff !! (ax AND 0x0fff, if ax=0x1000 then zero-flag=1 );die: jne die ! es 必须在64kB的边界;xor bx,bx ! bx 是段内的开始地址;rp_read:#ifdef __BIG_KERNEL__#define CALL_HIGHLOAD_KLUDGE .word 0x1eff, 0x220 ! 调用 far * bootsect_kludge! 注意: as86不能汇编这;CALL_HIGHLOAD_KLUDGE ! 这是在setup.S中的程序;#elsemov ax,essub ax,#SYSSEG ! 当前es段值减system加载时的启始段值(0x1000);#endifcmp ax,syssize ! 我们是否已经都加载了?

(ax=0x7f00 ?);jbe ok1_read !! if ax <= syssize then 继续读;ret !! 全都加载完了,返回!

ok1_read:mov ax,sectors !! sectors=每磁道扇区数;sub ax,sread !! 减去当前磁道已读扇区数,al=当前磁道未读的扇区数(ah=0);mov cx,axshl cx,#9 !! 乘512,cx = 当前磁道未读的字节数;add cx,bx !! 加上段内偏移值,es:bx为当前读入的数据缓冲区地址;jnc ok2_read !! 如果没有超过64K则继续读;je ok2_read !! 如果正好64K也继续读;xor ax,axsub ax,bxshr ax,#9ok2_read:call read_track !! es:bx ->;缓冲区,al=要读的扇区数,也即当前磁道未读的扇区数;mov cx,ax !! ax仍为调用read_track之前的值,即为读入的扇区数;add ax,sread !! ax = 当前磁道已读的扇区数;cmp ax,sectors !! 已经读完当前磁道上的扇区了吗?

jne ok3_read !! 没有,则跳转;mov ax,#1sub ax,head !! 当前是磁头1吗?

jne ok4_read !! 不是(是磁头0)则跳转(此时ax=1);inc track !! 当前是磁头1,则读下一磁道(当前磁道加1);ok4_read:mov head,ax !! 保存当前磁头号;xor ax,ax !! 本磁道已读扇区数清零;ok3_read:mov sread,ax !! 存本磁道已读扇区数;shl cx,#9 !! 刚才一次读操作读入的扇区数 * 512;add bx,cx !! 调整数据缓冲区的起始指针;jnc rp_read !! 如果该指针没有超过64K的段内最大偏移量,则跳转继续读操作;mov ax,es !! 如果超过了,则将段地址加0x1000(下一个64K段);add ah,#0x10mov es,axxor bx,bx !! 缓冲区地址段内偏移量置零;jmp rp_read !! 继续读操作;read_track:pusha !! 将寄存器ax,cx,dx,bx,sp,bp,si,di压入堆栈;pushamov ax,#0xe2e ! loading... message 2e = . !! 显示一个.mov bx,#7int 0x10popamov dx,track !! track = 当前磁道;mov cx,sreadinc cx !! cl = 扇区号,要读的起始扇区;mov ch,dl !! ch = 磁道号的低8位;mov dx,head !!mov dh,dl !! dh = 当前磁头号;and dx,#0x0100 !! dl = 驱动器号(0);mov ah,#2 !! 功能2(读),es:bx指向读数据缓冲区;push dx ! 为出错转储保存寄存器的值到堆栈上;push cxpush bxpush axint 0x13jc bad_rt !! 如果出错,则跳转;add sp, #8 !! 清(放弃)堆栈上刚推入的4个寄存器值;poparetbad_rt: push ax ! 保存出错码;call print_all ! ah = error, al = read;xor ah,ahxor dl,dlint 0x13add sp,#10popajmp read_trackprint_all:mov cx,#5 ! 出错码 + 4个寄存器mov bp,spprint_loop:push cx ! 保存剩余的计数值call print_nl ! 为了增强阅读性,打印换行cmp cl, #5jae no_reg ! 看看是否需要寄存器的名称mov ax,#0xe05 + A - lsub al,clint 0x10mov al,#Xint 0x10mov al,#:int 0x10no_reg:add bp,#2 ! 下一个寄存器call print_hex ! 打印值pop cxloop print_loopretprint_nl: !! 打印回车换行。

mov ax,#0xe0d ! CRint 0x10mov al,#0xa ! LFint 0x10retprint_hex:mov cx, #4 ! 4个十六进制数字mov dx, (bp) ! 将(bp)所指的值放入dx中print_digit:rol dx, #4 ! 循环以使低4比特用上 !! 取dx的高4比特移到低4比特处。

mov ax, #0xe0f ! ah = 请求的功能值,al = 半字节(4个比特)掩码。

and al, dl !! 取dl的低4比特值。

add al, #0x90 ! 将al转换为ASCII十六进制码(4个指令)daa !! 十进制调整adc al, #0x40 !! (adc dest, src ==>; dest := dest + src + c )daaint 0x10loop print_digitretkill_motor:push dxmov dx,#0x3f2xor al,aloutbpop dxret!! 数据区sectors:.word 0 !! 当前每磁道扇区数。

(36||18||15||9)disksizes: !! 每磁道扇区数表.byte 36, 18, 15, 9msg1:.byte 13, 10.ascii "Loading".org 497 !! 从boot程序的二进制文件的497字节开始setup_sects:.byte SETUPSECSroot_flags:.word CONFIG_ROOT_RDONLYsyssize:.word SYSSIZEswap_dev:.word SWAP_DEVram_size:.word RAMDISKvid_mode:.word SVGA_MODEroot_dev:.word ROOT_DEVboot_flag: !! 分区启动标志.word 0xAA55

相关文章