Linux练级宝典->进程控制详解(进程替换,fork函数)

目录

进程创建

fork函数

写时拷贝

进程终止 

进程退出码

exit函数

_exit函数

return,exit _exit之间的区别和联系

进程等待

进程等待的必要性

获取子进程status

进程等待的方法

wait

waipid

多子进程创建理解

非阻塞轮询检测子进程

进程程序替换

替换原理

exec系列替换函数

命名理解

shell(linux指令解释器)简易版


进程创建

fork函数

我喜欢叫分叉函数,用处就是,在当前进程的基础下,创建一个子进程。

返回值

返回值有三种:0  -1  子进程的PID

返回值 == 0:此时说明当前进程是子进程。

返回值 == -1:说明子进程创建失败。

返回值 == 子进程的PID:说明此时是父进程(因为父进程要管理子进程的退出)。

内核中fork函数:

  • 分配一块内存创建子进程PCB,将其放入管理队列中。
  • 父进程的内容拷贝到子进程。
  • 添加子进程到系统进程列表中。
  • fork返回,开始执行代码。

我们发现在fork前的代码执行了一次,后面的代码子进程和父进程都执行了一次。

fork函数为什么能有两种返回值?

 fork函数在内核中,给子进程分配空间并且管理起来。此时fork函数还没结束的。它还有一个return语句:

此时在返回时二者都要进行返回,所以这个返回值就可以有两个。 然后我们在PCB中是有保存PID和PPID的,此时就可以根据这个确认谁是子进程谁是父进程,此时就能控制返回值。

写时拷贝

顾名思义:只有写的时候才进行拷贝。

我们子进程和父进程是共享一块空间的,为什么不直接给子进程创建新的空间,如果父子进程只有读操作,重新开辟空间copy一份父进程的变量给子进程不是浪费吗?

所以只有子进程和父进程需要更改变量时,才需要把父进程的变量拷贝一个新的变量给到子进程。

什么时候,代码也会被替换?那就是程序替换的范畴了下面会说。

fork通常用于程序替换

 要和 exec函数等程序替换函数配合使用。

可以把子进程的执行逻辑直接切换成别的程序。但是父进程依然可以对子进程进行管理(守护进程)。

进程终止 

进程退出三种情况:

1.代码运行完毕,结果正确。

2.代码运行完毕,结果不正确。

3.代码异常终止(进程崩溃)。

进程退出码

我们先了解下:main函数到底是什么?

main函数在我们写程序中开来就是一切的起源。但是他是怎么被启动的。

VS2013中main函数被一个叫做__tmainCRTStartup的函数调用。而这个函数又被操作系统通过加载器调用。这就是说main函数也是被系统操作的。

通常我们main函数的return值是0,而这个0,其实就是一个退出码。

0退出码代表执行成功,0以外都代表了执行错误

 我们知道C语言中有个函数可以获取错误代码的信息 strerror(数字)。

我们编写一个这样的代码

执行后 

 我们发现只有0是success,其他都是一些错误表示,所以退出码的选择。

可以用echo $? 查看上一次程序的退出码是什么。

exit函数

exit函数就是用来用来退出程序的。并且有三步:

1.执行用户定义的atexit清理函数。

2.关闭这个进程打开的所有流,所有的缓存数据都被写入。

3.调用_exit函数终止进程。

所有数据都会被写入相对的流重点,才会退出。

_exit函数

_exit函数就是 exit函数的退化版,因为exit函数已经包含了_exit。因为_exit不会做任何处理,直接关闭进程。

 这个代码执行结果就是:没有任何输出,我们的printf是先写到对应屏幕的缓冲区,此时_exit在还没写入时就把程序关了,所以就没有任何输出结果。

return,exit _exit之间的区别和联系

return通常都只是返回,但是在main函数中返回即终止主进程,整个代码完结。exit和_exit就是在子函数中强制退出进程用的。

下图是exit和_exit的区别。

进程等待

进程等待的必要性

1.僵尸进程:如果父进程一直不读取子进程退出信息,子进程就变成僵尸进程了,内存泄漏

2.僵尸进程无法被主动删除。

