用汇编语言实现系统引导——8086汇编语言学习记录

    学习王爽著汇编语言到了最后一阶段,完成了课程设计2,内容要求如下。

    编写一个不需要在现有操作系统环境中运行的程序:

    列出功能选项,让用户通过键盘进行选择功能,功能如下:

  1.         reset pc                  ;重启计算机
  2.         start system            ;  引导现有的操作系统
  3.         clock                       ;进入时钟程序
  4.         set clock                 ; 设置时间

    书中给出了系统启动相关的必备知识:

  1. 开机后,CPU通电自动进入FFFF:0开始执行,此处有一条跳转指令,CPU执行后转去执行BIOS中的硬件系统检测和初始化程序。
  2. 初始化程序建立BIOS所支持的中断向量,将BIOS提供的中断例程登记在中断向量表中。
  3. 完成后调用INT 19进行操作系统引导,INT 19先控制0号软驱,读取0道0面1扇区的内容到0:7c00h,并将CS:IP指向0:7c00h。
  4. 若0号软驱中没有软盘,或发生IO错误,则转为读取硬盘C的0道0面1扇区的内容到0:7c00h,并将CS:IP指向0:7c00h。

    由此可知该程序的实现应该是通过向软盘写入引导程序,完成要求的功能,而软盘可以通过虚拟机模拟,具体方法可以参考我写的方法虚拟机创建软盘方法

    具体的功能实现不是重点,该课程设计的难度主要是对整个系统的流程的掌握上,细分下可以分成,写入软盘程序、跳转程序、实际引导程序三个模块,因此着重关心程序框架。


    1.写入软盘程序

    通过调用int 13h的3号功能可以实现软盘写入

    直接磁盘服务(Direct Disk Service——INT 13H)

功能03H 
功能描述:写扇区
入口参数:AH=03H
AL=扇区数
CH=柱面
CL=扇区
DH=磁头
DL=驱动器,00H~7FH:软盘;80H~0FFH:硬盘
ES:BX=缓冲区的地址

出口参数:CF=0——操作成功,AH=00H,AL=传输的扇区数,否则,AH=状态代码,参见功能号01H中的说明

;write_1.asm
assume cs:code
code segment
dcode:
     ;这里写要写入的程序
start:
	mov ax,cs
	mov es,ax
	mov bx,offset dcode

	mov dx,0 
	mov cx,1
	mov ah,3
	mov al,1

	int 13h
	
	mov ax,4c00h
	int 21h
code ends
end start

2.跳转程序

    这里是最重要的内容,也是我在编写该课程设计时候思考最久的内容,为什么不在软盘中直接写入需求的引导程序,而要加入一个跳转程序,主要原因在于偏移地址的校对

    在汇编语言程序的编写中,不可避免的要使用标号,而编译器在编译的时候,会根据标号在源程序中的位置转化为立即数,例如call,jmp near等。

    要写入的程序(dcode)并不会在写入程序中被执行,仅仅只是写入到软盘中,而当在开机执行引导程序的时候,需要确保所有的跳转能正确执行,说起来可能非常抽象,可以看如下两小段程序。

;write1.asm
code segment
    start:
    int 13h;写入dcode到软盘
    dcode:
        jmp near fun
        fun:
            mov ax,1
code ends
end start

;write2.asm
code segment
    dcode:
        jmp near fun
        fun:
            mov ax,1
    start:
    int 13h;写入dcode到软盘
