写一个操作系统 gnix OS(二):CPU读写外设和boot的交接过程验证

本文介绍了在实模式到保护模式转变过程中,CPU与显卡、硬盘控制器等IO接口的交互原理。通过BIOS中断实现的清屏和打印字符在保护模式下不再适用,因此需要通过直接读写IO端口控制显存和硬盘控制器。内容详细阐述了显卡的显存和IO端口以及硬盘的扇区定位、LBA模式,并提供了读取硬盘扇区到内存的boot程序实现。最后,通过bochs调试验证了程序的正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、背景和目的

在(一)中的boot程序通过使用BIOS提供的0x10中断实现了清屏和打印字符。但:

  • BIOS最终要交接出去,CPU的工作方式要从实模式切换到保护模式,而保护模式下是没有中断向量表的;
  • CPU是高速设备,外部IO一般都是低速设备。若通过增加CPU等待时间来向下兼容IO是得不偿失的;
  • CPU使用TTL电平,而不同外设的电气属性不同,无法和CPU很好兼容;
  • CPU内部的数据是并行的传输方式,而IO的数据串并行方式都有存在;

基于以上原因,在CPU和外设之间加了一层IO接口层。显卡、网卡、声卡、硬盘控制器等都属于IO接口。CPU可通过主板上的硬件接口和各IO控制器内部的寄存器(端口)直接相连。通过对这些控制器端口的读写操作,进而达到驱动外设工作,进行数据交互的目的。

本文的写作目的是将CPU与显卡、硬盘控制器这两种IO接口之间进行交互,进而实现在显示器中显示信息和读写IDE硬盘数据的过程进行描述和实现。

二、理论说明

1 显卡

  • 无论是液晶显示器还是CRT显示器,它们都是由显卡进行驱动和控制显示;而无论是哪种显卡,它都提供了两种可编程接口:IO端口显存
  • 显存——是显卡内部的一块内存。显卡的工作就是不断地读取显存中地内容并将其发送到显示器。
  • 实模式下的CPU的寻址能力是1MB,显存的用于文本模式显示的部分被映射到了0xB800~0xBFFFF这32KB的空间。也就说,我们对这块地址进行读写操作,便可以将数据读出或写入显存,进而在显示器中进行显示。
  • 显示器上的每个字符占两个字节:低字节是字符的ASCII码高字节为前景色和背景色设置

2 机械硬盘

  • 机械硬盘IDE——盘片存储数据,磁头读取数据;一方面盘片自转,另一方面磁头的摆动,这两种动作的配合实现了磁头读取盘片任意位置的数据;
  • 为便于管理,将整个盘面划分为多个同心的圆环,这个同心圆环就被成为磁道;而一个磁道又被划分为若干个小扇形,这个扇形就被成为扇区扇区是硬盘存储的最基本单位,每个扇区的大小为512字节
  • 扇区的定位方式有两种:
    • CHS模式——即通过柱面-磁头-扇区的方式定位;
    • LBA模式——规定扇区从0开始依次递增编号,而不必考虑实际的物理结构;(boot程序放在0扇区,loader程序放在2扇区及之后)
  • 硬盘控制器集成在了硬盘内部(这也是IDE的由来-Integrated Driver Electronics)。硬盘控制器的主要端口寄存器如下表所示:
IO端口的primary通道端口用途
0x1F0数据的读取或写入
0x1F1写操作时,用于写命令的参数;读操作时,存储读取失败状态的信息
0x1F2要读取或写入的扇区个数
0x1F3LBA的低8位 0~7bit
0x1F4LBA的中低8位 8~15bit
0x1F5LBA的中高8位 16~24bit
0x1F6低4位:LBA的高4位 25~27bit; 第4位:0–主盘,1–从盘; 第6位:1–LBA模式 0–CHS模式; 第5、7位:固定为1
0x1F7写操作时,写0xEC–硬盘识别,写0x20–读扇区, 写0x30–写扇区;读操作时,第3位:1–硬盘已准备好数据,第7位:1–硬盘正忙

三、boot程序实现写显示器和读IDE硬盘扇区

MBR只有512字节,这么小的空间无法为内核准备好环境,因此boot程序的作用是要将位于2扇区及之后的几个扇区中的loader程序读入到内存,完成初始化环境和加载内核的任务。
因为此时还未实现loader程序,但还要验证正确性。因此我们将0扇区的boot程序读入到内存的0x900区域,通过bochs的GUI调试功能,验证读硬盘操作是否成功。

  • boot汇编代码的编写
        org 0x7c00
;-------------------------------------
;主函数
;-------------------------------------
main:   mov ax, 3
        int 0x10

        mov ax, cs
        mov ss, ax
        mov ds, ax
        mov ax, 0xb800
        mov es, ax
        mov sp, 0x7c00

        mov si, info
        mov di, 0
        call print
xchg bx, bx				;bochs的魔数断点

        mov ecx, 0x0    ;要读取的LBA扇区地址(源地址)
        mov edi, 0x900  ;读取到内存的地址(目标地址)
        mov bx, 1       ;读取的扇区个数
        call rd_disk
xchg bx, bx
        jmp $


;--------------------------------------------------
;print子函数——通过将数据写入显存的方式,在显示器中显示字符
;--------------------------------------------------
print:  
    .next:
        mov al, [ss:si]
        cmp al, 0       ;1byte数据写入到al
        jz .done
        mov ah, 0x0f    ;字符的显示属性设置:黑底白字
        mov [es:di], ax
        inc si
        add di, 2
        loop .next
    .done:
    ret


;--------------------------------------------------
;读取硬盘数据子函数——读取某位置的n个扇区数据
;--------------------------------------------------
rd_disk:
        ;第一步:确定要读取的扇区个数
        mov dx, 0x1f2
        mov ax, bx
        out dx, al
        ;第二步:确定要读取扇区的LBA首地址
        mov dx, 0x1f3
        mov al, cl
        out dx, al

        mov dx, 0x1f4
        shr ecx, 8
        mov al, cl
        out dx, al

        mov dx, 0x1f5
        shr ecx, 8
        mov al, cl
        out dx, al

        ;第三步:确定LBA的高4位地址和配置相关模式
        mov dx, 0x1f6
        shr ecx, 8
        and cl, 0x0f    ;低四位有效
        mov al, 0b1110_0000 ;LBA模式--主盘--LBA的2427位地址
        or al, cl
        out dx, al

        ;第四步:设置读硬盘命令
        mov dx, 0x1f7
        mov al, 0x20
        out dx, al

        ;第五步:确定硬盘的当前状态(是否忙碌)
    .busy:
        mov dx, 0x1f7
        in al, dx
        nop
        nop
        nop     ;短暂延时
        and al, 0b1000_1000
        cmp al, 0b0000_1000
        jnz .busy

        ;第六步:若硬盘空闲,则读取n个扇区数据
        mov ax, bx
        mov dx, 256
        mul dx      ; ax = bx * dx
        mov cx, ax  
        mov dx, 0x1f0
    .read_s:
        in ax, dx
        mov [ds:edi], ax
        add edi, 2
        loop .read_s

    ret

info: db "HELLO WORLD! I am gnix!", 0
times 510-($-$$) db 0
dw 0xaa55
  • 在bochs的配置文件bochsrc中开启魔数断点功能
    在这里插入图片描述
  • 我们在print函数之后和rd_disk函数之后添加断点。可预见的结果是:
  • 当执行到第一次断点时,0x7c00为起始地址的512字节存放着boot程序代码,同时bochs显示器中显示字符;
  • 当执行到第二次断点时,0x900为起始地址的512字节也存放boot程序(我们在代码中设置的是将0扇区,即boot程序加载到此处)。
    在这里插入图片描述
    上图为第一次断点的执行结果,与猜想一相符。

在这里插入图片描述
上图为第二次断点的执行结果1,显示的初始地址为0x9002,其之后的512字节内存有显示字符3,说明boot代码已成功从硬盘的0扇区加载到内存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值