操作系统的引导

一、源代码
!bootsect.s 
!当 PC 的电源打开后,80x86 结构的 CPU 将自动进入实模式,并从地址 0xFFFF0 开始自动执行程序代码,这
!个地址通常是 ROM-BIOS 中的地址。PC 机的 BIOS 将执行某些系统的检测,在物理地址 0 处开始初始化中
!断向量。此后,它将可启动设备的第一个扇区读入内存地址 0x7C00 处,并跳转到这个地方。此代码即为引导扇
!区的代码,它将完成自身的复制迁移,加载setup.s设置程序,最后在终端显示启动字符串,并跳到setup程序执
!行。

.globl begtext,begdata,begbss,endtext,enddata,endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

SETUPLEN=4
BOOTSEG=0x07c0
INITSEG=0x9000
SETUPSEG=0x9020

entry start
start:
!将自身从内存0x7c00处复制到0x90000处
!ds:si=>es:di 执行cx次movw
    mov ax,#BOOTSEG
    mov ds,ax
    mov ax,#INITSEG
    mov es,ax
    sub si,si
    sub di,di
    mov cx,#256
    rep 
    movw
    jmpi go,INITSEG !段间跳转,设置cs为INITSEG

go:
    mov ax,cs
    mov ds,ax!cs=>ds
    mov es,ax!cs=>es

!从磁盘加载setup程序到内存0x90200处
!调用0x13号中断
!输入:ah为功能号,0x02为从磁盘读到内存,al为读取扇区个数
!dh为磁头号,dl为驱动器号,ch为柱面(磁道)号,cl为开始扇区,es:bx指向数据缓冲区
!如果出错,CF标志置位,ah返回出错码
load_setup:
    xor dx,dx
    mov cx,#0x0002
    mov ax,#0x0200+SETUPLEN
    mov bx,#0x0200
    int #0x13
    jnc ok_load_setup
    !复位磁盘控制器,重试
    !调用0x13号中断
    !ah为功能号,0x0为复位,dl为驱动器号
    xor dl,dl
    xor ah,ah
    int #0x13
    j load_setup

!显示字符串
!调用0x10号中断
!输入:ah功能号为0x13表示显示字符串,al为0x01表示光标停在字符串结尾处
!es:bp指向显示字符串的起始位置,cx为字符串长度,dh为显示位置行号,dl为列号,bh为显示页号,bl为字符属性
ok_load_setup:

!读取光标位置
!调用0x10号中断
!ah功能号为0x03,bh为页号
!输出dh为光标位置行号,dl为列号
    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#25
    mov bx,#0x000A
    mov bp,#msg1
    mov ax,#0x1301
    int 0x10

    jmpi 0,SETUPSEG!跳转到setup程序段执行

msg1:
    .byte 13,10
    .ascii "Funix is booting..."
    .byte 13,10,13,10!ASCII码:13为回车,10为换行

.org    510 !定位当前偏移地址为510
    .word 0xAA55!有效引导扇区的标志

.text
endtext:
.data
enddata:
.bss
endbss:
!setup.s
!被bootsect.s加载到内存0x90200处,首先显示一个字符串,然后打印光标位置、内存尺寸以及磁盘参数信息

.globl begtext,begdata,begbss,endtext,enddata,endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

BOOTSEG=0x07c0
INITSEG=0x9000
SETUPSEG=0x9020

entry start
start:
    mov ax,cs
    mov ds,ax
    mov es,ax
    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#23
    mov bx,#0x0007
    mov bp,#msg1
    mov ax,#0x1301
    int 0x10

    mov ax,#INITSEG
    mov ds,ax!设置硬件信息保存的段地址为INITSEG
    mov ah,0x03
    xor bh,bh
    int 0x10!读取光标位置
    mov [0],dx!保存到ds:0x0处

    !取扩展内存的大小值
    !调用0x15号中断,ah功能号为0x88取系统所含扩展内存大小
    !返回ax=从0x100000(1M)处开始的扩展内存大小(KB),若出错则CF置位,ax=出错码
    mov ah,#0x88
    int 0x15
    mov [2],ax!保存到ds:0x2处

    !取第一个硬盘的信息
    !第一个硬盘参数表的首地址是中断向量0x41的向量值,第二个硬盘参数表紧接其后,为0x46。
    !表长为16个字节
    !ds:si=>es:di
    mov ax,#0x0000
    mov ds,ax
    lds si,[4*0x41]!取中断向量0x41的值,即hd0参数表的地址->ds:si
    mov ax,#INITSEG
    mov es,ax
    mov di,#0x0080
    mov cx,#0x10
    rep
    movsb

    mov ax,#INITSEG
    mov ds,ax
    mov ax,#SETUPSEG
    mov es,ax

    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#11
    mov bx,#0x0007
    mov bp,#cur
    mov ax,#0x1301
    int 0x10!打印提示字符串

    mov ax,[0x0]!取出光标位置
    call print_hex!打印数值
    call print_nl!打印回车换行

    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#12
    mov bx,#0x0007
    mov bp,#mem
    mov ax,#0x1301
    int 0x10

    mov ax,[0x2]
    call print_hex!打印内存大小

    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#25
    mov bx,#0x0007
    mov bp,#cyl
    mov ax,#0x1301
    int 0x10

    mov ax,[0x80]
    call print_hex!打印柱面个数
    call print_nl

    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#8
    mov bx,#0x0007
    mov bp,#head
    mov ax,#0x1301
    int 0x10

    mov ax,[0x80+0x02]
    call print_hex!打印磁头个数
    call print_nl

    mov ah,#0x03
    xor bh,bh
    int 0x10
    mov cx,#8
    mov bx,#0x0007
    mov bp,#sect
    mov ax,#0x1301
    int 0x10

    mov ax,[0x80+0x0e]
    call print_hex!打印每磁道扇区数
    call print_nl

