在写setup时遇到的问题与思考

在写setup时遇到的问题与思考

首先遇到这个问题需要搞明白什么是setup。

在编写操作系统的过程中,我们首先要写汇编程序,然后将我们的代码编译为二进制之后写入软盘(或者硬盘),然后由BIOS将软盘(或者硬盘)中的内容加载到内存当中(0x7c00处,为什么是这里看另一篇文章)并执行,这样我们的操作系统内核就运行起来了。

但是这样我们面临一个问题。我们是将编译后的二进制文件(boot.o)写入的位置是0磁盘0磁道1扇区的位置,而且要求最后两个字节是0x55aa。(这是约定俗成的,不然谁知道上哪里去找你的代码呢?而且有这样的特征才能够被识别为一个操作系统)这样就有一个限制,因为一个扇区大小只有512字节,最后两个字节还必须要求是0x55aa,那么我们就只有510个字节的大小可以用了,这显然是不够我们来写一个操作系统内核的。这种情况改怎么办呢?

答案是我们写一个setup,按照与boot相同的方式写到内存当中就行了,当然不是一个随便的位置,而是参考实模式下1MB的内存布局表

image-20250412104004378

发现有两个空闲的可用区域,这就是我们可以放的地方,只要将setup加载到这两个位置即可。

BIOS完成任务之后会将CPU控制权移交给boot,所以只要在boot中写一个跳转到setup的逻辑,就能够摆脱512字节的限制了。

以下是我的实现代码(setup只是输出了一个"Hello World")

; boot.asm
[org 0x7c00]          ; 指定程序加载地址为 0x7C00(BIOS 加载引导扇区的地址)
[bits 16]             ; 16 位实模式

section .text         ; 定义代码段
global _start         ; 全局入口标签

_start:
    ; 保存启动驱动器号
    mov [boot_drive], dl    ; 将 dl(启动驱动器号)保存到内存变量 boot_drive

    ; 初始化段寄存器
    xor ax, ax          ; 将 ax 清零(ax = 0)
    mov ds, ax          ; 设置数据段寄存器 ds = 0
    mov es, ax          ; 设置附加段寄存器 es = 0
    mov ss, ax          ; 设置栈段寄存器 ss = 0
    mov sp, 0x7c00      ; 设置栈指针 sp = 0x7C00

    ; 打印 "Booting..."
    mov si, boot_msg    ; 将 boot_msg 的地址加载到 si 寄存器
    call print_string   ; 调用 print_string 函数

    ; 加载 setup 到内存 0x90000
    mov ah, 0x02        ; BIOS 中断功能号 ah = 0x02(读扇区)
    mov al, 1           ; al = 1(读取 1 个扇区)
    mov ch, 0           ; ch = 0(柱面号 0)
    mov cl, 2           ; cl = 2(扇区号 2)
    mov dh, 0           ; dh = 0(磁头号 0)
    mov dl, [boot_drive] ; dl = 启动驱动器号(从内存加载)
    mov bx, 0x9000      ; bx = 0x9000(目标段地址)
    mov es, bx          ; es = bx(设置目标段地址)
    xor bx, bx          ; bx = 0(偏移量清零)
    int 0x13            ; 调用 BIOS 中断 0x13(读磁盘)
    jc disk_error       ; 如果 CF 标志置位(错误),跳转到 disk_error

    ; 跳转到 setup
    jmp 0x9000:0x0000   ; 远跳转到 0x9000:0x0000

print_string:
    mov ah, 0x0e        ; ah = 0x0e(BIOS 打印字符功能)
.loop:
    lodsb               ; 从 ds:si 加载字节到 al,并递增 si
    cmp al, 0           ; 比较 al 和 0(检查字符串结束)
    je .done            ; 如果 al = 0,跳转到 .done
    int 0x10            ; 调用 BIOS 中断 0x10(打印字符)
    jmp .loop           ; 跳转回 .loop 继续处理
.done:
    ret                 ; 返回

disk_error:
    mov si, error_msg   ; 将 error_msg 地址加载到 si
    call print_string   ; 调用 print_string
    jmp $               ; 死循环

boot_msg db 'Booting...', 0    ; 定义字符串 "Booting...",以 0 结尾
error_msg db 'Disk read error!', 0 ; 定义错误字符串
boot_drive db 0                ; 定义变量存储启动驱动器号

times 510-($-$$) db 0          ; 填充到 510 字节
dw 0xaa55                      ; 引导扇区签名
; setup.asm
[org 0x0000]          ; 假设加载到 0x90000,偏移从 0 开始
[bits 16]             ; 16 位实模式

section .text
global _start

_start:
    ; 初始化段寄存器
    mov ax, 0x9000      ; ax = 0x9000(段地址)
    mov ds, ax          ; ds = 0x9000
    mov es, ax          ; es = 0x9000
    mov ss, ax          ; ss = 0x9000
    mov sp, 0x1000      ; sp = 0x1000(栈指针)

    ; 打印 "Hello, World! from setup"
    mov si, hello_msg   ; si = hello_msg 地址
    call print_string   ; 调用 print_string

    jmp $               ; 死循环

print_string:
    mov ah, 0x0e        ; ah = 0x0e(BIOS 打印字符功能)
.loop:
    lodsb               ; 从 ds:si 加载字节到 al,并递增 si
    cmp al, 0           ; 比较 al 和 0
    je .done            ; 如果 al = 0,跳转到 .done
    int 0x10            ; 调用 BIOS 中断 0x10
    jmp .loop           ; 继续循环
.done:
    ret                 ; 返回

hello_msg db 'Hello, World! from setup', 0 ; 定义字符串

其实最重要的就是知道各种寄存器,然后语句就容易理解了(问GPT吧,这个还是自己哪里不会问哪里)

最后是我遇到的问题:

一开始我是将

boot_msg db 'Booting...', 0    ; 定义字符串 "Booting...",以 0 结尾
error_msg db 'Disk read error!', 0 ; 定义错误字符串
boot_drive db 0                ; 定义变量存储启动驱动器号

三行前面加了一个[section .data]数据段声明,希望将它们放到数据段当中,但是报错了。为什么会出现这样的问题呢?

因为分段是通过section或者segment语句将代码和数据分组,在现代的程序当中,比如说ELF格式,链接器会将这些段映射到内存的不同区域上。但是在引导扇区(boot)当中,使用的是NASM的-f bin格式编译成的纯二进制文件,而不是ELF等格式,-f bin是不支持多段链接的,所有的代码都必须连续存储的。实模式下的地址计算都是相当于0x7c00的偏移计算的(因为这个程序是从0x700开始的),如果进行了段分离之后,会导致mov si,boot_msg这些语句找不到正确的位置,从而导致程序出错。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

下雨的清晨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值