import IPython, urllib; print("[]("+"https://gitee.com/eatcosmos/linux_kernel_012/blob/books/notes/" + urllib.parse.quote("/".join(IPython.extract_module_locals()[1]["__vsc_ipynb_file__"].split("/")[-1:]))+")")
[](https://gitee.com/eatcosmos/linux_kernel_012/blob/books/notes/1.3.2%20%E5%BC%80%E6%9C%BA%E5%BC%95%E5%AF%BC%E7%A8%8B%E5%BA%8F.ipynb)
BIOS (Basic Input/Output System,基本输入 / 输出系统) 是计算机接通电源后执行的第一个程序。BIOS 首先会做硬件检查,判断是否满足计算机运行的条件,例如,内存条如果没插好,BIOS 会提示错误信息,某些情况下主板会发出蜂鸣声警告等。此时BIOS就相当于是一个操作系统,它负责启动计算机,并加载操作系统。
做完硬件检查之后要确定启动顺序,当选中某块磁盘之后,控制权限就会交给这块磁盘上的 MBR (Master Boot Record,主引导记录)。MBR 位于磁盘/floppya/的第一个扇区,一个 MBR 512 字节,其中最后两个字节是 0x55 和 0xAA,表明这个设备可以启动。
ld -m elf_x86_64 -Ttext 0x0 -s --oformat binary -o linux.img bootsect.o
回顾 1.3.1 节的 bootsect.S 的 1d 选项,-Ttext0x0 的含义正是将目标文件 bootsect.o 的代码段放到 linux.img 的开头,也就是在第一个扇区。同时我们可以用如下命令查看第一个扇区的最后两个字节为 0x55AA,可以用来启动。
%%bash
xxd -s 510 linux.img # 解释参数:
# xxd 缩写自 extract hex dump,是 Linux 系统中一个非常有用的十六进制转储工具。
# -s 510 表示从第 510 个字节开始显示
# linux.img 表示要显示的文件
# 输出结果:
# 000001fe: 55aa U.
000001fe: 55aa U.
!apt install bsdmainutils
Reading package lists... Done
Building dependency tree
Reading state information... Done
Suggested packages:
wamerican | wordlist whois vacation
The following NEW packages will be installed:
bsdmainutils
0 upgraded, 1 newly installed, 0 to remove and 43 not upgraded.
Need to get 181 kB of archives.
After this operation, 652 kB of additional disk space will be used.
Get:1 http://mirrors.aliyun.com/ubuntu focal/main amd64 bsdmainutils amd64 11.1.2ubuntu3 [181 kB]
Fetched 181 kB in 0s (1053 kB/s) [0m[33m
debconf: delaying package configuration, since apt-utils is not installed
7[0;23r8[1ASelecting previously unselected package bsdmainutils.
(Reading database ... 23484 files and directories currently installed.)
Preparing to unpack .../bsdmainutils_11.1.2ubuntu3_amd64.deb ...
7[24;0f[42m[30mProgress: [ 0%][49m[39m [..........................................................] 87[24;0f[42m[30mProgress: [ 20%][49m[39m [###########...............................................] 8Unpacking bsdmainutils (11.1.2ubuntu3) ...
7[24;0f[42m[30mProgress: [ 40%][49m[39m [#######################...................................] 8Setting up bsdmainutils (11.1.2ubuntu3) ...
7[24;0f[42m[30mProgress: [ 60%][49m[39m [##################################........................] 8update-alternatives: using /usr/bin/bsd-write to provide /usr/bin/write (write) in auto mode
update-alternatives: warning: skip creation of /usr/share/man/man1/write.1.gz because associated file /usr/share/man/man1/bsd-write.1.gz (of link group write) doesn't exist
update-alternatives: using /usr/bin/bsd-from to provide /usr/bin/from (from) in auto mode
update-alternatives: warning: skip creation of /usr/share/man/man1/from.1.gz because associated file /usr/share/man/man1/bsd-from.1.gz (of link group from) doesn't exist
7[24;0f[42m[30mProgress: [ 80%][49m[39m [##############################################............] 8
7[0;24r8[1A[J
%%bash
ls -l linux.img # -rwxr-xr-x 1 root root 512 Feb 13 15:17 linux.img
hexdump -C linux.img | tail -n 2
# 0x00000000 - 0x000001ff (512字节) 是有效数据行
#000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.| 最后一行有效数据行(0x1f0 行)
# 00000200 下一行地址行(0x200 行)因为 hexdump 的输出在文件结束后会显示下一个地址行
xxd -s 510 linux.img # 从0个开始数数
xxd -s 511 linux.img
-rwxr-xr-x 1 root root 512 Feb 13 15:17 linux.img
000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa |..............U.|
00000200
000001fe: 55aa U.
000001ff: aa .
为什么 bootsect.S boot_flag: .word 0xaa55 而不是 0x55aa?
在文件层面我们认为开头是低地址,也就是类似编辑器输入,认为地址是越来越高的。
所谓小端模式:小地址放小地址/低地址。
这个问题涉及到计算机的字节序(Endianness)问题。让我们详细解释:
- 正确的引导扇区标志:
- 主引导记录(MBR)的最后两个字节必须是 0x55 0xAA(按顺序排列)
- 在磁盘上的字节顺序是:第510字节是 0x55,第511字节是 0xAA
-
代码中的定义:
boot_flag: .word 0xaa55 ; 这里实际存储的是 0x55 0xAA -
为什么这样写:
- 这是由 x86 架构的小端字节序(Little-Endian)决定的
- .word 指令在内存中的存储顺序是低位字节在前,高位字节在后
- 当使用 .word 0xAA55 时:
- 低字节是 0x55 (存储在低地址)
- 高字节是 0xAA (存储在高地址)
- 在磁盘镜像中的实际顺序就是 0x55 0xAA
- 验证方法: hexdump -C linux.img | tail -n 1
输出示例: 000001f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa 可以看到最后两个字节确实是 0x55 0xAA
-
错误写法示例: 如果写成 .word 0x55aa,实际存储顺序会是: 低字节 0xAA (地址510) 高字节 0x55 (地址511) 最终得到 0xAA 0x55,这将导致引导失败
-
历史原因:
- 这个约定源于 IBM PC 5150 (1981年) 的设计
- BIOS 会检查这两个特定位置的字节值
- 已成为所有 x86 兼容 BIOS 的标准要求
- 因此,虽然代码中写的是 0xAA55,但由于小端存储的特性,实际在磁盘上的排列顺序正是 BIOS 要求的 0x55 0xAA。这是汇编器自动处理字节序的结果。其实这个存储是符合直觉的。
1981 年 8 月,IBM 公司最早的个人电脑 IBM PC 5150 上市,使用的是 Intel 的第一代个人电脑芯片 8088。8088 芯片本身需要占用 0x0000~0x03FF(1KB) 的地址空间,用来保存各种中断处理程序(引导程序本身就是中断信号 INT 19h 的处理程序)。所以,内存只剩下 0x0400 至 0x7FFF 的地址空间可以使用。为了把尽量多的连续内存留给操作系统,引导程序就被放到了内存地址的尾部。因为一个扇区是 512 字节(十六进制为 0x200),引导程序本身也需要一段内存保存数据,系统就另外给它留出 512 字节。所以,引导扇区的预留位置就变成了 0x7FFF - 0x200 - 0x200 + 0x1 = 0x7C00。
0x03FF转换为十进制 0x0400转换为十进制=416^2=1024 0x8000转换为十进制=816^3=32768B 16*16=256
在 1.3.1 节的例子中,bootsect.S 中定义了 BOOTSEG = 0x7C0,汇编代码将被加载到内存地址 0x7C00 执行。需要注意的是,早期的 8086 处理器的寄存器都是 16 位的,地址线是 20 位**,这就意味着 CPU 的寻址能力是 1MB(2 的 20 次方),但是只采用一个寄存器只能寻址 64KB(2 的 16 次方),所以它采用了基地址加偏移的方式寻址,也就是使用两个寄存器的值拼接一个真实的物理地址。它的计算方式是物理地址等于基地址左移 4 位加上偏移值,例如下面的代码:
mov %ds:(%ax), %bx # movw和mov区别?
上述代码表示以 ds 寄存器为基地址,以 ax 寄存器为偏移值计算一个地址,然后取这个内存地址处的值送入 bx 寄存器。其中,真实物理地址的值是 ds 的值左移 4 位再加上 ax 寄存器的值。
movw $BOOTSEG, %ax ; ax = 0x7C0
movw %ax, %ds ; ds = 0x7C0
movw $msg, %bx ; bx = msg的偏移地址(假设是 0x0005)
; 实际物理地址 = 0x7C0 << 4 + 0x0005 = 0x7C05