Linux-0.01 引导代码分析

Linux-0.01 的引导部分主要由两个源代码完成:boot.s 与 head.s 。boot.s 由 BIOS 加载执行,head.s 是 32 位的引导代码,在最后会调用 main() 函数,完成系统的引导。

boot.s 代码:

;;boot.s;; boot.s is loaded at 0x7c00 by the bios-startup routines, and moves itself; out of the way to address 0x90000, and jumps there.;; It then loads the system at 0x10000, using BIOS interrupts. Thereafter; it disables all interrupts, moves the system down to 0x0000, changes; to protected mode, and calls the start of system. System then must; RE-initialize the protected mode in it's own tables, and enable; interrupts as needed.;; NOTE! currently system is at most 8*65536 bytes long. This should be no; problem, even in the future. I want to keep it simple. This 512 kB; kernel size should be enough - in fact more would mean we'd have to move; not just these start-up routines, but also do something about the cache-; memory (block IO devices). The area left over in the lower 640 kB is meant; for these. No other memory is assumed to be "physical", ie all memory; over 1Mb is demand-paging. All addresses under 1Mb are guaranteed to match; their physical addresses.;; NOTE1 abouve is no longer valid in it's entirety. cache-memory is allocated; above the 1Mb mark as well as below. Otherwise it is mainly correct.;; NOTE 2! The boot disk type must be set at compile-time, by setting; the following equ. Having the boot-up procedure hunt for the right; disk type is severe brain-damage.; The loader has been made as simple as possible (had to, to get it; in 512 bytes with the code to move to protected mode), and continuos; read errors will result in a unbreakable loop. Reboot by hand. It; loads pretty fast by getting whole sectors at a time whenever possible.; 1.44Mb disks:sectors = 18; 1.2Mb disks:; sectors = 15; 720kB disks:; sectors = 9.globl begtext, begdata, begbss, endtext, enddata, endbss.textbegtext:.databegdata:.bssbegbss:.textBOOTSEG = 0x07c0;引导程序被加载到的地址INITSEG = 0x9000;引导程序将自己移动到的目标地址SYSSEG  = 0x1000;系统核心被加载到的地址ENDSEG= SYSSEG + SYSSIZE;系统代码的结束地址,其中 SYSSIZE 在 Makefile 中定义entry start;程序入口标识start:movax,#BOOTSEG;将引导程序被加载的地址移动到 AX 中movds,ax;将数据段的基址设置为引导程序的起始地址movax,#INITSEG;将上文提到的目标地址设置到 AX 中moves,ax;将 AX 中的值设置到 ES 中,使用基址加偏移的方式移动地址movcx,#256;将 CX 计数器的值设置为 256subsi,si;清空 SIsubdi,di;清空 DIrep;重复执行 movw 256 次,实际上是将 512K 的数据(引导程序自身)搬移到INITSEG 处movw;搬移指令,数据寻址方式,源:DS:SI,目标:ES:DIjmpigo,INITSEG;jmpi 段间跳转指令,由于当前代码已经被复制到 INITSEG ,在不同的段。64K 一个段go:movax,cs;将代码段的地址移动到 AXmovds,ax;将 AX 的值移动到 DSmoves,ax;将 AX 的值移动到 ESmovss,ax;将 AX 的值移动到 SS,堆栈段的基址movsp,#0x400;栈顶指针,堆栈段的大小设置为 512K。堆栈可能是向下发展,否则会覆盖 INITSEG 的代码movah,#0x03;BIOS 10H 中断的 03H 服务,读取当前光标位置,dh:行,dl:列int0x10;执行 10H 中断movcx,#24;将 CX 的值设置为 24,需要显示字符的个数movbx,#0x0007;设置显示属性; page 0, attribute 7 (normal)movbp,#msg1;将要显示字符的起始地址加载到 BPmovax,#0x1301;13H显示字符串(ES:BP=显示串地址)AL=显示输出方式(1:字符串中只含显示字符,其显示属性在BL中,显示后,光标位置改变),综合起来功能是:向屏幕写字符串并移动光标int0x10;执行中断; ok, we've written the message, now; we want to load the system (at 0x10000)movax,#SYSSEG;将 AX 的值设置为:SYSSEGmoves,ax;将 ES 的值设置为 SYSSEGcallread_it;调用 read_it,call 指令会使用堆栈寄存器callkill_motor;调用 kill_motor,关闭软驱; if the read went well we get current cursor position and save it for; posterity.movah,#0x03; read cursor posxorbh,bhint0x10; save it in known place, con_init fetchesmov[510],dx; it from 0x90510.将当前光标位置存储到系统代码最后一个段的最后两个字节; now we want to move to protected mode ...cli; 关闭中断; first we move the system to it's rightful placemovax,#0x0000;AX 清零cld; DF 被置为 0,SI 与 DI 增加do_move:moves,ax; 设置目标代码段索引:0addax,#0x1000   ;将代码段索引加一cmpax,#0x9000   ;将目标代码段索引与结束段索引进行比较jzend_move     ;如果相等则完成复制,跳转到 end_movemovds,ax;复制时的源代码段subdi,di;DI 清零subsi,si;SI 清零mov cx,#0x8000;复制的字节数repmovsw;按 word(16bit) 的方式移动jdo_move;循环执行,直到复制完成; then we load the segment descriptorsend_move:movax,cs; 在完成加载系统代码后,代码还在当前段执行,由于没有使用数据段,数据都存放在当前代码段,因此需要恢复 DS 到当前段基址movds,ax        ;将 DS 设置为正确的值lidtidt_48;加载中断描述符表 load idt with 0,0lgdtgdt_48;加载全局描述符表 load gdt with whatever appropriate; that was painless, now we enable A20 参考 Intel 8042 芯片资料callempty_8042;调用empty_8042moval,#0xD1; 控制码out#0x64,al           ; 输出控制码callempty_8042moval,#0xDF; A20 onout#0x60,alcallempty_8042; well, that went ok, I hope. Now we have to reprogram the interrupts :-(; we put them right after the intel-reserved hardware interrupts, at; int 0x20-0x2F. There they won't mess up anything. Sadly IBM really; messed this up with the original PC, and they haven't been able to; rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,; which is used for the internal hardware interrupts as well. We just; have to reprogram the 8259's, and it isn't fun.参考 Intel 8259 中断控制器的芯片资料moval,#0x11; 初始化控制码out#0x20,al; 输出控制码,send it to 8259A-1.word0x00eb,0x00eb; jmp $+2, jmp $+2 机器码,由于当前在代码段,会被执行,延迟作用out#0xA0,al; and to 8259A-2.word0x00eb,0x00ebmoval,#0x20; start of hardware int's (0x20)out#0x21,al.word0x00eb,0x00ebmoval,#0x28; start of hardware int's 2 (0x28)out#0xA1,al.word0x00eb,0x00ebmoval,#0x04; 8259-1 is masterout#0x21,al.word0x00eb,0x00ebmoval,#0x02; 8259-2 is slaveout#0xA1,al.word0x00eb,0x00ebmoval,#0x01; 8086 mode for bothout#0x21,al.word0x00eb,0x00ebout#0xA1,al.word0x00eb,0x00ebmoval,#0xFF; 设置中断控制器的标志位,关闭全部中断 mask off all interrupts for nowout#0x21,al.word0x00eb,0x00ebout#0xA1,al; well, that certainly wasn't fun :-(. Hopefully it works, and we don't; need no steenking BIOS anyway (except for the initial loading :-).; The BIOS-routine wants lots of unnecessary data, and it's less; "interesting" anyway. This is how REAL programmers do it.;; Well, now's the time to actually move into protected mode. To make; things as simple as possible, we do no register set-up or anything,; we let the gnu-compiled 32-bit programs do that. We just jump to; absolute address 0x00000, in 32-bit protected mode.movax,#0x0001; protected mode (PE) bit PE 将被设置为 1,将要进入保护模式lmswax; lmsw是指装入机器状态字,即实际进入保护模式

