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
|