基本必备知识
fork函数的基本说明
介绍
fork函数用于创建一个新的进程,下面引用一段话介绍一下:
一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己
函数原型
1 | pid_t fork( void); |
其中pid_t是一个宏定义,其本身是int型的数据。
函数的返回
fork函数调用非常的神奇,一次调用将会有两次放回,一次是在父进程之中返回,另一次是在子进程里面返回。
而父进程的的返回又分为两种情况:
- 返回值大于零:成功创建子进程并返回子进程的PID
- 返回值为-1:发生错误,创建子进程失败
- 返回值为0:子进程的返回值是0
wait函数与waitpid函数的基本说明
waitpid函数
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束.
函数原型
1 | pid_t waitpid(pid_t pid, int *statusp, int options); |
- pid :
所等待的子进程
pid参数有四种情况:pid > 0
:只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。pid = 0
:waitpid所等待的进程是同一个进程组里的任意一个子进程。pid = -1
:wait所等待的进程是父进程的任意一个子进程。pid < -1
:waitpid所等待的进程是一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。
- statusp :
回收的子进程的退出状态
- 如果
statusp
是一个空指针,则表示父进程不关心子进程结束的状态。 - 如果
statusp
不是一个空指针,则会在status
放上关于导致返回的子进程的状态信息,相关信息请看下面的表格
- 如果
宏(返回值) | 信息 |
---|---|
WIFEXITED(status) | 如果子进程通过调用exit 或者一个返回(returm )正常终止,就返回真。 |
WEXITSTATUS(status) | 返回一个正常终止的子进程的退出状态。 |
WIFSIGNALED(status) | 如果子进程是因为一个未被捕获的信号终止的,那么就返回真。 |
WTERMSIG(status) | 返回导致子进程终止的信号的编号。 |
WIFSTOPPED(status) | 如果引起返回的子进程当前是停止的,那么就返回真。 |
WSTOPSIG(status) | 返回引起子进程停止的信号的编号。 |
WIFCONTINUED(status) | 如果子进程收到SIGCONT信号重新启动,则返回真。 |
- options :
默认行为
可以通过将options
设置为常量WNOHANG
、WUNTRACED
和WCONTINUED
或是它们的组合来修改默认行为。
常量 | 行为 |
---|---|
WNOHANG | 如果等待集合中的任何子进程都还没有终止,那么就立即返回(返回值为0)。默认的行为是挂起调用进程,直到有子进程终止。在等待子进程终止的同时,如果还想做些有用的工作,这个选项会有用。 |
WUNTRACED | 挂起调用进程的执行,直到等待集合中的一个进程变成已终止或者被停止。返回的PID为导致返回的已终止或被停止子进程的PID。默认的行为是只返回已终止的子进程。当你想要检查已终止和被停止的子进程时,这个选项会有用。 |
WCONTINUED | 挂起调用进程的执行,直到等待集合中一个正在运行的进程终止或等待集合中一个被停止的进程收到SIGCONT信号重新开始执行。 |
函数的返回
- 如过成功则返回子进程的
PID
- 如果参数
options
为WNOHANG则返回0
- 如果发生了其他的错误则会返回
-1
wait函数
函数原型
1 | pid_t wait(int *status); |
其实调用wait(&status)
等价于调用waitpid(- 1, &status, 0 )
,这里就不对这个函数做过多的解释了。
函数的返回
- 如过成功则返回子进程的
PID
- 如果发生了错误则会返回
-1
signal函数的基本说明
函数的声明
1 | void (*signal(int sig, void (*func)(int)))(int); |
signal函数用于修改信号与其相关联的默认行为,例如接收到SIGKILL
信号默认行为就是终止接收进程,接收到SIGCHLD
信号默认行为就是忽略这个信号。但有一点特殊的是,SIGSTOP
和SIGKILL
,这两个的信号的默认行为是无法修改的。
参数
sig
:待修改关联的信号func
:修改关联信号的新处理函数
fork函数的使用例子
例子1
代码:
1 | void fork0() |
它输出的内容是:
1 | Hello from parent,pid = 18913 |
在fork
函数的介绍里面有说过,fork
函数是一次调用两次返回,我们就可以通过在父进程与子进程中返回的内容的不同来分辨当前进程是父进程还是子进程,从而达到我们所需要的效果。fork
函数在子进程之中返回 0,所以Hello from child,pid = 18914
应是由子进程在完成 if 条件语句后转跳到printf
函数进行输出。而父进程中fork函数返回的是子进程的pid(18914),则Hello from parent,pid = 18913
应是由父进程进行输出。
例子2
代码:
1 | void fork1() |
输出到屏幕的内容如下:
1 | linux> ./f 1 |
父进程调用fork函数
创建了一个新的进程活后,此时系统之中就会出现了两个基本一样的进程,无论是代码或是数据都基本一样,就相当于把父进程复制了一份,但由于fork函数
返回的值不同,会有相应的逻辑判断,从而导致基本一样的进程执行的代码却不太一样。
例子3
代码:
1 | void fork3() |
输出到屏幕的内容如下:
1 | linux> ./f 3 |
主要是理解了fork函数
的使用后,弄清楚它的逻辑,既能够很清楚的理解它的输出为什么是这样。当然,不同的机器跑出来的结果有可能会不一样,个别内容的输出顺序可能会跟我上面的不一样,这得看CPU如何处理这些进程。
下面是一个大致的流程图:
例子4
代码:
1 | void fork5() |
输出到屏幕的内容如下:
1 | linux> ./f 5 |
下面是一个大致的流程图:
例子5
代码:
1 | void fork18() |
你觉得这个代码所输出的内容会是什么呢?
输出到屏幕的内容如下:
1 | linux> ./f 18 |
其实这printf
的缓冲机制有关,printf
并不会直接把内容直接打印到屏幕上,而是放在了缓存之中,所以在执行fork
的时候,缓冲区的内容也会被放到子进程相应的地方。一般来说,缓冲区满后或是遇到了换行符才会把内容输出到屏幕上。
例子6
代码:
1 | void fork9_handler(int sig) |
由于在子进程中有死循环,所以该程序无法正常的结束,可以另一个终端窗口发送SIGINT
信号给子进程令程序正常结束。所以输出入下:
1 | linux> ./f 9 |
但是我在运行上面的程序时,在子进程死循环时挂起进程,再发送SIGINT
信号给子进程,然而此时父进程却不能够正常的退出了:
1 | linux> ./f 9 |
且在另一个终端查看进程时仍能够看到父进程还在运行着,但是拿着同样的程序在另外一台机器上跑确是能够正常的退出,或许这是Windows子系统在某方面没做好吧。
于是我又再次修改了代码,进行实验:
1 | void fork9_handler(int sig) |
运行结果如下:
1 | linux> ./f 9 |
这一次父进程能够正常的退出了,所以我个人怀疑是wait函数
在Window子系统下会发生一些未知的错误。