当前位置: 首页 > ds >正文

在写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 = 0mov es, ax          ; 设置附加段寄存器 es = 0mov ss, ax          ; 设置栈段寄存器 ss = 0mov sp, 0x7c00      ; 设置栈指针 sp = 0x7C00; 打印 "Booting..."mov si, boot_msg    ; 将 boot_msg 的地址加载到 si 寄存器call print_string   ; 调用 print_string 函数; 加载 setup 到内存 0x90000mov 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; 跳转到 setupjmp 0x9000:0x0000   ; 远跳转到 0x9000:0x0000print_string:mov ah, 0x0e        ; ah = 0x0e(BIOS 打印字符功能)
.loop:lodsb               ; 从 ds:si 加载字节到 al,并递增 sicmp al, 0           ; 比较 al 和 0(检查字符串结束)je .done            ; 如果 al = 0,跳转到 .doneint 0x10            ; 调用 BIOS 中断 0x10(打印字符)jmp .loop           ; 跳转回 .loop 继续处理
.done:ret                 ; 返回disk_error:mov si, error_msg   ; 将 error_msg 地址加载到 sicall print_string   ; 调用 print_stringjmp $               ; 死循环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 = 0x9000mov es, ax          ; es = 0x9000mov ss, ax          ; ss = 0x9000mov sp, 0x1000      ; sp = 0x1000(栈指针); 打印 "Hello, World! from setup"mov si, hello_msg   ; si = hello_msg 地址call print_string   ; 调用 print_stringjmp $               ; 死循环print_string:mov ah, 0x0e        ; ah = 0x0e(BIOS 打印字符功能)
.loop:lodsb               ; 从 ds:si 加载字节到 al,并递增 sicmp al, 0           ; 比较 al 和 0je .done            ; 如果 al = 0,跳转到 .doneint 0x10            ; 调用 BIOS 中断 0x10jmp .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这些语句找不到正确的位置,从而导致程序出错。

http://www.xdnf.cn/news/4064.html

相关文章:

  • Circular Plot系列(一): 环形热图绘制
  • 《马小帅的Java闯关记》
  • 模型部署与提供服务
  • QpushButton 扩展InteractiveButtonBase
  • k230摄像头初始化配置函数解析
  • nproc命令查看可用核心数量详解
  • [Windows] 智绘教 v20250403a 屏幕批注工具
  • day 12 三种启发式算法:遗传算法、粒子群算法、退火算法
  • 用卷积神经网络 (CNN) 实现 MNIST 手写数字识别
  • Python函数完全指南:从零基础到灵活运用
  • 深度学习中保存最优模型的实践与探索:以食物图像分类为例
  • GTID(全局事务标识符)的深入解析
  • 高翔《视觉SLAM十四讲》中第13讲,单目稠密重建中的RMODE数据集
  • TS 元组
  • 2025年PMP 学习三
  • 游戏开发的TypeScript(4)TypeScript 的一些内置函数
  • TF-IDF算法详解
  • C# 定时器实现
  • 正态分布习题集 · 题目篇
  • 递归算法详解(Java 实现):从原理到高阶应用
  • 类和对象(上)
  • C语言 指针(5)
  • 两台电动缸同步算法
  • n8n 构建一个 ReAct AI Agent 示例
  • 数理性能大幅提升|暴雨一体机适配DeepSeek Prover v2
  • C++类_成员函数指针
  • 移动 Trae 目录到 E 盘 - 解决 C 盘空间不足问题
  • 在Qt Creator中使用CUDA
  • 录播课制作技术指南
  • Javase 基础加强 —— 03 集合