目录
进程程序替换
替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。
调用exec并不创建新进程,所以调用exec前后该进程的id并未改变
程序替换就是通过特定的接口,加载磁盘上的一个全新的程序(数据和代码)。加载到调用进程的上下文中(地址空间中),重新改一下页表和物理内存的关系就行,进程替换并没有创建新的子进程
替换函数
其实有六种以exec开头的函数,统称exec函数,本质就是加载程序的函数(从磁盘加载到内存):
#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[]); int execve(const char *path, char *const argv[], char *const envp[]);
int execl(const char *path,const char *arg,...);
path:路径+目标文件名
…:可变参数列表
函数里可传入多个参数,最后一个参数,必须是NULL,表示参数传递完毕
演示:
注意这里ls的路径
这段程序正常运行
加上execl,注意第一个参数和最后一个参数之间的参数,是我们在命令行上如何使用,就在这里如何书写
在调用excel之后打印出来的内容跟直接调用ls一模一样
execl运行成功以后,原程序的内容便不再执行,我们原始代码里还有一个printf没打印出来
execl是程序替换,调用该函数成功之后,会将当前进程的所有代码和数据进行替换,包括已经执行的和没执行的
所以,一旦调用成功,后续所有代码都不会再执行
故意写一个错代码
execl调用失败
execl函数出错的时候才有返回值,返回值是-1,成功时没有返回值
我们一般这样写,如果执行了exit,则一定调用失败了
替换函数创建子进程
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> int main() { pid_t id = fork(); if(id == 0) { //子进程 printf("子进程开始运行, pid: %d\n", getpid()); execl("/usr/bin/ls","ls","-a","-l",NULL); sleep(3); } 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; }
阻塞等待, 一定是子进程先运行完毕,然后父进程获取之后,才退出!
创建子进程原因:如果不创建,替换的就是父进程,如果创建了,替换子进程,就不会影响父进程。
当子进程加载新程序,子进程和父进程代码会分离,也就是子进程会写时拷贝
其余替换函数介绍
exec系列函数的功能其实iu是加载器的底层接口
int execv(const char *path,char *const argv[])
path:文件路径+文件名
argv:指针数组,最后要以NULL结尾
execv中的v可看作vector方便记忆。
int execlp(const char *file,const char *arg,...);
file:文件名(要执行谁),execlp,p代表会在path环境中查找
arg:命令行模式下使用格式,命令行参数必须以NULL结尾
int execvp(const char *file,char *const argv[]);
file:文件名(你要执行谁)
argv:指针数组
还有execv系列
函数名带
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
系统直接调用的是execve,上面的这些函数最终调用的都是excve这个接口,
使用替换致函执行其它文件程序
创建一个mycmd.c文件
修改makefile,方便一次生成俩个文件
这里的all不需要依赖关系,只要依赖方法
我们现在想用exce来执行mycmd
记住这个路径
成功调用了mycmd文件
测试-b命令
程序运行成功
也可把程序里面的路径设置为相对路径(用.或..)
使用替换致函执行其它语言文件
创建俩个文件
测试一下代码
sh文件
也可加上执行权限直接运行
execlp
最终boss:execle
int execle(const char *path,const char *arg,...,char *const envp[]);
path:路径+文件名
arg:命令行模式下使用格式,命令行参数必须以NULL结尾
envp:环境变量
mycmd.c文件
我们使用getenv获取环境变量,getenv根据环境变量名获取环境变量
Come on这个环境变量并不存在,这是我们自己写的
exec.c文件
执行这个文件,注意我们没有给函数传环境变量
我们传一个环境变量
我们发现come on打印成功,这是因为再exec.c文件中父进程把环境变量通过execle传给了子进程,子execle函数调用了mycmd.c文件,mycmd.c通过getenv获取到了环境变量Come on
Shell简单实现
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #define NUM 1024 #define SIZE 32 #define EP " " char cmd_line[NUM];//保存完整的字符串 char* g_garp[SIZE];//保存打散后的字符串 int main() { while (1) { printf("[root@localhost myshell]# "); fflush(stdout); memset(cmd_line, '\0', sizeof cmd_line); if (fgets(cmd_line, sizeof cmd_line, stdin) == NULL) { continue; } cmd_line[strlen(cmd_line) - 1] = '\0'; g_garp[0] = strtok(cmd_line, EP); int index = 1;//作为打散后要保存字符串的下标 if (strcmp(g_garp[0], "ls") == 0) { g_garp[index++] = "--color=auto"; } if (strcmp(g_garp[0], "ll") == 0) { g_garp[0] = "ls"; g_garp[index++] = "-l"; g_garp[index++] = "--color=auto"; } while (g_garp[index++] = strtok(NULL, EP));//分隔字符串,并复制 if (strcmp(g_garp[0], "cd") == 0) { if (g_garp[1] != NULL); chdir(g_garp[1]); continue; } pid_t id = fork(); if (id == 0) { printf("下面子进程要进行的是\n"); execvp(g_garp[0], g_garp); exit(0); } //父进程获取子进程信息 int status = 0; pid_t ret = waitpid(id, &status, 0); if (ret > 0) printf("exit code:%d\n", WEXITSTATUS(status)); } return 0; }
如果这样打印,没有换行,这是因为我们没有刷新缓冲区
这种情况会自动换行,是因为我们按了回车,多了\n,我们把\n改为\0就行
输入cd命令没反应
这里输入cd其实是想让父进程执行自己的命令,这种命令叫内置命令(内建命令)
内建命令本质其实就是shell中的一个函数调用
系统中有一个调用接口chdir,表示更改当前目录
这里chdir(g_garv[1])//表示切换到数组下标为1的这个路径
chdir函数用于改变当前工作目录。调用参数是指向目录的指针