Linux 0.11 main:初始化工作

1,281 阅读5分钟

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)内存划分为几块
  • 实际内存大小不同,划分不同

mm-arrange.png


初始化 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.png

所以初始化时,所有页设置为 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:设置中断门

gate.jpg


初始化块设备、字符设备

// 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:键盘、显示器

  1. 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
  2. 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

buffer-init.png

  • 初始化 header 和 buf 块:两头往中间走(左->建头,右<-建块)相遇即止。
  • 初始化 hash_table[307]:全部置为 NULL

(跑起来之后:哈希表 + 双向链表 -> LRU)


读写硬盘不能直接访问硬件,要过 buffer:

buffer.png

  • 读块时:先找 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]]