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

【进程控制二】进程替换和bash解释器

【进程控制二】进程替换

  • 1.exec系列接口
  • 2.execl系列
    • 2.1execl接口
    • 2.2execlp接口
    • 2.3execle
  • 3.execv系列
    • 3.1execv
    • 3.2总结
  • 4.实现一个bash解释器
    • 4.1内建命令

通过fork创建的子进程,会继承父进程的代码和数据,因此本质上还是在执行父进程的代码
进程替换可以将别的进程的代码替换到自己的代码区,让自己去执行别人的代码
进程替换是通过exec系列系统调用接口实现的

1.exec系列接口

先看看man手册中的exec接口:
在这里插入图片描述
这些接口健壮度很高,就算错误地使用了接口,结果也不容易出错

2.execl系列

execl隶属于exec系列,加上l代表list,表示参数采用列表

2.1execl接口

int execl(const char *pathname, const char *arg, ...);
  • pathname:指定用于替换的进程的路径
  • arg:以何种方式运行进程
  • ...:以何种方式运行该进程
  • NULL:当参数列表list结束,必须以NULL结尾
  • 返回值:如果调用成功,该函数不会返回,因为当前进程的映像被替换

我们现在要替换ls指令到自己的进程中,ls指令在/usr/bin/ls中
我们希望以ls -l -a的形式来调用这个进程,因此我们的三个参数 “ls”, “-l”, "-a"就是这个指令拆分出来的三个字符串
最后以NULL结尾


#include<unistd.h>
#include<stdio.h>int main()
{printf("程序替换前\n");execl("/usr/bin/ls", "ls", "-l", "-a", NULL);//执行ls -l并替代当前进程printf("程序替换后\n");     return 0;
}

输出结果:
在这里插入图片描述
我们成功在当前进程中替换成了ls指令,并以ls -l -a的形式调用
但没有打印“程序替换后”,因为进程替换是用别的进程的代码区覆盖掉自己原先的代码区,所以execl一旦执行,整个进程的代码都被替换了,那么printf(“程序替换后\n”);就会被覆盖掉,最后不输出

2.2execlp接口

int execlp(const char* file, const char* arg, ... );
  • file:指定替换的进程名称(不用指明路径,会自动去环境变量PATH指定的路径中查找)
  • arg:以何种方式运行进程
  • ...: 运行该进程的选项
  • 最后以NULL结尾
  • 返回值:如果调用成功,该函数不会返回,因为当前进程的映像被替换
int main()    
{    printf("程序替换前\n");    execlp("ls","-ls""-l","-a",NULL);  printf("程序替换后\n");        return 0;    
} 

2.3execle

int execle(const char *pathname, const char *arg, ... ,char *const envp[] );
  • pathname:指定用于替换的进程的路径
  • arg:以何种方式运行进程
  • ...:以何种方式运行该进程
  • NULL:当参数列表list结束,必须以NULL结尾
  • envp:指针数组存储环境变量,用于设置新程序的环境变量,数组必须以 NULL 结束
  • 返回值:如果调用成功,该函数不会返回,因为当前进程的映像被替换
int main()    
{    const char* _env[] = {"My_env = 666666666666666666666",NULL};printf("程序替换前\n");    execlp("/usr/bin/ls","-ls","-l","-a",NULL,_env);  printf("程序替换后\n");        return 0;    
} 

execle可以给替换后的进程指定环境变量表
在这里插入图片描述

3.execv系列

v就是vector,以数组的形式,把选项都存在数组中,将整个数组传入

3.1execv

int execv(const char *pathname, char *const argv[]):
  • pathname:指定用于替换的进程的路径
  • argv:指定以何种方式调用进程,将这些选项存储在一个数组中
int main()    
{    char* set[] = {"ls","-a","-l",NULL};printf("程序替换前\n");    execv("/usr/bin/ls",set);  printf("程序替换后\n");        return 0;    
} 

将我们要执行程序的方法用数组存起来再把数组传过去
在这里插入图片描述

3.2总结

在这里插入图片描述
其他接口就不一一演示了
健壮度演示:

int main()    
{    char* set[] = {"ls","-a","-l",NULL};printf("程序替换前\n");    execvp("/usr/bin/ls",set);  //自动查找可执行文件并执行,但我们主动传递了文件路径也不会出错printf("程序替换后\n");        return 0;    
} 

