main 函数开始执行:继续做各种初始化。
ch2. init/main.c
看 main() 函数的代码:
设置根设备、硬盘
ROOT_DEV = ORIG_ROOT_DEV;
drive_info = DRIVE_INFO; // 复制0x90080处的硬盘参数
- 拿 boot 时写的[[机器系统数据]](见 0x90 开头的 -> 想机器系统数据)
- 写一份到 C 中
规划物理内存
memory_end = (1<<20) + (EXT_MEM_K<<10); // 内存大小=1Mb + 扩展内存(k)*1024 byte
memory_end &= 0xfffff000; // 忽略不到4kb(1页)的内存数
if (memory_end > 16*1024*1024) // 内存超过16Mb,则按16Mb计
memory_end = 16*1024*1024;
if (memory_end > 12*1024*1024) // 如果内存>12Mb,则设置缓冲区末端=4Mb
buffer_memory_end = 4*1024*1024;
else if (memory_end > 6*1024*1024) // 否则若内存>6Mb,则设置缓冲区末端=2Mb
buffer_memory_end = 2*1024*1024;
else
buffer_memory_end = 1*1024*1024; // 否则设置缓冲区末端=1Mb
main_memory_start = buffer_memory_end;
- 把全部(MAX 16 MB)内存划分为几块
- 实际内存大小不同,划分不同
初始化 RAMDISK
#ifdef RAMDISK
main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
初始化 RAMDISK:把一块内存当硬盘用。
- 用
rd_init(kernel/blk_drv/randisk.c)初始化之:- 设备表 += 虚拟盘的 handler
- 内存清零
初始化主内存
// main
mem_init(main_memory_start,memory_end);
主内存初始化(mm/memory.c)
-
主内存:
LOW_MEM=1MB->end_mem=16MB- 从 1MB 开始管理
- 前面的不在 kernel 内存管理(给用户程序:分配、释放)的范围
-
初始化:
- 所有页的
mem_map标为USED - 算可用的页号(start_mem 开始,到 end_mem)
- 将可用的页
mem_map标为 0:可用
- 所有页的
内存管理:OS 给 app 分配内存——一次一个页
app malloc/free:
- 当前页不够,则 sys call:向系统要一个页:粒度为页
- 在 overlay 管理获得的页:粒度为字节
- 有空下来不用了的页:还给 OS
OS 用 mem_map 记录页的「引用计数」:记录这页给多少人用了:
所以初始化时,所有页设置为 mem_map[i] = USED = 100,表示被 100 个进程占用,但 Linux 0.11 最多 64 个进程,即不可用。
初始化 trap
// main
trap_init(); // kernel/traps.c
初始化 trap:
- 中断:from hw,走中断门:不查 CPL
- 异常:from trap,走 trap 门:查 CPL
- 初始化:
- 把各种 handler 挂到 IDT 上
- handler:中断服务程序,写在 kernel/asm.s、kernel/system_call.s
- 用 set_xxx_gate 函数(include/asm/system.h)完成挂的工作
- 就是拼接制造 IDT 表项
- 然后写到 idt 中,就是 boot/head.s 建的那个,在 boot/head.h 引入了 C。
GATE:IA32 从用户(PL 3)跳内核(PL 0)的机制
- 直接跳不行:可能未经授权访问他人
- 只能走固定的门(中断):
- 接受授权检查,再执行特定功能
- 不确定性 -> 确定
set_xxx_gate:设置中断门
初始化块设备、字符设备
// main
blk_dev_init(); // kernel/blk_drv/ll_rw_blk.c
chr_dev_init(); // kernel/chr_drv/tty_io.c
- 块设备:dev 分小块,每块有块号,独立、随机 r/w:访问要过缓冲区
- 字符设备:char stream I/O
初始化块设备:
-
读写块设备(硬盘、软盘)不是直接访问硬件的:都要走缓冲区:
进程 <---------- ❌ ---------> 块设备 进程 <--r/w--> 缓冲区 <--同步--> 块设备 └─> 用 请求项 控制- 用一个 request 表关联设备块与缓冲块
-
初始化块设备:
- 定义块沟通请求项表:request
- 所有表项置为空闲(
dev=-1)、互不挂接(next=NULL)
初始化字符设备:
- 空函数:为以后做准备
初始化 tty
// main
tty_init(); // kernel/chr_drv/tty_io.c
初始化 tty:键盘、显示器
rs_init(chr_drv/serial.c):设置串口 1、2- 设中断,挂到 IDT:
rs{1,2}_interrupt(rs_io.s,看不懂):- 收字 -> 缓冲:read_q
- 发字 -> 设备:write_q
- 设硬件:通信的波特率啥的
- 设屏蔽:允许 8259A 中断:
- IRQ3:串口1
- IRQ4:串口2
- 设中断,挂到 IDT:
con_init(console.c):设置控制台- console = 显示器 + 键盘
- 总之就是设置显示器、键盘(具体实现看不懂)
- 设置好之后就可以:
- 用
con_write()从write_q(写缓冲队列)拿字打到屏幕上 - 响应敲键盘的中断
- 用
初始化时间
// main
time_init(); // 就在 main.c:main 函数上面一些
设置开机启动时间:
- 从 CMOS 读:
CMOS_READ- 依次读取:秒分时日月年
- 但 CMOS 慢,可能不一致:一年的最后一天最后一秒(2022/12/31 23:59:59)开机,读年的时候还是前一年(2022/12/31),还没读到秒已经下一年了(2023/01/01),不一致(2022/12/01 00:00:00)
- 所以读两次秒:秒分时日月年秒,一开始的秒和最后一个秒不一致就全部重新读,直到一致
- 把 CMOS 拿到的 BCD 码转为正常的二进制补码:
BCD_TO_BIN - 写到
startup_time变量:1970/01/01 至今的秒数
初始化调度程序
// main
sched_init(); // kernel/sched.c
初始化调度程序以及「进程 0」,这里和后面一节 [[3.创建进程0]] 关系紧密,就提到后面写了。
初始化缓冲区
// main
buffer_init(buffer_memory_end); // fs/buffer.c
初始化块设备缓冲区:buffer
- 初始化 header 和 buf 块:两头往中间走(左->建头,右<-建块)相遇即止。
- 初始化
hash_table[307]:全部置为 NULL
(跑起来之后:哈希表 + 双向链表 -> LRU)
读写硬盘不能直接访问硬件,要过 buffer:
- 读块时:先找 buffer 中有无:
(dev^block)%307
初始化硬盘、软盘
// main
hd_init(); // 硬盘初始化,kernel/blk_drv/hd.c
floppy_init(); // 软驱初始化,kernel/blk_drv/floppy.c
初始化硬盘:
blk_dev[]: 0 1 2 3 4 5 6
块设备表 no-dev mem fd hd ttyx tty lp
硬盘
- 设
blk_dev[3]的读写请求函数为do_hd_request - 设
0x2E中断:hd_interrupt - 允许硬盘硬件发出中断信号
初始化软盘基本同上。
开中断
// main
sti();
开中断:中断处理都设置好了。
之后的事:关于进程
虽然还是在 main 函数中,但和现在这些工作有区别了:
- [[3.创建进程0]]
- [[4.fork创建进程1]]
- [[5.after-fork-0-schedule]]
- [[6.after-fork-1-init]]