Linux:进程替换
什么是进程替换?
我们的可执行程序,在运行起来的时候就上一个进程
一个进程就会有他的内核数据结构+代码和数据
把一个已经成型的进程的代码和数据替换掉,这就叫进程替换
也就是可以通过系统调用把当前进程替换位我们需要的进程
那么替换后,会创建一个新进程吗?
不会,只是在旧进程的壳子执行新进程;替换进程后,之前的代码不会执行,因为已经被替换了
进程怎样替换?
进程替换是需要接口函数的,总不能什么都没有直接替换吧
来看看都有什么:
上图的【int execl(const char *pfin,“const char *arg, ...);】这种后面的省略号,是指参数可变
来举个栗子捏:
test1.c
#include<stdio.h>
#include<unistd.h>int main()
{printf("test1.c ... begin!\n");execl("/usr/bin/ls", "ls", "-l", "-a", NULL);printf("test1.c ... end!\n");return 0;
}
makefile:
test1.out:test1.cgcc -o $@ $^
.PHONY:clean
clean:rm -f test1.out
解释一下:就是把我们本来的代码通过execl来执行execl引用的程序,成为替换的新进程
替换的过程?
而execl的返回值我们并不关心,因为一旦替换成功,就不会向后继续运行旧进程了
而只要继续运行旧进程,那就一定是替换失败了
所以我们的替换接口函数只有失败返回值,没有成功返回值
替换的过程本质上是把这个新程序加载到内存上
一个程序怎样加载到内存上呢?
别忘了exec*!它类似一种Linux上的加载函数,做的是从外设拷贝到内存是操作系统的活
多进程版的进程替换
将代码改成多进程版就是fork创建子进程,让子进程自己替换,父进程只需要等待就好了
也就是说这里被替换的是子进程捏,父进程还在等待
test1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if(id == 0){printf("exec begin\n");//进程替换execl("/usr/bin/ls", "ls", "-al", NULL);printf("exec end\n");exit(0);}int rid = waitpid(id, NULL, WUNTRACED);if(rid > 0){printf("wait success\n");}return 0;
}
makefile:
test1.out:test1.cgcc -o $@ $^
.PHONY:clean
clean:rm -f test1.out
解释一下:我们在if(rid==0)分支里,先打印了一下,然后进入我们的接口,接口里是我们的ls命令,我们ls命令也是文件,也是C语言写的,我们平时执行命令也是执行该程序,所以可以在接口里替换为ls命令
这样就是替换成功了
我们刚刚说了替换失败会继续执行原进程,来看看吧:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("test1.c ... begin!\n");pid_t id = fork();if (id == 0){sleep(2);execl("/usr/bin/lsss", "lsss", "-l", "-a", NULL);printf("替换失败捏");exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){printf("father wait success,child exit code:%d\n",WEXITSTATUS(status));}printf("test1.c ... end!\n");return 0;
}
你看,没执行execl里面的程序
以前创建子进程让它完成任务,是让子进程执行父进程代码的一部分,现如今可以让子进程执行一个全新的程序
进程有对应的数据和代码,创建子进程会有新的地址空间和页表;替换本身就是覆盖,子进程在数据部分进行写时拷贝,当执行新程序的时候新程序也要进行写时拷贝,所以此后父子间几乎完全独立(本来数据和代码是继承父进程的,现在是全新的了)
来仔细介绍一下我们的替换接口函数:
这些函数的前四个字母都是exec,后面跟着的:
p: 表示自动搜索路径;e: 表示自己维护环境变量;l:表示采用列表传参;v:表示采用数组传参;
execl
int execl(const char *path, const char *arg, ...);
l就是list(列表),传参数列表
剩下的...带选项,在命令行中怎样写命令的参数,就怎么写(下图有例子捏)结束时传NULL
路径表明我们想要执行谁,后面的选项表示我们想要怎样执行
execv
v是vector的意思,动态数组
int execv(const char *path, char *const argv[]);
前面的参数是路径,后面的参数是指针数组,execl的是可变参数,execv的传参需要传指针数组指针数组是自己写的
execvp
带p的意思就是用户可以不用传要执行文件的路径(但要传文件名)
char *const argv[] = {"ps", "-ef", NULL};
execvp("ps", argv);
那它是怎么做到的呢?
别忘了p是可以自己搜路径,在环境变量里搜路径
execlp
就是采用列表传参,但是不用输路径
execlp("ps", "ps", "-ef", NULL);
execvpe
execvpe
带e的需要自己组装环境变量
int execvp(const char *file, char *const argv[], char *const envp[]);
以替换 "ls -al" 为例,示例一下剩余接口:
//l代表传参数列表,分成一个个字符串传
execl("/usr/bin/ls", "ls", "-al", NULL);//p表示系统会去PATH环境变量中找
execlp("ls", "ls", "-al", NULL);//v代表传数组,把命令分成字符串,放进字符串数组里,一起传
char* argv[] = {"ls", "-al", NULL};
execv("/usr/bin/ls", argv);
替换自己写的进程
刚刚我们引用了系统的进程,我们也可以引用我们自己写的程序
下面就来替换段C++代码(文件后缀: .cpp , .cc ,.cxx)
来先写一个自己的程序:
#include<iostream>using namespace std;int main()
{cout << "hello C++!" << endl;cout << "hello C++!" << endl;cout << "hello C++!" << endl;cout << "hello C++!" << endl;return 0;
}
然后再也一个父进程:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("test1.c ...begin!\n");pid_t id = fork();if (id == 0){sleep(2);execl("./mypragma", "mypragma", NULL);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){printf("father wait success,child exit code:%d\n", WEXITSTATUS(status));}printf("test1.c ... end!\n");return 0;
}
tips:别忘了makefile默认是从上到下形成可执行程序,所以第一个要是主程序,其他的依赖程序依次往下排哦
.PHONY:all
all:testexec mypragmamypragma:mypragma.ccg++ -o $@ $^ -std=c++11testexec:testexec.cgcc -o $@ $^
.PHONY:clean
clean:rm -f testexec mypragma
来看看:
程序替换并未形成新进程,我们来验证一下:
test1.c:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("test1.c ...begin!\n");pid_t id = fork();if (id == 0){ printf("i am a process,pid:%d\n",getpid());sleep(2);execl("./mypragma", "mypragma", NULL);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){printf("father wait success,child exit code:%d\n", WEXITSTATUS(status));}printf("test1.c ... end!\n");return 0;
}
mypragma.cpp
#include<iostream>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;int main()
{printf("i am a new process,pid:%d\n",getpid());cout << "hello C++!" << endl;cout << "hello C++!" << endl;cout << "hello C++!" << endl;cout << "hello C++!" << endl;return 0;
}
看,这两个进程pid是一样的
自己组装环境变量
我们刚刚说环境变量可以自己组装,来引用
看看吧:
test1.c:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>int main()
{printf("test1.c ...begin!\n");pid_t id = fork();if (id == 0){sleep(2);char *const argv[] = { (char*)"mypragma",NULL };char* const envp[] = { (char*)"LIKE=521",(char*)"LOVE=1314",NULL };execvpe("./mypragma", argv, envp);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid > 0){printf("father wait success,child exit code:%d\n", WEXITSTATUS(status));}printf("test1.c ... end!\n"); return 0;
}
mypragma.cpp:
#include<iostream>using namespace std;int main(int argc,char *argv[],char *env[])
{int i = 0;for (; argv[i]; i++){printf("argv[%d]: %s\n", i, argv[i]);}printf("-------------------------------\n");for (i = 0; env[i]; i++){printf("env[%d]: %s\n", i, env[i]);}printf("-------------------------------\n");cout << "hello C++!" << endl;cout << "hello C++!" << endl;cout << "hello C++!" << endl;cout << "hello C++!" << endl;return 0;
}
哦?
父进程本身自己的环境变量,来自于父进程的父进程:bash
你也可以用全新的环境变量,也可以用修改后的环境变量:使用putenv()
往进程加环境变量: putenv() ,程序替换不会替换环境变量,我们可以通过带e的接口函数(例如execpe())设置新的环境变量,这会覆盖原本的环境变量。
用法:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main(){char *p;if((p = getenv("USER")))printf("USER =%s\n", p);putenv("USER=test");printf("USER+5s\n", getenv("USER"));}
这些都是:
#include <unistd.h>`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[]);
tips:只有execve是真正的系统调用,其它五个函数最终都调用 execve(execve在man手册第2节,其它函数在man手册第3节)
相关文章:

Linux:进程替换
什么是进程替换? 我们的可执行程序,在运行起来的时候就上一个进程 一个进程就会有他的内核数据结构代码和数据 把一个已经成型的进程的代码和数据替换掉,这就叫进程替换 也就是可以通过系统调用把当前进程替换位我们需要的进程 那么替换…...

带你认识:数据仓库宽表~~~浅显易懂
1. 构建宽表的目的 讲宽表我想从为什么需要宽表入手,而不是一上来就抠概念。因为我觉得一门知识叫什么名字并不是最核心的,关键是搞清楚它的诞生背景以及如何在特定场景用好它。 构建宽表的目的很简单,就是为了"一站式"尽可能多的展示我们需要…...

记录|MessageBox.Show()的使用
目录 前言一、解析1.1 代码1.2 具体图片解析 更新时间 前言 遇到了其他人写的MessageBox.Show()的用法,有点懵,特此记录。 一、解析 1.1 代码 MessageBox.Show("登录失败!", "用户登录", MessageBoxButtons.OK, MessageBoxIcon.E…...

LabVIEW软件定制开发公司的前景如何?
LabVIEW软件定制开发公司的前景在当前的技术发展环境下展现出一定的潜力与挑战。这一领域的市场前景主要受到工业自动化、物联网、智能制造等技术趋势的推动,同时也受到行业竞争、技术更新以及人才市场的制约。 市场需求与增长潜力 随着工业4.0、物联网和智能制…...

vue3列表页搜索条件封装
搜索框组件 封装常用搜索框组件,类型有: input(默认值)selectselectV2 (value/label键值对数组)datePickeryear 集成新增、修改、删除、导入、导出按钮,支持slot自定义其他按钮封装搜索、重置按钮封装按钮权限封装导入弹框 本例仅…...

十三、切片的复制
1、使用函数copy 注意点:复制前必须再声明一个与要复制对象类型相同的切片 var cheeses make([]int, 5)cheeses[0] 1cheeses[1] 2cheeses[2] 3cheeses[3] 4cheeses[4] 5var myCheeses make([]int, 5)copy(myCheeses, cheeses) 使用copy函数将cheeses的数据…...

Java Stream API 的应用:提取并处理多属性集合
Java Stream API 是一个功能强大的工具,可以帮助开发者高效地处理集合数据。本篇博客将专注于一个具体的应用示例,即如何使用 Java Stream API 从一个对象列表中提取多个属性值,并进行过滤和去重。这种技术在处理需要从多个字段中提取数据的情…...

【技术方案】智慧城市大数据平台技术方案(Doc原件)
第1章 总体说明 1.1 建设背景 1.2 建设目标 1.3 项目建设主要内容 1.4 设计原则 第2章 对项目的理解 2.1 现状分析 2.2 业务需求分析 2.3 功能需求分析 第3章 大数据平台建设方案 3.1 大数据平台总体设计 3.2 大数据平台功能设计 3.3 平台应用 第4章 政策标准保障体系 4.1 政策…...

vue项目中引入字体文件样式
需求:关于一些样式需要自定义的,所以需要ui提供字体文件,然后引入项目中,就可实现自定义 首先看一下实现效果图: 第一步:新建一个字体样式文件用于放字体文件和css样式 font.css文件: /* 数字特殊字体 */ font-face {/*给字体命名*/font-family: DINCondBold;/*引入字体文件*…...

Android 11强制App固定user_rotation方向显示
Android11 强制App按照user_rotation方向显示。 diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index b1d349d8e93d..f7fd2983c668 100644 --- a/services/core/java/com/an…...

Harbor仓库push显示
背景: 在做测试时发现harbor仓库端口开放这,却一直登录不上去,重启harbor资源包docker-compose还是不行,修改了docker.service文件不行,json文件也不行,以下是涉及到的命令和报错(好像是这个&am…...

Windows 上设置 MySQL 的主从复制
Windows 上设置 MySQL 的主从复制 一、前言1. 环境准备2. 主服务器配置3. 从服务器配置6. 测试复制7. 注意事项 一、前言 在 Windows 上设置 MySQL 的主从复制涉及几个步骤。下面是一个详细的指南,帮助你实现这一过程。 1. 环境准备 安装 MySQL: 确保你…...

鸿蒙内核源码分析(原子操作篇) | 谁在为原子操作保驾护航
基本概念 在支持多任务的操作系统中,修改一块内存区域的数据需要“读取-修改-写入”三个步骤。然而同一内存区域的数据可能同时被多个任务访问,如果在修改数据的过程中被其他任务打断,就会造成该操作的执行结果无法预知。 使用开关中断的方…...

vue3+ts封装axios以及解决跨域问题
目录 一、前言二、封装axios三、 解决跨域四、调用接口五、运行结果 一、前言 前端请求后端数据时,会用到axios,但是如果不将axios封装好,会导致代码冗余 二次封装的好处如下: 求头能统一处理便于接口的统一管理解决回调地狱配置…...

各厂家BI对比
帆软BI、奥威BI、永洪BI、思迈特BI、亿信华辰BI是国内知名的BI产品,不少企业在选型BI软件时都需要对这些BI软件进行了解,从中选择适合自己的一款。经过过年的发展,这些BI(商业智能)软件各自在多个行业中都有广泛的应用…...

SQL - 触发器
触发器是在插入、更新和删除语句前后自动执行的一堆SQL代码,但是触发器被触发后只会执行一次,通常我们使用触发器增强数据的一致性。创建触发器 -- 创建触发器 drop trigger if exists payments_after_insert; delimiter $$ -- 在 payments表 insert 之后…...

Redis中缓存穿透、缓存击穿、缓存雪崩的详解
如何理解Redis缓存的穿透、击穿、雪崩问题: 缓存穿透 是指缓存中和数据库中都没有数据,而用户不断访问,导致这个不存在的数据每次请求都要到存储层去查询,这样失去了意义。 缓存穿透的解决方案有哪些? 缓存null值布隆过滤增强…...

[Meachines] [Medium] Popcorn SQLI+Upload File+PAM权限提升
信息收集 IP AddressOpening Ports10.10.10.6TCP:22,80 $ nmap -p- 10.10.10.6 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 5.1p1 Debian 6ubuntu2 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: …...

【Linux】python进程管理之supervisor安装使用教程
安装supervisor pip install supervisor生成配置文件 echo_supervisord_conf > /etc/supervisord.conf修改配置文件 vim /etc/supervisord.conf[unix_http_server] file/run/supervisor.sock ; the path to the socket file[supervisord] logfile/var/log/supervisord.log…...

BEM架构
视频 总结: BEM架构:一个命名类的规范而已,说白了就是如何给类起名字使用sass的目的:在<style>中模块化的使用类名,同时减少代码数量 1、 BEM架构 (通义灵码查询结果) BEM (Block Ele…...

物联网(IoT)详解
物联网(IoT)详解 1. IoT定义简介2. IoT工作原理3. IoT关键技术4. 物联网与互联网区别5. IoT使用场景6. 开源物联网平台7. 参考资料 1. IoT定义简介 首先第一个问题,什么是物联网(IoT)? 物联网(英文&#…...

ansync/await 运行流程图
1、流程图: 2、await 之后的方法是何时执行,如何执行的? await 的方法在 Task 执行完成之后,通过调用 Finish 方法执行的。 具体的执行步骤是先将 MoveNext 方法注册到 Task 的回调里,然后在 Task 执行完后调用这个方法…...

生产环境docker nginx+php8.0镜像
生产环境docker nginxphp8.0镜像 自定义创建php8.0镜像,创建dockerfile FROM php:8.0-fpm# 安装系统依赖 RUN sed -i s|http://deb.debian.org/debian|http://mirrors.aliyun.com/debian|g /etc/apt/sources.list && \apt-get update && apt-get i…...

【Hadoop】核心组件深度剖析:HDFS、YARN与MapReduce的奥秘
🐇明明跟你说过:个人主页 🏅个人专栏:《大数据前沿:技术与应用并进》🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、Hadoop简介 2、Hadoop生态系统概览 二、Hadoo…...

Docker Swarm部署SpringCloud Alibaba微服务踩坑记录
为了方便部署和维护微服务项目,还是得上集群部署方案,决定采用Docker的swarm,为什么不是k8s,因为部署骑来又是个新的工具,之前就一直用的docker,自带了类k8s的工具,索性就直接使用swarm了&#…...

深入理解Spring Boot中的AOP应用:从基础组件到高级功能的实现
深入理解Spring Boot中的AOP应用:从基础组件到高级功能的实现 在现代Java开发中,Spring Boot因其简洁性和强大的功能而被广泛采用。而AOP(面向切面编程)作为Spring框架的核心特性之一,为开发者提供了在不修改业务代码的…...

《区块链与监管合规:在创新与规范之间寻求平衡》
区块链技术作为近年来最具创新性和颠覆性的技术之一,已经在金融、供应链、医疗、物联网等多个领域展现出巨大的潜力。然而,随着其应用的不断拓展,如何应对监管和合规性要求成为了区块链发展道路上一个至关重要的问题。 区块链的去中心化、匿…...

Nuxt3【服务器】server 详解
server 文件夹中的内容,会被自动注册为API和服务器处理程序。 服务器 API 对应路径 server/api server/api/hello.ts export default defineEventHandler((event) > {return {hello: world} })页面中使用 <script setup lang"ts"> const { da…...

防火墙技术原理与应用
防火墙概述 防火墙概念 防火墙:通过一种网络安全设备,控制安全区域间的通信,隔离有害通信,进而阻断网络攻击。一般安装在不同安全区域边界处,用于网络通信安全控制,由专用硬件或软件系统组成。 根据网络安全信任程度和需保护的对象,划分安全区域 公共外部网络:Inter…...

【BUU】[NewStarCTF 2023 公开赛道]Final -CP读取文件内容
漏洞检测 访问首页发现是ThinkPHP5 的站点 用工具扫描一下,发现存在ThinkPHP5.0.23 RCE漏洞 访问验证,写入shell 成功写入shell. 根目录发现flag,但是权限不足 提权获取flag 准备提权,这里一开始尝试了find,但是find权限不足 尝试采用cp命令,移动到web目录,发现访问还是…...