code ends
end start

    重点在编译器对 jmp near fun 的编译上,为什么是用jmp near而不是jmp short,是因为jmp short记录的是jmp相对目标的偏移地址的差,而jmp near则和call等命令一样,记录的是绝对偏移地址。由于在源程序中的位置不同,因此两个程序看起来内容一样,但实际上在编译的exe文件中,对jmp near fun的编译结果是不同的。

    那么,在编译的时候,偏移地址的编译结果是code段第一行代码开始,从0开始往下。而在系统启动的时候,读取的内容目的地址是0:7c00h,然后从7c00h开始执行。这意味着,在写入的程序中,所有的子程序和跳转都不能正确跳转!

    例如对于write.asm,fun的偏移地址是3(jmp near fun假设占3字节),那么在二进制文件中存储的就是0003h,而实际操作系统读取引导后,需要正确跳转的目的是7c00h+3=7c03h。也就是说需要在源程序中,给跳转的偏移地址加上7c00h即可。

    

    但是,对于一个功能稍微复杂的程序,内容肯定不止一个子程序,不止一个跳转命令,若是要程序正确运行需要给每一个偏移加上7c00h,这样略显复杂,不如在读取后跳转到另一块内存,让偏移从0开始,并在源程序中把要写入的程序放在start之上,这样偏移就自然对齐了。

    这也是为什么我在第1部分的源程序start在要写入的内容的下方的原理。

    由于引导程序是在BIOS之上,操作系统之下,因此在编写的时候需要明确,通过使用INT13 AH=2从软盘读取出来的程序放在内存的哪个位置,虽然相比BIOS占用的内存,空闲区间很大,但仍然不是能随意设定的。我找到了这张图

    用汇编语言实现系统引导——8086汇编语言学习记录_第1张图片原文链接

    于是完成的write_1.asm的子内容如下

dcode:

    mov ax,50h
	mov es,ax
	mov bx,0

	mov dx,0 
	mov cx,2
	mov ah,2
	mov al,5

	int 13h

    jmp bstart
    stdin:
    dw 0h,50h
    nop
    nop
    bstart:
    mov bx,offset stdin
    add bx,7c00h
    jmp dword ptr cs:[bx]

    把主要的引导程序另外写入到软盘的从第2扇区开始的位置,并在读取后跳转到50:0的位置,这样就保证了跳转的正确性。


3.实际引导程序

    根据要求的4个功能,写出的源程序摘要如下

dcode:
    mov ax,cs
    mov ss,ax
    mov sp,offset stkend 

    jmp guide
    table dw sec1,sec2,sec3,sec4
    guide:

	mainlop:
	call cls    
	call showgui
	mov ah,0
	int 16h
	cmp al,31h
	jb mainlop
	cmp al,34h
	ja mainlop
	sub al,30h
	mov bl,al
	mov bh,0
	sub bx,1
	add bx,bx
	call word ptr table[bx]

	jmp mainlop

	db 128 dup (?)
    stkend:
        nop
showgui:
	jmp sguist
	strt dw 0,gstr4,gstr3,gstr2,gstr1
    gstr1:
	db "1.reset pc     "
    gstr2:
	db "2.start systeam"
    gstr3:
	db "3.clock        "
    gstr4:
	db "4.set clock    "
	sguist:
    ;此处省略输出代码
    ret

cls:    ;清空屏幕子程序
    push ax
    push bx
    push cx
    push es
    mov ax,0b800h
    mov es,ax
    mov bx,0
    mov cx,4000
    clslop:
    mov byte ptr es:[bx],' '
    add bx,2
    loop clslop
    pop es
    pop cx
    pop bx
    pop ax
    ret
sec1:
    mov bx,offset rebot
    jmp dword ptr cs:[bx]
    rebot:
        dw 0,0ffffh
sec2:
	mov ax,0
    mov es,ax

    mov bx,7c00h
    mov al,1
    mov ch,0
    mov cl,1
    mov dh,0
    mov dl,80h

    mov ah,2
    int 13h
    mov bx,offset inbot
    jmp dword ptr cs:[bx]
    inbot:
        dw 7c00h,0
sec3:
sec4:

    由于两个复杂的子模块代码没有经过优化,较为冗余也没有注释就删掉了,留下前两个简单的功能。

    由于是较为底层的程序,因此为了子程序的正确跳转,不能忘了手动分配栈空间,否则会错误。


4.补充

    前面的内容是基于王爽著汇编语言的内容写的方法,在其他书中看到了有这么一条伪指令。

    ORG 表达式

    表达式给出偏移地址,以表达式的值作为其后的程序段或数据块存放的起始地址的偏移量。

    也即,在写入程序中给要被写入的引导程序前加上 org 7c00h,即可正确让引导程序执行。省略的繁琐的跳转步骤,这是汇编语言书中没有讲到的。


你可能感兴趣的:(汇编语言,汇编语言)