[OS]Three Easy Pieces Chapter 5

Interlude: Process API

1 The fork() System Call

fork 系统调用创建一个新的进程,新的进程几乎和原来的进程完全一样,具体来说,它返回给 fork 调用的返回值和父进程是不同的。在父进程中,返回值是子进程的PID,而在子进程中,返回值是0. 新进程不会从 main 开始执行,而是从调用 fork 的指令的下一条指令开始执行。

需要注意的是,当创建好进程之后,此时系统中就有两个就绪的进程(父进程和子进程),二者谁先运行是不确定的,我们不能对此做任何假设。

2 The wait() System Call

有时候,父进程等待子进程完成是很有必要的,可以使用 wait 或者 waitpid 系统调用来实现。父进程调用 wait 阻塞等待子进程执行并完成,只有在子进程退出后,父进程才会从 wait 系统调用返回,继续执行。

3 Finally, The exec() System Call

exec 系统调用用另一个可执行程序的代码和静态数据覆盖当前进程的代码和可执行数据,堆栈等内存空间都被重新初始化,之后操作系统运行这个新的程序。

成功的 exec 调用不会返回。

4 Why? Motivating The API

之所以要这样设计 forkexec ,是因为这种设计可以允许shell在 fork 之后,exec 之前执行一些代码。这样可以让我们简单的实现一些有用的特性。

例如IO重定向(wc p3.c > p3.output),我们可以在子进程执行 exec 之前,关闭标准输出,然后打开p3.output文件,这样之后子进程运行的任何本来应该输出到标准输出的内容都会写入p3.output中。之所以会这样是因为在 exec 系统调用中,文件描述符是不会发生改变的,并且unix分配文件描述符是从0开始寻找第一个尚未分配的文件描述符,当我们关闭标准输出之后,子进程的1号文件描述符就可用了,这时如果我们打开一个文件,操作系统会将1号文件描述符分配给该文件。

5 Process Control And Users

除了 fork()、exec() 和 wait(),UNIX 系统中还有很多与进程交互的接口。例如,kill() 系统调用用于向进程发送信号,包括暂停、死亡和其他有用的指令。在大多数 UNIX shell 中,某些按键组合被配置为向当前运行的进程发送特定信号;例如,control-c 会向进程发送 SIGINT(中断)信号(通常会终止进程),而 control-z 则会发送 SIGTSTP(停止)信号,从而使进程在执行中途暂停(可以稍后使用命令恢复进程,例如许多 shell 中的 fg 内置命令)

整个信号系统提供了丰富的基础设施,用于向进程发送外部事件,包括在单个进程内接收和处理这些信号的方法,以及向单个进程和整个进程组发送信号的方法。要使用这种通信方式,进程应使用 signal() 系统调用来 “捕获 ”各种信号;这样做可确保当特定信号传送到进程时,进程将暂停其正常执行,并运行特定代码来响应该信号。

这自然会引出一个问题:谁能向进程发送信号,谁不能?一般来说,我们使用的系统可能会有多人同时使用;如果其中一人可以任意发送 SIGINT 等信号(中断进程,很可能终止进程),那么系统的可用性和安全性就会受到影响。因此,现代系统中包含了强烈的用户概念。用户在输入密码建立凭证后,登录以访问系统资源。然后,用户可以启动一个或多个进程,并对它们进行完全控制(暂停、杀死等)。用户通常只能控制自己的进程。

6 Useful Tools

还有许多命令行工具也很有用。

  • 例如,使用 ps 命令可以查看正在运行的进程;
  • top 工具也很有用,它可以显示系统进程及其占用的 CPU 和其他资源。
  • kill 命令可以用来向进程发送任意信号。

[OS]Three Easy Pieces Chapter 5
https://erlsrnby04.github.io/2024/10/18/OS-Three-Easy-Pieces-Chapter-5/
作者
ErlsrnBy04
发布于
2024年10月18日
许可协议