1、fork创建子进程
fork函数比较特殊,“调用一次返回两次”,调用它可以创建出一个子进程。可以看下例子:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
char *message;
int n;
pid = fork();
if (pid < 0)
{
perror("fork failed");
exit(1);
}
if (pid == 0)
{
message = "This is the child\n";
n = 6;
}
else
{
message = "This is the parent\n";
n = 3;
}
for(; n > 0; n--)
{
printf(message);
sleep(1);
}
return 0;
}
它的执行流程如图所示:
输出结果:
This is the parent
This is the child
This is the parent
This is the child
This is the parent
This is the child
book@book:/work/test/myprocess/forktest$ This is the child
This is the child
This is the child
book@book:/work/test/myprocess/forktest$
之所以出现第7行这种现象,是因为此时父进程已经运行结束了,返回控制台了,而子进程还没有结束,继续打印输出,所以就出现这种现象,当然这种情况也不一定发生,它取决于系统的进程调度与竞争。
2、exec族函数
当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以调用 exec 前后该进程的id并未改变。
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
它包含一系列的函数,不过也很容易区分:
p(path)必须是程序绝对路径;
l(list)每个命令行参数都当作参数传给它;
v(vector)先构造指向个参数的指针数组,以首地址发给它;
e(environment)把新的环境变量传递给它;
例如:
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int main(void)
{
// error usage
// execlp("ls", "-l", "-a", "-h", NULL);
// true usage
// file cmd params end
//execlp("ls", "ls", "-l", "-h", NULL);
// path cmd params end
//execl("/bin/ls", "ls", "-l", "-h", NULL);
char *argv[] = {"ls", "-l", "-h", NULL};
// file vector
execvp("ls", argv);
// Usually not here
perror("exec ps");
exit(1);
}
输出结果:
total 16K
-rwxrwxr-x 1 book book 8.5K 4月 24 21:02 exec
-rw-rw-r-- 1 book book 416 4月 24 21:02 exec.c
3、wait和waitpid函数回收子进程资源
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
参数说明
pid取值 | 说明 |
---|---|
< -1 | 任何进程组ID等于pid的绝对值的子进程。 |
-1 | 任何子进程 |
0 | 进程组ID与调用进程的ID相等的任何子进程 |
>0 | 对应pid的子进程 |
status对应的宏 | 说明 |
---|---|
WIFEXITED | 如果子项正常终止则返回true |
WEXITSTATUS | 返回子项的退出状态 |
WIFSIGNALED | 如果子进程被信号终止,则返回true |
WTERMSIG | 返回导致子进程终止的信号编号 |
WCOREDUMP | 如果子项产生了核心转储(段错误)则返回true |
WIFSTOPPED | 如果子进程由于传递信号而停止,则返回true |
WSTOPSIG | 返回导致子进程停止的信号编号 |
WIFCONTINUED | 如果子进程通过SIGCONT的交付而恢复,则返回true。 |
options取值 | 说明(重点关注 0 和 WNOHANG ) |
---|---|
0 | 阻塞等待 |
WNOHANG | 如果没有子进程退出立即返回(不阻塞) |
WUNTRACED | 如果子进程已停止也会返回。即使未指定此选项,也会提供已停止的跟踪子项的状态。 |
WCONTINUED | 如果已通过交付SIGCONT恢复了被阻止的孩子,也将返回。 |
返回值说明
函数 | 返回值 |
---|---|
wait | 成功返回pid,失败返回-1 |
waitpid | 成功返回pid,失败返回-1,非阻塞未回收返回0 |
wait/waitpid函数一次只能回收一个进程的资源,需要回收多个可以使用循环来进行处理。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid, wpid;
int status;
pid = fork();
if (pid < 0)
{
perror("fork failed");
exit(1);
}
else if(pid == 0)
{
// 子进程
printf("child: pid is %d\n", getpid());
sleep(2);
printf("child: process end!\n");
return 100;
}
else if(pid > 0)
{
// 父进程
// wait函数回收任意一个,不指定子进程
// 等价于 waitpid(-1, &status, 0);
wpid = wait(&status);
if(wpid == -1)
{
perror("parents: wait error");
exit(1);
}
// 可以打开man手册使用一系列的宏查看status对应的子进程状态
if(WIFEXITED(status))
{
printf("parents: child pid is %d, and exit with value: %d\n", wpid, WEXITSTATUS(status));
}
}
return 0;
}
输出结果:
child: pid is 2938
child: process end!
parents: child pid is 2938, and exit with value: 100
另外可以了解一下进程的一些状态:
孤儿进程是指父进程已经运行结束,而子进程仍然在运行,此时子进程会由init进程接管;
僵尸进程是指子进程运行结束,等待父进程回收时的状态。需要注意的是,任何进程终止时都有一段短暂的时间为僵尸进程,等待父进程彻底清除,此时不能用kill清除,因为kill是终止进程而不是清除进程。