Linux 0.11 源码

学习资料:

第一部分:进入内核前的苦力活

1.1 最开始的两行代码

图片

上图中启动区的定义:只要硬盘中的0盘0道1扇区的512个字节的最后两个字节分别是0x55和0xaa,BIOS就认为它是启动区。

image-20220222144214631

汇编代码bootsect.s 经过编译成二进制文件,存放在启动区的第一扇区。

图片

随后就会如刚刚所说,由 BIOS 搬运到内存的 0x7c00 这个位置,而 CPU 也会从这个位置开始,不断往后一条一条语句无脑地执行下去。

图片

ds:16位的段寄存器,具体表示 数据段寄存器,在内存寻址时充当段基址的作用。

1
2
3
mov ax, [0x0001] 
相当于
mov ax, [ds:0x0001]

为什么是 0x7c00呢?

为什么主引导记录的内存地址是0x7C00?

0x7C00的由来:

1981年8月,IBM公司最早个人电脑IBM PC 5150搭配的操作系统是86-DOS。这个操作系统需要的内存最少是32KB。内存地址从0X0000开始编号,32KB内存就是0x0000 ~ 0x7FFF

8088芯片本身占用0x0000 ~ 0x03FF用来保存各种中断处理程序的储存位置。(主引导记录本身就是中断信号INT 19h的处理程序。)因此,内存剩下 0x0400 ~ 0x7FFF可以使用。

**为了把尽量多的连续内存留给操作系统,主引导记录就被放到了内存地址的尾部。**由于一个扇区是512字节,主引导记录本身也会产生数据,需要另外留出512字节保存。所以, 它的预留位置变成了:

1
0x7FFF - 512 - 512 + 1 = 0x7C00

计算机启动后,32KB内存的使用情况如下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
+--------------------- 0x0
| Interrupts vectors
+--------------------- 0x400
| BIOS data area
+--------------------- 0x5??
| OS load area
+--------------------- 0x7C00
| Boot sector
+--------------------- 0x7E00
| Boot data/stack
+--------------------- 0x7FFF
| (not used)
+--------------------- (...)

Master Boot Record,MBR 主引导记录: 拥有BIOS控制权的存储设备的第一个扇区 最前面的512个字节计算机是如何启动的

