linux学习第19、20天(父子进程)
ps ajx -->查看pid,ppid,gid,sid
父子进程
父子进程相同:
刚fork后,data段、text段、堆,栈、环境变量、全局变量、进程工作目录位置、信号处理方式
父子进程不同:
进程id、返回值、各自的父进程、进程创建时间、闹钟、未决信号集
父子进程共享:
读时共享、写时复制——全局变量
1:文件描述符
2:mmap映射区
gdb调试
注意要在fork前调试
设置父进程调式:set follow-fork-mode parent(默认也是)
设置子进程调试:set follow-fork-mode child
exec函数族
fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。
其实有六种以exec开头的函数,统称exec函数:
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[]);
execlp函数
加载一个进程,借助PATH环境变量
int execlp(const char *file, const char *arg, ...); 成功:无返回;失败:-1
参数1:要加载的程序的名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回。
该函数通常用来调用系统程序。如:ls、date、cp、cat等命令。
可以看到,程序执行了ls -lh功能
execl函数
加载一个进程, 通过 路径+程序名 来加载。
int execl(const char *path, const char *arg, ...); 成功:无返回;失败:-1
对比execlp,如加载"ls"命令带有-l,-F参数
execlp("ls", "ls", "-l", "-F", NULL); 使用程序名在PATH中搜索。
execl("/bin/ls", "ls", "-l", "-F", NULL); 使用参数1给出的绝对路径搜索。
用execl也能执行ls这些,把路径给出来就行,但是这样麻烦,所以对于系统指令一般还是用execlp
execvp函数
孤儿进程和僵尸进程
孤儿进程
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
僵尸进程
僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
注意:
kill 对僵尸进程无效,只能杀死父进程结束僵尸进程。这里要注意,每个进程结束后都必然会经历僵尸态,时间长短的差别而已。
子进程终止时,子进程残留资源PCB存放于内核中,PCB记录了进程结束原因,进程回收就是回收PCB。
wait回收子进程
wait函数
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。
pid_t wait(int *status); 回收子进程退出资源,阻塞回收任意一个
返回值:
成功:清理掉的子进程pid;
失败:-1 (没有子进程)
status是传出参数
该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留pcb资源
③ 获取子进程结束状态(退出原因)。
当进程终止时,操作系统的隐式回收机制会:
1.关闭所有文件描述符
2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)
可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:
1. WIFEXITED(status) 为非0 → 进程正常结束
WEXITSTATUS(status) 上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
2. WIFSIGNALED(status) 为非0 → 进程异常终止
WTERMSIG(status) 上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
*3. WIFSTOPPED(status) 为非0 → 进程处于暂停状态
WSTOPSIG(status) 上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
获取子进程退出值和异常终止信号
正常情况
信号终止情况
waitpid函数
pid_t waitpid(pid_t pid, int *status, int optains);
指定回收某一个进程,可以设置非阻塞
参数:
pid:指定回收的子进程pid
>0:待回收的的子进程pid
-1:任意子进程
0:同组子进程
status:(传出)回收进程的状态
optains:WNOHANG指定回收方式为非阻塞
返回值:
>0:表成功回收的子进程pid
0:函数调用时,参3指定了WNOHANG,并且,没有子进程结束
-1:失败,error
总结:
wait、waitpid:一次调用,只回收一个子进程。
想回收多个。用while
例子(错误版)(这是因为,子进程里的pid和父进程里的pid不是一个pid,父进程里的pid还是0)
- //指定回收一个子进程错误示例
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- #include <sys/wait.h>
- #include <pthread.h>
- int main(int argc, char *argv[])
- {
- int i;
- pid_t pid, wpid;
- for (i = 0; i < 5; i++) {
- if (fork() == 0) { // 循环期间, 子进程不 fork
- if (i == 2) {
- pid = getpid();
- printf("------pid = %d\n", pid);
- }
- break;
- }
- }
- if (5 == i) { // 父进程, 从 表达式 2 跳出
- sleep(5);
- //wait(NULL); // 一次wait/waitpid函数调用,只能回收一个子进程.
- //wpid = waitpid(-1, NULL, WNOHANG); //回收任意子进程,没有结束的子进程,父进程直接返回0
- //wpid = waitpid(pid, NULL, WNOHANG); //指定一个进程回收
- printf("------in parent , before waitpid, pid= %d\n", pid);
- wpid = waitpid(pid, NULL, 0); //指定一个进程回收
- if (wpid == -1) {
- perror("waitpid error");
- exit(1);
- }
- printf("I'm parent, wait a child finish : %d \n", wpid);
- } else { // 子进程, 从 break 跳出
- sleep(i);
- printf("I'm %dth child, pid= %d\n", i+1, getpid());
- }
- return 0;
- }
代码运行如下,可以看到,传到父进程里的pid是0,但是
例子(正确版)
注意:一个wait或者waitpid一次只能清理一个子进程,清理多个用循环。