8)main

main

在完成了前面的所有准备后,进入了操作系统核心代码,继续跟随着《Linux源码趣读》,对main函数一天探究竟:

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
// init/main.c
void main(void) {
ROOT_DEV = ORIG_ROOT_DEV;
drive_info = DRIVE_INFO;
memory_end = (1<<20) + (EXT_MEM_K<<10);
memory_end &= 0xfffff000;
if (memory_end > 16*1024*1024)
memory_end = 16*1024*1024;
if (memory_end > 12*1024*1024)
buffer_memory_end = 4*1024*1024;
else if (memory_end > 6*1024*1024)
buffer_memory_end = 2*1024*1024;
else
buffer_memory_end = 1*1024*1024;
main_memory_start = buffer_memory_end;

mem_init(main_memory_start,memory_end);
trap_init();
blk_dev_init();
chr_dev_init();
tty_init();
time_init();
sched_init();
buffer_init(buffer_memory_end);
hd_init();
floppy_init();

sti();
move_to_user_mode();
if (!fork()) {
init();
}

for(;;) pause();
}

第一部分:参数取值与计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// init/main.c
void main(void) {
ROOT_DEV = ORIG_ROOT_DEV;
drive_info = DRIVE_INFO;
memory_end = (1<<20) + (EXT_MEM_K<<10);
memory_end &= 0xfffff000;
if (memory_end > 16*1024*1024)
memory_end = 16*1024*1024;
if (memory_end > 12*1024*1024)
buffer_memory_end = 4*1024*1024;
else if (memory_end > 6*1024*1024)
buffer_memory_end = 2*1024*1024;
else
buffer_memory_end = 1*1024*1024;
main_memory_start = buffer_memory_end;
...
}

包括根设备 ROOT_DEV,之前在汇编语言中获取的各个设备的参数信息 drive_info,以及通过计算得到的表示内存边界的值:

  • main_memory_start、main_memory_end
  • buffer_memory_start、buffer_memory_end

设备参数信息是 setup.s 这个汇编程序调用 BIOS 中断获取的各个设备的信息,并保存在约定好的内存地址 0x90000 处。

第二部分:初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// init/main.c
void main(void) {
...
mem_init(main_memory_start,memory_end);
trap_init();
blk_dev_init();
chr_dev_init();
tty_init();
time_init();
sched_init();
buffer_init(buffer_memory_end);
hd_init();
floppy_init();
...
}

这段代码非常规整,但需要逐个击破,因为每一个 init 都可能包含着操作系统某个模块的运作秘密:

  • mem_init(main_memory_start, memory_end);
    • 初始化内存管理系统。这包括设置可用内存区域,初始化内存分配器等。main_memory_startmemory_end定义了可用内存的范围。
  • trap_init();
    • 初始化中断和异常处理。设置中断向量表,初始化中断处理程序。这对于处理硬件中断和异常是必不可少的。
  • blk_dev_init();
    • 初始化块设备。块设备是指以块为单位进行数据传输的设备,比如硬盘和软盘。这一步通常包括注册块设备驱动程序和初始化相关数据结构。
  • chr_dev_init();
    • 初始化字符设备。字符设备是指以字符为单位进行数据传输的设备,比如键盘、鼠标和串口设备。这一步通常包括注册字符设备驱动程序和初始化相关数据结构。
  • tty_init();
    • 初始化终端设备(TTY)。这是字符设备的一种,用于处理终端输入输出。TTY是Unix/Linux系统中重要的设备,负责与用户进行交互。
  • time_init();
    • 初始化时间和定时器。这一步通常包括设置系统时钟,初始化定时器中断,以便操作系统能够进行时间管理。
  • sched_init();
    • 初始化调度系统。调度系统负责管理进程的执行,分配CPU时间片。包括设置调度队列、初始化调度算法等。
  • buffer_init(buffer_memory_end);
    • 初始化缓冲区管理系统。缓冲区管理用于暂存数据,提升I/O操作的效率。buffer_memory_end定义了缓冲区内存的结束地址。
  • hd_init();
    • 初始化硬盘设备。这一步通常包括检测硬盘,设置硬盘控制器,准备硬盘驱动程序以便进行读写操作。
  • floppy_init();
    • 初始化软盘设备。类似于硬盘初始化,这一步包括检测软盘驱动器,设置软盘控制器,准备软盘驱动程序以便进行读写操作。

第三部分:切换用户态

1
2
3
4
5
6
7
8
9
10
// init/main.c
void main(void) {
...
sti();
move_to_user_mode();
if (!fork()) {
init();
}
...
}
  1. 开启中断:通过 sti() 使能中断,系统可以响应硬件中断请求。
  2. 切换到用户模式:通过 move_to_user_mode() 将当前进程切换到受限的用户模式,保护内核空间的安全。
  3. 创建子进程:通过 fork() 创建一个新进程。在父进程中,fork() 返回子进程的PID;在子进程中,fork() 返回0。
  4. 初始化进程:在子进程中,执行 init() 函数,进一步完成系统的初始化工作,并启动用户态的各种服务和进程。

这个 init 函数是在一个新的进程里执行的,我们把这个进程叫做进程 1。

这个 init 函数会设置终端的标准 IO,并且又创建出一个执行 shell 程序的进程,用来接受用户的命令,这个新创建的进程叫做进程 2。

在这里我们就可以不断输入命令,交给操作系统去执行了,而操作系统最大的作用,就是如此

第四部分:死循环

1
2
3
4
void main(void) {
...
for(;;) pause();
}

如果没有任何任务可以运行,操作系统会一直陷入后面这个死循环。


8)main
https://carl-5535.github.io/2024/08/05/Linux0.11/8)main/
作者
Carl Chen
发布于
2024年8月5日
许可协议