当前位置: 首页 > news >正文

Linux进程控制核心:创建·等待·终止·替换

目录

  • Linux进程控制
    • 1. 进程创建
      • 创建方式
      • 创建流程
      • 内核行为
      • 函数介绍
    • 2. 进程等待
      • 等待原因
      • 函数介绍
      • 等待的两种方式
        • 阻塞等待
        • 非阻塞等待
    • 3. 进程终止
      • 终止原因
      • 终止方式和函数介绍
    • 4. 进程替换
      • 概念
      • 原理
      • 多进程程序替换
      • 函数介绍

Linux进程控制

1. 进程创建

创建方式

​ 1.命令行直接运行程序(本质也是bash函数帮我们创建)

​ 2.fork函数创建子进程(我们自己的代码创建)

创建流程

​ 1.分配新的内存块和内核数据结构给子进程

​ 2.将父进程部分数据结构内容拷至子进程

​ 3.添加子进程到系统进程列表当中

​ 4.fork返回,开始调度器调度

内核行为

创建进程时,先创子进程的内核相关数据结构(task_struct + mm_struct + 页表) 在加载代码和数据。创建时,相关数据结构拷贝一份,修改部分信息(pid,ppid等信息独有信息)。代码和数据刚开始只有一份,后续进行写时拷贝产生父子数据分离

当一个进程调用fork之后,就有两个二进制代码相同的进程。进而他们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程

代码等只读属性的数据共享,读写属性的数据写时拷贝

因为创建的进程各自有各自独立的地址空间,物理内存依靠页表和写时拷贝也不会发生冲突,所以一个进程挂掉不会影响其他进程,也就是进程独立性

函数介绍

#include <unistd.h>
pid_t fork(void);  // 返回值:// >0: 父进程,返回值为子进程PID// =0: 子进程// <0: 创建失败

为了让父进程方便对子进程进行标识,进而进行管理,所以给父进程子进程的pid,给子进程返回0只是为了确认是否创建成功,出错返回-1

2. 进程等待

任何子进程,在退出的情况下,一般必须要被父进程进行等待。进程在退出的时候如果父进程不管不顾,子进程会一直维持状态Z。

等待原因

​ 1.父进程通过等待,解决子进程退出的僵尸问题,回收系统资源(一定要考虑)

​ 2.获取子进程的退出信息(退出码,退出信号),知道子进程因为什么原因退出的(可选的功能)

函数介绍

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* status);//等待最近一个子进程退出
pid_t waitpid(pid_t pid,int * status,int options);
//等待pid为pid进程的退出,若pid参数值为-1,则等待任意一个子进程,与wait等效
//这两个函数返回值正常返回时为收集到的子进程id,如果调用出错则返回-1

调用这两个其中某一个函数时,父进程会进入阻塞等待状态,等待子进程状态发生改变。

status参数是输入型参数,记录子进程退出信息,

其中高16位不用,剩余16位的高8位记录退出码,低8位的最高一位记录core dump 状态,剩余7位记录退出信号。

如何获取到退出码和退出信号的值?

pid_t waitpid(pid_t pid,int * status,int options);
//自己使用位操作来提取
printf("quit code : %d , quit signal : %d", (status>>8)&0xFF, status & 0x7F)//库里的宏
WIFEXITED(status);//若为正常终止子进程的返回状态,则为真,(查看status低7位是否为0)
WEXITSTATUS(status);//若WIFEXITED非零,提取子进程退出码

等待的两种方式

阻塞等待

等待的时候,进程状态为S,D,此时,进程的pcb从运行队列被移动到某个等待队列,不被调度,等待子进程退出。

pid_t waitpid(pid_t pid,int * status,int options);

waitpid函数中,options参数默认为0,此时该函数将进行阻塞等待

非阻塞等待

调用函数后,子进程会很快给出返回值,不会再等待子进程退出后再给返回值

waitpid函数中,options参数为WNOHANG(宏定义),此时该函数将进行非阻塞等待。

此时,返回值有3中情况

​ 1. pid_t > 0:等待成功,子进程退出并且父进程回收成功

​ 2. pit_t < 0:等待失败。

​ 3. pit_t == 0:检测是成功的,只不过子进程还没退出,需要下次等待

非阻塞等待再检测子进程状态结束后,可以执行其他代码功能,不会一直等待,但是要进程下次检测,所以使用非阻塞轮询等待(非阻塞等待 + 循环),相比阻塞等待,非阻塞轮询等待可以允许父进程做其他事情。

3. 进程终止

终止原因

进程终止的必要性

​ 1.释放曾经的代码和数据所占的空间

​ 2.释放内核数据结构

释放数据结构时,task_struct延迟释放(此时为Z状态,僵尸状态)

为什么父进程bash要的到子进程的退出码呢?

要知道子进程退出的情况(成功,失败,失败的原因是什么?),本质也是想告诉用户错误原因

进程终止3种情况:

​ 1.代码跑完,结果正确

​ 2.代码跑完,结果不正确

​ 3.代码执行时,出现了异常,提前退出了(操作系统发现进程做了不该做的事情,操作系统杀了进程,一旦出现异常,退出码就没有意义了)。提前退出是因为操作系统像进程发送了信号(比如 kill -11,段错误)

如何判断是那种情况?

​ 1.先确认是否异常,是异常后查看是那种异常信号

​ 2.不是异常,看退出码

进程退出时,他会释放掉代码,将退出信息(退出码exit_code,退出信号exit_signal)写入进程pcb(task_struct)中等待父进程读取,此时进程状态为僵尸状态(Z状态,代码释放,pcb不释放)

终止方式和函数介绍

​ 1.mian函数return,表示进程终止(非main函数,return,函数结束)