ok: j ok !停止

!将ax中16位数字打印成按十六进制显示字符输出
print_hex:
    mov cx,#4!打印4个字符
    mov dx,ax!参数ax传递给dx
print_digit:
    rol dx,#4!循环左移4位,最高位变为最低位
    mov ah,#0xe!中断功能号,显示al中的字符
    mov al,dl
    and al,#0xf!高4位置零,设置al为显示数字
    add al,#0x30!转换ascii码
    cmp al,#0x39
    jbe good_digit!不超过9直接中断打印出来
    add al,#0x41-0x30-0xa!超过9加上'A'-'0'-10设置为大写字母显示
good_digit:
    int 0x10!调用中断0x10
    loop print_digit!循环打印4个字符
    ret 

!中断打印回车换行
print_nl:
    mov ax,#0xe0d
    int 0x10
    mov al,#0xa
    int 0x10
    ret

msg1:
    .ascii "Now we are in SETUP"
    .byte 13,10,13,10
cur:
    .ascii "Cursor POS:"
mem:
    .ascii "Memory SIZE:"
cyl:
    .ascii "KB"
    .byte 13,10,13,10
    .ascii "HD Info"
    .byte 13,10
    .ascii "Cylinders:"
head:
    .ascii "Headers:"
sect:
    .ascii "Sectors:"

.text
endtext:
.data
enddata:
.bss
endbss: 
二、无心插柳

在和室友交流的过程中,发现他setup.s中调用print_hex函数传递参数使用的是栈,下面讨论一下这种情况。

!不管代码是否优雅,纯粹是用栈实现一下

!此为调用
push ax
call print_hex
pop ax

!函数实现
print_hex:
    push bp!保存bp
    mov bp,sp!设置bp为此时刻的sp,此时栈中有三个字——ax,ip和bp
    push ds!保存ds
    mov cx,ss
    mov ds,cx!设置ds段地址为栈段地址
    mov cx,#4
    mov dx,(bp+4)!按照bp向上偏移4字节访问参数ax
print_digit:
    rol dx,#4
    mov ah,#0xe
    mov al,dl
    and al,#0xf
    add al,#0x30
    cmp al,#0x39
    jbe good_digit
    add al,#0x41-0x30-0xa
good_digit:
    int 0x10
    loop print_digit
    pop ds!恢复ds
    pop bp!恢复bp
    ret

对setup.s而言,上面的实现可谓很不优雅,因为print_hex函数传递参数可以用寄存器直接传递(像一部分那样),也可以直接访问0x900xx地址(不具有普遍性,不可取),用栈本身也是用内存,只不过此处使用的是默认的栈分配而非手动指定分配的内存。但是突然对栈有了更明确的理解:之前写汇编函数上来就是push ebp;mov ebp,esp感觉是习惯就没怎么思考,这次才想明白ebp的作用只是保存那一时刻的esp,因为之后再push,pop可能会改变esp的值,但ebp基本不变就能得到恒定的参数表达或者访问形式。注意call本身调用的时候相当于先push eip,然后段内近跳转,所以用ebp访问参数的时候不能忘了还有eip的偏移,同理ret相当于pop eip。

三、运行结果

这里写图片描述

查看Bochs配置文件bochs/bochsrc.bxrc

megs: 16
mode=flat, cylinders=204, heads=16, spt=38

发现扩展内存大小刚好是15M,加上1M为16M,其他柱面数,磁头数和每磁道扇区数都和结果一致。Bingo!

四、参考资料
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值