操作系统实践-BIOS

  • 基本概念:https://wiki.osdev.org/BIOS
  • 所有中断列表:http://www.ctyme.com/intr/int.htm
  • IBM PC 介绍:http://classiccomputers.info/down/IBM_PS2/documents/PS2_and_PC_BIOS_Interface_Technical_Reference_Apr87.pdf
  • x86 汇编手册:
    • https://www.felixcloutier.com/x86/
    • https://en.wikibooks.org/wiki/X86_Assembly
  • x86 官方文档:https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-instruction-set-reference-manual-325383.pdf

BIOS 固化在主板上,通过中断进行调用,可以进行一些基本操作,例如输出字符串、获取键盘输入等。开机后首先执行 BIOS。

常用的 BIOS 指令

在调用 BIOS 函数之前,需要先设置 AH 或 AX(或 EAX) 寄存器,然后执行对应的 INT 指令。例如 INT 0x13, AH=0 用于重置硬盘或软盘。

INT 0x10 显示类指令

INT 0x10, AH = 1 -- set up the cursor
INT 0x10, AH = 3 -- 获取光标位置
INT 0x10, AH = 0xE -- 显示字符
INT 0x10, AH = 0xF -- get video page and mode
INT 0x10, AH = 0x11 -- set 8x8 font
INT 0x10, AH = 0x12 -- detect EGA/VGA
INT 0x10, AH = 0x13 -- 显示字符串,具体寄存器设置可以参考:http://www.ctyme.com/intr/rb-0210.htm
INT 0x10, AH = 0x1200 -- Alternate print screen
INT 0x10, AH = 0x1201 -- turn off cursor emulation
INT 0x10, AX = 0x4F00 -- video memory size
INT 0x10, AX = 0x4F01 -- VESA get mode information call
INT 0x10, AX = 0x4F02 -- select VESA video modes
INT 0x10, AX = 0x4F0A -- VESA 2.0 protected mode interface

INT 0x13 外存类指令

INT 0x13, AH = 0 -- reset floppy/hard disk
INT 0x13, AH = 2 -- read floppy/hard disk in CHS mode
INT 0x13, AH = 3 -- write floppy/hard disk in CHS mode
INT 0x13, AH = 0x15 -- detect second disk
INT 0x13, AH = 0x41 -- test existence of INT 13 extensions
INT 0x13, AH = 0x42 -- read hard disk in LBA mode
INT 0x13, AH = 0x43 -- write hard disk in LBA mode

INT 0x15 内存类指令

INT 0x12 -- get low memory size
INT 0x15, EAX = 0xE820 -- get complete memory map
INT 0x15, AX = 0xE801 -- get contiguous memory size
INT 0x15, AX = 0xE881 -- get contiguous memory size
INT 0x15, AH = 0x88 -- get contiguous memory size

INT 0x15, AH = 0xC0 -- Detect MCA bus
INT 0x15, AX = 0x0530 -- Detect APM BIOS
INT 0x15, AH = 0x5300 -- APM detect
INT 0x15, AX = 0x5303 -- APM connect using 32 bit
INT 0x15, AX = 0x5304 -- APM disconnect

INT 0x16 键盘类指令

INT 0x16, AH = 0 -- read keyboard scancode (blocking)
INT 0x16, AH = 1 -- read keyboard scancode (non-blocking)
INT 0x16, AH = 3 -- keyboard repeat rate

示例

基本概念

X86 系列在 16 位实模式下,地址转换格式是:SEG:OFFSET,通过段地址左移4位加上偏移量得到物理地址。例如,0x07c0:0x0000 对应的就是 0x07c0 << 4 + 0x0 = 0x7c00

AT&T 语法中,句点开头的是伪指令。X86 汇编基本语法:

  • .global:声明全局符号,可被其他文件引用
  • .code16:16位模式
  • .equ:定义常量
  • .=:从当前位置对应机器码字节数开始,填充到指定字节数
  • .word:定义一个字
  • ljmp:第一个参数设置 CS 寄存器,第二个参数设置 EIP 寄存器。然后跳转执行。可以清理之前的 CPU 流水线缓存。

MBR 主引导扇区最后一个字节必须是 0xaa,倒数第二个字节必须是 0x55。X86 是小端模式,低字节在低地址。
下面例子在 MBR 的512字节中,显示开机的字符串。
通过 BIOS 显示字符串参考这里:http://www.ctyme.com/intr/rb-0210.htm

示例一:读取并执行磁盘第一个扇区的代码

这个例子把可执行文件中的二进制部分提取到第一个扇区,并在扇区最后两个字节填充 0xaa55,其他地方填0。最终在屏幕上打印字符串。

start.S

.code16
.global _start

.equ BOOTSEG, 0x07c0
ljmp $BOOTSEG, $_start

_start:
    mov $0x03, %ah
    int $0x10
    mov $BOOTSEG, %ax
    mov %ax, %es
    mov $_string, %bp
    mov $0x1301, %ax
    mov $0x0007, %bx
    mov $9, %cx
    int $0x10
loop:
    jmp loop

_string:
    .ascii "hello los"

.=510

signature:
    .word 0xaa55

Makefile

先用汇编器 as 把汇编代码转为可重定位目标文件,然后通过链接器,根据链接脚本得到 ELF 可执行目标文件。最后,把 ELF 中的二进制部分复制出来即可。

all: start.bin

start.bin: start.S start.ld
    as --32 start.S -o start.o
    ld -T start.ld start.o -o start.elf
    objcopy -O binary start.elf start.bin

.PHONY= clean
clean:
    rm -f *.o *.bin *.elf

start.ld

在使用 LD 链接器时,通过 -T script_name 指定自定义的链接脚本。通过链接脚本,可以指定每个目标文件的段的信息,例如起始地址,排列顺序等。

  • *(.text):提取所有目标文件的代码段
  • /DISCARD/:要忽略的段
OUTPUT_FORMAT(elf32-i386)
OUTPUT_ARCH(i386)

SECTIONS {
    .text 0x0000 : {
        *(.text)
    }
    
    /DISCARD/ : {
        
    }
}

编译,并通过 QEMU 执行

make
qemu-system-i386 -boot a -fda start.bin

执行成功的话,会在屏幕上看到打印的字符串。

示例二:加载并执行更多扇区的代码

操作系统无法在一个扇区内放下。通常在软盘的第一个扇区放置 IPL(Initial program loader,启动程序加载器)。

BIOS 把第一个扇区的数据加载到内存后,把控制权交给扇区的第一条指令。然后这个扇区负责把操作系统加载到内存并跳转执行(仍然需要借助 BIOS 的函数)。

软盘概念

  • 磁头 header:每个盘面对应一个磁头,上面的是 0 号,下面的是 1 号
  • 柱面 cylinder:每个盘面分成多个同心圆环,每个圆环就是一个柱面,最外层是 0 号,最内层是 79 号,共 80 个
  • 扇区 sector:每个柱面分成 18 个扇区,编号 1-18。每个扇区 512Byte
  • 软盘总容量:2 个磁头 * 80 个柱面 * 18 个扇区 * 512Byte = 1440KB

BIOS 默认加载的是 0 号磁头、0 号柱面、1 号扇区。

x86 实模式的可用地址

x86寄存器分类:

  • 8个通用寄存器:EAX、EBX、ECX、EDX、ESI、EDI、ESP、EBP
  • 1个标志寄存器:EFLAGS
  • 6个段寄存器:CS、DS、ES、FS、GS、SS
  • 5个控制寄存器:CR0、CR1、CR2、CR3、CR4
  • 8个调试寄存器:DR0、DR1、DR2、DR3、DR4、DR5、DR6、DR7
  • 4个系统地址寄存器:GDTR、IDTR、LDTR、TR
  • 其他寄存器:EIP、TSC等。

其中,通用寄存器在不同模式下可以用的不一样:

32位16位8位
EAXAXAH、AL
EBXBXBH、BL
ECXCXCH、CL
EDXDXDH、DL
ESISI
EDIDI
ESPSP
EBPBP

实模式下通过 ES * 16 + BX 表示地址。因为 ES 和 BX 两个寄存器都是 16bit 的,所以最大可用地址是 1MB。

开机后,BIOS 会把第一个扇区 512B 的内容加载到内存的 0x7c00 地址处,即 0x7c00 ~ 0x7dff。从 0x7d00 开始直到 0x9fbff (1MB 处)都可以给操作系统用。

代码

除了上一个例子的 start.S,还有个 main.S。

start.S
.code16
.global _bootstart

.equ BOOTSEG, 0x07c0
ljmp $BOOTSEG, $_bootstart

_bootstart:
        mov $0x03, %ah
        int $0x10
        mov $BOOTSEG, %ax
        mov %ax, %es
        mov $_string, %bp
        mov $0x1301, %ax
        mov $0x0007, %bx
        mov $9, %cx
        int $0x10
        jmp readDisk

readDisk:
        mov $0x0800, %ax
        mov %ax, %es
        mov $0x02, %ah
        mov $1, %al
        mov $0, %ch
        mov $2, %cl
        mov $0, %dh
        mov $0, %dl
        int $0x13
        jc error
        jmp _start

