我们可以通过调用fork来创建一个新进程。这个系统调用会复制当前进程,在进程表中创建一个实体,而且与当前进程具有相同的属性。新进程与原始进程几乎是相同的,执行相同的代码,但是具有其自己的数据空间,环境与文件描述符。
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
正如我们在图中所看到的,父进程中的fork调用会返回新的子进程的PID。新进程会继续执行,就如原始进程一样,所不同的是子进程中的fork调用返回0。这可以使得父进程与子进程彼此区分。
如果fork失败则会返回-1。这通常是由于父进程可以拥有的子进程的数量(CHILD_MAX)所引起的,在这种情况下,errno会被设置为EAGAIN。如果在进程表中没有足够的空间,或是没有足够的虚拟内存,errno变量会被设置为ENOMEM。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
char *message;
int n;
printf("fork program starting\n");
pid = fork();
switch(pid)
{
case -1:
perror("fork failed\n");
exit(1);
case 0:
message = "This is the child";
n = 5;
break;
default:
message = "This is the parent";
n = 3;
break;
}
for(; n > 0; n--)
{
puts(message);
sleep(1);
}
exit(0);
}
结果:
fork program starting
This is the parent
This is the child
This is the parent
This is the child
This is the parent
This is the child
$ This is the child
This is the child
程序在调用 fork()时被分为两个独立的进程。程序通过 fork()调用返回的非零值确定父进程,并根据该值来设置消息的输出次数,两次消息的输出之间间隔一秒。
等待一个进程
当用fork启动一个子进程时,子进程就有了他自己的生命周期并且独立运行。有时,我们希望知道一个子进程何时结束。例如,在前一个例子中,父进程在子进程之前结束,从而我们得到的输出结果有点乱。我们可以通过调用 wait()来让父进程子进程的结束。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *stat_loc);
wait系统调用将暂停父进程直到他的子进程结束为止。这个函数调用返回子进程的PID,它通常是已经结束运行的子进程的PID。状态信息允许父进程了解子进程的退出状态,即子进程的 main 函数返回的值或子进程中 exit 函数的退出码。如果 stat_loc 不是空指针,状态信息将被写入它所指向的位置。
我们可以用 sys/wait.h 中定义的宏来解释状态信息,如下表所示:
宏 | 说明 |
WIFEXITED(stat_val) | 如果子进程正常结束,它就取一个非零值 |
WEXITSTATUS(stat_val) | 如果WIFEXITED为非零,它返回子进程的退出码 |
WIFSIGNALED(stat_val) | 如果子进程因为一个未捕获的信号而终止,它就取一个非零值 |
WTERMSIG(stat_val) | 如果WIFSIGNALED非零,它就返回一个信号代码 |
WIFSTOPPED(stat_val) | 如果子进程意外终止,它就取一个非零值 |
WSTOPSIG(stat_val) | 如果WIFSTOPPED非零,它就返回一个信号代码 |
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
pid_t pid;
char *message;
int n;
int exit_code;
printf("fork program starting\n");
pid = fork();
switch(pid)
{
case -1:
perror("fork failed\n");
exit(1);
case 0:
message = "This is the child";
n = 5;
exit_code = 37;
break;
default:
message = "This is the parent";
n = 3;
exit_code = 0;
break;
}
for(; n > 0; n--)
{
puts(message);
sleep(1);
}
if(pid != 0)
{
int stat_val;
pid_t child_pid;
child_pid = wait(&stat_val);
printf("Child has finished: PID = %d\n", child_pid);
if(WIFEXITED(stat_val))
printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
else
printf("Child terminated abnormally\n");
}
// exit(exit_code);
return exit_code;
}
fork program starting
This is the child
This is the parent
This is the parent
This is the child
This is the parent
This is the child
This is the child
This is the child
Child has finished: PID = 1582
Child exited with code 37
$
父进程(从 fork 调用中获得一个非零的返回值)用 wait 系统调用将自己的执行挂起,知道子进程的状态信息出现为止。这发生在子进程调用 exit的时候。我们将子进程的退出码设置为37。父进程然后继续执行,通过 wait 调用的返回值来判断子进程是否正常终止。如果是,就从状态信息中提取出子进程的退出吗。
switch(pid)
{
case -1:
perror(“fork failed”);
exit(1);
case 0:
message = “This is the child”;
n = 3;
break;
default:
message = “This is the parent”;
n = 5;
break;
}
如果我们使用./fork2 & 命令来运行上面这个程序,然后在子进程结束之后父进程结束之前调用 ps 程序,我们将会看到如下阴影显示的一行(一些系统可能使用<zombie>而不是<defunct>)。
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
004 S 0 1273 1259 0 75 0 - 589 wait4 pts/2 00:00:00 su
000 S 0 1274 1273 0 75 0 - 731 schedu pts/2 00:00:00 bash
000 S 500 1463 1262 0 75 0 - 788 schedu pts/1 00:00:00 oclock
000 S 500 1465 1262 0 75 0 - 2569 schedu pts/1 00:00:01 emacs
000 S 500 1603 1262 0 75 0 - 313 schedu pts/1 00:00:00 fork2
003 Z 500 1604 1603 0 75 0 - 0 do_exi pts/1 00:00:00 fork2 <defunct>
000 R 500 1605 1262 0 81 0 - 781 - pts/1 00:00:00 ps
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *stat_loc, int options);
因此,如果想让父进程周期性地检查某个特定的子进程是否已终止,就可以使用如下调用方式:
waitpid(child_pid, (int *) 0, WNOHANG);
如果子进程没有结束或意外终止,它就返回0,否返回 child_pid。如果 waitpid 失败,它将返回 -1 并设置 errno。失败的情况包括:没有子进程(errno设置为ECHILD)、调用被某个信号中断(EINTR),或选项参数无效(EINVAL)。