Linux高并发服务器开发 第十六天(execlp/execl 进程回收/孤儿进程/僵尸进程 wait/waitpid回收 进程间的通信)
目录
1.exec函数族
1.1execlp
1.2execl
1.3练习
2.进程回收
3.孤儿进程
4.僵尸进程
5.wait回收
6.waitpid回收
7.进程间通信 IPC
7.1 进程间通信的方法
7.2管道pipe
1.exec函数族
- 工作原理:
- 将当前进程的 .text、.data ... 替换为所要加载的程序的 .text、.data ... ,然后让进程从新的 .text 第一条指令开始执行。但 进程 ID 不变。
- 工作特性:
- exec函数族函数,一旦调用成功执行新程序,不会返回!只有调用失败才返回, 错误值 -1, errno
- 通常使用时,我们只需在 execxxx() 函数后,调用 perror 和 exit, 无需 if 判断。
1.1execlp
- p: PATH 。 该函数在使用时,自动借助 环境变量 PATH,找寻可执行程序。
- 可以用来调用,系统的程序。
// 该函数,通常用来执行系统程序:ls、data、cp、cat ... 命令
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
参数:
参1:带加载的程序名字。需要配合 PATH 使用。
参2:argv0 -- 可执行文件名
参3:argv1
参4:argv2
...:argvN
哨兵:NULL,最后必须传入一个NULL
指针来标识参数列表的结束
返回值:
成功:不返回
失败:-1, errrno
- 示例:
int main(int argc, char *argv[])
{pid_t pid = fork();if (pid == 0) {//execlp("ls", "-l", "-F", "-a", NULL); 这样传参错误!!!execlp("ls", "ls", "-l", "-F", "-a", NULL);perror("/bin/ls exec error");exit(1);} else if (pid > 0) {sleep(1);printf("parent\n");}return 0;
}
1.2execl
- 直接指定要加载的程序绝对访问路径。可以是系统可以执行文件,也用户自定义可执行文件。
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
参数:
参1:带加载的带有路径的程序名字。
参2:argv0 -- 可执行文件名
参3:argv1
参4:argv2
...:argvN
哨兵:NULL
示例:
int main(int argc, char *argv[])
{pid_t pid = fork();if (pid == 0) {//execl("/bin/ls", "ls", "-l", "-F", "-a", NULL);execl("./while", "while", "aa", "bb", "cc", "dd", NULL);perror("/bin/ls exec error");exit(1);} else if (pid > 0) {sleep(1);printf("parent\n");}return 0;
}
1.3练习
- 编写程序创建子进程,子进程使用 exec族函数,获取当前系统中的进程详细信息,打印到一个文件中。
- 实现命令:ps aux > out
int main(int argc, char *argv[])
{pid_t pid = fork();if (pid == 0) {int fd = open("out", O_WRONLY|O_CREAT|O_TRUNC, 0644);if (fd == -1)sys_err("open error");// 重定向dup2(fd, STDOUT_FILENO);execlp("ps", "ps", "a", "u", "x", NULL);perror("execlp error");exit(1);} else if (pid > 0) {sleep(1);printf("I am parent\n");}return 0;
}
2.进程回收
- fork后的子进程,其父进程有义务在子进程结束时,回收该子进程pcb。隔辈进程无回收关系。
- 进程终止:
1. 关闭所有文件描述符
2. 释放用户空间分配的内存。
3. 进程的 pcb 残留在内核。保存进程结束的状态(正常:退出值。异常:终止其运行的信号编号)
3.孤儿进程
父进程,先于子进程终止。子进程沦为 “孤儿进程”。会被 init 进程领养。
使用命令:ps ajx
ppid(父进程id)pid(进程id)gid(进程组id)sid(会话id)
4.僵尸进程
子进程终止,父进程未终止,但尚未对子进程回收。在此期间,子进程为 “僵尸进程”。
杀死进程命令:kill -9 进程id 。 只能杀死活跃的进程,对僵尸进程无效!!!
5.wait回收
- 只有 “父、子” 进程之间存在,回收关系。 爷孙进程、兄弟进程、叔侄进程... 不存回收关系!
#include <sys/wait.h>
pid_t wait(int *wstatus);
参:
传出参数。回收进程的状态。传 NULL,只回收进程的pcb,不获取退出状态。
返回值:
成功:回收的进程pid
失败:-1, errno
- 函数的作用:
1. 阻塞等待子进程退出(终止)。
2. 回收子进程残留在内核的 pcb。
3. 获取子进程的退出状态(正常、异常)。—— 传出参数 :wstatus
- 回收子进程退出状态:
- 正常退出:
- 判断 WIFEXITED(status) 为真。
- 进一步使用 WEXITSTATUS(status) 获取退出值。
- 异常退出:
- 判断 WIFSIGNALED(status) 为真。
- 进一步 使用 WTERMSIG(status) 获取杀死子进程的信号的编号。
- 回收示例:
int main(int argc, char *argv[])
{int status = 0;pid_t wpid = 0;pid_t pid = fork();if (pid == -1)sys_err("fork err");else if (pid == 0) {printf("I'm child pid = %d\n", getpid());
#if 1execl("./abnor", "abnor", NULL);perror("execl err");exit(1);
#endifsleep(1);exit(73);} else {wpid = wait(&status); // 保存子进程退出的状态.if (wpid == -1)sys_err("wait err");if (WIFEXITED(status)) { // 宏函数为真,说明子进程正常终止.// 获取退出码printf("I'm parent, pid = %d child, exit code = %d\n", wpid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) { // 宏函数为真, 说明子进程被信号终止.// 获取信号编号printf("I'm parent, pid = %d child, killed by %d signal\n", wpid, WTERMSIG(status));}}
6.waitpid回收
pid_t waitpid(pid_t pid, int *wstatus, int options);
参数:
pid:
>0: 通过pid指定 回收某一个子进程。
-1: 回收任意子进程。
0: 回收 与父进程属于同一个进程组的 子进程。
—— 子进程创建成功后,默认,会自动加入父进程进程组。
wstatus:(传出)回收子进程状态。 --- 类似于 wait() 的参数。
options:WNOHANG —— 指定回收方式为 “非阻塞”。
0 —— 指定回收方式为 “阻塞”。等同于 wait()
返回值:
> 0: 表成功回收的进程pid
0: 函数调用时参3指定了 WNOHANG,子进程没有结束。
-1:失败。 errno
示例:waitpid 回收 N 个子进程。
int main(int argc, char *argv[])
{int i = 0;pid_t pid, wpid;for(i = 0; i < 5; i++) {pid = fork();if (pid == 0)break;}if (5 == i) { // 父进程/*while ((wpid = wait(NULL))!=-1) { // 阻塞等待子进程结束,回收printf("wait child %d\n", wpid);} *//* while ((wpid = waitpid(-1, NULL, 0))!=-1) { // 使用 waitpid 阻塞等待子进程结束,回收printf("wait child %d\n", wpid);} *///while ((wpid = waitpid(-1, NULL, WNOHANG))!=-1) { // 使用 waitpid 非 阻塞回收子进程while ((wpid = waitpid(0, NULL, WNOHANG))!=-1) { // 使用 waitpid 非 阻塞回收子进程if (wpid > 0) {printf("wait child %d\n", wpid); // 正常回收一个子进程} else if (wpid == 0) {sleep(1);continue;}}printf("catch All child finish\n");} else {sleep(i);printf("%dth child, pid = %d\n", i+1, getpid());}return 0;
}
总结:
==**一次wait、waitpid 调用,只能回收一个子进程!!!!**==
想回收 N 个子进程,需要将 wait、waitpid 调用 放于 循环中。
7.进程间通信 IPC
- 进程间通信的原理,借助 多个进程使用同一个 内核,借助内核,传递数据。
7.1 进程间通信的方法
1. 管道:最简单。
2. 信号:开销小。
3. mmap映射:速度快,非血缘关系间。
4. socket(本地套接字):稳定性好!
7.2管道pipe
- 实现原理:Linux 内核 使用环形队列机制,借助缓冲区(4k)实现。
- 特质:
1. 本质:伪文件(实为内核缓冲区)
2. 用于进程间通信,用两个文件描述符引用,一个读端,一个写端。
3. 规定,数据从管道写端流入,从读端流出。
- 局限性:
1. 自己写,不能自己读。
2. 管道中的数据,读走没!不能反复读取!
3. 半双工通信。(对讲机)
4. 应用于血缘关系进程间。
使用的函数:
// 函数,调用成功,自动创建匿名管道,返回两个文件描述符,无需open,但需手动 close
int pipe(int pipefd[2]);
参:
fd[0]:管道读端。r
fd[1]:管道写端。w
返回值:
成功:0
失败:-1, errno
- 父子进程 管道通信 IPC。