【Linux杂货铺】进程控制

秋刀鱼的滋味@ 2024-07-31 11:07:04 阅读 92


目录

🌈前言🌈

📁 进程创建

📂 fork函数

📂 写实拷贝

📂 创建进程的目的

📂 创建失败原因

📁 进程终止

📂 概念

📂 场景

📂 退出方法

📁 进程等待

📂 概念

📂等待方式

📁 进程替换

📂 原理

📂 替换函数

📂 命名解释 

📁 总结


🌈前言🌈

        欢迎收看本期【Linux杂货铺】,本期内容将讲解Linux中如何管理控制进程,包含了创建进程,进程终止,等待进程,进程替换等内容,期间会拓展讲解写实拷贝,系统调用接口的使用等内容。

📁 进程创建

📂 fork函数

        在Linux中fork函数是非常重要的函数,是用来在已有的进程中创建一个新的进程。新进程为子进程,原有进程为父进程。

<code>#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不变。

        通过下图,可知,将替换进程数据覆盖到原有进程的物理内存中,此后,进程的数据就变为了替换进程的数据。

📂 替换函数

<code>#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是系统调用接口,其他的则是库函数。

<code>#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♪(・ω・)ノ



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。