目录
🌈前言🌈
📁 进程创建
📂 fork函数
📂 写实拷贝
📂 创建进程的目的
📂 创建失败原因
📁 进程终止
📂 概念
📂 场景
📂 退出方法
📁 进程等待
📂 概念
📂等待方式
📁 进程替换
📂 原理
📂 替换函数
📂 命名解释
📁 总结
🌈前言🌈
欢迎收看本期【Linux杂货铺】,本期内容将讲解Linux中如何管理控制进程,包含了创建进程,进程终止,等待进程,进程替换等内容,期间会拓展讲解写实拷贝,系统调用接口的使用等内容。
📁 进程创建
📂 fork函数
在Linux中fork函数是非常重要的函数,是用来在已有的进程中创建一个新的进程。新进程为子进程,原有进程为父进程。
#include <unistd.h>
pid_t fork(void);
返回值: 有两个返回值,对于父进程返回子进程的pid,子进程返回0,出错返回-1
进程调用fork函数,内核做:
1. 分配新的内存块和内存数据结构给子进程。
2. 将父进程部分数据结构拷贝到子进程。
3. 添加子进程到系统进程列表中。
4. fork返回,开始调度器调度。
📂 写实拷贝
通常,父子进程代码共享,父子不在写入时,数据也是共享的即指向同一块内存块。当任意一方进行写入时,会创建新的内存块,不在指向同一块内存块。
因为有页表的存在,所以在上层,看见的虚拟地址是不变的,但实际的物理地址却不同。
在C/C++中,我们看到的地址都是虚拟地址,不是实际的物理内存地址,通过页表的映射,来操作物理地址上的数据。
所以,写实拷贝就是,当共享代码和数据的父子进程,任意一方修改数据时,会创建新的物理内存,改变虚拟地址与物理地址的映射,虚拟地址不变。
📂 创建进程的目的
1. 父进程希望复制自己,使父子进程执行不同的代码段。(通过if 判断来执行不同的代码)
2. 一个进程要执行一个不同的程序。(进程替换)
📂 创建失败原因
1. 系统有太多的进程。
2. 实际用户的进程数超过了限制。
📁 进程终止
📂 概念
1. 释放代码和数据占用的空间。
2. 释放内核数据结构(页表,地址空间),但是PCB延迟处理,并将进程进程设为Z(僵尸状态),等待父进程处理。
📂 场景
1. 程序运行完毕,结果正确。
2. 程序运行完毕,结果不正确。(退出码)
进程想要告诉父进程运行完毕,结果是否正确。怎么告诉呢,就有了退出码的概念。退出码的作用,就是告诉父进程,子进程退出情况是成功,还是失败,如果失败,失败原因是什么。
3. 程序异常终止。(退出信号)
异常终止的概念是,操作系统发现了进程做了不该做的事,例如野指针的使用等。本质是操作系统向进程发送信号,杀掉进程,此时,退出码没有意义。
📂 退出方法
1. main函数中,直接return。
2. exit() : 库函数,会刷新缓冲区,封装了_exit()。
3. _exit():系统调用接口,不会刷新缓冲区。
📁 进程等待
📂 概念
子进程退出时,父进程如果不进行处理,就会造成"僵尸问题",即子进程无法被杀死,一直存在,进而造成内存泄漏。
所以,父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息。
📂等待方式
这里介绍的是第三种方式,waitpid方式,这三个参数分别是什么意思。
返回值:
> 0 : 正常的返回时,waitpid返回收集到的子进程的进程id。
== 0 : 非阻塞等待,设置了WNOHNAG,waitpid发现没有退出的子进程可收集。
< 0 : 调用中出错,返回-1,error会被设置成相应的值以指示错误所在。例如参数给出错误的pid。
1. pid:
pid = -1,等待任意一个子进程,与wait等效。
pid > 0 ,等待进程pid与参数pid相等的子进程。
2. status:
如果想要查看进程的退出码和退出信号,可以传递一个int类型的数据,返回时,通过操作status来查看退出码和退出信息。
status参数是通过位图来操作的,最低的8位是终止信号;此地八位是退出码,通过&操作,来实现查看退出信息。
标准提供了宏函数,帮助查看进程是否是正常终止,以及退出码。
WIFEXITED(status) : 若正常终止子进程,返回值为真 >0。
WEXITSTATUS(status):拓WIFEXITED非0,提取子进程退出码。
3.options:
进程等待分为阻塞等待 和 非阻塞等待。两者的区别在于进程是否可以做其他的事情。
例如,张三给李四打电话,李四说马上下来,张三就一直拿着电话,问好了吗,这就是阻塞等待。而如果张三打完电话,就打游戏,刷会视频,过了会再打一次电话,一直到李四下来,这就是非阻塞等待。
WNOHANG:若pid指定的子进程还没有结束,waitpid函数返回0,不予以等待。若正常结束,返回子进程的ID。
所以,非阻塞等待 + 循环 就是实现了非阻塞轮询,就是打完电话,没下来就玩游戏,打完游戏,再打一次电话,循环往复。
📁 进程替换
📂 原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一个exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间和代码和数据完全被新进程替换,从新进程的启动例程开始执行,
调用exec函数并不创建新进程,所以调用exec前后该进程id不变。
通过下图,可知,将替换进程数据覆盖到原有进程的物理内存中,此后,进程的数据就变为了替换进程的数据。
📂 替换函数
#include <unistd.h>
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[]);
函数调用成功则会加载新的程序到启动代码处,开始执行,不在返回。调用出错返回-1.所以exec* 函数只有出错有返回值,没有成功的返回值。
其中execve是系统调用接口,其他的则是库函数。
#include <unistd.h>
int main()
{
char *const argv[] = {"ps", "-ef", NULL};
char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
execl("/bin/ps", "ps", "-ef", NULL);
// 带p的,可以使用环境变量PATH,无需写全路径
execlp("ps", "ps", "-ef", NULL);
// 带e的,需要自己组装环境变量
execle("ps", "ps", "-ef", NULL, envp);
execv("/bin/ps", argv);
// 带p的,可以使用环境变量PATH,无需写全路径
execvp("ps", argv);
// 带e的,需要自己组装环境变量
execve("/bin/ps", argv, envp);
exit(0);
}
📂 命名解释
l ( list ): 表示参数采用列表。
v ( vector ) : 参数用数组。
p (path) : 有p自动搜索环境变量PATH。
e (env) : 表示自己维护环境变量。
📁 总结
以上,就是本期【Linux杂货铺】的主要内容了,其中介绍了如何创建进程;进程终止的三种场景,了解退出码和退出信号的概念;最后介绍了进程替换的概念,如何实现进程替换等内容。
如果,感觉本期内容对你有帮助,欢迎点赞,关注,评论。Thanks♪(・ω・)ノ