Linux进程控制

您所在的位置:网站首页 linux进程控制实验报告 Linux进程控制

Linux进程控制

2023-06-08 21:45| 来源: 网络整理| 查看: 265

进程替换 什么是进程替换?

进程替换是改变进程原有的执行代码,转而执行另一套进程代码的过程!

为什么要进程替换:

将进程看作一个任务处理单元我们写出指令,进程依次执行命令当我们需求比较多,但代码又无法改变时,就可以使用进程替换父进程可以创建子进程,用另一个程序的代码数据替换子进程代码数据转而让子进程执行新程序的代码数据

所以,进程替换的目的是让子进程帮我们执行特定任务,以应对不同场景!(例如当我们在某些时刻需要clear清屏时可以使用进程替换调用clear指令)

进程替换原理: 进程替换在Linux中时时刻刻都在出现,例如bash中,我们输入指令,指令是使用C语言写的可执行程序,bash会创建一个子进程然后进程替换执行我们输入的指令程序,所以bash是依靠创建子进程去运行我们的指令和可执行程序的,这样可以避免恶意程序导致bash和内核崩溃! 原理:用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。 进程替换

说明:

进程替换:让一个进程去执行一个在硬盘中的新的程序(从进程的角度),但并没有创建新的进程;进程替换只是新程序被加载了(从程序的角度)我们自己写的代码可以加载新的程序,操作系统创建进程数据结构,然后进程替换,新的代码和数据就被加载了,那么原进程的代码直接被替换了,没机会执行了程序替换是整体替换(数据和代码),不能局部替换程序替换只会影响调用的进程,子进程调用不影响父进程,因为进程具有独立性,子进程替换数据时会发生写时拷贝,与父进程的数据进行区分(这就是前面提到的,写时拷贝不一定只会发生在栈区或者数据区等)如果exec函数替换成功不会有返回值(被替换了原数据代码逻辑就没了),如果替换失败一定有返回值,所以不需要对程序替换的成功进行判断,如果成功就执行其他程序去了,如果失败直接执行异常终止就行了 进程替换函数

进程替换函数有七个,但实际上只有一个,即execve,其余6个是对execve的封装!

#include //替换函数库文件 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[]); int execve(const char *path, char *const argv[], char *const envp[]); //系统调用

因为都是exec开头,所以统称exec函数!

关于exec函数:

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回(因为代码数据已经被替换,是不可逆的)exec函数只有出错的返回值而没有成功的返回值,如果调用出错则返回-1

关于参数和函数命名:

函数名带l(list) : 表示参数采用列表函数名带v(vector) : 参数用数组函数名带p(path) : 自动搜索环境变量PATH(不需要输入精确路径)函数名带e(env) : 表示自己维护环境变量(环境变量需要自己传递) 概览

exec函数相互之间区别

exec调用简单举例如下:

#include #include //替换函数库文件 using namespace std; int main() { //替换程序的路径位置,以及调用选项 char *const argv[] = {"ps", "-ef", NULL}; //环境变量数组,最后为NULL结尾 char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL}; //不带p,需要写全替换程序的路径以及所有的参数选项 execl("/bin/ps", "ps", "-ef", NULL); // 带p的,可以使用环境变量PATH,无需写全路径 execlp("ps", "ps", "-ef", NULL); // 带e的,需要自己组装环境变量 execle("ps", "ps", "-ef", NULL, envp); execv("/bin/ps", argv); // 带p的,可以使用环境变量PATH,无需写全路径 execvp("ps", argv); // 带e的,需要自己组装环境变量 execve("/bin/ps", argv, envp); execvpe("ps",argv,envp); return 0; }

接下来我们逐一介绍这些函数的使用!(因为接口是C语言实现的,与C++编译器的要求有所不同,我们演示参数C编译器演示!)

execl替换函数

execl采用链表形式传递参数,后面待 l 的都是如此!

int execl(const char* path, const char* arg, ...);

execl参数解析:

path:待替换程序的准确路径(例如:/usr/bin/ls)arg:待替换程序名,例如ls… :可变参数列表,传递可变数目的程序选项(例如-a和-a -l等等)

execl参数 注意,无论是否有选项,最后一定要传递NULL/nullptr!

