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

从零开始制作 MyOS(三)

从零开始制作 MyOS(三)—— 切换保护模式

目标

今天的目标是从实模式切换到保护模式

保护模式切换流程

  1. 设置GDT (lgdt)
  2. 启用A20地址线
  3. 设置CR0.PE标志
  4. 远跳转刷新流水线
  5. 初始化保护模式段寄存器

关键组件解析

  1. GDT 设置
  • gdt_start​:定义全局描述符表
    • 空描述符(必须存在)
    • 代码段描述符(可执行、只读)
    • 数据段描述符(可读写)
  • gdt_descriptor​:包含GDT大小和基地址
  1. 内存布局
  • 0x7C00-0x7DFF:引导扇区
  • 0x10000:内核加载位置(64KB处)
  • 0x90000:保护模式栈指针
  1. 关键寄存器作用​:

​+ CR0​:控制寄存器,PE位控制保护模式

  • ​GDTR​:存储GDT的基址和界限
    ​+ 段寄存器​:在保护模式下变为段选择子

代码

; boot.asm - BIOS boot sector with protected mode switch
org 0x7C00       ; BIOS加载引导扇区到内存0x7C00处
bits 16          ; 16位实模式代码start:; === 初始化环境 ===cli         ; 禁用中断(防止设置过程中被中断)xor ax, ax  ; AX清零(比mov ax,0更高效)mov ds, ax  ; 数据段寄存器DS=0mov es, ax  ; 额外段寄存器ES=0mov ss, ax  ; 堆栈段寄存器SS=0mov sp, 0x7C00 ; 栈指针SP=0x7C00(向下增长)sti         ; 启用中断; === 显示加载信息 ===mov si, loading_msgcall print_string; === 加载磁盘内容 ===call load_diskjc disk_error  ; 如果出错跳转到错误处理; === 显示加载成功信息 ===mov si, loading_disk_successcall print_string; === 准备保护模式 ===call setup_gdt    ; 设置全局描述符表(GDT)call enable_a20    ; 启用A20地址线call switch_to_pm  ; 切换到保护模式; === 永远不会执行到这里 ===jmp $;============= 磁盘加载函数 =============
load_disk:; 参数:;   dl = 驱动器号 (0x80=第一硬盘);   dh = 磁头号;   ch = 柱面号;   cl = 起始扇区号(1-based);   al = 要读取的扇区数;   es:bx = 目标缓冲区地址; 返回:;   CF = 1表示出错mov dl, 0x80    ; 驱动器号(0x80=第一硬盘)mov dh, 0       ; 磁头号mov ch, 0       ; 柱面号mov cl, 2       ; 起始扇区号(1-based)mov al, 8       ; 要读取的扇区数(8*512=4KB)mov bx, 0x8000  ; 目标偏移地址mov es, bx      ; ES:BX = 0x8000:0x0000xor bx, bx      ; BX清零mov ah, 0x02    ; BIOS读扇区功能号int 0x13        ; 调用BIOS磁盘服务; === 错误重试机制(最多3次) ===mov byte [retry_count], 3
.retry:jnc .success    ; 成功则跳转; === 重置磁盘控制器 ===pushaxor ah, ah      ; 功能号0=重置磁盘int 0x13popadec byte [retry_count]jz .failure     ; 重试次数用完; === 重新尝试读取 ===int 0x13jmp .retry.success:ret             ; 成功返回.failure:stc             ; 设置进位标志表示错误ret             ; 返回;============= 保护模式设置 =============
setup_gdt:; 加载GDT描述符到GDTR寄存器lgdt [gdt_descriptor]retenable_a20:; === 通过键盘控制器启用A20地址线 ===; 步骤1: 发送禁用键盘命令call .wait_kbdmov al, 0xADout 0x64, al; 步骤2: 发送读取输出端口命令call .wait_kbdmov al, 0xD0out 0x64, al; 步骤3: 读取输出端口值call .wait_kbdin al, 0x60push ax          ; 保存原始值; 步骤4: 发送写入输出端口命令call .wait_kbdmov al, 0xD1out 0x64, al; 步骤5: 写回输出端口值(启用A20)call .wait_kbdpop axor al, 2         ; 设置A20使能位out 0x60, alret.wait_kbd:; 等待键盘控制器就绪in al, 0x64test al, 0x02    ; 检查输入缓冲区状态jnz .wait_kbd    ; 缓冲区不为空则继续等待retswitch_to_pm:; === 切换到保护模式 ===cli              ; 禁用中断mov eax, cr0     ; 加载控制寄存器or eax, 0x1      ; 设置PE(保护模式使能)位mov cr0, eax     ; 写回控制寄存器; 远跳转刷新流水线并加载CS段选择子jmp CODE_SEG:init_pm;============= 32位保护模式代码 =============
bits 32
init_pm:; === 初始化保护模式段寄存器 ===mov ax, DATA_SEGmov ds, axmov es, axmov fs, axmov gs, axmov ss, axmov esp, 0x90000 ; 设置32位栈指针; === 清屏 ===mov edi, 0xB8000 ; VGA文本缓冲区基址mov ecx, 80*25   ; 80列25行mov ax, 0x0F20   ; 黑底白字的空格
.clear:stosw             ; 写入显存loop .clear; === 显示保护模式激活信息 ===mov edi, 0xB8000 + 160 ; 第二行开始显示mov esi, pm_msg   ; 字符串地址mov ah, 0x0F      ; 白字黑底属性
.print_loop:lodsb             ; 从esi加载字符到altest al, al       ; 检查字符串结束(0)jz .halt          ; 如果为0则跳转到haltstosw             ; 写入字符+属性jmp .print_loop.halt:clihlt               ; 停止CPU执行;============= 错误处理 =============
disk_error:mov si, error_msgcall print_stringjmp $print_string:; === 实模式字符串打印函数 ===lodsb           ; 加载SI指向的字符到ALtest al, al     ; 检查是否字符串结束jz .done        ; 如果是则结束mov ah, 0x0E    ; BIOS显示字符功能mov bh, 0       ; 显示页面0int 0x10        ; 调用BIOS视频服务jmp print_string ; 继续处理下一个字符
.done:ret             ; 返回;============= 数据区 =============
retry_count db 0    ; 磁盘重试计数器
loading_msg db "Loading protected mode...", 0xD, 0xA, 0
loading_disk_success db "Disk loaded successfully!", 0xD, 0xA, 0
error_msg db "Disk error! System halted.", 0
pm_msg db "32-bit Protected Mode Activated!", 0  ; 确保以0结尾;============= GDT 定义 =============
gdt_start:dq 0 ; 第一个描述符必须为空描述符; 代码段描述符
gdt_code:dw 0xFFFF    ; 段界限(低16位)dw 0         ; 段基址(低16位)db 0         ; 段基址(中8位)db 10011010b ; 访问字节(参见下方说明)db 11001111b ; 标志位 + 段界限(高4位)db 0         ; 段基址(高8位); 数据段描述符
gdt_data:dw 0xFFFF    ; 段界限(低16位)dw 0         ; 段基址(低16位)db 0         ; 段基址(中8位)db 10010010b ; 访问字节db 11001111b ; 标志位 + 段界限(高4位)db 0         ; 段基址(高8位)gdt_end:; GDT描述符(供LGDT指令使用)
gdt_descriptor:dw gdt_end - gdt_start - 1 ; GDT大小(16位)dd gdt_start               ; GDT基地址(32位); 段选择子常量
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start; 引导签名
times 510 - ($ - $$) db 0  ; 填充剩余空间(510字节)
dw 0xAA55                  ; 引导扇区签名(最后2字节)

