Linux | 程序替换

news2024/11/26 2:37:54

前言

        本文主要记录小编学习程序替换中遇到的一些问题,并分享记录下来,希望可以给大家带来帮助;

一、初始程序替换

        所谓程序替换,就是将本进程的代码和数据进行替换,运行新程序的代码;我们之前在讲解进程地址空间的时候,子进程会复制父进程的PCB控制块等内核信息,其中也包括页表的映射,也就是说父进程和子进程共享同一块代码和数据,可是,当子进程对数据进行修改时,发生写时拷贝,这里一般清空下,我们进行写时拷贝的都是数据,并不会对代码进行更改,而我们今天的程序替换恰恰会对代码进行更改;

        我们程序替换的系统调用主要为以下几个;

        看着似乎好像很多,实际上,我们学会其中的几个,剩下几个都可以迎刃而解;接下来我就带着大家一起初步认识上面的几个系统调用;

二、如何进行程序替换

1、execl函数

        函数定义如下;

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

参数一:path

        该参数为我们要替换的程序的地址,如我们的ls指令,我们通过which或whereis来查询该指令所在路径;

        我们便可以把这个地址填到这个参数内;参数一是为了找到要替换程序的位置

参数二:

        这个第二个参数实际上是可变参数列表,对应着函数中的l,意味list,我们可以列出我们如何调用这条指令,以NULL结尾。还是比如ls指令,我们可以加上各种各样的选项,使 ls 个性化出不同的功能;比如,这里我们可以填 "ls", "-a", "-l", NULL;这个参数是让我们告诉OS如何运行这个程序;

返回值:

        这个函数的返回值非常奇怪,若失败则返回-1,而一旦成功,则没有返回值;

问题:如何看待没有返回值?

        首先思考,我们如果调用成功,我们应该有返回值吗?我们首先理解程序替换的本质,程序替换是将我们的代码和数据重新映射,并不是重新创建一个新PCB控制块、进程地址空间、页表等内核数据结构;而一旦程序替换成功,原来的代码和数据我们都无法访问了,因为页表的映射改变了,因此我们也无法保存下来这个返回值,因为代码都改变了;

注意:后面所有这种程序替换的返回值都一样

百闻不如一见:

        编译运行结果如下;

        结果如我们所料,我们打印了 main begin,却没有main end,因为程序替换成功后,整个代码都被替换掉了;

注意:后面的函数也与这个函数类似,就不会讲解这么详细了;

2、execv函数

        这个函数与之前函数不同的是将 l 变成了 v ,这里的v可以理解称vector,就像数组一样,因此这个函数使用的不同就是第二个参数并不像列表一样列出来,而是直接传入一个数组即可,这个数组依然以NULL结尾,有的环境下可能不需要,具体函数声明如下;

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

        编译运行结果如下;

3、execlp函数

        这个函数在我们execl函数基础上加了一个p,函数声明如下;

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

        此时仅仅第一个参数发生了变化,第一个参数变成了file,也就是文件名,此时我们直接输入可执行程序名即可,这时,操作系统就会在环境变量中PATH中,搜索每个路径是否存在该文件,如果有则直接执行,没有则报错返回;这里新增的p正是环境变量中的 PATH;还不了解环境变量的同学可以看看下面这篇博客;

Linux | 进程-CSDN博客

        有的小伙伴可能会疑惑,为什么要些两个ls呢?实际上我们应该将这个函数的参数分为两个部分来去理解;

        前面也有强调这个,希望大家能有所理解;

4、execvp函数

        会使用前面execlp函数,这个函数照葫芦画瓢就行了,唯一不同就是一个是以列表的形式列出来,一个是以字符指针数组的形式传参;具体使用如下;

5、execle函数

        这个函数与execl函数唯一不同的是这个函数可以传环境变量,如下声明所示;

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

6、execvpe函数

        想必有了前面的学习,这个函数看一眼应该都会使用了吧,这里的v是字符指针数组,p是会默认从环境变量PATH中找程序,e代表可传入环境变量,声明如下;

int execvpe(const char *file, char *const argv[],char *const envp[]);