#include #include int main() { printf("进程启动...\n"); int ret = execl("/usr/bin/ls","ls","-a","-l",NULL); if(ret == -1) printf("%s%d\n","ret=",ret); printf("进程替换失败!"); //如果进程替换失败则一定会这些这条语句 return 0; }

execl示例 如果路径有问题则:

//将上述该语句修改为 int ret = execl("/usr/","ls","-a","-l",NULL); //path路径错误

错误示范 可以发现execl的程序替换参数链表形式: execl的参数传递

execv替换函数

execv函数采用vector传递参数(数组的方式),后面带 v 的都是如此!

int execv(const char* path, char* const argv[]);

函数参数解析:

path:待替换程序的准确路径(例如:/usr/bin/ls)argv:待替换程序名和程序选项构成的参数表(指针数组)

execv参数

注意: 虽然execv只需传递两个参数,但在创建argv表时,最后一个元素仍然要设置为nullptr/NULL! 因为C++编译器对类型转换检查严格,而函数是早年C语言所实现的,所需参数argv是char* cosnt类型但里面的字符串是const char*类型,编译失败,所以我们退一步使用C语言进行演示!

#include #include int main() { char* const argv[] = {"ls","-a","-l",NULL}; //数组接收程序名和选项 printf("进程启动...\n"); int ret = execv("/usr/bin/ls",argv); if(ret == -1) printf("%s%d\n","ret=",ret); printf("进程替换失败!"); //如果进程替换失败则一定会这些这条语句 return 0; }

正常运行 同样的修改为错误路径测试,execv参数路径有问题也会出错:

int ret = execv("/usr/",argv); //path路径错误

错误示范 这里与execl的区别在于,execv的待替换程序名和选项参数是以数组的形式传递的! argv数组

execlp替换函数

execlp是execl的变种,与execl不同的是execlp会自动去PATH环境变量中寻址待替换程序路径,我们只需要传递待替换程序名即可!

int execlp(const char* file, const char* arg, ...);

execlp参数解析:

file:PATH中待替换程序名(不是路径,例如ls,clear等等)arg:待替换程序名… : 程序选项,以可变参数列表传递(与execl保持一致)

注意: 只能在环境变量表中的 PATH 变量中搜索,如果待程序路径没有在 PATH 变量中,是无法进行替换的! 所以execlp和execl各有优劣,应对不同场景! 这里我们结合上面进程创建等待的知识使用子进程替换演示:

#include #include //exit 函数头文件 #include #include #include int main() { pid_t id = fork(); if(id == 0) { printf("子进程创建成功\n"); execlp("ls", "ls", "-a", "-l", NULL); //程序替换 printf("子进程替换失败!\n"); exit(-1); } int status = 0; waitpid(id, &status, 0); //阻塞方式等待 if(WEXITSTATUS(status) != 255) //判断退出码是否异常 printf("父进程:子进程替换成功!\n"); else printf("父进程:子进程替换失败!\n"); return 0; }

execlp演示 如果我们指定的待替换程序不在PATH环境变量中则会报错:

execlp("exe", NULL); //不在PATH路径中的可执行程序

在这里插入图片描述 execlp只要待替换程序在PATH路径中,就可以替换!

execvp替换函数

同样的,execvp与execv大部分相同,只不过execvp是从PATH路径中寻址待替换程序!

int execvp(const char* file, char* const argv[]);

execvp参数解析:

file:PATH中待替换程序名(不是路径,例如ls,clear等等)argv:参数数组,包括待替换程序名和参数选项构成的指针数组 #include #include //exit 函数头文件 #include #include #include int main() { pid_t id = fork(); if(id == 0) { char* const argv[] = {"ls","-a","-l",NULL}; printf("子进程创建成功\n"); execvp("ls", argv); printf("子进程替换失败!\n"); exit(-1); } int status = 0; waitpid(id, &status, 0); //阻塞方式等待 if(WEXITSTATUS(status) != 255) //判断退出码是否异常 printf("父进程:子进程替换成功!\n"); else printf("父进程:子进程替换失败!\n"); return 0; }

execvp演示 同样的,如果PATH中不存在待替换程序路径则替换失败:

execvp("exe", argv);

