[linux进程控制]进程替换
文章目录
- 1.进程替换的概念和原理
- 2.如何完成进程替换
- 2.1exec系列函数
- 加载器的底层系统调用接口
- 基于execve的封装--库函数
- 2.2 int execl(const char *path, const char *arg, ...);
- 1.在当前进程进行替换
- 2.在子进程进行替换
- 2.3 int execv(const char *path, char *const argv[]);
- 2.4 int execlp(const char *file, const char *arg, ...);
- 2.5 int execvp(const char *file, char *const argv[]);
- 2.6 int execle(const char *path, const char *arg, ..., char * const envp[]);
- 1.Makefile: 一次生成多个可执行程序
- 2.子进程中执行自己写的C/C++程序
- 2.1自己写的C/C++程序
- 2.2执行自己写的C/C++程序
- 3.子进程中执行自己写的py/sh程序
- 4. int execle(const char *path, const char *arg, ..., char * const envp[]);
- 4.1自己写的C/C++程序
- 4.2执行自己写的C/C++程序
- 2.7 int execvpe(const char *file, char *const argv[], char *const envp[]);
1.进程替换的概念和原理
之前讲过fork()函数的用法:
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程不处理客户端请求,生成子
进程来处理这个请求。 - 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。==>进程替换
fork()之后 父子进程各自执行父进程代码的一部分 如果子进程想执行一个另外的/全新的程序要怎么做呢?
本来父子进程共享代码和数据 (数据写时拷贝) 子进程怎么去拥有自己的代码呢?
进程替换完成这个功能: 通过特定的接口 把磁盘上一个全新的程序(代码和数据)加载到子进程的地址空间中
当进程替换后 都会发生什么呢?
- 新程序的代码和数据加载到内存
- 进程替换并没有创建新的进程 所以该进程的pid并不改变
- 原进程即被替换的进程的PCB/进程地址空间/页表没有释放 新程序的PCB/进程地址空间/页表没有被创建 而是将原进程的PCB/进程地址空间/页表的内容完全替换成新程序的PCB/进程地址空间/页表的内容 页表左侧替换成新程序对应的虚拟地址 页表右侧替换成新程序的物理地址
- 旧进程的用户空间代码和数据完全被新程序替换 从新程序的启动例程开始执行
如何完成这一系列操作?
操作系统的接口exec函数: 加载新程序代码和数据到内存 建立新的映射关系
2.如何完成进程替换
2.1exec系列函数
加载器的底层系统调用接口
#include <unistd.h>
int execve(const char *filename, char *const argv[], char *const envp[]);
基于execve的封装–库函数
#include <unistd.h>
extern char **environ;
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[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
命名理解
l(list) : 参数采用列表形式
v(vector) : 参数用数组形式
p(path) : 自动搜索环境变量PATH
e(env) : 自己维护环境变量
2.2 int execl(const char *path, const char *arg, …);
1.在当前进程进行替换
int execl(const char *path, const char *arg, ...);
execl: l--list 像结点一样将参数传入可变参数列表
path: 路径+目标文件名
arg: 可变参数列表 可以传入多个参数(个数不定)
调用时: 以NULL结尾 表示参数传递完毕
- execl()函数: 函数调用成功后 当前的代码和数据完全被新程序的代码和数据替换
- 函数调用失败后会返回-1(执行原代码 对原代码无影响) 表示调用新程序失败 调用成功没有返回值 因为调用成功后当前代码和数据完全被替换 而execl()就在其中
代码演示
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
printf("当前进程开始运行\n");
//execl("/usr/bin/ls", "ls", NULL);
execl("/usr/bin/ls", "ls", "-l", "-a", "-i", NULL);
//execl("/usr/bin/top", "top", NULL);
//execl("/usr/bin/ls", "ls", "--color=auto", "-l", NULL);
ls是一个用C语言写的程序 他也有main函数 实际上 这里的传参就是把参数传给了ls程序里的main函数 这也与我们之前讲到的命令行参数的空间布局相呼应
exit(1);
printf("当前进程结束!\n");
return 0;
}
为什么在xshell下执行ls 就会有颜色 而在我们的程序里执行ls 还要加
"--color=auto"
才会有颜色?
因为在bash进程里给ls取了别名 自动就会加"--color=auto"
2.在子进程进行替换
在子进程进行进程替换的目的
- 不影响父进程 让父进程聚焦在读取数据,解析数据,指派子进程执行代码
- 让子进程被替换的目的是去执行新的程序而不是执行父进程的不同代码 所以让子进程进行替换的目的就是让父进程仍然执行他自己的代码 让子进程执行一个新的程序 并且父进程不受影响
对子进程进行程序替换的理解
- 子进程被替换前 父子进程代码共享 数据写时拷贝
- 当子进程发生进程替换 会有一份新的程序(代码+数据)完全覆盖子进程原有内容 此时会发生写时拷贝 即当子进程发生进程替换 子进程在物理内存中将会有自己的单独空间(新程序)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char*argv[], char *env[])
{
pid_t id = fork();
if(id == 0)
{
//子进程
printf("子进程开始运行, pid: %d\n", getpid());
sleep(3);
execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
exit(1);
}
else
{
//父进程
printf("父进程开始运行, pid: %d\n", getpid());
int status = 0;
pid_t id = waitpid(-1, &status, 0);
if(id > 0)
{
printf("wait success, exit code: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
2.3 int execv(const char *path, char *const argv[]);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define NUM 16
int main(int argc, char*argv[], char *env[])
{
pid_t id = fork();
if(id == 0)
{
//子进程
printf("子进程开始运行, pid: %d\n", getpid());
sleep(3);
char *const _argv[NUM] = {
(char*)"ls",
(char*)"-a",
(char*)"-l",
(char*)"-i",
NULL
};
//execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
execv("/usr/bin/ls", _argv);
exit(1);
}
else
{
//父进程
printf("父进程开始运行, pid: %d\n", getpid());
int status = 0;
pid_t id = waitpid(-1, &status, 0);
if(id > 0)
{
printf("wait success, exit code: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
2.4 int execlp(const char *file, const char *arg, …);
自动在环境变量PATH中查找文件名的路径
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define NUM 16
int main(int argc, char*argv[], char *env[])
{
pid_t id = fork();
if(id == 0)
{
//子进程
printf("子进程开始运行, pid: %d\n", getpid());
sleep(3);
char *const _argv[NUM] = {
(char*)"ls",
(char*)"-a",
(char*)"-l",
(char*)"-i",
NULL
};
//execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
//execv("/usr/bin/ls", _argv);
execlp("ls", "ls", "-a", "-l", NULL);
exit(1);
}
else
{
//父进程
printf("父进程开始运行, pid: %d\n", getpid());
int status = 0;
pid_t id = waitpid(-1, &status, 0);
if(id > 0)
{
printf("wait success, exit code: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
2.5 int execvp(const char *file, char *const argv[]);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define NUM 16
int main(int argc, char*argv[], char *env[])
{
pid_t id = fork();
if(id == 0)
{
//子进程
printf("子进程开始运行, pid: %d\n", getpid());
sleep(3);
char *const _argv[NUM] = {
(char*)"ls",
(char*)"-a",
(char*)"-l",
(char*)"-i",
NULL
};
//execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
//execv("/usr/bin/ls", _argv); //和上面的execl只有传参方式的区别
//execlp("ls", "ls", "-a", "-l", NULL);
execvp("ls", _argv);
exit(1);
}
else
{
//父进程
printf("父进程开始运行, pid: %d\n", getpid());
int status = 0;
pid_t id = waitpid(-1, &status, 0);
if(id > 0)
{
printf("wait success, exit code: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
2.6 int execle(const char *path, const char *arg, …, char * const envp[]);
1.Makefile: 一次生成多个可执行程序
2.子进程中执行自己写的C/C++程序
2.1自己写的C/C++程序
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("can not execute!\n");
exit(1);
}
if(strcmp(argv[1], "-a") == 0)
{
printf("hello a!\n");
}
else if(strcmp(argv[1], "-b") == 0)
{
printf("hello b!\n");
}
else
{
printf("default!\n");
}
return 0;
}
2.2执行自己写的C/C++程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define NUM 16
//const char *fileaddr = "/home/lhr/linux/procreplace/cmd";
const char *fileaddr = "./cmd";
int main(int argc, char*argv[], char *env[])
{
pid_t id = fork();
if(id == 0)
{
//子进程
printf("子进程开始运行, pid: %d\n", getpid());
sleep(3);
char *const _argv[NUM] = {
(char*)"ls",
(char*)"-a",
(char*)"-l",
(char*)"-i",
NULL
};
execle(fileaddr, "cmd", "-a", NULL);
//execle(fileaddr, "cmd", "-a", NULL);
//execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
//execv("/usr/bin/ls", _argv); //和上面的execl只有传参方式的区别
//execlp("ls", "ls", "-a", "-l", NULL);
//execvp("ls", _argv);
exit(1);
}
else
{
//父进程
printf("父进程开始运行, pid: %d\n", getpid());
int status = 0;
pid_t id = waitpid(-1, &status, 0);
if(id > 0)
{
printf("wait success, exit code: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
3.子进程中执行自己写的py/sh程序
test.py
#! /usr/bin/python3.6
print("hello Python")
print("hello Python")
print("hello Python")
test.sh
#! /usr/bin/bash
echo "hello shell!"
echo "hello shell!"
echo "hello shell!"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define NUM 16
int main(int argc, char*argv[], char *env[])
{
pid_t id = fork();
if(id == 0)
{
//子进程
printf("子进程开始运行, pid: %d\n", getpid());
sleep(3);
在命令行下: python test.py === ./test.py (自动调用解释器)
//execlp("./test.py", "test.py", NULL);
//execlp("bash", "bash", "test.sh", NULL);
//execlp("python", "python", "test.py", NULL);
py/sh/java: 有解释器 在解释器(也是一个程序)内编译执行对应程序
exit(1);
}
else
{
//父进程
printf("父进程开始运行, pid: %d\n", getpid());
int status = 0;
pid_t id = waitpid(-1, &status, 0);
if(id > 0)
{
printf("wait success, exit code: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
4. int execle(const char *path, const char *arg, …, char * const envp[]);
4.1自己写的C/C++程序
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("can not execute!\n");
exit(1);
}
printf("获取环境变量: VIRTUALENV: %s\n", getenv("VIRTUALENV"));
if(strcmp(argv[1], "-a") == 0)
{
printf("hello a!\n");
}
else if(strcmp(argv[1], "-b") == 0)
{
printf("hello b!\n");
}
else
{
printf("default!\n");
}
return 0;
}
4.2执行自己写的C/C++程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define NUM 16
//const char *fileaddr = "/home/lhr/linux/procreplace/cmd";
const char *fileaddr = "./cmd";
int main(int argc, char*argv[], char *env[])
{
/*char *const _env[NUM] = {
(char*)"VIRTUALENV=547993",
NULL
};*/ok
pid_t id = fork();
if(id == 0)
{
//子进程
printf("子进程开始运行, pid: %d\n", getpid());
sleep(3);
char *const _env[NUM] = {
(char*)"VIRTUALENV=547993",
NULL
};//ok
char *const _argv[NUM] = {
(char*)"ls",
(char*)"-a",
(char*)"-l",
(char*)"-i",
NULL
};
execle(fileaddr, "cmd", "-a", NULL, _env);
exit(1);
}
else
{
//父进程
printf("父进程开始运行, pid: %d\n", getpid());
int status = 0;
pid_t id = waitpid(-1, &status, 0);
if(id > 0)
{
printf("wait success, exit code: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
_env
写在父进程子进程都可以
写在父进程: _env作为环境变量传参给execle cmd.c从环境变量_env获取
写在子进程: _env作为环境变量传参给execle 即使进程替换 原来的代码和数据被完全替换 但是环境变量不会被替换 所以cmd.c仍能找到
还有另外两种方法 详见码云
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define NUM 16
//const char *fileaddr = "/home/lhr/linux/procreplace/cmd";
const char *fileaddr = "./cmd";
int main(int argc, char*argv[], char *env[])
{
extern char** environ;
putenv("VIRTUALENV=666");
pid_t id = fork();
if(id == 0)
{
//子进程
printf("子进程开始运行, pid: %d\n", getpid());
sleep(3);
char *const _argv[NUM] = {
(char*)"ls",
(char*)"-a",
(char*)"-l",
(char*)"-i",
NULL
};
// putenv("VIRTUALENV=547993");//ok:env不会被替换 cmd.c仍能获取
// execle(fileaddr, "cmd", "-a", NULL);//err:不传env, 指定子进程接收的是空环境变量
execl(fileaddr, "cmd", "-a", NULL);//ok: 此函数不用传env参数 默认继承父进程
//execle(fileaddr, "cmd", "-a", NULL, env);//只有在shell下export VIRTIUAL=1 子进程才能获取到env
//execle(fileaddr, "cmd", "-a", NULL, environ);//子进程可以获取env
//execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
//execv("/usr/bin/ls", _argv);
//execlp("ls", "ls", "-a", "-l", NULL);
//execvp("ls", _argv);
exit(1);
}
else
{
//父进程
printf("父进程开始运行, pid: %d\n", getpid());
int status = 0;
pid_t id = waitpid(-1, &status, 0);
if(id > 0)
{
printf("wait success, exit code: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
特殊讲解
execle(fileaddr, "cmd", "-a", NULL, env);//只有在shell下export VIRTIUAL=1 子进程才能获取到env
execle(fileaddr, "cmd", "-a", NULL, environ);//子进程可以获取env
由此我们可以有一个大胆的猜想:shell进程创建子进程运行main main里put了一个env
然后main创建了一个子进程运行cmd 传env cmd获取不到env 传environ cmd可以获取env
即当我们put了env environ里就有 而env是shell进程的环境变量传给main的
此时main的env还没有TMPENV 如果提前在shell下export了一个TMPENV
此时再运行main main的env里就有了一个TMPENV 此时的put相当于对TMPENV进行了修改
如此再传env cmd也能获取到TMPENV
2.7 int execvpe(const char *file, char *const argv[], char *const envp[]);
同上类比