修改 ch10 错别字

This commit is contained in:
上官永石
2022-07-12 21:24:15 +08:00
parent aad42209ac
commit edc675c41c

View File

@@ -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 程序分割