Linux:进程替换和知识整合

news2024/11/28 16:01:52

文章目录

  • 进程程序替换
    • 替换原理
    • 进程替换的理解
  • 环境变量与进程替换
  • 命令行解释器
    • 实现逻辑

进程程序替换

前面已经学习了子进程的创建,但是子进程的创建不管怎么说,都是父进程代码的一部分,那么实际上如果想要子进程执行新的程序呢?

也就是说,执行全新的代码和访问全新的数据,不再和父进程有瓜葛呢?这个时候就引入了关于进程替换的概念

替换原理

在这里插入图片描述
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变

进程替换的理解

首先演示基本用法:

单进程下的用法

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

int main()
{
    printf("execl begin:\n");
    execl("/usr/bin/ls", "ls", "-a", "-l", "-n", NULL);
    printf("execl end:\n");
    return 0;
}

调用结果:

[test@VM-16-11-centos 11-8]$ ./myprocess 
execl begin:
total 28
drwxrwxr-x  2 1003 1003 4096 Nov  9 10:50 .
drwxrwxrwt 16 1003 1003 4096 Nov  8 20:40 ..
-rw-rw-r--  1 1003 1003   74 Nov  8 20:41 Makefile
-rwxrwxr-x  1 1003 1003 8416 Nov  9 10:50 myprocess
-rw-rw-r--  1 1003 1003  175 Nov  9 10:47 myprocess.c

从中看出,它的基本原理就是在进程中进行进程的替换

为什么最后输出的printf不被调用呢?

这是因为,执行到进程替换函数的时候,如果成功,整个进程的代码和数据都会被替换为所需替换的目标代码和数据,这样在后续执行的时候都会使用这份新的代码和数据,因此不会调用后续出现的代码

多进程版本的程序替换

将上述的代码更改为含有子进程的代码,具体如下:

#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)
    {
        // child
        printf("pid:%d,begin to exec!\n",getpid());
        sleep(3);
        execl("/usr/bin/ls","ls","-a","-l",NULL);
        printf("pid:%d,end to exec!\n",getpid());
    }
    else 
    {
        // father
        printf("wait child\n");
        pid_t rid = waitpid(-1,NULL,0);
        if(rid > 0)
        {
            printf("wait success\n");
        }
    
    }
    return 0;
}

实验结果如下:

[test@VM-16-11-centos 11-8]$ ./myprocess 
wait child
pid:18212,begin to exec!
total 28
drwxrwxr-x  2 test test 4096 Nov  9 11:05 .
drwxrwxrwt 16 test test 4096 Nov  8 20:40 ..
-rw-rw-r--  1 test test   74 Nov  8 20:41 Makefile
-rwxrwxr-x  1 test test 8672 Nov  9 11:05 myprocess
-rw-rw-r--  1 test test  695 Nov  9 11:05 myprocess.c
wait success

从中看出多进程替换中增加了父进程对子进程的等待和回收的部分功能

那在多进程下应该如何理解进程替换呢?用下面图示的过程来演示:

在这里插入图片描述
从这里的进程替换中可以发掘出一些东西,替换的是进程,而不是代码,所以这里可以替换的内容有很多,甚至可以是Java写的程序运行起来的进程等等,看下面的实验

下面实现一个cpp程序

#include <iostream>

int main()
{
    std::cout<<"this is a cpp program"<<std::endl;
    return 0;
}

对前面的程序进行修改

#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)
    {
        // child
        printf("pid:%d,begin to exec!\n",getpid());
        sleep(3);
        execl("./cpptest","./cpptest",NULL);
        //execl("/usr/bin/ls","ls","-a","-l",NULL);
        printf("pid:%d,end to exec!\n",getpid());
    }
    else 
    {
        // father
        printf("wait child\n");
        pid_t rid = waitpid(-1,NULL,0);
        if(rid > 0)
        {
            printf("wait success\n");
        }
    
    }
    return 0;
}

对Makefile进行修改

.PHONY:all
all:myprocess cpptest

cpptest:cpptest.cc 
	g++ -o $@ $^

myprocess:myprocess.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -rf myprocess cpptest

这里利用的是Makefile自带的自我推演能力,使用Makefile进行自我推演可以推演出,现在需要myprocess和cpptest,而这两个程序又会分别进行执行运行

此时进行运行,此时会做出如下的实验结果:

[test@VM-16-11-centos 11-8]$ ./myprocess 
wait child
pid:23071,begin to exec!
this is a cpp program
wait success

从中不难看出,确实实现了进程的替换,而且替换的还是其余进程

这也就解释了在不同的公司中是可以存在分块进行构建模块功能的,最后都可以通过进程的形式链接起来