error:
loop:
        jmp loop

_string:
        .ascii "hello los"

.=510

signature:
        .word 0xaa55
main.S
.code16
 .global _start

_start:
        mov $0x06, %ah
        mov $0, %al
        mov $0, %cx
        mov $2479, %dx
        mov $0x07, %bh
        int $0x10

        mov $0x03, %ah
        int $0x10
        mov $0x800, %ax
        mov %ax, %es
        mov $_string, %ax
        mov %ax, %bp
        mov $0x13, %ah
        mov $0x1, %al
        mov $0, %bh
        mov $07, %bl
        mov $13, %cx
        int $0x10

osloop:
        jmp osloop

_osstring:
        .ascii "\n main os boot"
Makefile
COBJS += 
ASOBJS += start.S main.S

all: los.img

los.img: los.elf
        objcopy -O binary los.elf los.img

los.elf: *.o
        ld -T start.ld %@ -o los.elf

%.o: %.S
        as -g --32 $(ASOBJS) -o %@

.PHONY= clean run
clean:
        rm -f *.o *.bin *.elf

run:
        qemu-system-i386 -boot a -fda los.img
start.ld
COBJS += 
ASOBJS += start.S main.S

all: los.img

los.img: los.elf
        objcopy -O binary los.elf los.img

los.elf: *.o
        ld -T start.ld %@ -o los.elf

%.o: %.S
        as -g --32 $(ASOBJS) -o %@

.PHONY= clean run
clean:
        rm -f *.o *.bin *.elf

run:
        qemu-system-i386 -boot a -fda los.img

通过 GDB 配合 QEMU 进行调试

通过 QEMU 启动内核

启动时,必须指定调试参数。此时 QEMU 会等待 GDB 指令。

qemu-system-i386 -s -S -boot a -fda los.img --nographic
  • -S 表示“freeze CPU at start up”
  • -gdb tcp::1234表示启动gdbserver,默认开启

打开 GDB 链接 QEMU

  • 打开 gdb,不带任何参数
  • 关联要调试的内核对应的 elf 文件,注意需要在编译时通过 -g 参数指定调试信息
  • 设置断点
  • 启动
root@osboxes:~# gdb
GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
For help, type "help".
Type "apropos word" to search for commands related to "word".

(gdb) file  test/x86/bios-disk/los.elf 
Reading symbols from test/x86/bios-disk/los.elf...done.

(gdb) target remote localhost:1234
Remote debugging using localhost:1234
0x0000fff0 in ?? ()

(gdb) break _start
Breakpoint 1 at 0x200: file main.S, line 5.

(gdb) c
Continuing.
q
^C
Program received signal SIGINT, Interrupt.
loop () at start.S:34
34		jmp loop
  • target remote localhost:1234:连接 QEMU 远程调试
  • break *0x7c00:设置内存地址上的断点
功能06H 功能描述:设置闹钟 入口参数:AH=06H CH=BCD码格式的小时 CL=BCD码格式的分钟 DH=BCD码格式的秒 出口参数:CF=0——操作成功,否则,闹钟已设置或时钟已停止 (8)、功能07H 功能描述:闹钟复位 入口参数:AH=07H 出口参数:无 (9)、功能0AH 功能描述:读取天数计数,仅在PS/2有效,在此从略 (10)、功能0BH 功能描述:设置天数计数,仅在PS/2有效,在此从略 (11)、功能80H 功能描述:设置声音源信息 入口参数:AH=80H AL=声音源 =00H——8253可编程计时器,通道2 =01H——盒式磁带输入 =02H——I/O通道上的"Audio In" =03H——声音产生芯片 出口参数:无 8、直接系统服务(Direct System Service) INT 00H —“0”作除数 INT 01H —单步中断 INT 02H —非屏蔽中断(NMI) INT 03H —断点中断 INT 04H —算术溢出错误 INT 05H —打印屏幕和BOUND越界 INT 06H —非法指令错误 INT 07H —处理器扩展无效 INT 08H —时钟中断 INT 09H —键盘输入 INT 0BH —通信口(COM2:) INT 0CH —通信口(COM1:) INT 0EH —磁盘驱动器输入/输出 INT 11H —读取设备配置 INT 12H —读取常规内存大小(返回值AX为内存容量,以K为单位) INT 18H —ROM BASIC INT 19H —重启动系统 INT 1BH —CTRL+BREAK处理程序 INT 1CH —用户时钟服务 INT 1DH —指向显示器参数表指针 INT 1EH —指向磁盘驱动器参数表指针 INT 1FH —指向图形字符模式表指针
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值