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节)

相关推荐
云飞云共享云桌面23 分钟前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
Peter_chq1 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
一坨阿亮2 小时前
Linux 使用中的问题
linux·运维
dsywws3 小时前
Linux学习笔记之vim入门
linux·笔记·学习
幺零九零零4 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
wclass-zhengge4 小时前
Docker篇(Docker Compose)
运维·docker·容器
李启柱4 小时前
项目开发流程规范文档
运维·软件构建·个人开发·设计规范
free5 小时前
netstat中sendq/recvq用于排查发送端发送数据的问题
服务器
小林熬夜学编程5 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
力姆泰克5 小时前
看电动缸是如何提高农机的自动化水平
大数据·运维·服务器·数据库·人工智能·自动化·1024程序员节