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.c
	gcc -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.c
	gcc -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 mypragma
 
mypragma:mypragma.cc
        g++ -o $@ $^ -std=c++11
 
testexec:testexec.c
        gcc -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节)

相关推荐
xuanzdhc2 小时前
Linux 基础IO
linux·运维·服务器
愚润求学2 小时前
【Linux】网络基础
linux·运维·网络
bantinghy2 小时前
Linux进程单例模式运行
linux·服务器·单例模式
小和尚同志3 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
帽儿山的枪手3 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
shadon1789 天前
回答 如何通过inode client的SSLVPN登录之后,访问需要通过域名才能打开的服务
linux
AWS官方合作商9 天前
AWS ACM 重磅上线:公有 SSL/TLS 证书现可导出,突破 AWS 边界! (突出新功能的重要性和突破性)
服务器·https·ssl·aws
小米里的大麦9 天前
014 Linux 2.6内核进程调度队列(了解)
linux·运维·驱动开发
程序员的世界你不懂9 天前
Appium+python自动化(三十)yaml配置数据隔离
运维·appium·自动化
算法练习生9 天前
Linux文件元信息完全指南:权限、链接与时间属性
linux·运维·服务器