exec函数族
exec
函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,即在调用进程内部执行一个可执行文件
exec
执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈都已经被新的内容取代,调用失败返回-1
一般会先fork
创建一个子进程,再调用exec
fork
仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值
- 在父进程中,fork返回新创建子进程的进程ID
- 在子进程中,fork返回0
- 如果出现错误,fork返回一个负值
fork只拷贝fork要执行的代码到新的进程
#include <unistd.h>
int execl(const char *path, const char *arg, /* (char *) NULL */);
- path: 可执行文件的路径
- arg: 执行文件的参数列表,必须以NULL结尾
int execlp(const char *file, const char *arg, /* (char *) NULL */);
// 会到环境变量中找可执行文件
- file: 给出文件名就可以了,会自己去找
int execv(const char *path, char *const argv[]);
- argv: 参数的数组
int execvpe(const char *file, char *const argv[],
char *const envp[]);
- envp: 指定环境变量
示例
#include <unistd.h>
#include <stdio.h>
int main() {
pid_t pid = fork();
if (pid > 0) { // 父进程
printf("I am parent: %d\n", getpid());
sleep(1);
}
else if (pid == 0) { // 子进程
execl("hello", "hello", NULL);
perror("execlp");
printf("I am child: %d\n", getpid()); // 子进程的代码不再执行
}
for (int i = 0; i < 3; i++) {
printf("i = %d, pid = %d\n", i, getpid());
}
return 0;
}
I am parent: 16936
hello world // 执行指定的文件
i = 0, pid = 16936
i = 1, pid = 16936
i = 2, pid = 16936 // 继续执行父进程的代码
进程退出、孤儿进程、僵尸进程
exit函数和_exit函数
#include <stdlib.h>
void exit(int status); // 标准C库函数
#include <unistd.h>
void _exit(int status); // Linux内核函数
- status: 进程退出时的状态信息,父进程回收子进程资源时用到
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main() {
printf("hello\n"); // 换行会刷新缓冲区
printf("word");
exit(0); // exit也会刷新缓冲区,再调用_exit()结束程序
return 0;
}
hello
world
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main() {
printf("hello\n");
printf("word"); // 先放在缓冲区中,没有刷新输出,程序就结束了
_exit(0); // 没有刷新缓冲区
return 0;
}
hello
孤儿进程
父进程运行结束,但子进程还在运行,称为孤儿进程(Orphan Process)
出现孤儿进程时,内核会将其父进程设置为init
,init
进程会循环wait()
,这样当孤儿进程结束后,由init
进程进行内核区的资源回收
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
pid_t pid = fork(); // 写时复制:copy on write
if (pid > 0) {
printf("I am parent, pid: %d, ppid: %d\n", getpid(), getppid());
}
else if (pid == 0) {
sleep(1); // 等父进程先结束
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
}
for (int i = 0; i < 3; i++) {
printf("i: %d, pid: %d\n", i, getpid());
}
return 0;
}
$./orphan
I am parent, pid: 25283, ppid: 20601
i: 0, pid: 25283
i: 1, pid: 25283
i: 2, pid: 25283
$ I am child, pid: 25284, ppid: 1880 // 父进程变了
i: 0, pid: 25284
i: 1, pid: 25284
i: 2, pid: 25284
僵尸进程
进程终止时,父进程尚未回收,子进程残留资源存放于内核中,变成僵尸进程(Zombie)
僵尸进程不能被kill -9
杀死
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
int main() {
pid_t pid = fork(); // 写时复制:copy on write
if (pid > 0) { // 父进程没有回收子进程资源
while (1) {
printf("I am parent, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
else if (pid == 0) { // 子进程先结束
sleep(1);
printf("I am child, pid: %d, ppid: %d\n", getpid(), getppid());
}
for (int i = 0; i < 3; i++) {
printf("i: %d, pid: %d\n", i, getpid());
}
return 0;
}
I am parent, pid: 28586, ppid: 20601
I am parent, pid: 28586, ppid: 20601
I am child, pid: 28587, ppid: 28586
i: 0, pid: 28587
i: 1, pid: 28587
i: 2, pid: 28587
I am parent, pid: 28586, ppid: 20601
kavin 28586 0.0 0.0 4524 800 pts/1 S+ 10:38 0:00 ./zombie
kavin 28587 0.0 0.0 0 0 pts/1 Z+ 10:38 0:00 [zombie] <defunct>
// defunct不存在,Z表示僵尸进程
进程回收
每个进程退出时,内核会释放该进程的资源,包括打开的文件、占用的内存。但仍然会保留一些信息,如进程控制块PCB的信息(包括进程号、退出状态、运行时间等)
父进程可以调用
wait
或waitpid
得到子进程的状态同时清除子进程
一次wait
或waitpid
只能清除一个子进程,多个子进程应使用循环
wait函数
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *wstatus);
// 等待任意一个子进程结束,回收其资源
- wstatus: int指针,进程退出时的状态信息,是传入也是传出参数,会把子进程退出的状态写到wstatus
- 成功返回子进程ID,失败返回-1
调用wait
函数的进程会被挂起(阻塞),直到它的子进程退出或者收到一个不能被忽略的信号才被唤醒,如果没有子进程或者子进程都结束了,立即返回-1
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid;
for (int i = 0; i < 5; i++) {
pid = fork(); // 创建5个子进程
if (pid == 0) break; // 不让子进程再创建子进程
}
if (pid > 0) { // 父进程
while (1) {
printf("I am parent, pid=%d\n", getpid());
sleep(1);
}
}
else if (pid == 0) { // 子进程先退出,称为僵尸进程
printf("I am child, pid=%d\n", getpid());
}
return 0;
}
I am child, pid=26070
I am child, pid=26071
I am parent, pid=26069
I am child, pid=26072
I am child, pid=26073
I am child, pid=26074
I am parent, pid=26069
...
$ ps aux
kavin 26069 0.0 0.0 4524 728 pts/1 S+ 14:53 0:00 ./wait
kavin 26070 0.0 0.0 0 0 pts/1 Z+ 14:53 0:00 [wait] <defunct>
kavin 26071 0.0 0.0 0 0 pts/1 Z+ 14:53 0:00 [wait] <defunct>
kavin 26072 0.0 0.0 0 0 pts/1 Z+ 14:53 0:00 [wait] <defunct>
kavin 26073 0.0 0.0 0 0 pts/1 Z+ 14:53 0:00 [wait] <defunct>
kavin 26074 0.0 0.0 0 0 pts/1 Z+ 14:53 0:00 [wait] <defunct>
父进程调用wait
阻塞等待子进程结束,回收其资源
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid;
for (int i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) break;
}
if (pid > 0) { // 父进程
while (1) {
printf("I am parent, pid=%d\n", getpid());
int ret = wait(NULL);
if (ret == -1) break;
printf("child die, pid=%d\n", ret);
sleep(1);
}
}
else if (pid == 0) { // 子进程
while (1) {
printf("I am child, pid=%d\n", getpid());
sleep(1);
}
}
return 0;
}
I am child, pid=26868
I am parent, pid=26867 // 父进程在wait处阻塞
I am child, pid=26869
I am child, pid=26870
I am child, pid=26871
I am child, pid=26872
kill -9 26868 // 逐一杀死子进程
kill -9 26869
kill -9 26870
kill -9 26871
kill -9 26872
child die, pid=26868 // 对应wait就执行,然后再次阻塞
child die, pid=26869
child die, pid=26870
child die, pid=26871
child die, pid=26872 // 直到子进程都退出后,父进程也退出
退出信息相关宏函数
通过下面几个函数,可以得知子进程结束的状态
宏 | 含义 |
---|---|
WIFEXITED(status) | 这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值 |
WEXITSTATUS(staus) | 当 WIFEXITED 返回非零值时,可以用这个宏来提取子进程的返回值+ 如果子进程调用 exit(5) 退出,WEXITSTATUS(status) 就返回5+ 如果进程不是正常退出的,也就是说, WIFEXITED 返回0,这个值就毫无意义 |
WIFSIGNALED(staus) | 非0,异常终止 |
WTERMSIG(staus) | 如果WIFSIGNALED 返回非零值,可以获取使进程退出的信号编号 |
用WEXITSTATUS
打印进程正常退出状态码
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid;
for (int i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) break;
}
if (pid > 0) {
// 父进程
while (1) {
printf("I am parent, pid=%d\n", getpid());
int st;
int ret = wait(&st); // 阻塞等待回收子进程
if (WIFEXITED(st)) { // 正常退出,打印状态码
printf("exit state code=%d\n", WEXITSTATUS(st));
}
if (WIFSIGNALED(st)) { // 异常退出,打印信号
printf("killed by signal: %d\n", WTERMSIG(st));
}
if (ret == -1) break;
printf("child die, pid=%d\n", ret);
sleep(1);
}
}
else if (pid == 0) {
printf("I am child, pid=%d\n", getpid());
sleep(1);
exit(0);
}
return 0;
}
I am child, pid=29902
I am child, pid=29903
I am parent, pid=29901
I am child, pid=29904
I am child, pid=29905
I am child, pid=29906
exit state code=0 // 正常退出,打印状态码
child die, pid=29902
I am parent, pid=29901
exit state code=0
child die, pid=29903
I am parent, pid=29901
exit state code=0
child die, pid=29904
I am parent, pid=29901
exit state code=0
child die, pid=29905
I am parent, pid=29901
exit state code=0
child die, pid=29906
I am parent, pid=29901
exit state code=0
用WTERMSIG
打印进程异常退出状态码
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid;
for (int i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) break;
}
if (pid > 0) { // 父进程
while (1) {
printf("I am parent, pid=%d\n", getpid());
int st;
int ret = wait(&st); // 阻塞等待回收子进程
if (WIFEXITED(st)) {
printf("exit state code=%d\n", WEXITSTATUS(st));
}
if (WIFSIGNALED(st)) {
printf("killed by signal: %d\n", WTERMSIG(st));
}
if (ret == -1) break;
printf("child die, pid=%d\n", ret);
sleep(1);
}
}
else if (pid == 0) { // 子进程
while (1) {
printf("I am child, pid=%d\n", getpid());
sleep(1);
}
exit(0);
}
return 0;
}
$ kill -9 30023
killed by signal: 9 // 异常退出,打印信号
child die, pid=30023
waitpid函数
回收指定进程的子进程,可以设置是否阻塞
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
- pid
pid > 0: 指定子进程的pid
pid = 0: 回收当前进程组的所有子进程
pid = -1: 回收所有子进程,相当于wait(最常用)
pid < -1: 回收绝对值相等的某个进程组的子进程
- options: 设置阻塞或者非阻塞
0: 阻塞
WNOHANG: 非阻塞
- 返回值
> 0: 返回子进程的id
= 0: options=WNOHANG,还有子进程活着
= -1: 没有子进程
阻塞的情况
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid;
for (int i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) break;
}
if (pid > 0) { // 父进程
while (1) {
printf("I am parent, pid=%d\n", getpid());
int st;
int ret = waitpid(-1, &st, 0); // 阻塞
if (WIFEXITED(st)) {
printf("exit state code=%d\n", WEXITSTATUS(st));
}
if (WIFSIGNALED(st)) {
printf("killed by signal: %d\n", WTERMSIG(st));
}
if (ret == -1) break;
printf("child die, pid=%d\n", ret);
sleep(1);
}
}
else if (pid == 0) { // 子进程
while (1) {
printf("I am child, pid=%d\n", getpid());
sleep(1);
}
exit(0);
}
return 0;
}
$ kill -9 6414
killed by signal: 9
child die, pid=6414 // 父进程执行后又阻塞
非阻塞的情况
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid;
for (int i = 0; i < 5; i++) {
pid = fork();
if (pid == 0) break;
}
if (pid > 0) { // 父进程
while (1) {
printf("I am parent, pid=%d\n", getpid());
sleep(1);
int st;
int ret = waitpid(-1, &st, WNOHANG);
if (ret == -1) break; // 没有子进程了
else if (ret == 0) continue; // 还有子进程
else if (ret > 0) { // 回收了某个子进程,打印状态码
if (WIFEXITED(st)) {
printf("exit state code=%d\n", WEXITSTATUS(st));
}
if (WIFSIGNALED(st)) {
printf("killed by signal: %d\n", WTERMSIG(st));
}
printf("child die, pid=%d\n", ret);
}
}
}
else if (pid == 0) { // 子进程
while (1) {
printf("I am child, pid=%d\n", getpid());
sleep(1);
}
exit(0);
}
return 0;
}
// 非阻塞,父进程和子进程都在打印输出
$ kill -9 8380
killed by signal: 9
child die, pid=8380 // 子进程被结束
// 子进程全部结束后,waitpid返回-1,父进程也退出