zoukankan      html  css  js  c++  java
  • Linux0.01 引导代码分析-boot.s

    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
    .text
    begtext:
    .data
    begdata:
    .bss
    begbss:
    .text
    
    BOOTSEG = 0x07c0;引导程序被加载到的地址
    INITSEG = 0x9000;引导程序将自己移动到的目标地址
    SYSSEG  = 0x1000;系统核心被加载到的地址
    ENDSEG	= SYSSEG + SYSSIZE;系统代码的结束地址,其中 SYSSIZE 在 Makefile 中定义
    
    entry start;程序入口标识
    start:
    	mov	ax,#BOOTSEG;将引导程序被加载的地址移动到 AX 中
    	mov	ds,ax;将数据段的基址设置为引导程序的起始地址
    	mov	ax,#INITSEG;将上文提到的目标地址设置到 AX 中
    	mov	es,ax;将 AX 中的值设置到 ES 中,使用基址加偏移的方式移动地址
    	mov	cx,#256;将 CX 计数器的值设置为 256
    	sub	si,si;清空 SI
    	sub	di,di;清空 DI
    	rep;重复执行 movw 256 次,实际上是将 512K 的数据(引导程序自身)搬移到INITSEG 处
    	movw;搬移指令,数据寻址方式,源:DS:SI,目标:ES:DI
    	jmpi	go,INITSEG;jmpi 段间跳转指令,由于当前代码已经被复制到 INITSEG ,在不同的段。64K 一个段
    go:	mov	ax,cs;将代码段的地址移动到 AX
    	mov	ds,ax;将 AX 的值移动到 DS
    	mov	es,ax;将 AX 的值移动到 ES
    	mov	ss,ax;将 AX 的值移动到 SS,堆栈段的基址
    	mov	sp,#0x400;栈顶指针,堆栈段的大小设置为 512K。堆栈可能是向下发展,否则会覆盖 INITSEG 的代码
    
    	mov	ah,#0x03;BIOS 10H 中断的 03H 服务,读取当前光标位置,dh:行,dl:列
    	int	0x10;执行 10H 中断
    	
    	mov	cx,#24;将 CX 的值设置为 24,需要显示字符的个数
    	mov	bx,#0x0007;设置显示属性	; page 0, attribute 7 (normal)
    	mov	bp,#msg1;将要显示字符的起始地址加载到 BP
    	mov	ax,#0x1301;13H显示字符串(ES:BP=显示串地址)AL=显示输出方式(1:字符串中只含显示字符,其显示属性在BL中,显示后,光标位置改变),综合起来功能是:向屏幕写字符串并移动光标
    	int	0x10;执行中断
    
    ; ok, we've written the message, now
    ; we want to load the system (at 0x10000)
    
    	mov	ax,#SYSSEG;将 AX 的值设置为:SYSSEG
    	mov	es,ax;将 ES 的值设置为 SYSSEG
    	call	read_it;调用 read_it,call 指令会使用堆栈寄存器
    	call	kill_motor;调用 kill_motor,关闭软驱
    
    ; if the read went well we get current cursor position and save it for
    ; posterity.
    
    	mov	ah,#0x03	; read cursor pos
    	xor	bh,bh
    	int	0x10		; save it in known place, con_init fetches
    	mov	[510],dx	; it from 0x90510.将当前光标位置存储到系统代码最后一个段的最后两个字节
    		
    ; now we want to move to protected mode ...
    
    	cli			; 关闭中断
    
    ; first we move the system to it's rightful place
    
    	mov	ax,#0x0000;AX 清零
    	cld			; DF 被置为 0,SI 与 DI 增加
    do_move:
    	mov	es,ax		; 设置目标代码段索引:0
    	add	ax,#0x1000   ;将代码段索引加一
    	cmp	ax,#0x9000   ;将目标代码段索引与结束段索引进行比较
    	jz	end_move     ;如果相等则完成复制,跳转到 end_move
    	mov	ds,ax		;复制时的源代码段
    	sub	di,di;DI 清零
    	sub	si,si;SI 清零
    	mov 	cx,#0x8000;复制的字节数
    	rep
    	movsw;按 word(16bit) 的方式移动
    	j	do_move;循环执行,直到复制完成
    
    ; then we load the segment descriptors
    
    end_move:
    
    	mov	ax,cs		; 在完成加载系统代码后,代码还在当前段执行,由于没有使用数据段,数据都存放在当前代码段,因此需要恢复 DS 到当前段基址
    	mov	ds,ax        ;将 DS 设置为正确的值
    	lidt	idt_48		;加载中断描述符表 load idt with 0,0
    	lgdt	gdt_48		;加载全局描述符表 load gdt with whatever appropriate
    
    ; that was painless, now we enable A20 参考 Intel 8042 芯片资料
    
    	call	empty_8042;调用empty_8042
    	mov	al,#0xD1		; 控制码
    	out	#0x64,al           ; 输出控制码
    	call	empty_8042
    	mov	al,#0xDF		; A20 on
    	out	#0x60,al
    	call	empty_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 中断控制器的芯片资料
    
    	mov	al,#0x11		; 初始化控制码
    	out	#0x20,al		; 输出控制码,send it to 8259A-1
    	.word	0x00eb,0x00eb	; jmp $+2, jmp $+2 机器码,由于当前在代码段,会被执行,延迟作用
    	out	#0xA0,al		; and to 8259A-2
    	.word	0x00eb,0x00eb
    	mov	al,#0x20		; start of hardware int's (0x20)
    	out	#0x21,al
    	.word	0x00eb,0x00eb
    	mov	al,#0x28		; start of hardware int's 2 (0x28)
    	out	#0xA1,al
    	.word	0x00eb,0x00eb
    	mov	al,#0x04		; 8259-1 is master
    	out	#0x21,al
    	.word	0x00eb,0x00eb
    	mov	al,#0x02		; 8259-2 is slave
    	out	#0xA1,al
    	.word	0x00eb,0x00eb
    	mov	al,#0x01		; 8086 mode for both
    	out	#0x21,al
    	.word	0x00eb,0x00eb
    	out	#0xA1,al
    	.word	0x00eb,0x00eb
    	mov	al,#0xFF		; 设置中断控制器的标志位,关闭全部中断 mask off all interrupts for now
    	out	#0x21,al
    	.word	0x00eb,0x00eb
    	out	#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.
    
    	mov	ax,#0x0001	; protected mode (PE) bit PE 将被设置为 1,将要进入保护模式
    	lmsw	ax		; lmsw是指装入机器状态字,即实际进入保护模式

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

    	jmpi	0,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 是键盘控制器
    	.word	0x00eb,0x00eb
    	in	al,#0x64	;将#0x64端口的状态值读取到 AL中, 8042 status port
    	test	al,#2		;测试 AL 的第二位是否为 1, is input buffer full?
    	jnz	empty_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 是 64
    die:	jne die	;按扇区复制,要求对齐
    	xor bx,bx;BX 清零,用来标记起始地址,后面读取数据后 BX 会变化
    rp_read:
    	mov ax,es;将 ES 的值加载到 AX 中
    	cmp ax,#ENDSEG;将 AX 的值与结束地址进行比较,判断是否完成加载
    	jb ok1_read;如果小于#ENDSEG,跳转到 ok1_read
    	ret;过程结束,返回,通过跳转到存储在堆栈中的返回地址继续执行
    ok1_read:;在没有填满数据到 #ENDSEG 时执行
    	mov ax,#sectors;将扇区数加载到 AX 中,实际为 AL
    	sub 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_track
    	mov cx,ax;将 CX 设置为 AX,AX 中存放的是上次读取的扇区数
    	add ax,sread;将 AX 的值设置为 AX + sread = Total Sectors
    	cmp ax,#sectors;判断是否已经读完
    	jne ok3_read;如果没有读完,跳转到 ok3_read
    	mov ax,#1;将 AX 设置为 1,运行到此处是代表当前磁道的扇区已经读完
    	sub ax,head;AX = AX - head
    	jne ok4_read;如果不为 0,当前盘面已经读完,跳转到 ok4_read
    	inc 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;清空 BX
    	jmp 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 ax
    	push bx
    	push cx
    	push 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_rt
    	pop dx
    	pop cx
    	pop bx
    	pop ax;恢复现场
    	ret
    bad_rt:	mov ax,#0
    	mov dx,#0
    	int 0x13;软盘复位,13H的00H号功能——软盘系统复位,AH=00H 功能号,DL=驱动器号,总之:将软驱A复位
    	pop dx
    	pop cx
    	pop bx
    	pop ax
    	jmp 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 dx
    	mov dx,#0x3f2
    	mov al,#0
    	outb
    	pop dx
    	ret
    
    gdt:
    	.word	0,0,0,0		; dummy
    
    	.word	0x07FF		; 8Mb - limit=2047 (2048*4096=8Mb)
    	.word	0x0000		; base address=0
    	.word	0x9A00		; code read/exec
    	.word	0x00C0		; granularity=4096, 386
    
    	.word	0x07FF		; 8Mb - limit=2047 (2048*4096=8Mb)
    	.word	0x0000		; base address=0
    	.word	0x9200		; data read/write
    	.word	0x00C0		; granularity=4096, 386
    
    idt_48:;
    	.word	0			; idt limit=0
    	.word	0,0			; idt base=0L
    
    gdt_48:
    	.word	0x800		; gdt limit=2048, 256 GDT entries
    	.word	gdt,0x9		; gdt base = 0X9xxxx
    	
    msg1:
    	.byte 13,10
    	.ascii "Loading system ..."
    	.byte 13,10,13,10
    
    .text
    endtext:
    .data
    enddata:
    .bss
    endbss:
  • 相关阅读:
    Pull Request
    选择器
    常见HTTP状态码
    286. Walls and Gates
    200. Number of Islands
    1. Two Sum
    名片管理系统(python实现)
    k近邻算法(简单版)
    基数排序
    递归算法的调试
  • 原文地址:https://www.cnblogs.com/Proteas/p/2335684.html
Copyright © 2011-2022 走看看