Linux进程控制(一)

一、fork函数

在linux中,父进程通过fork函数创建子进程,子进程返回0,父进程返回子进程的pid,出现错误返回-1。

当运行fork函数时,OS会为子进程创建task_struct、mm_struct(进程地址空间)、页表,子进程继承父进程的代码,数据通过写时拷贝的方式进行共享和独立。因此父子进程互相不影响,做到相互独立。

而在fork之前父进程独自运行,fork之后父子进程分别执行,因此父子进程都会有一个返回值,当父进程先return后,子进程再return,此时就发生写时拷贝(注意:父子进程的先后执行顺序取决于调度器)。

如上图,二号进程被执行两次,就是因为fork函数中,父进程会执行第二次printf代码,而子进程也会执行第二次printf代码。

那么子进程如何继承父进程的代码呢???或者说子进程是全部继承父进程的代码吗???

一般来说,父子进程共享fork之后的代码,子进程只能执行fork之后的代码。在CPU中有程序计数器eip,其作用是用来保存正在执行指令的下一条指令,当进行fork时,eip会拷贝给子进程,子进程就会从eip指向的位置开始执行,eip还可以改成main函数入口,此时子进程就会从main函数开始执行。

写时拷贝 

在不写入数据时,父子进程代码、数据共享;当任意一个进程写入数据时,就以写时拷贝的方式进行。

如上图,在未修改数据之前,父子进程共享数据块、代码块,当父子进程任意一方要修改数据时,就会触发写时拷贝,OS为要修改数据的一方开辟一块空间并将原数据拷贝到这块空间中,并为他生成新的页表。

为什么不在fork时把父进程数据全部给子进程呢
  1. 父进程的数据子进程并不是全部需要的,可能只是只读操作,这会产生空间浪费;
  2. 值拷贝需要修改的数据,其他数据共享,这种层面技术角度难以实现;
  3. fork函数直接全部拷贝给子进程,会增减fork的成本,消耗空间时间。

写时拷贝优点在于:只拷贝修改的数据,也就是用最小的成本实现,这是一种延迟拷贝策略,只有当真正需要的时候(进程需要立刻使用,如果进程不是立刻使用,OS会分配给需要的进程)才会开辟空间,拷贝数据,这也就变相的提高了内存使用效率。

 二、进程终止

在c/cpp代码中,main函数是入口,而代码中的return 0是什么意思???

  1. return 0是return给谁???
  2. return 其他数值可以吗???

在代码执行完毕后,结果正确则返回0,不正确返回非0,而各个非0的数字有代表退出码,不同的退出码代表不同的进程退出信息,方便快速定位程序的问题。

  1. 在main函数中,return代表进程退出;在其他函数中代表返回值,该函数调用结束;
  2. 在代码中的任意位置使用exit表示进程退出。

如上图,使用echo $? 打印最近一次的退出码,第一次打印111,代表主函数的返回信息,而第二次打印0,代表echo进程成功执行,返回0。

内核在进程终止时的作用

进程是由内核结构和进程代码、数据构成,内核结构也就是task_struct和mm_struct,而在进程终止时OS可能并不会将内核的数据结构释放。创建一个进程主要分为开辟空间,初始化进程数据两步。而linux会维护一张废弃的数据结构链表,当一个进程被释放(结构被释放,空间没有被释放)时,其不需要的数据就会被维护在这个数据结构链表中,当这个进程再次被创建的时候,OS就会从数据结构链表中找到该进程对应的task_struct和mm_struct,只需要改进成的数据进行初始化即可

三、进程等待 

  1. 如果子进程退出时,父进程不做处理就可能造成僵尸进程,最终造成内存泄漏问题;
  2. 父进程无法拿到子进程的返回值,且僵尸进程无法被杀死。

因此父进程通过进程等待的方式回收子进程的资源并获取子进程的退出信息。

通过wait证明进程等待

用wait的方式回收子进程,让子进程从Z状态进入X状态;

  1. pid_t  wait ( int *status ) ; pid_t>0表示等待成功,否则失败;它可以等待任意一个退出的子进程;
  2. pid_t  waitpid ( pid_t  pid , int  *status , int options) ; 参数pid代表要等待的进程pid,-1表示等待任意进程;options是0表示阻塞等待;
  3. status是一个输出型参数,通过调用该函数从函数内部拿到特定的数据;若不关心子进程退出状态可设为NULL :例如当子进程代码执行完毕返回退出信息到子进程的task_struct时,该函数从子进程的task_struct内部拿到子进程的退出码。

详解status

waitpid(pid_t pid , int *status , int options)函数方法中,options默认是0,叫做阻塞等待,

如上图,在代码中子进程经过count 5次循环后break,exit的退出码是99,在父进程代码中,可以直接通过status获得子进程的退出码,而status的次八位表示子进程的退出状态,status的低七位表示异常退出,因为这个进程收到某个特定的信号。

如上图,status的低七位表示子进程收到的某个特定信息。当一个进程出现异常时,只关心退出信号,退出码毫无意义。

  • WIFEXITED(status): 查看进程是否正常返回,子进程正常结束返回时,则为真。
  • WEXITSTATUS(status):查看进程的退出码,若WIFEXITED非零,直接拿到子进程退出码。

四、进程程序替换

子进程执行的是父进程的代码块,如何让创建出来的子进程执行完全不一样的、全新的程序呢???此时就需要用到进程程序替换,由OS完后该工作。

一般在服务器设计时需要子进程干两件事情:

  1. 让子进程执行父进程的代码块;
  2. 让子进程执行磁盘中全新的程序(shell,让客户端执行对应的程序)。

程序替换原理就是:将磁盘中的程序加载到内存中并重新建立要执行程序的页表映射,此时子进程就不再共享父进程的数据及代码而是执行该程序对应的数据及代码。

如何实现进程程序替换?

执行一个全新的程序,需要解决什么问题???

  1. 先确定程序的位置;
  2. 程序可携带选项执行(明确程序如何执行即要不要带选项);

int execl(const char *path , const char *arg, ...); 为例

第一个参数表示上述问题一;

第二个参数表示可携带的选项(就是命令行的写法(ls -a -l))最后必须是NULL,表示【如何执行程序】的参数传递完毕。

执行上述代码后发现,最后一个printf并没有执行,这是因为一旦execl将程序替换成功后,就会将后面的代码全部替换(execl之后的原先代码就不再存在),因此不执行。

那么该程序替换函数是否需要返回值呢???

当execl替换成功后,即使由返回值int ret = execl(......),此时也没有机会再执行后续代码,因为后面的代码已经被替换了。但是如果程序替换失败,就不会执行execl(......)代码,而是通过返回值来判断失败原因。


进程创建与进程替换

如上图,在子进程内部进行程序替换,如果替换失败则exit(1),此时代码执行结果显示:子进程程序替换成功,父进程等待成功,说明父子进程各自执行且没有受到影响。子进程进程程序替换,不会影响父进程(进程之间的独立性)。

那么是如何做到的呢???

在数据层面是写时拷贝,但是当子进程要进城execl时,OS会识别到紫禁城的execl操作并且为了不让父进程代码受到影响就会将代码也进行写时拷贝,因此就可以理解为数据和代码都发生了写时拷贝。

函数名称    
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[]);

c程序调用cpp程序

如上图,在ALL依赖关系与方法中,ALL依赖mycmd与myexec两个方法,因此make ALL会依次生成mycmd与myexec两个程序。

在myexec.c文件中的execl函数会在对应的路径中搜索"mycmd",找到后并执行。

子进程环境变量传递

在函数execle中涉及到参数环境变量:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/760892.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言实现高精度计时和高精度延时微秒级别

C语言实现高精度计时和高精度延时微秒级别 目的说明环境说明一、高精度延时(微秒级别)二、测试例程三、测试结果 目的说明 在Windows下C语言实现高精度计时功能和高精度延时微秒级别环境说明 Dev-C V5.11一、高精度延时(微秒级别) void vDelayUS(u32 usDelay) {LARGE_INTEGER…

C语言例:表达式10<<3+1的值

10的二进制 00001010 10<<3 01010000 十制左移m位&#xff0c;乘以。 0101 0000 十进制80 10<<31 81

Day75:WEB攻防-验证码安全篇接口滥用识别插件复用绕过宏命令填入滑块类

目录 图片验证码-识别插件-登录爆破&接口枚举 登录爆破 接口枚举 图片验证码-重复使用-某APP短信接口滥用 滑块验证码-宏命令-某Token&Sign&滑块案例 知识点&#xff1a; 1、验证码简单机制-验证码过于简单可爆破 2、验证码重复使用-验证码验证机制可绕过 3、…

突破编程_C++_C++11新特性(完美转发 forward)

1 完美转发的概念 C11 中引入的完美转发&#xff08;Perfect Forwarding&#xff09;是一种强大的编程技巧&#xff0c;它允许在编写泛型函数时保留参数的类型和值类别&#xff08;即左值或右值&#xff09;&#xff0c;从而实现更为高效且准确地传递参数。这种技巧在编写包装…

在springboot中利用Redis实现延迟队列

文章目录 前言一、基本思路二、springboot实现案例三、测试总结 前言 在开发过程中&#xff0c;有很多场景都需要用到延迟队列来解决。目前支持延迟队列的中间件也不少&#xff0c;特别是基于JMS模式下的消息中间件基本上都支持延迟队列。但是有时我们项目规模可能比较小&…

浅谈Spring框架

一、什么是Spring&#xff1f; Spring是一个开源框架&#xff0c;可以降低开发复杂度&#xff0c;提高开发效率&#xff0c;轻量级低耦合的框架。由于Spring的分层架构&#xff0c;可以自己选择整合其他组件&#xff0c;灵活性高 二、什么是IOC&#xff1f; IOC 叫做控制反转&…

如何在 Java 中造成内存泄漏?

如何在 Java 中造成内存泄漏&#xff1f; 应用程序创建一个长时间运行的线程&#xff08;或使用线程池来更快地泄漏&#xff09;。线程通过&#xff08;可选自定义&#xff09;加载类ClassLoader。该类分配一大块内存&#xff08;例如new byte[1000000]&#xff09;&#xff0…

Python PEP 8 代码风格指南

Python PEP 8 代码风格指南 0. 引言1. 空白字符2. 命名3. 表达式和语句4. 导入5. Pylint工具6. 要点总结 0. 引言 Python增强提案#8,也称作 PEP 8,是关于如何格式化Python代码的风格指南。 你可以按自己的方式编写Python代码,只要符合有效的语法规则。 然而,使用一致的风格可…

运维篇SHELL脚本实战案例

统计出每个IP的访问量有多少&#xff1f; 检查是否提供了日志文件的路径作为参数。使用awk从日志文件的每行中提取第一个字段&#xff08;假设这是IP地址&#xff09;。使用sort对提取的IP地址进行排序。使用uniq -c统计每个唯一IP地址的出现次数。最后&#xff0c;使用sort -…

一次消谐器在电力系统中的作用分析

一次消谐器是一种专门用于消除电力系统中的高次谐波的装置。它通过实时监测和分析系统中的谐波成分&#xff0c;采用先进的滤波技术&#xff0c;将谐波分量从系统中滤除&#xff0c;从而保持电力系统的稳定运行。 一次消谐器的主要作用体现在以下几个方面&#xff1a; 1. 保护电…

复习斐波那契(用C++写)

或者这样写&#xff1a; 斐波那契数列 题目描述 斐波那契数列是指这样的数列&#xff1a;数列的第一个和第二个数都为 1 1 1&#xff0c;接下来每个数都等于前面 2 2 2 个数之和。 给出一个正整数 a a a&#xff0c;要求斐波那契数列中第 a a a 个数是多少。 输入格式…

Java基础---IO流

1. File类 1.1 File的介绍 File是java.io.包下的类&#xff0c; File类的对象&#xff0c;用于代表当前操作系统的文件&#xff08;可以是文件、或文件夹&#xff09;。 注意&#xff1a;File类只能对文件本身进行操作&#xff0c;不能读写文件里面存储的数据。 1.2 File类…

Python模块-基础知识

Python模块-基础知识 1.模块分类&#xff1a; &#xff08;1&#xff09;自定义模块&#xff1a; 如果你自己写一个py文件&#xff0c;在文件内写入一堆函数&#xff0c;则它被称为自定义模块&#xff0c;即使用python编写的.py文件 &#xff08;2&#xff09;第三方模块&…

python初始化二维数据

1.遇到的问题 突然不知道什么原因&#xff0c;想起来实现一个矩阵的乘法&#xff0c;于是用python代码实现一下。 def matrix_multiply():a [[1, 2], [3, 4]]b [[5, 6, 7], [8, 9, 10]]m, n len(a[0]), len(b)if m ! n:print(we need a column equal b row!)m, t len(a),…

javaSE练习题(一)

1、BMI是根据体重测量健康的方式。通过以千克为单位的体重除以以米为单位的身高的平方计算出BMI。下面是16 岁以上人群的BMI图表: 编写一个java程序&#xff0c;提示用户输人以磅为单位的体重和以英寸为单位的身高&#xff0c;然后显示BMI值。注意: 1磅是0.453592 37千克而1英寸…

【工具类】adb常用命令

1. adb常用命令 1. adb常用命令 1.1. 常用命令1.2. 命令解析1.3. 参考资料 为了描述方便&#xff0c;假设需要通过 adb 操作 android 系统&#xff0c;本机是 ubuntu 系统 1.1. 常用命令 上传下载&#xff0c;/data/log 目录是手机上的目录&#xff0c;~/Downloads/log 是…

9大变频电源模块的测试参数及其重要性

变频电源是将交流电经过交流-直流-交流变换&#xff0c;从而得到输出为正弦波的交流电&#xff0c;广泛应用于家电、电机、电脑设备、测试单位、航空等领域。变频电源测试是确保系统稳定运行的重要步骤。 变频电源测试的重要参数 1. 输出电压和电流 可用万用表、电流表或者示波…

解决 Jupyter Notebook 中没有显示想要的内核的问题

如果在 Jupyter Notebook 的 “Kernel” 菜单中没有显示你想要的内核&#xff08;kernel&#xff09;&#xff0c;可能是因为该内核没有正确安装或配置到 Jupyter Notebook 中。在这种情况下&#xff0c;你可以尝试以下几个方法&#xff1a; 重新安装内核&#xff1a;首先&…

企业电脑如何管控(高效管控企业电脑的小技巧)

员工企业管理一直以来都是一个难题&#xff0c;难在人员多管理费劲。 因此高效管理一直都是企业最头疼的问题。 而使用一款软件辅助管理是很多企业发现的最有效的方法&#xff0c;如域智盾软件。 域智盾软件是一款专业的文件加密和数据安全软件&#xff0c;适用于各种企业和个…

C++异常处理

C异常处理 try和catch 在C中&#xff0c;try 是异常处理的关键字&#xff0c;用于定义一个代码块&#xff0c;该代码块中可能抛出异常。如果在 try 块中发生了异常&#xff0c;程序会立即停止当前块的执行&#xff0c;并查找与之匹配的 catch 块来处理异常。 下面是一个基本…