主引导记录结构:

  • 第1-446字节:调用操作系统的机器码;
  • 第447-510字节:分区表(Partition table); 【作用:将硬盘分成若干个区】
  • 第511-512字节:主引导记录签名(0x55和0xAA

计算机启动过程:

  1. 通电
  2. 读取ROM里面的BIOS,用来检查硬件
  3. 硬件检查通过
  4. BIOS根据指定的顺序,检查引导设备的第一个扇区(即主引导记录),加载在内存地址 0x7C00
  5. 主引导记录把操作权交给操作系统

详解计算机启动过程:《X86汇编语言—从实模式到保护模式》

1.2 自己给自己挪个地儿

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
mov ax,0x07c0
mov ds,ax		;1.1节讲过这两行代码
mov ax,0x9000
mov es,ax
mov cx,#256
sub si,si
sub di,di
rep movw		;rep 表示重复执行后面的命令; movw表示复制一个字(word 16位) 其实就是不断重复地复制一个字
;重复执行多少次呢?是 cx 寄存器中的值,也就是 256 次。
;从哪复制到哪呢?是从 ds:si 处复制到 es:di 处。
;一次复制多少呢?刚刚说过了,复制一个字,16 位,也就是两个字节。
; 更直白地说:将内存地址0x7c00处开始往后的512字节的数据,原封不动复制到0x90000处

经过上面的几行指令后,

1
2
3
4
5
ds = 0x07c0
es = 0x9000
cx = 256
si = 0
di = 0

此时CPU寄存器的总图如下:

图片

将内存地址 0x7c00 处开始往后的 512 字节的数据,原封不动复制到 0x90000 处。 如下图:

图片

1
2
3
4
jmpi go,0x9000	; jmpi是一个段间跳转指令,表示跳转到 0x9000:go 处执行
go: 
  mov ax,cs
  mov ds,ax

段基址 : 偏移地址 ** 这种格式的内存地址的计算: 段基址要先左移四位,所以是跳转到0x90000 + go**这个内存地址处执行。

go是一个标签,最终编译成机器码的时候被翻译成一个值,这个值 就是go这个标签在文件内的偏移地址。

图片

到此为止,前两回的内容,其实就是一段 512 字节的代码和数据,从硬盘的启动区先是被移动到了内存 0x7c00 处,然后又立刻被移动到 0x90000 处,并且跳转到此处往后再稍稍偏移 go 这个标签所代表的偏移地址处,也就是 mov ax,cs 这行指令的位置。

1.3 做好最最基础的准备工作

接着上一节内容:

图片

1
2
3
4
5
go: mov ax,cs
    mov ds,ax
    mov es,ax
    mov ss,ax
    mov sp,#0xFF00	;sp 栈基址寄存器

回顾 CPU中的关键寄存器:

图片

cs寄存器 表示 代码段寄存器,CPU当前正在执行的代码在内存中的位置,由cs : ip 这组寄存器配合指向的,cs是基址,ip是偏移地址。

sp寄存器被赋值为 0xFF00,所以目前的栈顶地址是 ss:sp所指向的地址0x9FF00处。

到这里,操作系统的一些最最最基础的准备工作就做好了。具体如下:

  1. 代码从硬盘移到内存,又从内存挪了个地方,放在了 0x90000处;
  2. 数据段寄存器ds 和 代码段寄存器cs 此时都被设置为 0x9000,也为跳转代码和访问内存代码 奠定了同一个内存的基址地址,方便了跳转和内存访问,因为仅仅需要指定偏移地址 即可;
  3. 栈顶地址被设置为 0x9FF00,具体表现为栈段寄存器ss为0x9000,栈基址寄存器sp为0xFF00。栈是向下发展的,这个栈顶地址0x9FF00要远远大于此时代码所在的位置0x90000,所以栈向下发展就很难撞见代码所在的位置,也就比较安全。这也是为什么给栈顶地址设置为这个值的原因,其实只需要离代码的位置远远的即可。

这一部分其实就是把 代码段寄存器cs,数据段寄存器ds,栈段寄存器ss和栈基址寄存器sp分别设置好了值,方便后续使用。

再拔高一下,其实操作系统在做的事情,就是给如何访问代码,如何访问数据,如何访问栈进行了一下 内存的初步规划。其中访问代码和访问数据的规划方式就是 设置了一个基址而已,访问栈就是把栈顶指针指向了一个远离代码位置的地方而已。

图片

三种段寄存器的作用:(来自Intel手册)

图片

1.4 把自己在硬盘里的其他部分也放到内存来

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
; load the setup-sectors directly after the bootblock.
; Note that 'es' is already set up.

load_setup:
	mov	dx,#0x0000		; drive 0, head 0
	mov	cx,#0x0002		; sector 2, track 0
	mov	bx,#0x0200		; address = 512, in INITSEG
	mov	ax,#0x0200+SETUPLEN	; service 2, nr of sectors
	int	0x13			; read it
	jnc	ok_load_setup		; ok - continue
	mov	dx,#0x0000
	mov	ax,#0x0000		; reset the diskette
	int	0x13
	j	load_setup

ok_load_setup:
	...

int 0x13表示 发起0x13号中断,

【TODO】1.5 进入保护模式前的最后一次折腾内存

1.6 先解决段寄存器的历史包袱问题

1.7 六行代码就进入了保护模式

1.8 烦死了又要重新设置一边idt和gdt

1.9 Intel内存管理两板斧:分段与分页

1.10 进入main函数前的最后一跃!

总结与回顾

第二部分:大战前期的初始化工作

2.11 整个操作系统就20几行代码

2.12 管理内存前先划分处三个边界值

2.13 主内存初始化 mem_init

2.14 中断初始化 trap_init

2.15 块设备请求项初始化 blk_dev_init

2.16 控制台初始化 tty_init

2.17 时间初始化 time_init

2.18 进程调度初始化 sched_init

2.19 缓冲区初始化 buffer_init

2.20 硬盘初始化 hd_init

总结与回顾

第三部分:一个新进程的诞生

3.21 新进程诞生全局概述

3.22 从内核态切换到用户态

3.23 如果让你来设计进程调度

第四部分 shell程序的到来

第五部分 从一个命令的执行看操作系统各模块的运作

第六部分 操作系统哲学与思想