虽然使用的是execvp,但我们主动传递了文件路径也不会出错
在这里插入图片描述

4.实现一个bash解释器

在这里插入图片描述
接下来要把字符串以空格为分割进行打散,strtok函数可以帮助我们实现
在这里插入图片描述
代码如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define NUM 1024//输入命令行字符串
#define SIZE 64 //打散后的命令行字符串
#define SEP " " //字符串分隔符int lastcode = 0;//上个进程的退出码const char* getUsername()
{const char* name = getenv("USER");if(name) return name;else return "none";
}const char* getHostname()
{const char* hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "none";
}const char* getCwd()
{const char* cwd = getenv("PWD");if(cwd) return cwd;else return "none";
}int GetUserCommand(char* command,int num)
{printf("[%s@%s %s]#",getUsername(),getHostname(),getCwd());fgets(command,num,stdin);//在fgets()函数的眼里,换行符’\n’也是它要读取的一个普通字符而已。在读取键盘输入的时候会把最后输入的回车符也存进数组里面,即会把’\n’也存进数组里面command[strlen(command) - 1] = '\0';//将输入的\n清除掉return strlen(command);
}void CommandSplit(char* in,char* out[])
{int argc = 0;out[argc++] = strtok(in,SEP);while(out[argc++] = strtok(NULL,SEP));
}int execute(char* argv[])//执行命令
{pid_t id = fork();if(id < 0) return -1;else if(id == 0)//child process{execvp(argv[0],argv);//程序替换}else//father process{int status = 0;pid_t rid = waitpid(id,&status,0);if(rid > 0){lastcode = WEXITSTATUS(status);//刷新退出码}}return 0;
}int main()
{	while(1){char UserCommand[NUM];//用于保存即将输入的命令行字符串char* argv[SIZE];//保存将会被打散的字符串//GetUserCommand(UserCommand,sizeof(UserCommand));//打印提示符&&获取用户命令字符串CommandSplit(UserCommand,argv);//分割字符串execute(argv);//执行命令}return 0;
}

4.1内建命令

我们实现bash后,可能会遇见一个问题:cd指令进入某个文件夹似乎没用
在这里插入图片描述

因为指令cd是进入某个文件夹,而进入此文件夹当然是由当前的父进程进入
如果由子进程去执行,由于写时拷贝的原因父进程并不会进去
对于像cd这样的指令我们称为内建命令,也就是不能让子进程来完成的命令,只能父进程亲自执行

我们需要主动添加内建命令的判断

char cwd[1024];//父进程要进入的文件路径char* homepath()
{char* home = getenv("HOME");if(home) return home;else return (char*)".";
}
void cd(const char* path)
{chdir(path);//切换当前的工作目录char tmp[1024];getcwd(tmp,sizeof(tmp));sprintf(cwd,"PWD=%s",tmp);putenv(cwd);
}
int doBuildin(char* argv[])
{if(strcmp(argv[0], "cd") == 0){char *path = NULL;if(argv[1] == NULL) path = homepath();else path = argv[1];cd(path);return 1;}return 0;
}

在这里插入图片描述
内建命令不止cd,像export,kill和history等等也是内建命令

完整代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>#define NUM 1024//输入命令行字符串
#define SIZE 64 //打散后的命令行字符串
#define SEP " " //字符串分隔符int lastcode = 0;//上个进程的退出码
char cwd[1024];//父进程要进入的文件路径const char* getUsername()
{const char* name = getenv("USER");if(name) return name;else return "none";
}const char* getHostname()
{const char* hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "none";
}const char* getCwd()
{const char* cwd = getenv("PWD");if(cwd) return cwd;else return "none";
}int GetUserCommand(char* command,int num)
{printf("[%s@%s %s]#",getUsername(),getHostname(),getCwd());fgets(command,num,stdin);//在fgets()函数的眼里,换行符’\n’也是它要读取的一个普通字符而已。在读取键盘输入的时候会把最后输入的回车符也存进数组里面,即会把’\n’也存进数组里面command[strlen(command) - 1] = '\0';//将输入的\n清除掉return strlen(command);
}void CommandSplit(char* in,char* out[])
{int argc = 0;out[argc++] = strtok(in,SEP);while(out[argc++] = strtok(NULL,SEP));
}char* homepath()
{char* home = getenv("HOME");if(home) return home;else return (char*)".";
}
void cd(const char* path)
{chdir(path);//切换当前的工作目录char tmp[1024];getcwd(tmp,sizeof(tmp));sprintf(cwd,"PWD=%s",tmp);putenv(cwd);
}
int doBuildin(char* argv[])
{if(strcmp(argv[0], "cd") == 0){char *path = NULL;if(argv[1] == NULL) path = homepath();else path = argv[1];cd(path);return 1;}return 0;
}int execute(char* argv[])//执行命令
{pid_t id = fork();if(id < 0) return -1;else if(id == 0)//child process{execvp(argv[0],argv);//程序替换}else//father process{int status = 0;pid_t rid = waitpid(id,&status,0);if(rid > 0){lastcode = WEXITSTATUS(status);//刷新退出码}}return 0;
}int main()
{	while(1){char UserCommand[NUM];//用于保存即将输入的命令行字符串char* argv[SIZE];//保存将会被打散的字符串//GetUserCommand(UserCommand,sizeof(UserCommand));//打印提示符&&获取用户命令字符串CommandSplit(UserCommand,argv);//分割字符串int n = doBuildin(argv);//判断是否是内建命令并执行if(n) continue;execute(argv);//执行命令}return 0;
}

相关文章:

【进程控制二】进程替换和bash解释器

【进程控制二】进程替换 1.exec系列接口2.execl系列2.1execl接口2.2execlp接口2.3execle 3.execv系列3.1execv3.2总结 4.实现一个bash解释器4.1内建命令 通过fork创建的子进程&#xff0c;会继承父进程的代码和数据&#xff0c;因此本质上还是在执行父进程的代码 进程替换可以将…...

线性回归策略

一种基于ATR(平均真实范围)、线性回归和布林带的交易策略。以下是对该策略的全面总结和分析: 交易逻辑思路 1. 过滤条件: - 集合竞价过滤:在每个交易日的开盘阶段,过滤掉集合竞价产生的异常数据。 - 价格异常过滤:排除当天开盘价与最高价或最低价相同的情况,这…...

Linux下的c/c++开发之操作Redis数据库

C/C 操作 Redis 的常用库 在 C/C 开发中操作 Redis 有多种方式&#xff0c;最主流的选择是使用第三方客户端库。由于 Redis 官方本身是使用 C 编写的&#xff0c;提供的 API 非常适合 C/C 调用。常见的 Redis C/C 客户端库包括&#xff1a; hiredis&#xff1a;官方推荐的轻量…...

Bitmap、Roaring Bitmap、HyperLogLog对比介绍

一、Bitmap(位图)概述 Bitmap 是一种用位(bit)来表示集合元素是否存在的数据结构。每个位代表一个元素的状态(0或1),非常节省空间且支持快速集合操作。 常见Bitmap类型: 普通Bitmap 最简单的位数组,适合元素范围固定且不稀疏的场景。例如,元素范围是0~1000,用1001…...

JavaScript 的编译与执行原理

文章目录 前言&#x1f9e0; 一、JavaScript 编译与执行过程1. 编译阶段&#xff08;发生在代码执行前&#xff09;✅ 1.1 词法分析&#xff08;Lexical Analysis&#xff09;✅ 1.2 语法分析&#xff08;Parsing&#xff09;✅ 1.3 语义分析与生成执行上下文 &#x1f9f0; 二…...

fastapi项目中数据流转架构设计规范

一、数据库层设计 1.1 ORM模型定义 class SysUser(Base):__table_args__ {"mysql_engine": "InnoDB","comment": "用户表"}id: Mapped[int] mapped_column(Integer, primary_keyTrue, autoincrementTrue, comment"用户ID&quo…...

NHANES指标推荐:FMI

文章题目&#xff1a;Exploring the relationship between fat mass index and metabolic syndrome among cancer patients in the U.S: An NHANES analysis DOI&#xff1a;10.1038/s41598-025-90792-9 中文标题&#xff1a;探索美国癌症患者脂肪量指数与代谢综合征之间的关系…...

【JDBC】JDBC常见错误处理方法及驱动的加载

MySQL8中数据库连接的四个参数有两个发生了变化 String driver "com.mysql.cj.jdbc.Driver"; String url "jdbc:mysql://127.0.0.1:3306/mydb?useSSLfalse&useUnicodetrue&characterEncodingutf8&serverTimezoneAsia/Shanghai"; 或者Strin…...

React中useState中更新是同步的还是异步的?

文章目录 前言一、useState 的基本用法二、useState 的更新机制1. 内部状态管理2. 状态初始化3. 状态更新 三、useState 的更新频率与异步行为1. 异步更新与批量更新2. 为什么需要异步更新&#xff1f; 四、如何正确处理 useState 的更新1. 使用回调函数形式的更新2. 理解异步更…...

Vim编辑器命令模式操作指南

Vim 的命令模式&#xff08;即 Normal 模式&#xff09;是 Vim 的核心操作模式&#xff0c;用于执行文本编辑、导航、搜索、保存等操作。以下是命令模式下的常用操作总结&#xff1a; 1. 模式切换 进入命令模式&#xff1a;在任何模式下按 Esc 键&#xff08;可能需要多次按&a…...

车载以太网驱动智能化:域控架构设计与开发实践

title: 车载以太网驱动专用车智能化&#xff1a;域控架构设计与开发实践 date: 2023-12-01 categories: 新能源汽车 tags: [车载以太网, 电子电气架构, 域控架构, 专用车智能化, SOME/IP, AUTOSAR] 引言&#xff1a;专用车智能化转型的挑战与机遇 专用车作为城市建设与工业运输…...

如何利用技术手段提升小学数学练习效率

在日常辅导孩子数学作业的过程中&#xff0c;我发现了一款比较实用的练习题生成工具。这个工具的安装包仅1.8MB大小&#xff0c;但基本能满足小学阶段的数学练习需求。 主要功能特点&#xff1a; 参数化出题 可自由设置数字范围&#xff08;如10以内、100以内&#xff09; 支…...

C# DataGrid功能总览

目录 前言一、DataGrid基础功能1.DataGrid基础属性2.DataGridTextColumn属性3.DataGridTemplateColumn属性4.表DataGrid点击单元格或行时弹出两个按钮 二、其他功能1.表行DataGrid出现斑马纹效果2.表行DataGrid字体、行背景标红 前言 最近所实现的功能里&#xff0c;表DataGri…...

BGP路由策略 基础实验

要求: 1.使用Preva1策略&#xff0c;确保R4通过R2到达192.168.10.0/24 2.用AS_Path策略&#xff0c;确保R4通过R3到达192.168.11.0/24 3.配置MED策略&#xff0c;确保R4通过R3到达192.168.12.0/24 4.使用Local Preference策略&#xff0c;确保R1通过R2到达192.168.1.0/24 …...

第9讲、深入理解Scaled Dot-Product Attention

Scaled Dot-Product Attention是Transformer架构的核心组件&#xff0c;也是现代深度学习中最重要的注意力机制之一。本文将从原理、实现和应用三个方面深入剖析这一机制。 1. 基本原理 Scaled Dot-Product Attention的本质是一种加权求和机制&#xff0c;通过计算查询(Query…...

2025B难题练习

1.启动多任务排序 拓扑排序 每次选入度为0的点 对每次选的点进行排序 package mainimport ("bufio""fmt""os""slices""strings" )func main() {scanner : bufio.NewScanner(os.Stdin)scanner.Scan()text : scanner.Text()…...

双向长短期记忆网络-BiLSTM

5月14日复盘 二、BiLSTM 1. 概述 双向长短期记忆网络&#xff08;Bi-directional Long Short-Term Memory&#xff0c;BiLSTM&#xff09;是一种扩展自长短期记忆网络&#xff08;LSTM&#xff09;的结构&#xff0c;旨在解决传统 LSTM 模型只能考虑到过去信息的问题。BiLST…...

MySQL UPDATE 执行流程全解析

引言 当你在 MySQL 中执行一条 UPDATE 语句时&#xff0c;背后隐藏着一套精密的协作机制。从解析器到存储引擎&#xff0c;从锁管理到 WAL 日志&#xff0c;每个环节都直接影响数据一致性和性能。 本文将通过 Mermaid 流程图 和 时序图&#xff0c;完整还原 UPDATE 语句的执行…...

亚马逊云科技:开启数字化转型的无限可能

在数字技术蓬勃发展的今天&#xff0c;云计算早已突破单纯技术工具的范畴&#xff0c;成为驱动企业创新、引领行业变革的核心力量。亚马逊云科技凭借前瞻性的战略布局与持续的技术深耕&#xff0c;在全球云计算领域树立起行业标杆&#xff0c;为企业和个人用户提供全方位、高品…...

Gartner《How to Leverage Lakehouse Design in Your DataStrategy》学习心得

一、背景 随着数据量的爆炸式增长和数据类型复杂性的不断提高,企业面临着构建高效、灵活且经济的数据存储与处理架构的挑战。湖仓一体(Lakehouse)作为一种新兴的数据架构设计方法,融合了数据仓库和数据湖的优势,为这一挑战提供了创新的解决方案。Gartner发布了《How to L…...

【实测有效】Edge浏览器打开部分pdf文件显示空白

问题现象 Edge浏览器打开部分pdf文件显示空白或显示异常。 ​​​​​​​ ​​​​​​​ ​​​​​​​ 问题原因 部分pdf文件与edge浏览器存在兼容性问题&#xff0c;打开显示异常。 解决办法 法1&#xff1a;修改edge配置 打开edge浏览器&#x…...

RJ连接器的未来:它还会是网络连接的主流标准吗?

RJ连接器作为以太网接口的代表&#xff0c;自20世纪以来在计算机网络、通信设备、安防系统等领域中占据了核心地位。以RJ45为代表的RJ连接器&#xff0c;凭借其结构稳定、信号传输可靠、成本低廉等优势&#xff0c;在有线网络布线领域被广泛采用。然而&#xff0c;在无线网络不…...

Redis持久化机制详解:保障数据安全的关键策略

在现代应用开发中&#xff0c;Redis作为高性能的内存数据库被广泛使用。然而&#xff0c;内存的易失性特性使得持久化成为Redis设计中的关键环节。本文将全面剖析Redis的持久化机制&#xff0c;包括RDB、AOF以及混合持久化模式&#xff0c;帮助开发者根据业务需求选择最适合的持…...

shell脚本练习(6):备份MySQL数据库表

一、脚本编写 编写脚本如下&#xff1a; #!/bin/bash# 系统数据库 SYS_DB"information_schema|mysql|performance_schema|sys"# 需要备份的数据库 DBmysql -N -e "show databases" | egrep -v $SYS_DBfor i in $DB;do# 备份的路径BAK_PATH"/server/…...

深度学习模型基本框架

简介&#xff1a; 归纳了一套基本框架&#xff0c;以帮助使用者快速创建新的模型&#xff0c;同时有paddlepaddle版本和pytorch版本的&#xff0c;它们虽有差别&#xff0c;但是对于初级使用者&#xff0c;只是两种不同但是很相近的语法而已。都采用paddle平台作为载体来存项目…...

[Java][Leetcode middle] 134. 加油站

方法一&#xff0c;自己想的&#xff0c;超时 双重循环 从第一个点开始循环尝试&#xff0c; 如果最终能走到终点&#xff0c;说明可行。 public int canCompleteCircuit(int[] gas, int[] cost) {int res -1;int n gas.length;int remainGas;int j;for (int i 0; i < …...

DeepSeek 大模型部署全指南:常见问题、优化策略与实战解决方案

DeepSeek 作为当前最热门的开源大模型之一&#xff0c;其强大的语义理解和生成能力吸引了大量开发者和企业关注。然而在实际部署过程中&#xff0c;无论是本地运行还是云端服务&#xff0c;用户往往会遇到各种技术挑战。本文将全面剖析 DeepSeek 部署中的常见问题&#xff0c;提…...

嵌入式培训之数据结构学习(五)栈与队列

一、栈 &#xff08;一&#xff09;栈的基本概念 1、栈的定义&#xff1a; 注&#xff1a;线性表中的栈在堆区&#xff08;因为是malloc来的&#xff09;&#xff1b;系统中的栈区存储局部变量、函数形参、函数返回值地址。 2、栈顶和栈底&#xff1a; 允许插入和删除的一端…...

RabbitMQ--进阶篇

RabbitMQ 客户端整合Spring Boot 添加相关的依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </dependency> 编写配置文件&#xff0c;配置RabbitMQ的服务信息 spri…...

Android Studio报错Cannot parse result path string:

前言 最近在写个小Demo&#xff0c;参考郭霖的《第一行代码》&#xff0c;学习DrawerLayout和NavigationView&#xff0c;不知咋地&#xff0c;突然报错Cannot parse result path string:xxxxxxxxxxxxx 反正百度&#xff0c;问ai都找不到答案&#xff0c;报错信息是完全看不懂…...