gdb调试:
gdb调试的时候,只能跟踪一个进程,在fork()之前,通过指令设置gdb跟踪父进程还是跟踪子进程,默认跟踪父进程
设置父进程调试路径:(gdb)set follow-fork-mode parent (默认)
设置子进程调试路径:(gdb)set follow-fork-mode child
exec函数族:
fork()创建子进程后执行的是和父进程相同的程序(但有可能是不同代码分支)
子进程往往要调用一种exec()函数,以指定子进程去执行另一个程序
当进程调用exec()时,该进程的用户空间代码和数据,完全被新程序取代
从新程序开始执行,调用exec()并不创建新进程,所以进程id并不改变,换核不换壳
int execlp(const char *file, const char *arg, ...);
功能:使进程执行某一程序。成功无返回值,失败返回 -1
参1: 要执行程序的名,当PATH中所有目录搜索后都没有参1,则出错返回
参2: argv0 注意,此函数从argv0开始算,所以参2好多时候都和参1一样
参3: argv1
...: argvN
哨兵:NULL,代表参数的结束,放在所有参数的最后
该函数经常用来执行系统程序,如ls、data、cp、cat等
execlp("ls", "ls", "-l", "-h", NULL);
int execl(const char *path, const char *arg, ...);
参1:要执行程序的路径
一般用来执行自己的可执行程序
execl("./a.out", "./a.out", "NULL");
int execvp();
ps ajx --> pid ppid gid sid
孤儿进程:
父进程先于子进程死,子进程沦为“孤儿进程”,会被 init 进程领养,领养的目的是回收子进程。
僵尸进程:
子进程死,父进程尚未对子进程进行回收,子进程用户空间中的内容都没了,但子进程残留的资源(PCB进程控制块)存放于内核中,为“僵尸进程”。
kill对其无效。
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB进程控制块还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量S?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个程。
wait函数:回收子进程退出资源, 阻塞回收任意一个。
pid_t wait(int *status)
参数:(out)回收进程的状态。
返回值:成功: 回收进程的pid
失败: -1, errno
函数作用1:阻塞等待子进程退出(父进程啥也不干,等子进程死)
函数作用2:清理子进程残留在内核的PCB资源
函数作用3:通过传出参数,得到子进程结束状态
waitpid函数:指定某一个进程进行回收。可以设置非阻塞。 waitpid(-1, &status, 0) == wait(&status);
pid_t waitpid(pid_t pid, int *status, int options)
参1:指定回收某一个子进程pid
>0:待回收的子进程pid
-1:任意子进程
0:同组的子进程
参2:(传出)回收进程的状态。
参3:WNOHANG 指定回收方式为,非阻塞,父进程就不傻等了。
返回值:
>0:表示成功回收的子进程 pid
0:函数调用时,参3指定了WNOHANG,并且 没有子进程结束。
-1:失败。errno
总结:wait、waitpid 一次调用,只回收一个子进程。想回收多个。用while循环
借助宏函数来判断进程终止的具体原因
获取子进程正常终止值:以下这些是宏函数,把存放被回收进程的状态的变量status传入
WIFEXITED(status) 如果为真,说明进程正常结束,则调用 WEXITSTATUS(status),得到 子进程 的退出值。
获取导致子进程异常终止信号:
WIFSIGNALED(status)如果为真,说明进程异常结束,则调用 WTERMSIG(status),得到 导致子进程异常终止的信号编号。
===========================
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/9121ce3efdb6a4668ee2194009060704.png)
不同进程间的内核空间(3G-4G)可以互相连通,进程间通信,说白了,就是内核空间中的一块缓冲区,这个缓冲区一般大小是4096
进程间通信的常用方式,特征:
管道:简单
信号:开销小,速度快,但传输数据的种类和数据量 都有限,
mmap映射(共享内存映射):非血缘关系进程间
socket(本地套接字):稳定,实现比较复杂
管道:
只能在 有亲缘关系的 进程之间通信。例如父子,兄弟,叔侄
实现原理:内核借助环形队列机制,使用内核缓冲区实现。
特质:
1.伪文件
Linux有7种文件类型:普通文件、目录、软链接、字符设备、块设备、管道、套接字。
其中,普通文件、目录、软链接是真正占用磁盘空间的,其他4个都是伪文件,都不占用磁盘空间,只占用内存,占用内存的一块缓冲区。
2.管道中的数据只能一次读取。
3.数据在管道中,只能单向流动。
原理:
内核使用环形队列机制,借助内核缓冲区(4k)实现。
局限性:
1.自己写,不能自己读。
2.数据不可以反复读。
3.半双工通信。
4.血缘关系进程间可用。
pipe函数: 创建,并打开管道。
int pipe(int fd[2]);
参数:fd[0]: 读端。
fd[1]: 写端。
返回值: 成功: 0 失败: -1 errno
管道的读写行为:
读管道:
1. 管道有数据,read返回实际读到的字节数。
2. 管道无数据:
1)无写端,read返回0 (类似读到文件尾)
2)有写端,read阻塞等待。
写管道:
1. 无读端, 异常终止。 (SIGPIPE信号导致的)
2. 有读端:
1) 管道已满, 阻塞等待
2) 管道未满, 返回写出的字节个数。