本文是上一博文 Linux 内核学习笔记:进程 2 的创建及执行(第 2 部分)续篇。
创建 update 进程
在上一博文 Linux 内核学习笔记:进程 2 的创建及执行(第 2 部分)中,我们提到在进程 2 刚开始执行时,进程 2 关闭了标准输入设备文件 /dev/tty0 并用 /etc/rc 文件代替之。现在是 /etc/rc 文件占据了进程描述符 task_struct 的 filp[20] 的第 1 项,即 0 项。
创建 update 进程
shell 程序开始执行后,要读取标准输入设备文件上的信息,即进程描述符的 filp[20] 的第 1 项。因为标准设备输入文件 /dev/tty0 已经被 /etc/rc 替换,所以 shell 程序开始读取 /etc/rc 文件内容。
/etc/rc 文件的内容可以通过运行 Linux 0.11 系统来得到:
1 | /etc/update & |
根据 /etc/rc 文件的第一条命令 /etc/update &
,shell 程序先创建一个新进程。这个新进程的进程号是 3,任务号也是 3(之前已有进程 0、1、2)。创建完毕后,加载 update 程序,并最终将执行权转交给 update 进程,由它去执行。关于进程创建和程序加载,请参考之前博文:
- 进程创建 -> Linux 内核学习笔记:进程 1 的创建及执行(第 1 部分)
- 程序加载 -> Linux 内核学习笔记:进程 2 的创建及执行(第 2 部分)
update 进程作用
对于 update 进程,《Linux 内核设计的艺术》有一个简要的阐述:
update 进程有一项很重要的任务:将缓冲区中的数据同步到外设(软盘、硬盘等)上。由于主机与外设的数据交换速度远低于主机内部的数据处理速度,因此,当内核需要往外设上写数据的时候,为了提高系统的整体执行效率,并不把数据直接写入外设上,而是先写入缓冲区,之后,根据实际情况,再将数据从缓冲区同步到外设。
每个一段时间,update 进程就会被唤醒,把数据往外设上同步一次,之后这个进程会被挂起,即被设置为可中断等待状态,等待着下一次被唤醒后继续执行,如此周而复始。
updata 进程执行后,并没有同步任务,于是该进程被挂起,系统进行调度,最终切换到 shell 进程执行。
此时,各进程状态如下图所示:
图片来源:《Linux 内核设计的艺术》
切换到 shell 进程(原进程 2)
shell 程序读取 /etc/rc 文件的第一条命令并创建 update 进程后,开始读取并处理该文件的第二条命令 echo "/dev/hd1" > /etc/mtab
,即将字符串 “/dev/hd1” 写入虚拟盘中的 /etc/mtab 文件。执行完毕后,shell 程序会继续循环调用 read 函数读取 /etc/rc 文件上的内容。read 函数对应的系统调用函数是 sys_read。
由于 /etc/rc 是普通文件,读取结束后,read 函数返回 -ERROR
。这个返回值将导致 shell 进程退出。退出将执行 exit 函数:
1 | // init/main.c --------------------------------- |
任务调度
tell_father 函数执行完毕后,开始执行 schedule 函数,进行任务调度。此时,各进程状态如下图所示:
图片来源:《Linux 内核设计的艺术》
虽然没有进程处于就绪态,但 schedule 函数对信号的检测,影响到了进程切换:
1 | // kernel/sched.c ----------------------------- |
处理僵死子进程
之前从进程 1 切换到进程 2 是在执行 sys_waitpid 时,调用 schedule 函数实现的。所以从进程 2 切换回进程 1 后,会继续执行 schdule 函数,并最终回到 sys_waitpid 函数:
1 | // kernel/exit.c ------------------------------ |
回到 repeat 处继续执行后,对僵死的进程 2 进行处理:
1 | // kernel/exit.c ------------------------------ |
跳出循环
进程 1 中,sys_waitpid 函数返回的值为 2,也即 wait 函数要返回的值为 2。而进程 2 的进程号 pid = 2,所以返回 init 函数后就跳出 while 循环:
1 | // init/main.c --------------------------------- |
重建 shell 进程
进程 1 接着执行,重建 shell 进程:
1 | // init/main.c --------------------------------- |
轮转到新的进程 shell 进程执行时,shell 程序重新打开标准输入设备文件 /dev/tty0 (之前是 /etc/rc),这使得 shell 程序开始执行后,不再退出。并通过调用 rw_char 函数将 shell 进程的状态设置为可中断等待状态。这样所有的进程都处于可中断等待状态,系统再次切换到进程 0 去执行,系统实现怠速。
按《Linux 内核设计的艺术》的说法:
怠速以后,操作系统用户将通过 shell 进程提供的平台与计算机进行交互。shell 进程处理用户指令的工作原理如下:用户通过键盘输入信息,存储在指定的字符缓冲队列上。该缓冲队列上的内容,就是 tty0 文件的内容。shell 进程会不断读取缓冲队列上的信息。如果用户没有下达指令,缓冲队列就不会有数据,shell 进程将会被设置为可中断等待状态,即被挂起。如果用户通过键盘下达指令,将产生键盘中断,中断服务程序会将字符信息存储在缓冲队列上,并给 shell 进程发信号,信号将导致 shell 进程被设置为就绪态,即被唤醒。唤醒后的 shell 进程继续从缓冲队列中读取信息并处理。处理完毕后,shell 进程将再次被挂起,等待下一次键盘中断被唤醒。