​ 2.代码调用exit函数(代码任意位置调用exit,都表示进程终止)

#include<stdlib.h>
void exit(int status);

​ 3._exit(),系统调用终止进程,exit和 _exit的区别就是前者会刷新缓冲区,后者不会,前者是C语言库函数,后者是系统调用,exit内部会调用 _exit。

补充

bash内部维护着一个变量"?",这个变量会获取最近一个子进程的退出码,比如我们自己写的程序里面,最有一句return 0;0就会被?获取,我们在命令行也可以通过echo $?打印查看

退出码一般0表示正确,非零表示错误,错误有对应的错误信息,可以通过函数来查看

#include<string.h>
strerror(errorcode);

4. 进程替换

概念

一个进程在执行过程中调用exec*系列函数,将进程的代码和数据替换,可以执行起来新的程序。

原理

一个进程在执行时,会有自己的进程内核数据结构(pcb)+代码和数据,当进行程序替换时(调用exec*函数),会将新程序的代码和数据覆盖在旧程序的物理内存上,虚拟内存和页表以及pcb几乎不变,只会改变一些属性,这样就可以用旧的进程“外壳”执行新的进程,叫做进程替换

站在被替换进程的角度,本质就是这个进程被加载到内存,所以说exec类似于Linux系统上的加载函数

exec系列函数执行完毕后,旧进程的后续代码不被执行(旧进程的代码和数据已经被替换了),因此这些 函数的返回值大部分情况可以忽略,替换失败的时候返回值才有数据在内存中

多进程程序替换

由于进行程序替换后,父进程的后续代码不会执行,因此我们可以创建一个子进程,让子进程去执行新的程序,因为进程具有独立性,子进程执行完新的程序后不会影响父进程的后续执行,这样就可以实现在进程中启动另外一个进程,并且不影响本身的进程执行

内存变化:

创建子进程时,会创建新的内核数据结构,但是代码和数据刚开始共享,子进程进行程序替换,操作系统会将磁盘上新程序的代码和数据加载到内存,由于父子进程页表相同,因此加载的位置就是父进程的代码和数据的地址,进程具有独立性,子进程不可以影响父进程,会有写时拷贝发生,将代码和数据放在不同于父进程代码和数据的物理内存的位置,在修改页表映射,此时父子进程完全解耦,完全分离

函数介绍

  #include <unistd.h>extern char **environ; //外部声明环境变量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 execvpe(const char *file, char *const argv[],char *const envp[]);//path参数表示要进行替换的程序的位置,即相对路径或者绝对路径//file表示要进行替换的程序名称,替换时,操作系统会去PATH环境变量里面的路径下寻找该程序//arg是可变参数,表示替换程序时所带的命令行参数,以NULL结尾//argv[]是可变参数表,表内元素为命令行参数,最后一个元素为NULL,//envp[]是环境变量表,替换后的进程内部的环境变量表就是由该参数传递,并且是整体传递//替换前的子进程的环境变量表(由父进程拷贝的)不会默认传递给替换后的程序,若要进行替换,则需要进行外部声明改进程的环境变量表后,直接进行传递即可
#include <stdlib.h>char *getenv(const char *name);//若想增加自定义的环境变量给子进程的环境变量表,则可以使用函数getenv,该函数那个进程调用,则把环境变量加入到(name的内容)该进程的环境变量表中

这6个函数都是用来进行进程替换的函数,但是他们不是系统调用函数,是GNU C语言标准中的函数,是C语言封装出来的函数,真正的系统进程替换系统调用函数只有一个,上面6个函数底层都是调用execve系统调用函数

#include <unistd.h>int execve(const char *filename, char *const argv[],char *const envp[]);
http://www.dtcms.com/a/312044.html

相关文章:

  • Qt 信号和槽正常连接返回true,但发送信号后槽函数无响应问题【已解决】
  • 深入解析Java Stream Sink接口
  • Design Compiler:Milkyway库的创建与使用
  • 1-7〔 OSCP ◈ 研记 〕❘ 信息收集▸主动采集E:SMB基础
  • 硬件-可靠性学习DAY1——系统可靠性设计指南:从原理到实践
  • Markdown 中的图表 Mermaid 与 classDiagram
  • Thread 中的 run() 方法 和 start() 方法的
  • 笔记:C语言中指向指针的指针作用
  • MQTT协议测试环境部署
  • 错误: 找不到或无法加载主类 原因: java.lang.ClassNotFoundException
  • (nice!!!)(LeetCode 每日一题) 2561. 重排水果 (哈希表 + 贪心)
  • UNet改进(29):记忆增强注意力机制在UNet中的创新应用-原理、实现与性能提升
  • 【嵌入式汇编基础】-ARM架构基础(三)
  • 动态规划解最长回文子串:深入解析与优化问题
  • 【redis】基于工业界技术分享的内容总结
  • JS的作用域
  • 第15届蓝桥杯Python青少组中/高级组选拔赛(STEMA)2024年1月28日真题
  • sqli-labs:Less-20关卡详细解析
  • MFC 实现托盘图标菜单图标功能
  • 中州养老Day02:服务管理护理计划模块
  • 中之人模式下的虚拟主持人:动捕设备与面捕技术的协同驱动
  • 2025系规教材改革后,论文怎么写?
  • 错误处理_IncompatibleKeys
  • 在Linux上对固态硬盘进行分区、格式化和挂载的步骤
  • CH32V单片机启用 FPU 速度测试
  • MVVM——ArkUI的UI开发模式
  • 使用Python开发Ditto剪贴板数据导出工具
  • 使用C++实现日志(2)
  • MCP终极指南 - 从原理到实战(基础篇)
  • 面试实战,问题二十二,Java JDK 17 有哪些新特性,怎么回答