Process Lifecycle

update Sep 18, 2017 23:31

This note is for the 3rd class of Tufts comp111 2017 fall.

这节课主要介绍了一些有关 process 的内容,课堂上老师提供了很多不错的示例程序,但是基本上,需要先对一些 system function 的用法和原理有了解。

Mode Switching

在 interrupt stage, 处理器查看有无 pending interrupt(signal),如果没有,则进入 fetch stage 开始执行当前进程的下一条指令;如果有 pending interrupt,processor 做如下事情:

  1. 将pc(program counter)设为 interrupt handler 的 starting address;

  2. 从 user mode 进入 kernel mode,从而使得 interrupt processing 变得 privileged;

另外,开始执行 handler 时,已经将process的context存入了 process control block。(这里的context需要保存所有可能被 handler 执行所影响的内容,包括 pc, processor registers, stack information);

因为 system call 会造成 mode switching,需要改变context,耗时,所以例如在进行写操作的时候,我们使用 buffered printf 而不是直接 write(),这样可以减少 system call 的数量。

Changing Process State

Mode switch 不一定需要更改process的状态,但是进行process state changing 的步骤比较复杂:

  1. 保存 processor contex,包括 pc 和其他 regesters;

  2. 更新 process control block 中当前 Running 状态的进程信息,包括更改 state,更新离开 Running sate 的原因等等;

  3. 将 process control block 移动到合适的queue中(Ready, Blocked on Event i, Ready/Suspend);

  4. 选择另一个process执行;

  5. 更新选择的 process 的 control block,把 state 设为 Running;

  6. 更新 memory management data structure;

  7. 将 context of processor 恢复到刚选中的 process 离开 Running 时候的状态;

关于 SIGCHLD

Linux 中默认对于 SIGCHLD 是 ignore 的,我们可以通过自定义 signal handler 来实现异步 reap 子进程结束码的操作。但是需要注意,如果短时间内有大量 SIGCHLD 同时到达, 系统不会对其进行排队,而是直接丢弃,这也是为什么在 handler 中必须使用 while loop + WNOHANG option 进行 waitpid 的操作。如果不这么做,就有一定概率一些 SIGCHLD 会被丢弃,从而产生 zombie 进程。

但是,如果我们在 handler 中使用 while loop + waitpid(WNOHANG),相当于每次接收到 SIGCHLD 的时候都会进入循环,直到 waitpid(WNOHANG) 返回 0 为止,此时可以确定当前没有遗留的 zombie。

另外要注意,wait 函数和 SIGCHLD 是没有关系的,SIGCHLD 只是用来触发 handler 调用 wait。即使之前有 SIGCHLD 因为拥挤被抛弃了,循环调用 wait 的时候,仍然可以处理掉。

关于 fork()

fork() 会将当前进程分裂为两个,并且child会继承parent几乎所有的属性,include pc and buffer, 这也是为什么 child 会在 fork() 的位置继续执行,而且在 fork 之前 buffer 内没有 flush 的内容最终会被 print 两遍的原因。

如果 parent 先死,则其所有child(包括死后产生的zombie)都会被 pid 为 1 的 init 收养,最终被回收。

如果 child 先死,parent 需要通过 直接wait 或者调用 signal(SIGCHLD, handler) 的方法处理死去的孩子,否则的话孩子会变成 zombie. 如果parent持续产生子进程而又不处理,就会产生很多僵尸进程占满 process table,就会出现 resource leak。

因为 process switch 会需要时间,在不涉及 I/O 操作的时候,fork 很多子进程分别工作会比直接逐一操作更慢。但是在涉及到大量 I/O 访问的情况下,比如 web server,fork 就很有好处。

关于 exec()

exec() 会将当前进程的执行代码替换为exec要执行的代码,但是不改变pid,同时,之前设置的环境变量和fd之类的context会保持不变。

关于 Linux 环境变量

这里arrow-up-right 有一篇很好的文章。

Small Programs in Class

running a "cat" command in the foreground with explicit wait:

output:

Example: running a "cat" command in the background with implicit wait:

output:

running a user-typed command in the foreground without arguments:

这个程序可以类似于shell一样,新建child process运行用户的程序,parent则等待,之后重复过程。

running a user-typed command in the background without arguments:

这是主程序,用户可以输入一个可执行文件,然后会新建子进程执行,同时parent并不wait,当子程序结束的时候,SIGCHLD 会被 reaper 处理;

这是用来测试的可执行文件的代码:

output:

Test for orphan process

output

Test for zombie process (僵尸孩子)

使用 ps aux | grep 'Z' 可以检查zombie process;

output:

Test for buffer and fork

Output(don't flush)

flush: