进程的创建/终止/等待/替换

目录

一、进程创建

(一)fork函数的概念

(二)fork函数示例

二、进程终止

(一)退出码的概念

(二)退出码的含义

(三)相关函数和指令

三、进程等待

(一)概念

(二)相关函数

(三)status值的意义

(四)阻塞等待和非阻塞等待

四、进程替换

(一)概念及使用

(二)进程替换的原理

(三)exec*函数的返回值

五、模拟shell


 进程的介绍详见:Linux操作系统——进程-CSDN博客

一、进程创建

(一)fork函数的概念

        在上述一文中提及了 fork() 函数,其作用是从已存在的进程(父进程)中创建新的子进程。

        在讲 fork()函数之前,我们得先知道当子进程被创建时,操作系统会有什么操作。

操作系统将会给创建成功的子进程:

        1、给子进程分配新的内存块和内核数据结构(PCB、进程地址空间、页表等,并构建对应的映射关系);

        2、将父进程的部分数据结构内容拷贝至父进程;

        3、把子进程添加到系统进程列表中;

        4、fork返回,调度器开始调度。

        对于在程序中调用 fork() 函数,子进程会拷贝父进程的代码以及数据,也就是在 fork() 函数调用以后,会分成两个执行流分别执行程序。

(二)fork函数示例

        fork() 函数在调用后,就存在了父子进程。对于父进程来说,fork() 函数会返回子进程的PID。而对于子进程,fork() 函数会返回0;创建子进程失败后,fork() 函数会返回-1。

        利用以上特性,可以接受 fork() 函数的返回值使父子进程执行不同的代码。

#include<stdio.h>
#include <unistd.h>
int main()
{pid_t id = fork();if (id > 0)	{printf("父进程:  pid:%d ppid:%d", getpid(), getppid());}else if (id == 0) {printf("子进程:  pid:%d ppid:%d", getpid(), getppid());}else {printf("fork error\n");return 1;}return 0;
}

二、进程终止

(一)退出码的概念

#include<stdio.h>
int main()
{printf("Hello world\n");return 0;
}

        上面是我们初学C语言时都会敲的程序,那这里的 return 0 代表什么含义呢?

        实际上,每一个程序在执行完成以后都需要给父进程返回程序的执行状态,而这里的 return 0 则是程序执行完成后的返回状态,即退出码,使用标定程序是否正确执行完毕。

        我们可以设定程序不同的退出码以表明程序执行后不同的错误。

(二)退出码的含义

        C语言库为我们提供了不同的退出码对应的错误,当然我们也可以自定不同的码对应的错误。

#include<stdio.h>
#include <unistd.h>
#include<string.h>
int main()
{for (int i = 0; i < 150; ++i){printf("num[%d]:%s\n", i, strerror(i));}return 0;
}

(三)相关函数和指令

        当我们编辑程序时,除了使用 return 来返回退出码以为,也可以使用以下的函数来返回退出码。其中 exit() 为库函数,而 _exit() 为系统调用.

exit()     //库函数
_exit()    //系统调用

       二者的区别为 exit() 执行后会主动刷新缓冲区,而 _exit() 执行后不会刷新缓冲区。

        当程序执行完毕以后,我们也可以使用以下执行来输出最近执行的程序的退出码。

echo $?

三、进程等待

(一)概念

        上述提到了程序执行完毕后(进程退出)会进入僵尸状态,需要向父进程返回执行的状态,除此之外,还需要父进程来回收子进程占用的资源。父进程回收子进程的占用资源的过程我们称为进程等待。

(二)相关函数

1、系统调用 wait()

pid_t wait(int* status);

返回值:等待成功被等待进程的pid,失败返回-1。

参数:status输出型参数,获取子进程退出码和退出状态,不关心则可以设置成为NULL。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t id = fork();if (id == 0){//子进程printf("子进程:%d  父进程:%d  %d\n", getpid(), getppid());sleep(1);exit(0);//子进程退出}pid_t ret = wait(NULL);if (id > 0){//父进程printf("等待成功:%d\n", ret);}return 0;
}

2、系统调用 waitpid()

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

返回值:等待成功被等待进程的pid,失败返回-1。

参数:(1)pid:传入某个进程的PID可指定等待某个进程退出,当值为-1时即等待任何一个进程退出;

           (2)status:status输出型参数,获取子进程退出码和退出状态,不关心则可以设置成为NULL;除此之外,C语言还提供了两个宏,WIFEXITED(status) 和 WEXITSTATUS(status),分别用于表明进程是否正常退出和进程的退出码。

           (3)option:当传入0时为阻塞等待,WNOHANG为非阻塞等待。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t id = fork();if (id == 0){//子进程printf("子进程:%d  父进程:%d\n", getpid(), getppid());sleep(1);exit(0);//子进程退出}int status = 0;pid_t ret = waitpid(id, &status, 0);if (id > 0){//父进程printf("等待成功:%d\n", ret);}return 0;
}

(三)status值的意义

        无论是 wait()  还是 waitpid() 都有一个 status 输出型参数,如果传递为 NULL ,则表示不关心程序的执行状态,否则将返回进程的退出信息。

        stataus 并不是按照简单的 int 类型进行返回,而是按照比特位的不同代表不同的信息。

        在进程退出时,终止信号是评判一个进程是否正常退出;退出状态是评判一个进程运行的结果是否正确。

        下面是代码示例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t id = fork();if (id == 0){//子进程printf("子进程:%d  父进程:%d\n", getpid(), getppid());sleep(1);exit(1);//子进程退出}int status = 0;pid_t ret = waitpid(id, &status, 0);if (id > 0){//父进程printf("等待成功:%d  退出状态:%d  终止信号:%d\n", ret, (status>>8)&0X7F, status& 0XFF);}return 0;
}

(四)阻塞等待和非阻塞等待

        上述 wait() 默认是阻塞等待,而waitpid() 的第三个参数为0时,也是阻塞等待;当waitpid() 第三个参数为 WNOHANG(宏) 时,即非阻塞等待。

        阻塞等待:当父进程等待子进程退出时,若子进程未退出,则父进程将会被阻塞,暂停运行,直到子进程退出。

        非阻塞等待:当父进程等待子进程退出时,若子进程未退出,则父进程仍可以继续运行,此时可以使用轮询检测子进程退出状态。

        非阻塞等待时,父进程在轮询过程中执行其他程序。

        下面是非阻塞等待示例代码:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
int main()
{pid_t id = fork();if (id == 0){//子进程int cnt = 3;while (cnt--){printf("子进程:%d  父进程:%d\n", getpid(), getppid());sleep(1);}exit(1);//子进程退出}while (1){int status = 0;pid_t ret = waitpid(id, &status, WNOHANG);if (ret > 0){//父进程printf("wait success, exit code : % d, sig : % d\n", (status>>8)&0xFF, status & 0x7F);break;}else if (ret == 0){printf("wait done, but child is running...., parent running other things\n");}else{printf("waitpid call failed\n");break;}sleep();}return 0;
}

四、进程替换

(一)概念及使用

        将磁盘中的指定程序加载到内存中并执行。

#include <unistd.h>`
//execve的封装
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[]);
//系统调用
int execve(const char *filename, char *const argv[],char *const envp[]);

        其中函数名中的 l 代表 list 即参数列表;p 代表 path 即无需带路径,只需执行文件的名称即可,v 代表 vector ,即执行参数放入数组中,统一传递;e 代表 env 即可以传入自己写的环境变量。

        下面是示例代码,使用 exec 调用 ls:

#include <stdio.h>
#include <unistd.h>
int main()
{printf("process is running·····\n");execl("/usr/bin/ls"/*要执行的程序*/, "ls", "--color=auto", "-a", "-l", NULL//一定要用NULL结尾);printf("process is down·····\n");return 0;
}

        从上述结果中我们可以看出,最后一句 printf() 函数并没有执行,这是因为执行 execl() 函数将后续的代码与数据覆盖为了可执行文件 ls 的代码以及数据。

        因此我们可以利用进程间相互独立的特性,创新子进程去执行 execl() 函数,这样父进程仍会继续向后执行,二者互不影响。

#include <stdio.h>
#include <unistd.h>
int main()
{printf("process is running·····\n");pid_t id = fork();if (id == 0){//创建的子进程execl("/usr/bin/ls"/*要执行的程序*/, "ls", "--color=auto", "-a", "-l", NULL/*如何执行*/);//一定要用NULL结尾}slepp(1);printf("process is down·····\n");return 0;
}

(二)进程替换的原理

        程序替换的本质:用磁盘指定位置上的程序的代码和数据,覆盖进程自身的代码和数据,达到让进程执行指定程序的目的。

(三)exec*函数的返回值

        exec()函数仅在发生错误时返回。返回值为-1,设置errno以指示错误。

        exec*系列函数调用成功后并没有返回值,因为exec*一旦被调用成功,后续代码将被覆盖,无法进程返回。

五、模拟shell

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#define NUM 100
#define OPT_NUM 20
char LineCommand[NUM];
char* myargv[OPT_NUM];
int lastCode = 0;
int lastSig = 0;
int main()
{while (1){lastCode = 0;lastSig = 0;//切割字符串printf("用户名@主机名 当前路径:");fflush(stdout);char* s = fgets(LineCommand, sizeof(LineCommand), stdin);assert(s != NULL);LineCommand[strlen(LineCommand) - 1] = '\0';myargv[0] = strtok(s, " ");int i = 1;if (myargv[0] != NULL && (strcmp(myargv[0], "ls") == 0)){myargv[i++] = (char*)"--color=auto";}while ((myargv[i++] = strtok(NULL, " ")) != NULL);if (myargv[0] != NULL && (strcmp(myargv[0], "cd") == 0)){chdir(myargv[1]);continue;}if (myargv[0] != NULL && (strcmp(myargv[0], "echo") == 0)){if (strcmp(myargv[0], "echo") == 0){printf("%d, %d\n", lastCode, lastSig);}else{printf("%s\n", myargv[1]);}continue;}
#ifdef DEBUGfor (i = 0; myargv[i]; ++i){printf("%s\n", myargv[i]);}
#endif//执行命令pid_t id = fork();if (id == 0){execvp(myargv[0], myargv);exit(0);}int status = 0;int ret = waitpid(id, &status, 0);assert(ret > 0);lastCode = (status >> 8) & 0X7F;lastSig = status & 0XFF;}return 0;
}

        运行该程序,可以实现shell的效果。其中有些指令是需要父进程执行的,例如 cd 指令,如果创建子进程去执行 cd 指令, 那么改变的是子进程的目录,子进程在执行后被释放,而父进程(shell进程)并不会改变目录。因此 cd 指令需要父进程来执行。像这种不需要子进程来执行,而是让shell来执行的命令叫做内建/内置命令。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【c++丨STL】list的使用

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;C、STL 目录 前言 list简介 一、list的默认成员函数 构造函数(constructor) 析构函数 赋值重载 二、list的迭代器接口 迭代器的功能分类 三、list的容量…

CANoe导入CAN DataBase(DBC文件)

Canoe是一款用于汽车网络仿真和开发的工具&#xff0c;它支持导入DBC文件&#xff08;CAN Database文件&#xff09;以定义和配置CAN网络中的消息、信号和节点。 将DBC文件拷贝至我们的工程目录的DBC文件夹内&#xff0c;随后在Simulation Setup中右击DataBase&#xff0c;进…

nacos配置管理

1、增加依赖 <!--配置管理的依赖 --> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId><version>2.1.0.RELEASE</version> </dependency><de…

每日OJ题_牛客_奇数位丢弃_找规律/模拟_C++_Java

目录 牛客_奇数位丢弃_找规律/模拟 题目解析 C代码1模拟 C代码2找规律 Java代码找规律 牛客_奇数位丢弃_找规律/模拟 奇数位丢弃_牛客题霸_牛客网 描述&#xff1a; 对于一个由 0..n 的所有数按升序组成的序列&#xff0c;我们要进行一些筛选&#xff0c;每次我们丢弃去…

解决table下tr或td选中不生效的问题

目录 一、问题描述 1.首先我们来看一下代码结构 2.检查代码&#xff08;鼠标右键或按下F12&#xff09; 3.解决方案 一、问题描述 解决table下tr或td选中不生效&#xff0c;页面刷新无效果 1.首先我们来看一下代码结构 这里我们的结构是table标签下的tr&#xff0c;tr当…

学籍拍照助手,中小学新生学籍证件照电脑端拍照教程

新学期过半&#xff0c;许多中小学学籍管理员都忙碌起来&#xff0c;为孩子们准备学籍所需的证件照。传统的照相馆拍摄、向家长收集都存在一些弊端&#xff0c;下面就来介绍如何使用校园学籍拍照助手&#xff0c;更智能的完成学籍证件照的拍摄。 1. 准备工作在开始之前&#xf…

SE30 程序运行时间评估

日常执行报表的时候 可能会遇到报表反应时间太长 用户无法接受的情况&#xff0c;此时 作为IT同事 需要分析程序的运行时间&#xff0c;可以使用SAP标准事务码SE30. 1、选择运行时分析-测量-立即执行&#xff08;有些程序可能没有此按钮 需联系开发增加&#xff09; 2、以发…

T-Rex Label标注

这个是做大量数据集的时候用到的&#xff0c;但我觉得他比labelimg好用。 仙人指路✈trexlabel 基本标注 如果是从新开始的话就是 导入图片然后进行直接标注 如果是后期添加图片继续标注&#xff0c;选择你需要的数据集格式&#xff0c;导入即可。 如此&#xff0c;进去就…

部署zabbix遇到问题: cannot find a valid baseurl for repo:centos-sclo-rh/x86 64 怎么解决 ?

安装 Zabbix 前端包&#xff0c;提示cannot find a valid baseurl for repo&#xff1a;centos-sclo-rh/x86 64 安装zabbix前端包 # yum install zabbix-web-mysql-scl zabbix-apache-conf-scl 解决办法&#xff1a; 原因是&#xff1a;CentOS7的SCL源在2024年6月30日停止维护…

小程序+公众号统一账号unionid,实现pc+公众号+小程序统一身份

一、微信开放平台 注册开发者账号、绑定公众号、小程序 二、小程序端获取unionid 1获取code wx.login({success: res > {console.log("getCode", res.code)this.getOpenId(res.code)}}) 2通过code调用后台方法获取openid,unionid 小程序端 getOpenId: functi…

LeetCode【0037】解数独

本文目录 1 中文题目2 求解方法&#xff1a;递归回溯法2.1 方法思路2.2 Python代码2.3 复杂度分析 3 题目总结 1 中文题目 编写一个程序&#xff0c;通过填充空格来解决数独问题。数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只…

零碎02-接口文档管理

目录 一、背景故事 二、解决方案分析 1. 静态文档方案 2. Swagger Springfox 3. Knife4j增强方案 三、示例 1. 添加依赖 2. 配置Knife4j 3. 创建knife4j配置类 4. 启动Spring Boot项目并访问接口文档 5. 使用示例 6. 测试和使用 四、总结 一、背景故事 酷乐是一名…

指标体系构建:如何设计北极星指标设计?

目录 1 北极星指标的作用 2 北极星指标设计标准 标准1 标准2 标准3 标准4 标准5 标准6 3 小结 1 北极星指标的作用 北极星指标是公司业务成功的关键指标&#xff0c;反映了公司为用户带来的价值&#xff0c;有以下3点作用&#xff1a; ● 像北极星一样&#xff0c…

三菱FX5UPLC以太网Socket通信功能Passive开放的程序示例

Passive开放的通信流程如下所示。 参数设置 示例程序中使用的参数设置如下所示。 [CPU模块】 导航窗口↔[参数]↔[模块型号]↔[模块参数]-[以太网端口]-[基本设置]-[对象设备连接配置设置]↔[详细设置]→[以太网配置(内置以太网端口)]画面 【以太网模块】 [导航]中「参数]→[模…

【MATLAB源码-第292期】基于matlab的4ASK调制解调窄带通信系统仿真,输出各节点波形图以及误码率曲线图。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 窄带通信系统是指带宽较小、频谱利用效率较低的通信系统。与宽带通信系统相比&#xff0c;窄带系统的特点是信号的带宽相对较窄&#xff0c;因此需要更精确的调制技术来实现有效的通信。在窄带通信中&#xff0c;常见的调制方…

【搜索结构】AVL树的学习与实现

目录 什么是AVL树 AVL树的定义 插入函数的实现 左单旋和右单旋 左右双旋与右左双旋 什么是AVL树 AVL树实际上就是二叉搜索树的一种变体&#xff0c;我们都知道二i叉搜索树可以将查找的时间复杂度提升到O(logn)&#xff0c;极大提升搜索效率。但是在极端情况下&#xff0c;当…

【专题】2024年中国消费者消费意愿调查报告汇总PDF洞察(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p38242 当今时代&#xff0c;经济社会多元发展&#xff0c;消费市场复杂多变。消费者的行为、需求和支出意愿不断演变&#xff0c;深刻影响着各个领域的发展。家庭余钱的用途反映出消费者在储蓄、教育、医疗等方面的考量。在消费领域…

推荐一款游戏玩家性能优化工具:Razer Cortex

Razer Cortex是一款专为游戏玩家设计的性能优化工具&#xff0c;它旨在提升玩家的游戏体验。通过该软件&#xff0c;用户可以优化 PC 性能&#xff0c;从而提高游戏的流畅度&#xff0c;减少延迟并增强视觉效果&#xff0c;尤其在需要精准操作的游戏中&#xff0c;流畅的画面和…

人工智能(AI)对于电商行业的变革和意义

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/402a907e12694df5a34f8f266385f3d2.png#pic_center> &#x1f393;作者简介&#xff1a;全栈领域优质创作者 &#x1f310;个人主页&#xff1a;百锦再新空间代码工作室 &#x1f4de;工作室&#xff1a;新空间代…

1435:【例题3】曲线 一本通 代替三分

1435&#xff1a;【例题3】曲线 题目来源&#xff1a;一本通oj链接 代替三分 题意 给出t组数据&#xff0c;每组里面有n个函数&#xff0c;求出t组数据的函数的最小值 思路 函数是二次函数&#xff0c;具有单峰性&#xff0c;利用左右两边单调性的原理可以进行答案三分处…