路径错误 如果要使用该替换函数调用自己写的程序,只需要将自己写的程序路径添加到环境变量PATH即可!

execle替换函数

execle替换函数在execl的基础上,支持自定义环境变量表!(前面的替换默认继承系统传入的环境变量表)

int execl(const char* path, const char* arg, ..., char* const envp[]);

execle参数解析:

path:待替换程序的准确路径arg:待替换程序名… :待替换程序名和程序选项参数,以可变参数列表传递(与execl保持一致)envp:自定义环境变量表

我们自己实现一个打印前五个环境变量参数的程序,使用进程替换调用!

#include int main(int argc,char* argv[],char* envp[]) { //默认打印前五条环境变量 for(int i = 0;i char* const myenv[] = {"myval1=668","myval1=688","myval1=888" ,NULL}; printf("子进程创建成功\n"); execle("./Print","Print",NULL,myenv); printf("子进程替换失败!\n"); exit(-1); } int status = 0; waitpid(id, &status, 0); //阻塞方式等待 if(WEXITSTATUS(status) != 255) //判断退出码是否异常 printf("父进程:子进程execle替换成功!\n"); else printf("父进程:子进程execle替换失败!\n"); return 0; }

execle 由于我们传递的环境变量只有三条所以只打印了三条! 我们可以传递系统环境变量给execle,这样功能就与execl相同了!

extern char** environ; //声明环境变量表 execle("./Print","Print",NULL,environ);

传递系统环境变量 通过这个我们可以发现,如果主动传入环境变量后,待替换程序中的原环境变量表将被覆盖! 所以我们自己写的程序之所以可以继承环境变量表是因为bash在开启子进程后进程替换传递的是bash环境变量表,所以我们可以继承下去! 其他没有在exec后带e的替换函数都是默认传递bash环境变量表!

execve替换函数

execve是真正可以执行进程替换的系统调用,其他函数最终都会调用execve! 其他函数将参数处理后,传递给execve!

int execve(const char* filename, char* const argv[], char* const envp[]);

execve参数解析:

filename:待替换程序的精确路径argv:待替换程序名和程序选项,以数组形式传递envp:自定义环境变量表

使用execve打印我们自己的环境变量表:

#include #include //exit 函数头文件 #include #include #include int main() { pid_t id = fork(); if(id == 0) { char* const myenv[] = {"myval1=668","myval1=688","myval1=888" ,NULL}; char* const argv[] = {"Print",NULL}; printf("子进程创建成功\n"); execve("./Print",argv,myenv); //调用自己实现的Print打印环境变量程序 printf("子进程替换失败!\n"); exit(-1); } int status = 0; waitpid(id, &status, 0); //阻塞方式等待 if(WEXITSTATUS(status) != 255) //判断退出码是否异常 printf("父进程:子进程execve替换成功!\n"); else printf("父进程:子进程execve替换失败!\n"); return 0; }

execve 注意: 替换函数除了能替换为C/C++编写的程序外,还能替换为其他语言编写的程序,如 Java、Python、GO、Swift等等,虽然它们在语法上各不相同,但在操作系统看来都属于二进制可执行程序,数据位于代码段和数据段,使用统一的系统调用替换即可!

execvpe替换函数

execvpe是对execvp的进一步封装,使用方法与execvp一致,不过最后一个参数可以传递自定义环境变量表!

int execvpe(const char* file, char* const argv[], char* const envp[]);

execvpe参数解析:

file:待替换程序名(程序在PATH环境变量中)argv:待替换程序名和程序选项,以数组形式传递envp:自定义环境变量表 #include #include //exit 函数头文件 #include #include #include int main() { pid_t id = fork(); if(id == 0) { char* const myenv[] = {"myval1=668","myval1=688","myval1=888" ,NULL}; char* const argv[] = {"env",NULL}; printf("子进程创建成功\n"); execvpe("env",argv,myenv); //使用系统命令env打印我们自己的环境变量 printf("子进程替换失败!\n"); exit(-1); } int status = 0; waitpid(id, &status, 0); //阻塞方式等待 if(WEXITSTATUS(status) != 255) //判断退出码是否异常 printf("父进程:子进程execvpe替换成功!\n"); else printf("父进程:子进程execvpe替换失败!\n"); return 0; }

execvpe



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3