3.对于进程来说,最关心自己的就是父进程,因为父进程需要知道子进程的任务完成情况。

4.父进程通过进程等待的方式,回收子进程资源,获取对应的退出信息。

获取子进程status

这是一个wait和waitpid中的一个参数,这个参数是输入输出型(传入后,在函数中被更改后传出)。如果这个参数给到NULL则表示不关心子进程退出情况。

status是一个整型变量,但是status其实作用就是一个类似位图的运用。

 这里只研究低16位。后八位代表终止信号,即退出码。前7位代表的终止信号,即信号码,第八位的core dump标志。

 正常来说退出后只有退出状态被设置,剩下的都没设置,而终止后core dump也会被设置。

exitCode = (status >> 8) & 0xFF; //退出码
exitSignal = status & 0x7F;      //退出信号

通过位运算就能取出对应的码和信号值

进程等待的方法

wait

函数原型:pid_t wait(int * status);

作用:等待任意子进程

返回值:成功则返回对应的子进程pid,失败返回-1

参数:status,获取状态码。不关心就设置为NULL

waipid

函数原型:pid_t waitpid(pid_t pid, int* status, int options);

作用:等待指定子进程或任意子进程。

返回值:
1、等待成功返回被等待进程的pid。
2、如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0。
3、如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在。

参数:
1、pid:待等待子进程的pid,若设置为-1,则等待任意子进程。
2、status:输出型参数,获取子进程的退出状态,不关心可设置为NULL。
3、options:当设置为WNOHANG时,若等待的子进程没有结束,则waitpid函数直接返回0,不予以等待。若正常结束,则返回该子进程的pid

WNOHANG:设置以后,本来就是子进程不退出,父进程就一直卡着不动,如果设置了子进程没返回,那就先返回0给父进程,此时给父进程在一个循环里持续获取子进程退出信息,再循环体里父进程就可以继续做自己的事情

多子进程创建理解

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t ids[10];for (int i = 0; i < 10; i++){pid_t id = fork();if (id == 0){//childprintf("child process created successfully...PID:%d\n", getpid());sleep(3);exit(i); //将子进程的退出码设置为该子进程PID在数组ids中的下标}//fatherids[i] = id;}for (int i = 0; i < 10; i++){int status = 0;pid_t ret = waitpid(ids[i], &status, 0);if (ret >= 0){//wait child successprintf("wiat child success..PID:%d\n", ids[i]);if (WIFEXITED(status)){//exit normalprintf("exit code:%d\n", WEXITSTATUS(status));}else{//signal killedprintf("killed by signal %d\n", status & 0x7F);}}}return 0;
}

这个代码就是用来创建了十个子进程,然后把10个子进程的的pid放到数组里,之后再让父进程for循环等待子进程结束。

 可以看到我们上面的进程创建出来好,父进程是一个个等待结束的。

那能不能让父进程在没子进程退出时在执行一套自己的逻辑呢?

非阻塞轮询检测子进程

这里就要用到之前说的WNOHANG:WAIT NO HANG,就是说等待不会被挂起的意思。

此时我们给waitpid函数加上WNOHANG。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t id = fork();if (id == 0){//childint count = 3;while (count--){printf("child do something...PID:%d, PPID:%d\n", getpid(), getppid());sleep(3);}exit(0);}//fatherwhile (1){int status = 0;pid_t ret = waitpid(id, &status, WNOHANG);if (ret > 0){printf("wait child success...\n");printf("exit code:%d\n", WEXITSTATUS(status));break;}else if (ret == 0){printf("father do other things...\n");sleep(1);}else{printf("waitpid error...\n");break;}}return 0;
}

此时发现我们父进程除了等待外,还有其他的判断语句此时就是父进程在等待时能做其他事情的意思

进程程序替换

替换原理

本来子进程和父进程执行的是同一个代码,只是可以用if else 分支,但是怎么让子进程直接去执行另一个程序呢?

如上图,在我们创建好子进程后,将其与磁盘中的程序进行替换。 

进程替换时,有没有新的进程创建

都叫进程替换了,那就说明了就是把子进程替换了而已,只是这个新的程序依然可以被父进程管理。pid,进程地址空间,页表都没变,只有代码和数据变了。

那会发生写时拷贝吗?

当然,这里的整个 子进程的代码数据都变了,所以和父进程就发生了写时拷贝,此时就不会影响父进程的数据了

exec系列替换函数

总共有6种exec系列函数

一、int execl(const char *path, const char *arg, ...);

第一个参数是程序路径,第二个可变参数列表,表示的就是这个程序的选项,怎么执行这个程序。以NULL结尾

execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);

 上述代码就是:ls程序的执行。

二、int execlp(const char *file, const char *arg, ...);

第一个参数是要执行程序的名字,和环境变量搭配使用。 第二个和上面一样代表怎么执行这个程序

execlp("ls", "ls", "-a", "-i", "-l", NULL);

三、int execle(const char *path, const char *arg, ..., char *const envp[]);

 第一个参数是地址,第二个参数还是老样子,第三个参数是自己想设置的环境变量。

char* myenvp[] = { "MYVAL=2021", NULL };
execle("./mycmd", "mycmd", NULL, myenvp);

如上代码:执行后,在环境变量中就多了一项 MYVAL,此时这个VAL就能被直接使用。在一些程序中,可以配置一些环境变量,便于使用。

四、int execv(const char *path, char *const argv[]);

 第一个参数是要执行代码的地址,第二个就是一个字符指针(字符串)数组,只是把上面的在参数里传递,改编成了传递一个字符串数组。(一样要以NULL结束)

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execv("/usr/bin/ls", myargv);

五、int execvp(const char *file, char *const argv[]);

 第一个参数是要执行的程序的名字,第二个参数还是执行程序的选项的字符串数组。

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execvp("ls", myargv);

六、int execve(const char *path, char *const argv[], char *const envp[]);

 第一个参数是路径,第二个参数是选项和执行内容,第三个参数是要设置的环境变量。

char* myargv[] = { "mycmd", NULL };
char* myenvp[] = { "MYVAL=2021", NULL };
execve("./mycmd", myargv, myenvp);

上述全部函数成功调用,则直接执行新的程序,不再返回。

失败则返回-1.

命名理解

我们发现exec系列的函数,开头都是exec只是后面带的剩余字母不同:

l(list):表示参数采用列表的形式,可变参数列表。

v(vector):采用数组的形式。

p(path):表示自动搜索环境变量PATH,进行相应程序的查找。

e(env):表示可以传入自己设置的环境变量。

只有execve是系统调用,其它5个函数最终都是调用execve。 

shell(linux指令解释器)简易版

这里制作的shell采用父子进程制作,即bash(shell)接收命令,然后创建子进程然后把子进程的逻辑替换为对应指令即可

#include <stdio.h>
#include <pwd.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#define LEN 1024 //命令最大长度
#define NUM 32 //命令拆分后的最大个数
int main()
{char cmd[LEN]; //存储命令char* myargv[NUM]; //存储命令拆分后的结果char hostname[32]; //主机名char pwd[128]; //当前目录while (1){//获取命令提示信息struct passwd* pass = getpwuid(getuid());gethostname(hostname, sizeof(hostname)-1);getcwd(pwd, sizeof(pwd)-1);int len = strlen(pwd);char* p = pwd + len - 1;while (*p != '/'){p--;}p++;//打印命令提示信息printf("[%s@%s %s]$ ", pass->pw_name, hostname, p);//读取命令fgets(cmd, LEN, stdin);cmd[strlen(cmd) - 1] = '\0';//拆分命令myargv[0] = strtok(cmd, " ");int i = 1;while (myargv[i] = strtok(NULL, " ")){i++;}pid_t id = fork(); //创建子进程执行命令if (id == 0){//childexecvp(myargv[0], myargv); //child进行程序替换exit(1); //替换失败的退出码设置为1}//shellint status = 0;pid_t ret = waitpid(id, &status, 0); //shell等待child退出if (ret > 0){printf("exit code:%d\n", WEXITSTATUS(status)); //打印child的退出码}}return 0;
}

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

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

相关文章

RabbitMq--消息可靠性

12.消息可靠性 1.消息丢失的情况 生产者向消息代理传递消息的过程中&#xff0c;消息丢失了消息代理&#xff08; RabbitMQ &#xff09;把消息弄丢了消费者把消息弄丢了 那怎么保证消息的可靠性呢&#xff0c;我们可以从消息丢失的情况入手——从生产者、消息代理&#xff0…

Windows中在VSCode/Cursor上通过CMake或launch文件配置CUDA编程环境

前置步骤 安装符合GPU型号的CUDA Toolkit 配置好 nvcc 环境变量 安装 Visual Studio 参考https://blog.csdn.net/Cony_14/article/details/137510909 VSCode 安装插件 Nsight Visual Studio Code Edition 注意&#xff1a;不是vscode-cudacpp。若两个插件同时安装&#xff0c;…

Spark(8)配置Hadoop集群环境-使用脚本命令实现集群文件同步

一.hadoop的运行模式 二.scp命令————基本使用 三.scp命令———拓展使用 四.rsync远程同步 五.xsync脚本集群之间的同步 一.hadoop的运行模式 hadoop一共有如下三种运行方式&#xff1a; 1. 本地运行。数据存储在linux本地&#xff0c;测试偶尔用一下。我们上一节课使用…

聚焦两会:科技与发展并进,赛逸展2025成创新新舞台

在十四届全国人大三次会议和全国政协十四届三次会议期间&#xff0c;代表委员们围绕多个关键议题展开深入讨论&#xff0c;为国家未来发展谋篇布局。其中&#xff0c;技术竞争加剧与经济转型需求成为两会焦点&#xff0c;将在首都北京举办的2025第七届亚洲消费电子技术贸易展&a…

【音视频】ffmpeg命令提取像素格式

1、提取YUV数据 提取yuv数据&#xff0c;并保持分辨率与原视频一致 使用-pix_fmt或-pixel_format指定yuv格式提取数据&#xff0c;并保持原来的分辨率 ffmpeg -i music.mp4 -t "01:00" -pixel_format yuv420p music.yuv提取成功后&#xff0c;可以使用ffplay指定y…

【从零开始学习计算机科学】计算机体系结构(二)指令级并行(ILP)

【从零开始学习计算机科学】【从零开始学习计算机科学】计算机体系结构(二)指令级并行(ILP) ILP流水线(pipeline)流水线调度循环展开和循环流水循环展开。循环展开的具体步骤可以描述为,软件流水(循环流水)。我们可以通过流水线的思想处理循环的执行,即不需要这一次的…

android edittext 防止输入多个小数点或负号

有些英文系统的输入法,或者定制输入法。使用xml限制不了输入多个小数点和多个负号。所以代码来控制。 一、通过XML设置限制 <EditTextandroid:id="@+id/editTextNumber"android:layout_width="wrap_content"android:layout_height="wrap_conten…

2019年蓝桥杯第十届CC++大学B组真题及代码

目录 1A&#xff1a;组队&#xff08;填空5分_手算&#xff09; 2B&#xff1a;年号字符&#xff08;填空5分_进制&#xff09; 3C&#xff1a;数列求值&#xff08;填空10分_枚举&#xff09; 4D&#xff1a;数的分解&#xff08;填空10分&#xff09; 5E&#xff1a;迷宫…

从C#中的MemberwiseClone()浅拷贝说起

MemberwiseClone() 是 C# 中的一个方法&#xff0c;用于创建当前对象的浅拷贝&#xff08;shallow copy&#xff09;。它属于 System.Object 类&#xff0c;因此所有 C# 对象都可以调用该方法。 1. MemberwiseClone() 的含义 浅拷贝&#xff1a;MemberwiseClone() 会创建一个新…

笔记六:单链表链表介绍与模拟实现

在他一生中&#xff0c;从来没有人能够像你们这样&#xff0c;以他的视角看待这个世界。 ---------《寻找天堂》 目录 文章目录 一、什么是链表&#xff1f; 二、为什么要使用链表&#xff1f; 三、 单链表介绍与使用 3.1 单链表 3.1.1 创建单链表节点 3.1.2 单链表的头插、…