;进入保护模式后,段寄存器就变成了选择子,选择子的结构: ; 16 2 1 0 ; ——————————————— ; | 索引 | TI | RDL | ; ——————————————— ;其中TI=0时是从GDT中找描述符

jmpi0,8; jmp offset 0 of segment 8 (cs)选择子=8,跳到GDT中的索引号为1的描述符,即代码段,参考 GDT 的定义; This routine checks that the keyboard command queue is empty; No timeout is used - if this hangs there is something wrong with; the machine, and we probably couldn't proceed anyway.empty_8042:;8042 是键盘控制器.word0x00eb,0x00ebinal,#0x64;将#0x64端口的状态值读取到 AL中, 8042 status porttestal,#2;测试 AL 的第二位是否为 1, is input buffer full?jnzempty_8042;如果输入缓冲区没有满,就循环执行,如果满了,函数返回ret; This routine loads the system at address 0x10000, making sure; no 64kB boundaries are crossed. We try to load it as fast as; possible, loading whole tracks whenever we can.;; in:es - starting address segment (normally 0x1000); 读取过程:先读一个磁道,再读同磁道的另一个盘面,然后重复上面过程读取下一个磁道; This routine has to be recompiled to fit another drive type,; just change the "sectors" variable at the start of the file; (originally 18, for a 1.44Mb drive);1.44Mb 的软盘结构:2面、80道/面、18扇区/道、512字节/扇区,2880扇区,512字节/扇区X 2880扇区 = 1440 KB ,每个磁道 9K,每个段64K(7个磁道+2个扇区),注意下面的溢出处理sread:.word 1; 当前磁道已经读取的扇区数head:.word 0; 当前磁头号track:.word 0; 当前磁道号read_it:mov ax,es;ES 是系统代码的段基址(当前段读满后 ES 会被加一)test ax,#0x0fff;相当于 AX 高 4 位清零,低 4 位保持原来的值,0x0FFF 是 64die:jne die;按扇区复制,要求对齐xor bx,bx;BX 清零,用来标记起始地址,后面读取数据后 BX 会变化rp_read:mov ax,es;将 ES 的值加载到 AX 中cmp ax,#ENDSEG;将 AX 的值与结束地址进行比较,判断是否完成加载jb ok1_read;如果小于#ENDSEG,跳转到 ok1_readret;过程结束,返回,通过跳转到存储在堆栈中的返回地址继续执行ok1_read:;在没有填满数据到 #ENDSEG 时执行mov ax,#sectors;将扇区数加载到 AX 中,实际为 ALsub ax,sread;将 AX 的值减一,实际为 AL,在下面读磁盘时 AL 代表扇区数mov cx,ax;将 CX 的值设置为 AX 的值,此时 AX 中存放的上次读取的扇区数shl cx,#9;2 的 9 次方是 512,剩余扇区数乘以 512Byte 等于已经读取的字节数add cx,bx;CX 为 16 位寄存器,最多可以表示 64K 数据,如果已经读取的字节数加上基址(BX)超过当前段需要溢出处理jnc ok2_read;若CF没有置位则跳转,即:没有溢出就继续执行,此处表示如果读取的数据少于 64K 跳转执行je ok2_read;利用零标志ZF 作跳转判断条件,此处表示如果读取的数据等于 64K 跳转执行xor ax,ax;AX 清零,如果执行此处代码,表示读取发生了溢出即:当前磁道的剩余字节数超过了当前段需要的字节sub ax,bx;发生溢出,相当于0xFFFF-BX,等于还需要填写的字节数shr ax,#9;AX/512;每个扇区 512 Byte,此时 AX 中是还需要读取的扇区数ok2_read:;在没有填满当前段时执行call read_track;调用 read_trackmov cx,ax;将 CX 设置为 AX,AX 中存放的是上次读取的扇区数add ax,sread;将 AX 的值设置为 AX + sread = Total Sectorscmp ax,#sectors;判断是否已经读完jne ok3_read;如果没有读完,跳转到 ok3_readmov ax,#1;将 AX 设置为 1,运行到此处是代表当前磁道的扇区已经读完sub ax,head;AX = AX - headjne ok4_read;如果不为 0,当前盘面已经读完,跳转到 ok4_readinc track;如果为 0,当前盘面没有读完,增加磁道号,继续读下一个磁道ok4_read:;在当前盘面读完时执行mov head,ax;将 AX 的值保存到 head 中xor ax,ax;将 AX 清零,继续执行ok3_read:;在当前磁道没有读完时执行mov sread,ax;将下一个需要读取的磁道号保存到 sread 中shl cx,#9;将 CX 乘以 512,CX 代表剩余读取的扇区数,此时 CX 代表剩余读取的字节数add bx,cx;BX = BX + CX,此时 BX 为下次存放数据的起始地址jnc rp_read;如果没有超过 64K 继续读mov ax,es;如果超过 64K,将 ES(段基址)的值设置到 AX 中add ax,#0x1000;AH 加一mov es,ax;将 ES 设置为 AX,移动到下一个段xor bx,bx;清空 BXjmp rp_read;继续读当前磁道read_track:
        ;BIOS 13H的02H功能:将从磁盘上把一个或更多的扇区内容读进存贮器。因为这是一个低级功能,在一个操作中读取的全部扇区必须在同一条磁道上(磁头号和磁道号相同)
        ;入口参数: AH=02H ;功能号 ;         AL=扇区数 ;         CH、CL=磁道号的低8位数、位7-6表示磁道号的高2位,低6位放入所读起始扇区号 ;         DH、DL=磁头号、驱动器号 ;         ES:BX=数据缓冲区地址 ;返回:    AH=0:成功,AL=读取的扇区数; ;         如果CF=1,AX中存放出错状态:AH=错误码。 ;注意:    寄存器DS、BX、CX、DX不变。 ;磁头号:  软盘A面=0;软盘B面=1。 ;驱动器号:软驱A=0;软驱B=1;硬驱=80H
