【Linux进程控制(二)】进程程序替换(exec系列函数) and 自实现shell命令解释器

在这里插入图片描述

一、进程替换是什么?

fork()之后,父子各自执行父进程代码
的一部分。如果子进程想执行全新程序
就会用到进程的程序替换来完成这个功能

程序替换:通过特定接口,加载磁盘
上的一个权限程序(代码和数据)
加载到调用进程的地址空间中
以达到让子进程执行其他程序的目的

将新的磁盘上的程序加载加载到内存
并和当前进程页表重新建立映射
用操作系统相关接口即可完成
在这里插入图片描述

二、execl系列函数

man execl 查看exec系列函数

在这里插入图片描述

一共有6个函数,这些函数都是

  1. 成功无返回值
  2. 失败返回 -1
  3. 以NULL作为结束标识符

且都属于exec系列函数

我们先演示最简单的execl函数

execl = exec + l
表示它属于exec系列里的execl系列函数
l 表示list,即后面传参时一个个往后跟
像链表的结点一样去传参

execl 是程序替换,该函数调用成功之后
会将当前进程所有代码和数据都进行替换
包括已执行的和没执行的

代码测试

int main()
{
	
    printf("当前进程的开始代码\n");
    execl("/usr/bin/ls", "ls", "-l", NULL);
    
    printf("当前进程的结束代码\n");
    
    return 0;
}

测试结果
在这里插入图片描述
本来要执行后面的
printf(“当前进程的结束代码\n”);
被替换成 ls -l 命令

execlp函数

execlp函数名中p表示PATH
即这个函数会自己在环境变量(PATH)
中进行查找,不用自己传路径

在这里插入图片描述
代码测试

#define NUM 16
 
int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // 子进程
        // ls -a -l
        printf("子进程开始运行,pid: %d\n", getpid());
        sleep(3);                                                                                  
        // execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
        execlp("ls", "ls", "-a", "-l", NULL);
        exit(1);
    }
    else
    {
        // 父进程
        printf("父进程开始运行,pid: %d\n", getpid());
        int status = 0;
        pid_t id = waitpid(-1, &status, 0); // 阻塞等待
        if (id > 0) // 等待成功
        {
            // 打印子进程退出码
            printf("wait success exit code: %d\n", WEXITSTATUS(status));
        }
    }

    return 0;
}

测试结果
在这里插入图片描述
execle函数

execle函数比execl函数多个一个参数
这个参数就是环境变量

在这里插入图片描述
在这里插入图片描述

// mycmd.c
int main()                                                                                                            {   
	printf("获取环境变量: my_l: %s\n", getenv("my_l"));
	return 0;
}
// myproc.c
#define NUM 16
const char* myfile = "./mycmd";

int main()
{	
    // 定义一个环境变量,环境变量名my_l后面的=不要空格
    char* const _env[NUM] = { (char*)"my_l=999777888666", NULL }; 
    pid_t id = fork();
    if (id == 0)
    {	
        // 子进程
        printf("子进程开始运行,pid: %d\n", getpid());
        sleep(3);
        char* const argv[NUM] = { (char*)"ls", (char*)"-a", (char*)"-l", NULL };
        execle(myfile, "mycmd", "-a", NULL, _env);
        exit(1);                                                                                                      
    }
    else
    {
        // 父进程
        printf("父进程开始运行,pid: %d\n", getpid());
        int status = 0;
        pid_t id = waitpid(-1, &status, 0); // 阻塞等待
        if (id > 0) // 等待成功
        {
            // 打印子进程退出码
            printf("wait success exit code: %d\n", WEXITSTATUS(status)); 
        }
    }
return 0;
}

通过execle函数
用一个可执行文件(myproc.c)
执行另一个可执行文件(mycmd.c)
并通过另一个可执行文件(mycmd.c)
获取可执行文件(myproc.c)里定义的
环境变量并打印

执行结果
在这里插入图片描述

三、execv系列函数

execv = exec + v
表示它属于exec系列里的execv系列函数
v 表示vector,即后面传参时把你要传的
参数构建成一个指针数组传过去

在这里插入图片描述
execv函数
可以看到execl和execv除了
传参的方式不一样其他都一样

代码测试

#define NUM 16
 
