diff --git a/ch10/README.md b/ch10/README.md index 76da059..93d606b 100644 --- a/ch10/README.md +++ b/ch10/README.md @@ -24,7 +24,7 @@ #### 10.1.3 进程 ID -在说进程创建方法之前,先要简要说明进程 ID。无论进程是如何创建的,所有的进程都会被操作系统分配一个 ID。此 ID 被称为「进程ID」,其值为大于 2 的证书。1 要分配给操作系统启动后的(用于协助操作系统)首个进程,因此用户无法得到 ID 值为 1 。接下来观察在 Linux 中运行的进程。 +在说进程创建方法之前,先要简要说明进程 ID。无论进程是如何创建的,所有的进程都会被操作系统分配一个 ID。此 ID 被称为「进程ID」,其值为大于 2 的整数。1 要分配给操作系统启动后的(用于协助操作系统)首个进程,因此用户无法得到 ID 值为 1 。接下来观察在 Linux 中运行的进程。 ```shell ps au @@ -123,9 +123,9 @@ gcc fork.c -o fork - 传递参数并调用 exit() 函数 - main 函数中执行 return 语句并返回值 -**向 exit 函数传递的参数值和 main 函数的 return 语句返回的值都回传递给操作系统。而操作系统不会销毁子进程,直到把这些值传递给产生该子进程的父进程。处在这种状态下的进程就是僵尸进程。**也就是说将子进程变成僵尸进程的正是操作系统。既然如此,僵尸进程何时被销毁呢? +**向 exit 函数传递的参数值和 main 函数的 return 语句返回的值都会传递给操作系统。而操作系统不会销毁子进程,直到把这些值传递给产生该子进程的父进程。处在这种状态下的进程就是僵尸进程。**也就是说将子进程变成僵尸进程的正是操作系统。既然如此,僵尸进程何时被销毁呢? -> 应该向创建子进程册父进程传递子进程的 exit 参数值或 return 语句的返回值。 +> 应该向创建子进程的父进程传递子进程的 exit 参数值或 return 语句的返回值。 如何向父进程传递这些值呢?操作系统不会主动把这些值传递给父进程。只有父进程主动发起请求(函数调用)的时候,操作系统才会传递该值。换言之,如果父进程未主动要求获得子进程结束状态值,操作系统将一直保存,并让子进程长时间处于僵尸进程状态。也就是说,父母要负责收回自己生的孩子。接下来的示例是创建僵尸进程: @@ -327,7 +327,7 @@ gcc waitpid.c -o waitpid #### 10.3.1 向操作系统求助 -子进程终止的识别主题是操作系统,因此,若操作系统能把如下信息告诉正忙于工作的父进程,将有助于构建更高效的程序 +子进程终止的识别主题是操作系统,因此,若操作系统能把子进程结束的信息告诉正忙于工作的父进程,将有助于构建更高效的程序 为了实现上述的功能,引入信号处理机制(Signal Handing)。此处「信号」是在特定事件发生时由操作系统向进程发送的消息。另外,为了响应该消息,执行与消息相关的自定义操作的过程被称为「处理」或「信号处理」。 @@ -337,7 +337,7 @@ gcc waitpid.c -o waitpid > 进程:操作系统,如果我之前创建的子进程终止,就帮我调用 zombie_handler 函数。 > -> 操作系统:好的,如果你的子进程终止,我舅帮你调用 zombie_handler 函数,你先把要函数要执行的语句写好。 +> 操作系统:好的,如果你的子进程终止,我就帮你调用 zombie_handler 函数,你先把函数要执行的语句写好。 上述的对话,相当于「注册信号」的过程。即进程发现自己的子进程结束时,请求操作系统调用的特定函数。该请求可以通过如下函数调用完成: @@ -362,7 +362,7 @@ void (*signal(int signo, void (*func)(int)))(int); > 「子进程终止则调用 mychild 函数」 -此时 mychild 函数的参数应为 int ,返回值类型应为 void 。只有这样才能称为 signal 函数的第二个参数。另外,常数 SIGCHLD 定义了子进程终止的情况,应成为 signal 函数的第一个参数。也就是说,signal 函数调用语句如下: +此时 mychild 函数的参数应为 int ,返回值类型应为 void 。只有这样才能成为 signal 函数的第二个参数。另外,常数 SIGCHLD 定义了子进程终止的情况,应成为 signal 函数的第一个参数。也就是说,signal 函数调用语句如下: ```c signal(SIGCHLD , mychild); @@ -442,7 +442,7 @@ gcc signal.c -o signal > 发生信号时将唤醒由于调用 sleep 函数而进入阻塞状态的进程。 -调用函数的主题的确是操作系统,但是进程处于睡眠状态时无法调用函数,因此,产生信号时,为了调用信号处理器,将唤醒由于调用 sleep 函数而进入阻塞状态的进程。而且,进程一旦被唤醒,就不会再进入睡眠状态。即使还未到 sleep 中规定的时间也是如此。所以上述示例运行不到 10 秒后就会结束,连续输入 CTRL+C 可能连一秒都不到。 +调用函数的主体的确是操作系统,但是进程处于睡眠状态时无法调用函数,因此,产生信号时,为了调用信号处理器,将唤醒由于调用 sleep 函数而进入阻塞状态的进程。而且,进程一旦被唤醒,就不会再进入睡眠状态。即使还未到 sleep 中规定的时间也是如此。所以上述示例运行不到 10 秒后就会结束,连续输入 CTRL+C 可能连一秒都不到。 **简言之,就是本来系统要睡眠100秒,但是到了 alarm(2) 规定的两秒之后,就会唤醒睡眠的进程,进程被唤醒了就不会再进入睡眠状态了,所以就不用等待100秒。如果把 timeout() 函数中的 alarm(2) 注释掉,就会先输出`wait...`,然后再输出`Time out!` (这时已经跳过了第一次的 sleep(100) 秒),然后就真的会睡眠100秒,因为没有再发出 alarm(2) 的信号。** @@ -499,7 +499,7 @@ int main(int argc, char *argv[]) int i; struct sigaction act; act.sa_handler = timeout; //保存函数指针 - sigemptyset(&act.sa_mask); //将 sa_mask 函数的所有位初始化成0 + sigemptyset(&act.sa_mask); //将 sa_mask 成员的所有位初始化成0 act.sa_flags = 0; //sa_flags 同样初始化成 0 sigaction(SIGALRM, &act, 0); //注册 SIGALRM 信号的处理器。 @@ -670,7 +670,7 @@ gcc echo_mpserv.c -o eserver ![](https://s2.ax1x.com/2019/01/21/kP7Rjx.png) -如图所示,1 个套接字存在 2 个文件描述符时,只有 2 个文件描述符都终止(销毁)后,才能销毁套接字。如果维持图中的状态,即使子进程销毁了与客户端连接的套接字文件描述符,也无法销毁套接字(服务器套接字同样如此)。因此调用 fork 函数候,要将无关紧要的套接字文件描述符关掉,如图所示: +如图所示,1 个套接字存在 2 个文件描述符时,只有 2 个文件描述符都终止(销毁)后,才能销毁套接字。如果维持图中的状态,即使子进程销毁了与客户端连接的套接字文件描述符,也无法销毁套接字(服务器套接字同样如此)。因此调用 fork 函数后,要将无关紧要的套接字文件描述符关掉,如图所示: ![](https://s2.ax1x.com/2019/01/21/kPH7ZT.png) @@ -688,13 +688,13 @@ gcc echo_mpserv.c -o eserver 从图中可以看出,客户端的父进程负责接收数据,额外创建的子进程负责发送数据,分割后,不同进程分别负责输入输出,这样,无论客户端是否从服务器端接收完数据都可以进程传输。 -分割 I/O 程序的另外一个好处是,可以提高频繁交换数据的程序性能,图下图所示: +分割 I/O 程序的另外一个好处是,可以提高频繁交换数据的程序性能,如下图所示: ![](https://s2.ax1x.com/2019/01/21/kPbvtg.png) -根据上图显示可以看出,再网络不好的情况下,明显提升速度。 +根据上图显示可以看出,在网络不好的情况下,明显提升速度。 #### 10.5.2 回声客户端的 I/O 程序分割