push axpush bxpush cxpush dx;保护现场,此时 AL 等于剩余读取的扇区数,注意:下面的代码中 DX 作为临时数据存放饿寄存器mov dx,track;将 DX 设置为 track(磁道号),此时 DL 为磁道号,DX 中是临时数据mov cx,sread;将 CX 设置为 sread(扇区号),此时 CL 为扇区号inc cx;CX 加 1,加一的原因:磁盘的第一个扇区(512K)存放的是引导代码,不是系统代码,系统代码从第二个扇区开始,除了第一次其它从 1 开始读取 mov ch,dl;将 CH 设置为 DL,即磁道号,此时 CH 为磁道号-磁道号与扇区号已经设置完成mov dx,head;DX 设置为磁头号,此处为 DL,此时 DL 为磁头号,DL 中是临时数据mov dh,dl;将 DH 同样设置为磁头号,此时 DH 为磁头号mov dl,#0;将 DL 设置为 0,软驱A=0,DL 为驱动器号and dx,#0x0100;DX中的值除了 DH 的最后一位被保留,其它清零(上面那句代码可以去掉?),DH为磁头号mov ah,#2;将 AH 设置为 2,功能号int 0x13;调用 BIOS 13H 中断jc bad_rtpop dxpop cxpop bxpop ax;恢复现场retbad_rt:mov ax,#0mov dx,#0int 0x13;软盘复位,13H的00H号功能——软盘系统复位,AH=00H 功能号,DL=驱动器号,总之:将软驱A复位pop dxpop cxpop bxpop axjmp read_track;跳转到 read_track,即:如果读取当前扇区出现异常,继续读/* * This procedure turns off the floppy drive motor, so * that we enter the kernel in a known state, and * don't have to worry about it later. */kill_motor:push dxmov dx,#0x3f2mov al,#0outbpop dxretgdt:.word0,0,0,0; dummy.word0x07FF; 8Mb - limit=2047 (2048*4096=8Mb).word0x0000; base address=0.word0x9A00; code read/exec.word0x00C0; granularity=4096, 386.word0x07FF; 8Mb - limit=2047 (2048*4096=8Mb).word0x0000; base address=0.word0x9200; data read/write.word0x00C0; granularity=4096, 386idt_48:;.word0; idt limit=0.word0,0; idt base=0Lgdt_48:.word0x800; gdt limit=2048, 256 GDT entries.wordgdt,0x9; gdt base = 0X9xxxxmsg1:.byte 13,10.ascii "Loading system ...".byte 13,10,13,10.textendtext:.dataenddata:.bssendbss:

人生最大的错误是不断担心会犯错

Linux-0.01 引导代码分析

相关文章:

你感兴趣的文章:

标签云: