文章目录
1. 进程创建
1.1 fork函数
子进程拷贝父进程的PCB,子进程的大部分数据是来源于父进程,例如:内存指针(数据段和代码段)
父进程创建子进程成功之后,父进程是独立的两个进程,父子进程的调度取决于操作系统内核,进程是抢占的方式执行的,父子进程谁先运行是不确定的,子进程拥有自己独立的进程。
1.2 写时拷贝
一个进程就会有一个PCB,task_struct,父进程内存指针指向了父进程的进程虚拟地址空间,但是这个进程虚拟地址空间并不能真正的存储数据,真正的数据是存储在物理内存上的,因此,我们需要把进程虚拟地址空间和物理内存上的数据对应起来,我们就引入了页表。
当父进程执行fork函数创建了一个子进程,子进程会复制父进程的内存指针,进程虚拟地址空间中的内容还有页表中的对应关系,并且页表中所对应的数据的也是一样的,但是当在子进程中如果修改了数据的值,这时就会存在写时拷贝,会在物理内存中重新开辟一个空间。
1.3 vfork函数
使用vfork函数创建的出来的子进程和父进程指向同一个进程虚拟地址空间。
问题
在进行函数调用压栈的时候,如果子进程调用不出栈,那么父进程也将无法出栈。
解决方案
先把子进程处理完,再处理父进程。
补充: 执行ulimit -a,里面中有一个max user processes,这个是最大进程限制。
2. 进程终止
2.1 场景
- 从main函数的return返回
- 代码执行完毕获得目标结果
- 代码执行完毕未获得目标结果
- 程序崩溃,异常中止
2.2 终止的方法
- main函数return返回
- exit函数
- _exit函数
2.2.1 exit函数和_exit函数区别
在linux的标准库函数中,有一套称作高级I/O的函数,我们熟知的printf 、fopen 、fread 、fwrite都在此列,他们也被称作缓冲I/O。其特征是对应每一个打开的文件,都存在一个缓冲区, 在内存中都有一片缓冲区,每次读文件会多读若干条记录,这样下次读文件时就可以直接从内存的缓存中取出,每次写文件时也仅仅是写入到内存的缓冲区,等待满足一定的条件(达到一定的数量,或者遇到特定字符,如换行和文件结束符EOF),再将缓冲区的内容一次性的写入文件,这样就大大增加了文件读写的速度,但也为我们编程带来了一点点麻烦,如果有些数据,我们认为已经写入了文件,实际上因为没有满足特定的条件,他们还只是保存在缓冲区内,这时我们用_exit函数直接将程序关闭,缓冲区中的数据就会丢失,反之,如果想保证数据的完整性,就一定要使用exit函数。
- exit是一个库函数,_exit是一个系统调用函数。
- exit函数定义在stdlib.h中,而_exit定义在unistd.h中。
- exit和_exit都是用于终止的函数,但是exit会执行用户定义的清理函数和刷新缓冲区和关闭流等。
exit是一个库函数,_exit是一个系统调用函数,而在C库中会冲刷缓冲区关闭流。
void exit (int status)
void _exit (int status)
status会被进程等待接口获得,使用echo $?查看进程退出码
echo $?
因为我在这里的程序是正常运行然后退出的,所以进程退出码是0。
2.3 执行用户定义的清理函数
int atexit (void(*function)(void))
{
atexit(func);
printf("hello\n");
}
这里面的参数是一个指针函数,func是一个回调函数,当etexit退出时执行回调函数
2.4 刷新缓冲区的方式
- \n
- fflush函数
- exit()
- main函数的return
3. 进程等待
3.1 作用
父进程进行进程等待,子进程先于父进程退出,由于父进程在等待子进程,所以父进程会回收子进程退出资源,从而防止子进程变成僵尸进程。
3.2 wait函数
头文件
#include <sys/wait.h>
pid_t wait (int *status);
返回值
成功 返回子进程pid
失败 -1
接收的参数
整型指针 int * 4个字节,但只是用最右边的两个字节。
进程退出码是return ,exit(),exit()返回过来中间的数字。
coredump标志位,如果为1,表示进程有coredump产生,核心转储文件,如果为0,表示当前进程没有coredump产生。
终止信号,当前的程序是由什么信号导致终止的,比如有6号信号(double free)和11号信号(解引用空指针)。
正常情况
只看进程退出码。
异常情况下
进程退出码毫无意义,这时需要看终止信号和coredump标志位
补充
参数int* status是一个出参,调用者准备一个int类型变量,将地址传递给wait函数,wait函数在自己内部进行实现赋值,当wait函数返回值之后,调用者就可以通过int变量,获取进程退出的信息。
3.3 查看进程调用堆栈的命令
命令: pstack [pid]
3.4 阻塞
当前执行流调用某个函数的时候,一直没有返回,此时称为这个现象是阻塞。子进程退出会给父进程发送SIGCHLD信号,而父进程会忽略处理,wait内部就是判断是否接收到一个SIGCHLD信号,如果有,回收资源。
如何获取进程退出码?
(status>>8)& 0xFF
如何获取终止信号?
status & 0x7F
如何获取coredump?
(status>>7) & 0x1
注意
如果想要获取coredump,需要执行ulimit -a
命令: unlimit -c unlimited
功能: 接收coredump
命令: unlimit -c 0
功能: 不接收coredump
3.5 waitpid函数
pid_t waitpid(pid_t pid,int * status,int options)
返回值一共有四类,重点有这两个
-1 等待任一子进程,一旦等待到了,则返回,比如多个子进程一个子进程返回则返回。
>0 等待特定的子进程,大于0的值就是子进程PID号
options
WNOHANG 非阻塞等待方式(非阻塞等待需要搭配循环,直到完成函数功能)
0 阻塞等待