赞
踩
fork函数用于创建一个新的进程,下面引用一段话介绍一下:
一个进程,包括代码、数据和分配给进程的资源。fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己
pid_t fork( void);
其中pid_t是一个宏定义,其本身是int型的数据。
fork函数调用非常的神奇,一次调用将会有两次放回,一次是在父进程之中返回,另一次是在子进程里面返回。
而父进程的的返回又分为两种情况:
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束.
pid_t waitpid(pid_t pid, int *statusp, int options);
所等待的子进程
pid > 0
:只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。pid = 0
:waitpid所等待的进程是同一个进程组里的任意一个子进程。pid = -1
:wait所等待的进程是父进程的任意一个子进程。pid < -1
:waitpid所等待的进程是一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。回收的子进程的退出状态
statusp
是一个空指针,则表示父进程不关心子进程结束的状态。statusp
不是一个空指针,则会在status
放上关于导致返回的子进程的状态信息,相关信息请看下面的表格宏(返回值) | 信息 |
---|---|
WIFEXITED(status) | 如果子进程通过调用exit 或者一个返回(returm )正常终止,就返回真。 |
WEXITSTATUS(status) | 返回一个正常终止的子进程的退出状态。 |
WIFSIGNALED(status) | 如果子进程是因为一个未被捕获的信号终止的,那么就返回真。 |
WTERMSIG(status) | 返回导致子进程终止的信号的编号。 |
WIFSTOPPED(status) | 如果引起返回的子进程当前是停止的,那么就返回真。 |
WSTOPSIG(status) | 返回引起子进程停止的信号的编号。 |
WIFCONTINUED(status) | 如果子进程收到SIGCONT信号重新启动,则返回真。 |
默认行为
options
设置为常量WNOHANG
、WUNTRACED
和WCONTINUED
或是它们的组合来修改默认行为。常量 | 行为 |
---|---|
WNOHANG | 如果等待集合中的任何子进程都还没有终止,那么就立即返回(返回值为0)。默认的行为是挂起调用进程,直到有子进程终止。在等待子进程终止的同时,如果还想做些有用的工作,这个选项会有用。 |
WUNTRACED | 挂起调用进程的执行,直到等待集合中的一个进程变成已终止或者被停止。返回的PID为导致返回的已终止或被停止子进程的PID。默认的行为是只返回已终止的子进程。当你想要检查已终止和被停止的子进程时,这个选项会有用。 |
WCONTINUED | 挂起调用进程的执行,直到等待集合中一个正在运行的进程终止或等待集合中一个被停止的进程收到SIGCONT信号重新开始执行。 |
PID
options
为WNOHANG则返回 0
-1
pid_t wait(int *status);
其实调用wait(&status)
等价于调用waitpid(- 1, &status, 0 )
,这里就不对这个函数做过多的解释了。
PID
-1
void (*signal(int sig, void (*func)(int)))(int);
signal函数用于修改信号与其相关联的默认行为,例如接收到SIGKILL
信号默认行为就是终止接收进程,接收到SIGCHLD
信号默认行为就是忽略这个信号。但有一点特殊的是,SIGSTOP
和SIGKILL
,这两个的信号的默认行为是无法修改的。
sig
:待修改关联的信号func
:修改关联信号的新处理函数void fork0()
{
if (fork() == 0) {
printf("Hello from child,pid = %d\n",getpid());
}
else {
printf("Hello from parent,pid = %d\n",getpid());
}
}
它输出的内容是:
Hello from parent,pid = 18913
Hello from child,pid = 18914
在fork
函数的介绍里面有说过,fork
函数是一次调用两次返回,我们就可以通过在父进程与子进程中返回的内容的不同来分辨当前进程是父进程还是子进程,从而达到我们所需要的效果。
fork
函数在子进程之中返回 0,所以Hello from child,pid = 18914
应是由子进程在完成 if 条件语句后转跳到printf
函数进行输出。而父进程中fork函数返回的是子进程的pid(18914),则Hello from parent,pid = 18913
应是由父进程进行输出。
void fork1()
{
int x = 1;
pid_t pid = fork();
if (pid == 0) {
printf("Child has x = %d\n", ++x);
}
else {
printf("Parent has x = %d\n", --x);
}
printf("Bye from process %d with x = %d\n", getpid(), x);
}
输出到屏幕的内容如下:
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 1
Parent has x = 0
Child has x = 2
Bye from process 16 with x = 0
Bye from process 17 with x = 2
父进程调用fork函数
创建了一个新的进程活后,此时系统之中就会出现了两个基本一样的进程,无论是代码或是数据都基本一样,就相当于把父进程复制了一份,但由于fork函数
返回的值不同,会有相应的逻辑判断,从而导致基本一样的进程执行的代码却不太一样。
void fork3()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("L2\n");
fork();
printf("Bye\n");
}
输出到屏幕的内容如下:
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 3 L0 from process 18 L1 from process 18 L1 from process 19 L2 from process 18 L2 from process 20 L2 from process 19 L2 from process 21 Bye from process 18 Bye from process 22 Bye from process 20 Bye from process 23 Bye from process 19 Bye from process 24 Bye from process 21 Bye from process 25
主要是理解了fork函数
的使用后,弄清楚它的逻辑,既能够很清楚的理解它的输出为什么是这样。当然,不同的机器跑出来的结果有可能会不一样,个别内容的输出顺序可能会跟我上面的不一样,这得看CPU如何处理这些进程。
下面是一个大致的流程图:
void fork5()
{
printf("L0 from process %d,ppid:%d\n", getpid(),getppid());
if (fork() == 0) {
printf("L1 from process %d,ppid:%d\n", getpid(),getppid());
if (fork() == 0) {
printf("L2 from process %d,ppid:%d\n", getpid(),getppid());
}
}
printf("Bye from process %d,ppid:%d\n", getpid(),getppid());
}
输出到屏幕的内容如下:
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 5
L0 from process 38,ppid:4
Bye from process 38,ppid:4
L1 from process 39,ppid:38
Bye from process 39,ppid:1
L2 from process 40,ppid:39
Bye from process 40,ppid:1
下面是一个大致的流程图:
void fork18()
{
printf("fork");
if (fork() == 0)
{
printf("2018\n");
}
printf("2019\n");
}
你觉得这个代码所输出的内容会是什么呢?
输出到屏幕的内容如下:
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 18
fork2019
fork2018
2019
其实这printf
的缓冲机制有关,printf
并不会直接把内容直接打印到屏幕上,而是放在了缓存之中,所以在执行fork
的时候,缓冲区的内容也会被放到子进程相应的地方。一般来说,缓冲区满后或是遇到了换行符才会把内容输出到屏幕上。
void fork9_handler(int sig) { printf("Process %d received signal %d\n", getpid(), sig); exit(0); } void fork9() { int child_status; signal(SIGINT,fork9_handler); if (fork() == 0) { printf("HC: hello from child\n"); while(1) ; } else { printf("HP: hello from parent\n"); wait(&child_status); printf("CT: child has terminated\n"); } printf("Bye\n"); }
由于在子进程中有死循环,所以该程序无法正常的结束,可以另一个终端窗口发送SIGINT
信号给子进程令程序正常结束。所以输出入下:
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 9
HP: hello from parent
HC: hello from child
Process 90 received signal 2
CT: child has terminated
Bye
但是我在运行上面的程序时,在子进程死循环时挂起进程,再发送SIGINT
信号给子进程,然而此时父进程却不能够正常的退出了:
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 9 HP: hello from parent HC: hello from child ^Z [1]+ 已停止 ./f 9 hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ps PID TTY TIME CMD 4 tty1 00:00:00 bash 17 tty1 00:00:00 f 18 tty1 00:00:01 f 19 tty1 00:00:00 ps hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ kill -2 18 Process 18 received signal 2 hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ps PID TTY TIME CMD 4 tty1 00:00:00 bash 17 tty1 00:00:00 f 18 tty1 00:00:01 f <defunct> 20 tty1 00:00:00 ps hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ fg 1 ./f 9 ^Z [1]+ 已停止 ./f 9 hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ps PID TTY TIME CMD 4 tty1 00:00:00 bash 17 tty1 00:00:00 f 18 tty1 00:00:01 f <defunct> 21 tty1 00:00:00 ps
且在另一个终端查看进程时仍能够看到父进程还在运行着,但是拿着同样的程序在另外一台机器上跑确是能够正常的退出,或许这是Windows子系统在某方面没做好吧。
于是我又再次修改了代码,进行实验:
void fork9_handler(int sig) { printf("Process %d received signal %d\n", getpid(), sig); exit(0); } void fork9() { int child_status; signal(SIGCHLD, fork9_handler); //Add signal(SIGINT,fork9_handler); if (fork() == 0) { printf("HC: hello from child\n"); while(1) ; } else { printf("HP: hello from parent\n"); wait(&child_status); printf("CT: child has terminated\n"); } printf("Bye\n"); }
运行结果如下:
hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ./f 9 HP: hello from parent HC: hello from child ^Z [1]+ 已停止 ./f 9 hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ps PID TTY TIME CMD 4 tty1 00:00:00 bash 16 tty1 00:00:00 f 17 tty1 00:00:01 f 18 tty1 00:00:00 ps hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ kill -2 17 Process 17 received signal 2 hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ fg 1 ./f 9 Process 16 received signal 17 hpj@黄培纪:/mnt/d/code/C/Computer Systems/ch8$ ps PID TTY TIME CMD 4 tty1 00:00:00 bash 19 tty1 00:00:00 ps
这一次父进程能够正常的退出了,所以我个人怀疑是wait函数
在Window子系统下会发生一些未知的错误。
参考资料
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。