从某种意义来说,进程的替换已经可以被看成是一种系统调用了,站在系统的视角看内存中的所谓进程,实际上是一样的,系统高于一切,它可以对进程进行调度和分配

环境变量与进程替换

当进行进程替换的过程中,对于环境变量的角度来讲,是以什么样的情况进行的传递呢?

结论是:子进程对应的环境变量,是可以直接从父进程来的

对这个结论进行验证:

有关进程替换的一些函数
在这里插入图片描述

  1. execl函数,需要找到命令所在的文件目录,使用方法如下:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>


int main()
{
    pid_t id = fork();
    if(id == 0)
    {
        // child
        // 进行进程替换
        execl("/usr/bin/ls", "ls", "-a", "-l", "-d", NULL);
    }
    else 
    {
        // parent
        // 对子进程回收
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success\n");
        }
    }
    return 0;
}
  1. execlp函数:会到系统默认的路径下寻找命令
execlp("ls", "ls", "-a", "-l", "-d", NULL);
  1. execle函数:用一个程序调用另外一个程序,但环境变量是自己的环境变量,不是系统的,通过获取环境变量查看

如何在进程中添加一个环境变量?用到的是putenv函数:

void *putenv(char *name)

由此可以写出下面的程序

#include <iostream>

int main(int argc, char* argv[], char* env[])
{
    // 输出命令行参数
    for(int i = 0; argv[i]; i++)
    {
        std::cout << i << "->" << argv[i] << std::endl;
    }
    std::cout << "##############" << std::endl;
    
    // 输出环境变量
    for(int i = 0; env[i]; i++)
    {
        std::cout << i << "->" << env[i] << std::endl;
    }
    return 0;
}

上面是用于进程替换的函数,在这个基础上,对原程序进行修改

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

int main()
{
    // 在程序中新增环境变量
    char* myenv = "MYVAL1 = 11111111";
    putenv(myenv);
    pid_t id = fork();
    if(id == 0)
    {
        // child
        // 进行进程替换
        execl("./myprocess", "myprocess", NULL);
    }
    else 
    {
        // parent
        // 对子进程回收
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success\n");
        }
    }
}

运行程序如下:

在这里插入图片描述
从中看出,在子进程中是出现了新增的这个环境变量的,由此可以基本验证,在父进程中添加的环境变量会继承到子进程中

那么父进程的父进程是谁呢?答案是bash,那么是不是在bash中添加的环境变量也会继承到子进程中?

再对上面的程序进行修改

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

int main(int argc, char* argv[], char* env[])
{
    // 输出环境变量
    for(int i = 0; env[i]; i++)
    {
        printf("%d -> %s\n", i, env[i]);
    }
    // 在程序中新增环境变量
    char* myenv = {
        "MYVAL1 = 11111111",
        "MYVAL2 = 22222222",
        NULL
    };
    putenv(myenv);
    pid_t id = fork();
    if(id == 0)
    {
        // child
        // 进行进程替换
        execl("./mytest", "mytest", NULL);
    }
    else 
    {
        // parent
        // 对子进程回收
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success\n");
        }
    }
}

在这里插入图片描述
由此可以得出这样的一条线索化的示意图:

在这里插入图片描述
再次回到这张图

在这里插入图片描述

下面看execle函数

环境变量的传递方式?

前面的例子证明,子进程的环境变量是由父进程传递的,而execle函数就是一个显示传递环境变量的函数,它的第三个参数是envp[],实际上就是环境变量

那如何进行使用?看下面的程序

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

int main(int argc, char* argv[], char* env[])
{
    // 在程序中新增环境变量
    char* const myenv[] = {
        "MYVAL1 = 11111111",
        "MYVAL2 = 22222222",
        NULL
    };
    pid_t id = fork();
    if(id == 0)
    {
        // child
        // 进行进程替换
        execle("./mytest", "mytest", NULL, myenv);
    }
    else 
    {
        // parent
        // 对子进程回收
        pid_t rid = waitpid(-1, NULL, 0);
        if(rid > 0)
        {
            printf("wait success\n");
        }
    }
    return 0;
}

运行结果如下:
在这里插入图片描述
从中看出,通过这个函数可以把环境变量进行显示传递给子进程,并且是一种覆盖式传递

到此,有关进程替换的基本逻辑已经结束,那进程替换可以做什么实际的东西呢?

命令行解释器

在前面的认知中,命令行解释器,也就是bash,可以把用户在命令行中敲的命令转换成命令再输出,而实际上,这是一个逻辑很简单的过程:

bash程序相当于是一个一直在后台运行的程序,而当用户敲了一些命令行后,bash创建子进程,就将这些命令行转换为一个字符串数组,采用进程替换的方式就可以把要找的命令和选项替换到前台,那依据这个原理,其实我们自己也能实现一个命令行解释器:

实现逻辑

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

#define NUM 1024
#define SIZE 64
#define SEP " "

char cwd[1024];
char enval[1024];
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());
    char *r = fgets(command, num, stdin);
    if(r == NULL) return -1;
    command[strlen(command) - 1] = '\0';
    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)
    {
        execvp(argv[0], argv);
        exit(1);
    }
    else
    {
        int status = 0;
        pid_t rid = waitpid(id, &status, 0);
        if(rid > 0)
        {
            lastcode = WEXITSTATUS(status);
        }
    }
    return 0;
}

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 = ".";
        else path = argv[1];
        cd(path);
        return 1;
    }
    else if(strcmp(argv[0], "export") == 0)
    {
        if(argv[1] == NULL) return 1;
        strcpy(enval, argv[1]);
        putenv(enval); // ???
        return 1;
    }
    else if(strcmp(argv[0], "echo") == 0)
    {
        char *val = argv[1] + 1;
        if(strcmp(val, "?") == 0)
        {
            printf("%d\n", lastcode);
            lastcode = 0;
        }
        else
        {
            printf("%s\n", getenv(val));
        }
        return 1;
    }
    else if(0)
    {}

    return 0;
}

int main()
{
    while(1)
    {
        char usercommand[NUM];
        char *argv[SIZE];
        // 1. 打印提示符&&获取用户命令字符串获取成功
        int n = getUserCommand(usercommand, sizeof(usercommand));
        if(n <= 0) continue;
        // 2. 分割字符串
        // "ls -a -l" -> "ls" "-a" "-l"
        commandSplit(usercommand, argv);
        // 3. check build-in command
        n = doBuildin(argv);
        if(n) continue;
        // 4. 执行对应的命令
        execute(argv);
    }
}

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

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

相关文章

2023年中国脑电仿生电刺激仪发展趋势分析:智能化、精准化、使用感不断提高[图]

脑电仿生电刺激仪是一种通过直接数字频率合成技术合成脑电仿真低频生物电流&#xff0c;通过粘贴于两耳侧乳突、太阳穴或风池穴部位表皮的电极&#xff0c;用仿生物电自颅外无创伤地穿透颅骨屏障刺激小脑顶核区的电疗设备。此电流刺激可启动颅脑固有神经保护机制&#xff0c;改…

【MATLAB】史上最全的9种数据拟合算法全家桶

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 大家吃一顿火锅的价格便可以拥有9种数据拟合算法&#xff0c;绝对不亏&#xff0c;知识付费是现今时代的趋势&#xff0c;而且都是我精心制作的教程&#xff0c;有问题可随时反馈~也可单独获取某一算法的代码&#xff08…

Odoo 15开发手册第六章 模型 - 结构化应用数据

本章我们更进一步学习模型层&#xff0c;以及如何使用模型来设计支撑应用的数据结构。我们会探讨可用的模型类型&#xff0c;以及在使用这些类型时如何定义强制进行数据验证的约束。 模型由支持不同数据类型的数据字段组成&#xff0c;一些字段类型支持定义模型间的关联。对于…

delphi电子处方流转 sm2 sm4(医院)

【delphi电子处方流转(医院) sm2 sm4】支持 就诊登记、电子处方上传预核验、处方处方医保电子签名、电子处方上传、电子处方撤销、电子处方信息查询、电子处方审核结果查询、电子处方取药结果查询、电子处方药品目录查询等功能。技术交流Q 648437169 下载链接&#xff1a;http…

4. hdfs高可用集群搭建

简介 前面把hadoop机器已经准备好了&#xff0c;zk集群搭建好了&#xff0c;本本就是开始搭建hdfs环境 hadoop环境准备 创建hadoop用户 三台机器都创建hadoop用户 useradd hadoop -d /home/hadoop echo "1q1w1e1r" | passwd --stdin hadoophadoop用户相互免密登…

语义检索系统【全】:基于milvus语义检索系统指令全流程-快速部署版

搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术…

Linux操作系统基础 – 正则表达式快速入门

Linux操作系统基础 – 正则表达式快速入门 Linux Operating System Essentials - Introduction to Regular Expressions 通常在计算机科学领域&#xff0c;正则表达式被解释为对字符串操作的一种逻辑公式&#xff0c;即用事先定义好的特定字符及其组合组成所谓的“规则字符串”…

SpringBoot使用DevTools实现后端热部署

&#x1f4d1;前言 本文主要SpringBoot通过DevTools实现热部署的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页放风讲故事 &#x1f304;每日一句&…

GZ038 物联网应用开发赛题第10套

2023年全国职业院校技能大赛 高职组 物联网应用开发 任 务 书 &#xff08;第10套卷&#xff09; 工位号&#xff1a;______________ 第一部分 竞赛须知 一、竞赛要求 1、正确使用工具&#xff0c;操作安全规范&#xff1b; 2、竞赛过程中如有异议&#xff0c;可向现场考…

ai剪辑矩阵系统源码+无人直播系统源码技术开发

开发AI剪辑矩阵系统和无人直播系统源码&#xff0c;需要以下步骤&#xff1a; 1. 市场调研&#xff1a;了解市场需求和竞品情况&#xff0c;明确系统的功能和特点。 2. 系统设计&#xff1a;设计系统的整体架构和功能模块&#xff0c;包括视频剪辑、直播推流、实时互动、数据分…

【Linux】Ubuntu16.04下安装python高版本--源码安装

Ubuntu16.04下完美安装python高版本及对应版本的pip 方法一:直接用命令安装python3.6&#xff08;但我没安装成功&#xff09; 好像是因为Ubuntu16.04的软件仓库&#xff08;源&#xff09;中python的最高版本就是python3.5&#xff0c;所以无法直接用apt来安装 #方法一 sudo…

学习c#的第十四天

目录 C# 接口&#xff08;Interface&#xff09; 接口的特点 定义接口 接口继承 接口和抽象类的区别 C# 命名空间&#xff08;Namespace&#xff09; using 关键字 定义命名空间 嵌套命名空间 C# 接口&#xff08;Interface&#xff09; 接口定义了所有类继承接口时应…

Linux系统进程——进程的退出、子进程退出的收集、孤儿进程

进程退出 进程退出主要分为两种&#xff1a;正常退出、异常退出 正常退出 正常退出分为以下几种&#xff1a; 1.main函数调用return 2.进程调用exit(),标准c库 3.进程调用 _exit() 或者 _Exit() &#xff0c;属于系统调用 4.进程最后一个线程返回 5.最后一个线程调用pthrea…

asp.net网上书店管理系统VS开发sqlserver数据库web结构c#编程计算机网页源码项目

一、源码特点 asp.net网上书店管理系统 是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 asp.net网上书店系统1 二、功能介绍 本系统使用Microsoft Visual Studio 2019为开发工具&#xff0c;SQL Server为…

影响气膜建筑坍塌的原因

气膜建筑以其轻盈、透光、环保等特性&#xff0c;逐渐在建筑领域崭露头角。然而&#xff0c;这种建筑形式并非没有缺陷&#xff0c;其安全性与稳定性直接影响到建筑物的使用寿命和人员安全。 一、结构设计不合理 气膜建筑的结构设计是影响其稳定性的关键因素。良好的结构设计能…

LeetCode(24)文本左右对齐【数组/字符串】【困难】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 文本左右对齐 1.题目 给定一个单词数组 words 和一个长度 maxWidth &#xff0c;重新排版单词&#xff0c;使其成为每行恰好有 maxWidth 个字符&#xff0c;且左右两端对齐的文本。 你应该使用 “贪心算法” 来放置给定的单…

delphi电子处方流转 sm2 sm4(药店)

【delphi电子处方流转(药店)】支持 处方下载、处方核验、处方审核、药品销售出库明细上传、药品销售出库明细撤销等功能。技术交流Q 648437169 下载链接&#xff1a;https://download.csdn.net/download/liushenglin123/88543771

数据结构(c语言版本) 二叉树的遍历

要求 实现二叉树的创建&#xff0c;并输入二叉树数据 然后先序遍历输出二叉树、中序遍历输出二叉树、后序输出二叉树 例如二叉树为&#xff1a; 该二叉树的先序遍历结果为&#xff1a; A B D C E F 该二叉树的中序遍历结果为&#xff1a; B D A E C F 该二叉树的后序遍历结果…

从0开始学习JavaScript--JavaScript 函数

JavaScript中的函数是编写可维护、模块化代码的关键。本文将深入研究JavaScript函数的各个方面&#xff0c;包括基本语法、函数作用域、闭包、高阶函数、箭头函数等&#xff0c;并通过丰富的示例代码来帮助读者更好地理解和应用这些概念。 函数的基本语法 函数是一段可被重复…

Java网页版即时通讯聊天系统(附源码)

疫情期间,整天闷在家里又不能聚会,大把的空余时间差点让我发霉,后来有个客户发来新年祝贺,情不自禁想起了一件事情,就是他曾经提起过,要是在后台管理系统里面整合个聊天功能该多好啊,有了这个念头,马上行动起来!!! 一.系统演示 1.1 聊天窗体主界面演示 1.2 模拟两…