int main()
{
    pid_t id = fork();
    if (id == 0)
    {
        // 子进程
        // ls -a -l
        printf("子进程开始运行,pid: %d\n", getpid());
        sleep(3);
        char* const argv[NUM] = { (char*)"ls", (char*)"-a", (char*)"-l", NULL };
        execv("/usr/bin/ls", argv);                                                                                     
        // execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
        exit(1);
    }
    else
    {
        // 父进程
        printf("父进程开始运行,pid: %d\n", getpid());
        int status = 0;
        pid_t id = waitpid(-1, &status, 0); // 阻塞等待
        if (id > 0) // 等待成功
        {
            // 打印子进程退出码
            printf("wait success exit code: %d\n", WEXITSTATUS(status));
        }
    }

    return 0;
}

测试结果
在这里插入图片描述
execvp函数
我们学习上面几个函数
其他函数就很简单了
用法都是类似的

// 还是上面的代码,函数改成execvp函数
char* const argv[NUM] = { (char*)"ls", (char*)"-a", (char*)"-l", NULL };
execvp("ls", argv);

运行结果: 可以看到是能正常运行的
在这里插入图片描述
这些函数原型看起来很容易混
但只要掌握了规律就很好记

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

在这里插入图片描述
所有的接口底层都是调用
execle系统接口
在这里插入图片描述

四、如何执行其他C、C++二进制程序

形成两个可执行程序
用一个可执行程序
调用另一个可执行程序

代码实现

// 另一个可执行程序地址
const char* myfile = "/home/ff/Gogh/tempfile/2023/mycmd";
// const char* myfile = "./mycmd"; // 绝对路径和相对路径都是可以的

int main()
{

    pid_t id = fork();
    if (id == 0)
    {
        // 子进程
        printf("子进程开始运行,pid: %d\n", getpid());
        sleep(3);
        execl(myfile, "mycmd", "-a", NULL);
        // execlp("./mycmd", "./mycmd", NULL); 用execlp调用另一个可执行程序
        exit(1);
    }
    else
    {
        // 父进程
        printf("父进程开始运行,pid: %d\n", getpid());
        int status = 0;
        pid_t id = waitpid(-1, &status, 0); // 阻塞等待
        if (id > 0) // 等待成功
        {
            // 打印子进程退出码
            printf("wait success exit code: %d\n", WEXITSTATUS(status)); 
        }
    }

    return 0;
}

另一个可执行程序代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        printf("can not execute\n");
        exit(1);
    }
    if (strcmp(argv[1], "-a") == 0)
    {
        printf("hello a\n");
    }
    else if (strcmp(argv[1], "-b") == 0)
    {
        printf("hello b\n");                                                                                            
    }
    else
    {
        printf("default\n");
    }
    return 0;
}

运行结果
在这里插入图片描述
用一个可执行程序执行
其他语言的可执行程序

// 还是上面的代码
execlp("python", "python", "test.py", NULL);

通过上面的代码实现可以发现
exec*函数:功能就是加载器的底层接口

五、自实现shell命令解析器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

#define NUM 1024
#define SIZE 32
#define SEP " "

// 保存完整的命令行字符串
char cmd_line[NUM];
// 保存打散之后的命令行字符串
char* g_argv[SIZE];

// shell 运行原理:通过让子进程执行命令,父进程等待 && 解析命令
int main()                                                                                        
{
    // 0.命令行解释器,一定是一个不退出的常驻内存的进程
    while (1)
    {
        // 1.打印提示信息 
        printf("[ff@localhost myshell]#"); // 内容在缓冲区,没有\n无法刷新,有\n光标无法定位当前行
        fflush(stdout); // 用fflush函数刷新
        memset(cmd_line, '\0', sizeof cmd_line); // 初始化
        // 2.获取用户的键盘输入[输入的是各种指令和选项:"ls -a -l -i"]
        if (fgets(cmd_line, sizeof cmd_line, stdin) == NULL) 
        {
            // 读取出错
            continue;
        }
        cmd_line[strlen(cmd_line) - 1] = '\0'; // 清除输入的换行符
        // printf("echo: %s\n", cmd_line);
        // 3.命令行字符串解析:"ls -a -l -i" ->  "ls" "-a" "-l" "-i"
        g_argv[0] = strtok(cmd_line, SEP); // 第一次调用,要传入原始字符串
		int index = 1;
		if (strcmp(g_argv[0], "ls") == 0) // 让目录带颜色
		{
    		g_argv[index++] = "--color=auto";
		}
		if (strcmp(g_argv[0], "ll") == 0)
		{
    		g_argv[0] = "ls";
    		g_argv[index++] = "-l";
    		g_argv[index++] = "--color=auto";
		}
		while (g_argv[index++] = strtok(NULL, SEP)); // 第二次如果还要解析原始字符串,传入NULL
		// for (index = 0; g_argv[index]; index++ )
		//     printf("g_argv[%d]: %s\n", index, g_argv[index]); // g_argv解析完毕最后以NULL结尾
		// 4.TODO,内置命令,让父进程(shell)自己执行的命令叫做内置命令
		// 内置命令本质shell中的一个函数调用
		if (strcmp(g_argv[0], "cd") == 0)
		{
    		if (g_argv[1] != NULL) chdir(g_argv[1]);
    		continue;
		}
		// 5.fork
		pid_t id = fork();
		if (id == 0) // child
		{
    		printf("下面功能让子进程进行的\n");
    		execvp(g_argv[0], g_argv);
    		exit(1);
        }
        // father 阻塞等待
        int status = 0;
        pid_t ret = waitpid(id, &status, 0);
        if (ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));
     }
     return 0;
 }