编译

添加 Makefile 文件


make cleanmake

运行


# 在 ubuntu 的终端
make run-vga

结果

保护模式切换成功

遇到的问题

  1. 保护模式切换时,qemu 面板输出的内容一直刷新闪烁
  • 在保护模式下调用实模式的print_string函数段寄存器未正确设置导致内存访问错误
  1. 执行运行命令后打印不出切换成功的日志
  • 检查 qemu 执行时是否使用 serial 参数,代码中使用的 vga 模式输出字符串

后续开发任务

​内核开发​:

  • 实现基本屏幕输出(VGA文本模式)
  • 添加中断描述符表(IDT)
  • 支持键盘输入

​内存管理​:

  • 实现分页机制
  • 添加物理内存管理器

​高级功能​:

  • 多任务支持
  • 文件系统驱动
  • 用户模式切换
http://www.hskmm.com/?act=detail&tid=40301

相关文章:

  • 2025年知名的防火包裹卷材陶瓷纤维行业内知名厂家排行榜
  • 2025年真空连续干燥机厂家权威推荐榜单:真空耙式干燥机/真空冷冻干燥机/方形真空干燥机源头厂家精选
  • 2025年质量好的热镀锌钢销售厂家
  • 2025年广东中小学网课学习机服务商TOP3权威实力榜单:小学英语学习机/智能ai学生学习机/网课平板学习机服务商精选
  • 2025年知名的涂料光触媒行业内口碑厂家排行榜
  • 2025 年阳台光伏厂家最新推荐榜,技术实力与市场口碑深度解析:含逆变器、储能及光伏板优质企业
  • Linux CentOS7 安装源更新-IUS源
  • 2025年靠谱的20吨地磅行业内口碑厂家排行榜
  • 2025年阻燃改性尼龙隔热条厂家推荐及采购参考
  • 2025年专业的铝方通隔断厂家推荐及采购指南
  • 2025年比较好的机床旋转接头实力厂家TOP推荐榜
  • 2025年艺术、教育与管理国际学术会议(ICAEM 2025)- 第六期
  • 京东改标题
  • AnolisOS中的GPG检查失败问题
  • 2025年评价高的阶梯式养鸡设备厂家最新用户好评榜
  • 2025年可靠的车载便携式烟灰缸厂家最新权威实力榜
  • 2025 年 10 月果树苗、樱桃树苗、桃树苗、梨树苗种植基地品牌最新推荐 —— 技术实力与市场口碑深度解析
  • 2025年正规的制氮机厂家最新TOP实力排行
  • 2025年评价高的热管型余热锅炉厂家最新TOP排行榜
  • 2025年优秀的垃圾场防渗土工膜热门厂家
  • 2025年优质的电缆厂家最新TOP排行榜
  • 2025年有实力的PPH储罐厂家推荐及选择指南
  • 2025 年 10 月硫酸铝钾,无水硫酸铝,硫酸铝铵,除氟剂硫酸铝厂家最新推荐,产能、专利、环保三维数据透视
  • 2025年度河南登封代理记账服务TOP3专业机构权威推荐:专业财务咨询/税务合规咨询/工商登记代办服务精选。
  • 2025 年 10 月半导体级硫酸铝,电池级硫酸铝,超细硫酸铝厂家最新推荐,产能、专利、环保三维数据透视
  • 2025年耐用的膨化食品挤出机螺杆最新TOP厂家排名
  • 2025 年杂粮颗粒灌装机,小米颗粒灌装机,坚果颗粒灌装机 厂家最新推荐,技术实力与市场口碑深度解析
  • 2025年比较好的高速波纹管设备厂家最新TOP排行榜
  • 2025年耐用的共板法兰风管TOP实力厂家推荐榜
  • 2025年知名的不锈钢链轮生产加工最新TOP厂家排名