7、execve函数

        下图为man手册查询结果,细心的小伙伴已经发现了,最开始我们只说了有6个程序替换函数呀,不信的可以去初始程序替换那张图中查找是否有该函数,这个函数实际上才是真正的系统调用,上面的6个函数实际上就是对这个系统调用的封装;这个函数的使用规则也如上面几个函数,这里也就不做过多演示;

三、深刻理解程序替换

1、程序替换代码形式

        实际上,程序替换的代码分为两种,一种是直接调用程序替换函数进行替换,另一种则是先调用fork函数,再让子进程调用程序替换函数;也就是说,上面的代码可以改成如下形式;

        这样写代码的好处是什么呢?我们让子进程去执行程序替换的代码,即使程序替换的程序有问题,执行后会崩溃,也只是子进程崩溃,并不会影响我们的父进程;因为我们要清楚,我们的程序替换不仅可以执行系统指令,可以执行我们自己写的可执行程序,如下所示,我们写了一个sub.c的程序;

        我们在改以下原来main函数的代码,我们调用我们刚才自己写的程序sub;

        编译运行,结果果然如我们所料,我们也可以使用程序替换调用我们自己写的程序;

2、灵魂提问

问题:我们的环境变量是否会被替换?

        并不会,程序替换仅仅只是替换代码和数据,并不会替换我们的环境变量;

问题:程序替换和创建子进程

        创建子进程首先会为我们创建PCB、进程地址空间、页表等内核数据,然后将代码和数据加载进内存,此时内核数据+代码数据我们称这个为进程,这是进程的创建才完成,而程序替换指的是将当前程序的代码和数据换成别的程序的代码和数据,此时不并不会创建新的内核数据,仅仅将改变内核数据,如页表的映射,映射到新的物理地址;

四、模拟实现shell命令行

        有了如上的知识基础,我们可以模拟实现一个shell命令行小程序;首先我们要清楚一个命令行要具备什么?

1、一定是一个死循环,不断读取我们指令进行解析执行;

2、首先要有命令行提示;

3、获取用户输入指令

4、解析用户输入指令

5、判断内置命令,也就是有父进程自己完成的指令

6、创建子进程,进行程序替换

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

// 命令字符数组大小
#define NUM 128
// 解析后命令字符指针数组大小
#define SIZE 32
// 命令分隔符
#define SEP " "
// 是否调试
//#define DEBUG


// 保存用户输入命令
char cmd_line[NUM];
// 解析命令后保存的数组
char* args[SIZE];


char Myenv[64];
int main()
{
    while(1)
    {
        // 清空命令字符数组
        memset(cmd_line, 0, sizeof(cmd_line));

        // 1、显示提示符
        printf("[root@localhost]# ");
        fflush(stdout); // 必须刷新缓冲区

        // 2、获取用户输入
        fgets(cmd_line, sizeof(cmd_line), stdin);
        // 处理最后的换行符
        cmd_line[strlen(cmd_line) - 1] = '\0';
#ifdef DEUBG
        printf("%s\n", cmd_line);
#endif
        // 3、对用户输入命令解析
        args[0] = strtok(cmd_line, SEP);
        int index = 1;
        while(args[index++] = strtok(NULL, SEP));
#ifdef DEBUG 
        for(int i = 0; args[i]; i++)
        {
            printf("args[%d]: %s\n", i, args[i]);
        }
#endif

        // 4、内建命令处理与特殊命令处理
        if(strcmp(args[0], "cd") == 0)
        {
            if(args[1] != NULL)
            {
                // 更改当前目录
               int ret = chdir(args[1]);
               if(ret < 0)
               {
                   printf("更改失败\n");
               }
               continue;
            }
            else 
            {
                printf("命令格式有误\n");
                continue;
            }
        }
        if(strcmp(args[0], "ll") == 0)
        {
            args[0] = (char*)"ls";
            args[1] = (char*)"-l";
        }
        if(strcmp(args[0], "export") == 0)
        {
           if(args[1] != NULL)
           {
               strcpy(Myenv, args[1]);
               putenv(Myenv);
           }
        }
        // 5、创建子进程,并让子进程执行命令
        pid_t id = fork();
        if(id < 0)
        {
            // fork 函数调用失败
            printf("fork fail\n");
            continue;
        }
        else if(id == 0)
        {
            printf("MYVAL:%s\n", getenv("MYVAL"));
            // 子进程
            execvp(args[0], args);
            exit(-1);
        }
        else 
        {
            // 父进程
            int status = 0;
            waitpid(id, &status, 0);
            if(WIFEXITED(status))
            {
                // 正常退出
                printf("正常退出,退出码: %d\n", WEXITSTATUS(status));
            }
            else 
            {
                // 异常退出,获取退出信号
                printf("崩溃了,推出信号: %d\n", status & 0x7F);
            }
        }
    } 
        return 0;
}

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

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

相关文章

CDN技术:提升网络效能与用户满意度

网络的持续增长和数字内容的快速传播已经引发了对网络性能和用户体验的不断挑战。内容交付网络&#xff08;CDN&#xff09;技术应运而生&#xff0c;以应对这些挑战&#xff0c;实现内容高效分发&#xff0c;提升了整体网络质量。 CDN的核心原理在于通过在全球范围内部署多个服…

【C++初阶】类和对象——操作符重载const成员函数取地址重载日期类的实现

个人主页点击直达&#xff1a;小白不是程序媛 C系列专栏&#xff1a;C头疼记 目录 前言&#xff1a; 运算符重载 运算符重载 赋值运算符重载 前置和后置重载 const成员 取地址及const取地址操作符重载 使用函数操作符重载完成日期类的实现 前言&#xff1a; 上篇文…

YOLO v1(2016.5)

文章目录 AbstractIntroduction过去方法存在的问题我们提出的方法解决了... Unified DetectionNetwork DesignTrainingInference Comparison to Other Detection SystemsDeformable parts modelsR-CNNOther Fast DetectorsDeep MultiBoxOverFeatMultiGrasp ExperimentsConclusi…

java项目之机房预约系统(ssm框架)

项目简介 机房预约系统实现了以下功能&#xff1a; 管理员&#xff1a;个人中心、学生管理、教师管理、机房号管理、机房信息管理、申请预约管理、取消预约管理、留言板管理、论坛管理、系统管理。学生&#xff1a;个人中心、机房信息管理、申请预约管理、取消预约管理、留言…

Elasticsearch:使用 E5 嵌入模型进行多语言向量搜索

作者&#xff1a;JOSH DEVINS 在这篇文章中&#xff0c;我们将介绍多语言向量搜索。 我们将使用 Microsoft E5 多语言嵌入模型&#xff0c;该模型在零样本和多语言设置中具有最先进的性能。 我们将介绍多语言嵌入的一般工作原理&#xff0c;以及如何在 Elasticsearch 中使用 E…

蓝桥杯每日一题2023.10.29

螺旋折线 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 在图中我们可以观察到四个对角线的值均为特殊点&#xff0c;其他的点可以根据这几个 进行偏移量的计算从而进行表示&#xff0c;此题主要是找到规律即可 #include<bits/stdc.h> using namespace std; typedef long…

ENSP L2TP 配置

拓扑 真机模拟出差员工使用l2tp客户端接入公司内网。 客户端下载&#xff1a;URL 说明&#xff1a; 确保真机与AR1设备之间网络可达&#xff0c;且真机上可以访问到LNS的 10.100.1.254 真机网卡配置 真机上添加去往LNS的路由 测试 LNS配置 #l2tp enable #ip pool shass…

聊聊装饰模式

缘起 某日&#xff0c;阳光明媚&#xff0c;绿草花香。Leader突然找到了小明&#xff1a;“小明&#xff0c;如果让你将一个人的穿着使用代码来实现&#xff0c;你该怎么完成呢&#xff1f;” 小明一听&#xff0c;回答道:“Leader&#xff0c;这个不难&#xff0c;马上就完事…

【PyQt学习篇 · ④】:QWidget - 尺寸操作

文章目录 QWidget简介QWidget大小位置操作案例一案例二 QWidget尺寸限定操作案例 内容边距案例 QWidget简介 在PyQt中&#xff0c;QWidget是一个基本的用户界面类&#xff0c;用于创建可见的窗口组件。QWidget可以包含多种类型的子组件&#xff0c;如QPushButton、QLabel、QLi…

matlab 中的基本绘图指令与字符串操作指令

字符串指令 创建字符串 使用单引号将字符序列括起来创建字符串使用单引号创建的字符串是一个字符数组&#xff0c;每个字符都被视为一个独立的元素 可以通过索引访问每个字符使用双引号创建的字符串是一个字符串数组&#xff0c;整个字符串被视为一个元素 无法通过索引访问单个…

Linux shell编程学习笔记17:for循环语句

Linux Shell 脚本编程和其他编程语言一样&#xff0c;支持算数、关系、布尔、字符串、文件测试等多种运算&#xff0c;同样也需要进行根据条件进行流程控制&#xff0c;提供了if、for、while、until等语句。 之前我们探讨了if语句&#xff0c;现在我们来探讨for循环语句。 Li…

海外问卷调查是怎么做的?全方位介绍!

橙河这样说&#xff0c;相信大家应该不难理解。 国外问卷调查目前主要有三种形式&#xff1a;口子查、站点查和渠道查。橙河自己做的是渠道查。 站点查是最早的问卷形式&#xff0c;意思是我们需要登录到问卷网站上&#xff0c;就可以做问卷了。但想要在网站上做问卷&#xf…

YOLO轻量化改进 , 边缘GPU友好的YOLO改进算法!

在本文中&#xff0c;作者根据现有先进方法中各种特征尺度之间缺少的组合连接的问题&#xff0c;提出了一种新的边缘GPU友好模块&#xff0c;用于多尺度特征交互。此外&#xff0c;作者提出了一种新的迁移学习backbone采用的灵感是来自不同任务的转换信息流的变化&#xff0c;旨…

《Attention Is All You Need》阅读笔记

论文标题 《Attention Is All You Need》 XXX Is All You Need 已经成一个梗了&#xff0c;现在出现了很多叫 XXX Is All You Need 的文章&#xff0c;简直标题党啊&#xff0c;也不写方法&#xff0c;也不写结果&#xff0c;有点理解老师扣论文题目了。 作者 这个作者栏太…

Yolo-Z:改进的YOLOv5用于小目标检测

目录 一、前言 二、背景 三、新思路 四、实验分析 论文地址&#xff1a;2112.11798.pdf (arxiv.org) 一、前言 随着自动驾驶汽车和自动驾驶赛车越来越受欢迎&#xff0c;对更快、更准确的检测器的需求也在增加。 虽然我们的肉眼几乎可以立即提取上下文信息&#xff0c;即…

Arhas 常用命令

watch 函数执行数据观测: location 会有三种值 AtEnter&#xff0c;AtExit&#xff0c;AtExceptionExit。 对应函数入口&#xff0c;函数正常 return&#xff0c;函数抛出异常。 result 表示观察表达式的值&#xff1a; {params,returnObj,throwExp} eg: 查看是某个方法的参…

探索Apache HttpClient超时时间如何设定?

目录 一、Apache HttpClient模拟POST请求&#xff0c;调用第三方接口1、发起POST请求&#xff1a;2、模拟服务端3、通过postman测试一下4、Apache HttpClient 二、HTTP超时时间1、众所周知&#xff0c;HTTP使用的是TCP/IP 协议。2、TCP/IP超时时间设置3、HTTP连接超时时间如何设…

笔记Kubernetes核心技术-之Controller

2、Controller 2.1、概述 在集群上管理和运行容器的对象&#xff0c;控制器(也称为&#xff1a;工作负载)&#xff0c;Controller实际存在的&#xff0c;Pod是抽象的&#xff1b; 2.2、Pod和Controller关系 Pod是通过Controller实现应用运维&#xff0c;比如&#xff1a;弹…

前馈神经网络处理二分类任务

此文建议看完基础篇再来&#xff0c;废话不多说&#xff0c;进入正题 目录 1.神经元 1.1 活性值 1.2 激活函数 1.2.1 Sigmoid函数 1.2.2 Relu函数 2.基于前馈神经网络的二分类任务 2.1 数据集的构建 2.2 模型的构建 2.2.1 线性层算子 2.2.2 Logistic算子 2.2.3 层的串行组合…