尚硅谷爬虫note15n

1. 多条管道 多条管道开启&#xff08;2步&#xff09;&#xff1a; (1)定义管道类 &#xff08;2&#xff09;在settings中开启管道 在pipelines中&#xff1a; import urllib.request # 多条管道开启 #(1)定义管道类 #&#xff08;2&#xff09;在setti…

oracle检查字段为空

在Oracle数据库中&#xff0c;检查字段是否为空通常涉及到使用IS NULL条件。如果你想查询某个表中的字段是否为空&#xff0c;你可以使用SELECT语句结合WHERE子句来实现。这里有一些基本示例来展示如何进行这样的查询。 示例1: 检查单个字段是否为空 假设你有一个表employees…

虚幻基础:动画层接口

文章目录 动画层&#xff1a;动画图表中的函数接口&#xff1a;名字&#xff0c;没有实现。动画层接口&#xff1a;由动画蓝图实现1.动画层可直接调用实现功能2.动画层接口必须安装3.动画层默认使用本身实现4.动画层也可使用其他动画蓝图实现&#xff0c;但必须在角色蓝图中关联…

HarmonyOS学习第18天:多媒体功能全解析

一、开篇引入 在当今数字化时代&#xff0c;多媒体已经深度融入我们的日常生活。无论是在工作中通过视频会议进行沟通协作&#xff0c;还是在学习时借助在线课程的音频讲解加深理解&#xff0c;亦或是在休闲时光用手机播放音乐放松身心、观看视频打发时间&#xff0c;多媒体功…

绪论数据结构基本概念(刷题笔记)

&#xff08;一&#xff09;单选题 1.与数据元素本身的形式、相对位置和个数无关的是&#xff08;B&#xff09;【广东工业大学2019年829数据结构】 A.数据存储结构 B.数据逻辑结构 C.算法 D.操作 2.在数据结构的讨论中把数据结构从逻辑上分为&#xff08;C&#xff09;【中国…

GPTQ - 生成式预训练 Transformer 的精确训练后压缩

GPTQ - 生成式预训练 Transformer 的精确训练后压缩 flyfish 曾经是 https://github.com/AutoGPTQ/AutoGPTQ 现在是https://github.com/ModelCloud/GPTQModel 对应论文是 《Accurate Post-Training Quantization for Generative Pre-trained Transformers》 生成式预训练Tr…

git的使用方法

文章目录 前言git简介GIT的基本操作克隆仓库 (Clone)获取最新代码 (Pull)提交代码到远程仓库查看当前分支查看提交代码的日志git config 配置用户信息 GIT的实操 前言 git是一种软件版本管理工具&#xff0c;在多人团队软件开发中地方非常重要。 类似与SVN&#xff0c;git工具…

php虚拟站点提示No input file specified时的问题及权限处理方法

访问站点&#xff0c;提示如下 No input file specified. 可能是文件权限有问题&#xff0c;也可能是“.user.ini”文件路径没有配置对&#xff0c;最简单的办法就是直接将它删除掉&#xff0c;还有就是将它设置正确 #配置成自己服务器上正确的路径 open_basedir/mnt/qiy/te…

使用Langflow和AstraDB构建AI助手:从架构设计到与NocoBase的集成

本文由 Leandro Martins 编写&#xff0c;最初发布于 Building an AI Assistant with Langflow and AstraDB: From Architecture to Integration with NocoBase。 引言 本文的目标是演示如何创建一个集成了 NocoBase、LangFlow 和 VectorDB 工具的 AI 助手。作为基础&#xf…

6.聊天室环境安装 - Ubuntu22.04 - elasticsearch(es)的安装和使用

目录 介绍安装安装kibana安装ES客户端使用 介绍 Elasticsearch&#xff0c; 简称 ES&#xff0c;它是个开源分布式搜索引擎&#xff0c;它的特点有&#xff1a;分布式&#xff0c;零配置&#xff0c;自动发现&#xff0c;索引自动分片&#xff0c;索引副本机制&#xff0c;res…