目录
相关博客:
进程相关概念: 戳链接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/103213364
进程创建(fork):戳链接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/103302804
进程退出:戳链接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/103302874
进程等待(wait()/waitpid()):戳链接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/103302883
进程程序替换
为什么要进行进程替换 ?
前面说到, fork()创建子进程, fork创建的子进程要么和父进程执行一样的代码, 要么执行不同的代码分支(通过fork的返回值控制),
但这样还是不够灵活. 假如要有很多的功能已经用别的程序实现好了, 那么就不需要在父进程中控制父子进程执行不同的代码分
支, 让子进程在自己的分支中完成这些功能, 而是可以直接拿一个已有的程序替换掉子进程. 使子进程的代码完全变成所替换程序
的代码. 这样就方便了很多, 而且在子进程需要完成较为复杂的功能或是多项功能时, 分支就显得力不从心了.
所以进程往往要调用exec族中的某一个函数来进行程序替换, 让一个进程来执行另一个程序. 当进程调用一种exec函数时,该进程
的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec族中的函数并不会创建新进程,所以调用exec
前后被替换进程的iD并未改变。
进程替换的原理
如上图, 进程替换时, 替换的是PCB映射在内存中的代码和数据. 这样, 该进程PID虽然没有变, 但已经物是人非, 已经不是原来的那
个进程了.
如进行进程程序替换? 前面也说到了, exec族函数
替换函数exec族函数
exec族函数共有六个, 功能都是进程程序替换, 但多个不同的函数接口使得使用更加灵活.
其中exceve()是系统调用接口, 其余5个底层都封装了execve().
函数原型如下 :
int execl(const char *path, const char *arg0, ... /*, (char *)0 */);
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg0, ... /*,(char *)0, char *const envp[]*/);
int execve(const char *path, char *const argv[], char *const envp[]);
int execlp(const char *file, const char *arg0, ... /*, (char *)0 */);
int execvp(const char *file, char *const argv[]);
头文件: unistd.h
返回值 :
- 这六个函数返回值相同.
- 当函数调用失败, 返回 -1.
- 当调用成功, 即加载新的程序, 替换后的进程启动开始执行,exec族函数不再返回 .
- 特殊的地方是, 函数调用成功, 不返回
参数:
虽然有六个, 不好记, 但好在有规律. 可以发现, 函数名都是在exce的基础上, 加上l, v, e, p形成新的函数名, 加哪个字母都有各自的
含义 , 如下所示:
- l / v 必须有一个, 也只能有一个, 带l参数格式是列表, 带 v 参数格式是 数组
- p 有p, 会自动搜索环境变量PATH, 则可以不带路径, 只要文件名(但文件必须放在PATH环境变量指定路径). 没有p, 则必须指定文件路径
- e 有e, 则不使用当前环境变量, 需要自己设置环境变量, 没带e, 则使用当前环境变量, 无需设置环境变量
如下表:
函数名 | 参数格式 | 函数是否自带路径(通过PATH) | 是否使用当前环境变量 |
---|---|---|---|
execl | 列表 | 不带, 需要制定文件路径 | 使用 |
execlp | 列表 | 带, 但文件必须的放在指定目录 | 使用 |
execle | 列表 | 不带, 需要制定文件路径 | 不使用, 需要自己设置环境变量 |
execv | 数组 | 不带, 需要制定文件路径 | 使用 |
execvp | 数组 | 带, 但文件必须的放在指定目录 | 使用 |
execve | 数组 | 不带, 需要制定文件路径 | 不使用, 需要自己设置环境变量 |
来看一下这六个函数具体如何使用.
1. execv(参数格式是数组)
#include<stdio.h>
#include<unistd.h>
int main() {
char* arg[] = {"ls","-a","-l","/",NULL };//参数数组;//参数数组
execv("/bin/ls", arg);
printf("hello world!\n");
return 0;
}
可以看到进程被ls程序替换后, 只会执行ls的代码, 并不会再输出 hello world!
如下, 还可以通过main函数的参数传入, 我们用Shell调用ls等命令就是这个原理.
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc, char* argv[]){
char* arg[] = {"ls","-a","-l",NULL };//参数数组
pid_t pid = fork();
if(pid == -1){
perror("fork");
}
else if(pid == 0){
printf("替换子进程\n");
execv("/bin/ls", argv);
}
else{
sleep(1);
printf("替换父进程\n");
execv("/bin/ls", arg);
}
printf("hello world!\n");
return 0;
}
2.execl(参数格式是列表)
#include<stdio.h>
#include<unistd.h>
int main(){
execl("/bin/ls", "ls", "/", NULL);
printf("hello world!\n");
return 0;
}
3.execvp / execlp(不带替换程序的路径)
execvp
#include<stdio.h>
#include<unistd.h>
int main(int argc, char* argv[]){
execvp("ls", arg);
printf("hello world!\n");
return 0;
}
execlp
#include<stdio.h>
#include<unistd.h>
int main(){
char* arg[] = {"ls","-a","-l",NULL };//参数数组
execlp("ls", "-a", "-l", NULL);
printf("hello world!\n");
return 0;
}
4.execle / execve(需要自己设置环境变量)
execve
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(int argc, char* argv[]){
char* envp[] = {"PATH=/home/test", NULL};
execve("/bin/env", argv, envp);
return 0;
}
execle
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
char* envp[] = {"PATH=/home/test", NULL};
execle("/bin/env", "", NULL, envp);
return 0;
}
学习完进程的创建和替换后, 就可以利用这些知识, 写一个自己的Shell了
进程的创建在另一篇博客, 戳链接( ̄︶ ̄)↗https://blog.csdn.net/qq_41071068/article/details/103302804
具体代码, 持续更新