✨✨✨✨✨✨✨✨
本篇博客完,感谢阅读🌹
如有错误之处可评论指出
博主会耐心听取每条意见
✨✨✨✨✨✨✨✨

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

四六级高频词组12

目录 词组 其他链接 词组 501. &#xff08;a&#xff09; passion for 对…的热爱&#xff0c;热情 502. be patient with 对…耐心 503. pay for 赔偿&#xff0c; 付款&#xff0c; 报偿&#xff0c; 处罚 504. pay…for 付…的钱 505. &#xff08;be&#xff09; …

LabVIEW开发电能质量监测系统

LabVIEW开发电能质量监测系统 本研究基于LabVIEW开发了一个创新的电能质量监测系统&#xff0c;专注于暂态电能质量扰动信号的产生、分析与存储。该系统不仅模拟产生了电压骤降、电压波动、暂态振荡以及电压畸变等关键信号&#xff0c;还能够记录并存储这些扰动信号产生时的波…

数据库——审计及触发器

智能2112杨阳 一、目的与要求&#xff1a; 1.了解MySQL审计功能及实现方式 2.掌握触发器的工作原理、定义及操作方法 二、内容&#xff1a; 注&#xff1a; 在同一个触发器内编写多行代码&#xff0c;需要用结构begin ……end 函数current_user()获得当前登录用户名 1.…

cmake 从零开始源码安装(Ubuntu系统)

Ubuntu 系统安装 &#xff11;、安装编译工具和依赖库 ## 必要的 sudo apt install gsudo apt install make## 与make 同等级的构建工具&#xff0c;为了演示而安装的 sudo apt install ninja-build## 压缩工具库 sudo apt install unzip## 加密和传输(根据系统名称可能不一样…

重温经典struts1之文件上传

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 今天来学习下&#xff0c;每个项目都会有的文件上传功能&#xff0c;看看struts是怎么实现的。 步骤 编写三个jsp页面&#xff0c;一个是跳转到文件上传页面&#xff…

改造哈希表,封装unordered_map和unordered_set

正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 unordered_map是存的是pair是K&#xff0c;V型的&#xff0c;而unordered_set是K型的&#xff…

vsftp 使用虚拟用户 —— 筑梦之路

很久之前写过一遍安装vsftp的文章&#xff1a; CentOS 7 vsftpd服务器搭建记录——筑梦之路-CSDN博客 安装一条命令就可以搞定&#xff0c;这里不再赘述。 配置vsftpd.conf # /etc/vsftpd/vsftpd.conf文件修改以下配置#不允许匿名用户认证 anonymous_enableNO #NO表示所有用…

从 0 开始创建 SpringBoot 项目

从 0 开始创建 SpringBoot 项目 从 0 开始创建 SpringBoot 项目环境准备创建项目项目目录结构及说明编写代码参考 从 0 开始创建 SpringBoot 项目 环境准备 操作系统&#xff1a;Windows 10IDE&#xff1a;IntelliJ IDEA 2023.3.1Java 版本&#xff1a;jdk1.8 工具网盘链接&…

记一次挖矿病毒的溯源

ps&#xff1a;因为项目保密的原因部分的截图是自己在本地的环境复现。 1. 起因 客户打电话过来说&#xff0c;公司web服务异常卡顿。起初以为是web服务缓存过多导致&#xff0c;重启几次无果后觉得可能是受到了攻击。起初以为是ddos攻击&#xff0c;然后去查看web服务器管理…

【数据结构c实现】顺序表实现

文章目录 线性表线性表的顺序实现顺序表结构顺序表初始化增配空间Inc打印顺序表show_list线性表长度length尾部插入push_back头部插入push_front尾部删除pop_back头部删除pop_front按位置插入insert_pos按值查找find按位置删除delete_pos按值删除delete_val排序sort(冒泡&#…

【设计模式--行为型--观察者模式】

设计模式--行为型--观察者模式 观察者模式定义结构案例优缺点使用场景JDK中提供的实现例&#xff1a;警察抓小偷 观察者模式 定义 又被成为发布订阅模式&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生…

6.23删除二叉搜索树中的节点(LC450-M)

算法&#xff1a; 一共有五种可能的情况&#xff1a; 第一种情况&#xff1a;没找到删除的节点&#xff0c;遍历到空节点直接返回了找到删除的节点 第二种情况&#xff1a;左右孩子都为空&#xff08;叶子节点&#xff09;&#xff0c;直接删除节点&#xff0c; 返回NULL为根…

学习笔记10——Mysql的DDL语句

学习笔记系列开头惯例发布一些寻亲消息 链接&#xff1a;https://baobeihuijia.com/bbhj/contents/3/197161.html 数据库创建&#xff1a; CREATE DATABASE books&#xff1b; CREATE DATABASE IF NOT EXISTS books;更改字符集 ALTER DATABASE books CHARACTER SET gbk;库的删…

《数据结构、算法与应用C++语言描述》- 构建哈夫曼树

哈夫曼树 完整可编译运行代码见&#xff1a;Github::Data-Structures-Algorithms-and-Applications/_29huffmanTree 定长编码与可变长编码 定长编码 每个字符都用固定长度的编码来表示。 例如假设一个文本是由字符 a、u、x 和 z 组成的字符串&#xff0c;每个字符用2位二进…

ShardingSphereJDBC简单入门

ShardingSphere 介绍ShardingSphere-JDBCSharding-Sphere-ProxyShardingSphere-Sidecar混合架构运行模式DistSQL可拔插架构ShardingSphere的发展路线 主从复制ShardingSphere-JDBC功能SQL解析SQL支持程度SQL稳定支持SQL实验性支持 MySQL不支持SQL清单分页 数据分片垂直分片水平…

不再花冤枉钱!教你怎么选知识付费平台

在当今的知识付费市场中&#xff0c;用户面临的选择越来越多&#xff0c;如何从众多知识付费平台中正确选择属于自己的平台呢&#xff1f;下面&#xff0c;我们将为您介绍明理信息科技知识付费平台相比同行的优势&#xff0c;帮助您做出明智的选择。 一、创新的技术架构&#…

docker部署go gin框架 Windows环境

目录 文章目的是什么 环境介绍 Windows 环境下 docker 部署 go gin 详细步骤 运行容器时因为挂载文件可能会出现的问题 直接部署gin&#xff08;跳过运行容器时因为挂载文件可能会出现的问题&#xff09; 文章目的是什么 假设我们学习了 go 语言&#xff0c;在 Windows(本…

C语言 简单使用qsort 比较结构体字符串大小

1.先简单调用C语言封装好的冒泡排序 #include<stdio.h> #include<stdlib.h> #include<string.h> //qsort C语言封装好的冒泡排序 可比较任何类型 struct stu{char name[20];int age; }; //用户自己写的函数。函数名字也作为函数指针使用。是qsort函数的第四…

代码随想录第三十三天(一刷C语言)|斐波那契数爬楼梯使用最小花费爬楼梯

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 动态规划步骤&#xff1a; 确定dp数组以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 一、斐波那契数 思路&#xff1a;参考carl文档 1、dp[i]的定义为&#xff…

PDI/Kettle-9.2.0.0-R(对应jdk1.8)源码编译问题记录及源码结构简介

目录 &#x1f4da;第一章 前言&#x1f4d7;背景&#x1f4d7;目的&#x1f4d7;总体方向 &#x1f4da;第二章 代码结构初识基本结构&#x1f4d7;代码模块详情 ⁉️问题记录❓问题一&#xff1a;代码分支哪些是发布版本❗答&#xff1a;后缀-R的版本 ❓问题二&#xff1a;50…