Linux 0.11 源码

1. 结构

1
2
3
4
5
6
7
8
9
10
11
12
linux-0.11-master/
├── Makefile # 项目构建核心文件(编译、链接等规则)
├── .gitignore # Git 忽略规则配置文件
├── cscope.files # Cscope 代码索引工具的文件列表
├── README.md # 项目说明文档
├── fs/ # 文件系统模块(文件读写、目录管理、各类文件系统实现等)
├── include/ # 全局头文件目录(内核通用宏、结构体、函数声明等)
├── lib/ # 内核通用库函数(字符串、内存操作等)
├── mm/ # 内存管理模块(物理/虚拟内存分配、页表管理等)
├── init/ # 内核初始化模块(启动流程、进程1创建等)
├── boot/ # 引导加载相关(bootsect.s、setup.s 等启动汇编代码)
└── tools/ # 辅助工具(如编译/调试脚本、磁盘镜像生成工具等)

2. 源码解读

2.1 Start

电脑主板上提前写死的固件程序 BIOS 会将硬盘中启动区的 512 字节的数据,原封不动复制到内存中的 0x7c00 这个位置,并且在哪里开始执行。
故我们需要做的,就是将内核代码编译出来,放到硬盘的启动区,即0盘0道1扇区,然后 BIOS 会将内核代码加载到内存中,并跳转到内核代码的入口处开始执行。

linux 0.11 的入口程序在boot/bootsect.s中,该程序的作用是初始化硬件设备,设置中断向量,加载 setup 模块到内存中,并跳转到 setup 模块执行。
我对其代码做详细解释, 注释进行中文转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
! SYS_SIZE 是需要加载的内核大小(单位:clicks/点击,1个click=16字节)。
! 0x3000 换算成字节是 0x30000 = 192KB(注释原写196KB为近似值),对于早期
! Linux 版本来说完全足够
SYSSIZE = 0x3000

! bootsect.s (C) 1991 Linus Torvalds
!
! bootsect.s 会被BIOS的启动程序加载到内存地址 0x7c00 处,然后将自身
! 移动到 0x90000 地址(腾出原位置空间),并跳转到新地址继续执行。
!
! 随后它会通过BIOS中断,将setup程序加载到自身后方(0x90200),将内核系统
! 加载到 0x10000 地址处。
!
! 注意!当前内核最大长度限制为 8*65536 字节=512KB。这在未来也应该足够用,
! 我希望保持代码简洁。512KB的内核空间完全够用,尤其是它不像minix系统那样
! 包含缓冲区缓存(缓冲区缓存后续会单独实现)。
!
! 这段加载程序被设计得尽可能简单,若持续出现读盘错误会进入死循环。
! 需手动重启机器。程序会尽可能一次读取整个扇区数据,因此加载速度较快。

! 声明全局符号(供链接器使用,标记各段的起始/结束位置)
.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext: # 代码段起始
.data
begdata: # 数据段起始
.bss
begbss: # 未初始化数据段起始
.text # 回到代码段

SETUPLEN = 4 ! setup程序占用的扇区数(4个扇区=2048字节)
BOOTSEG = 0x07c0 ! bootsect的初始加载段地址(换算成绝对地址:0x07c0<<4=0x7c00)
INITSEG = 0x9000 ! bootsect搬迁后的目标段地址(0x9000<<4=0x90000,避开后续加载区域)
SETUPSEG = 0x9020 ! setup程序的加载段地址(0x9020<<4=0x90200,紧跟在搬迁后的bootsect后)
SYSSEG = 0x1000 ! 内核系统加载的起始段地址(0x1000<<4=0x10000,即64KB位置)
ENDSEG = SYSSEG + SYSSIZE ! 内核加载的结束段地址(加载到此处即停止)

! ROOT_DEV:根文件系统设备号规则
! 0x000 - 与启动软盘同类型的软盘设备
! 0x301 - 第一块硬盘的第一个分区,以此类推
ROOT_DEV = 0x306 # 0x306=主设备号0x03(硬盘)+次设备号0x06(第二块硬盘的第6个分区)

entry _start ! entry 标记程序的入口点,_start 是内核的入口函数

_start:
mov ax,#BOOTSEG ! 将BOOTSEG赋值给ax寄存器 (本条指令时立即寻址指令)
mov ds,ax ! 将ax的值赋值给ds寄存器,此时ds寄存器指向BOOTSEG (ds寄存器即为数据段寄存器,指向数据段起始地址,换句话说,就是对于数据的基址寄存器)
mov ax,#INITSEG ! 将INITSEG赋值给ax寄存器,INITSEG是bootsect将要被移动到的目标地址
mov es,ax ! 将ax的值赋值给es寄存器,此时es寄存器指向INITSEG (es寄存器即为扩展段寄存器,指向扩展段起始地址,换句话说,就是对于扩展数据的基址寄存器)
mov cx,#256 ! 将256赋值给cx寄存器,cx寄存器用于循环计数
sub si,si ! 将si寄存器清零,si寄存器用于源地址指针
sub di,di ! 将di寄存器清零,di寄存器用于目标地址指针
rep ! 重复执行以下指令,cx为计数器,每次执行后cx减1,直到cx为0
movw ! 将ds:si指向的数据段地址复制到es:di指向的扩展段地址,重复cx次,这里di和si会自动增加
jmpi go,INITSEG ! 跳转到INITSEG:go,即跳转到INITSEG段中的go标签处,此时es寄存器指向INITSEG,即跳转到INITSEG:go处执行
go: mov ax,cs ! 将cs寄存器的值赋值给ax寄存器,cs寄存器即为代码段寄存器,指向代码段起始地址(注意这里cs的值为0x9000)
mov ds,ax ! 将ax的值赋值给ds寄存器,此时ds寄存器指向cs,即指向代码段起始地址
mov es,ax ! 将ax的值赋值给es寄存器,此时es寄存器指向cs,即指向代码段起始地址
! 把栈指针sp设置为#0xFF00
mov ss,ax ! 将ax的值赋值给ss寄存器,此时ss寄存器指向cs,即指向代码段起始地址
mov sp,#0xFF00 ! arbitrary value >>512