目录
1.什么是进程替换
2.替换原理
3.替换函数
4.函数解释
5.具体应用
6.makefile构建多个文件
7.运行自己程序
8.运行其他语言程序
9.简易shell
什么是进程替换
fork之后的父子程序共享代码,如果子进程想执行一个全新的程序。就用进程替换来完成这个功能,通过特定的接口,加载磁盘上的一个权限的程序(代码和数据),加载到调用程序的地址空间中
替换原理
子进程往往要调用exec函数执行另一个程序,当调用时,进程的用户空间代码和数据完全被新程序替换,从新程序的启动历程执行,调用exec不创建新进程,前后进程id并未变化
从磁盘上加载一个新程序,将它加载到内存中,通过替换和页表产生映射,让子进程执行这个新的程序
替换函数
总共有7个函数,功能大致类似,但传入参数和具体使用方式有些不同
#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[]);
函数解释
这些函数如果调用成功不再返回,调用出错返回-1
只有出错的返回值没有成功的返回值
命名理解
l (list) : 表示参数采用列表
v (vector) : 参数用数组
p(path):由p自动搜索环境变量PATH
e(env):表示自己维护环境变量
函数名 | 参数格式 | 是否带路径 | 是否使用当前环境变量 |
---|---|---|---|
execl | 列表 | 不是 | 是 |
execlp | 列表 | 是 | 是 |
execle | 列表 | 不是 | 不是,须自己组装环境变量 |
execv | 数组 | 不是 | 是 |
execvp | 数组 | 是 | 是 |
execve | 数组 | 不是 | 不是,须自己组装环境变量 |
具体应用
execl
int execl(const char *path, const char *arg, …);
path是程序的路径
arg是传入的参数,传入结束,最后必须带一个NULL
下面是执行让程序执行ls命令
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("当前进程的开始代码\n");
execl("/usr/bin/ls", "ls", NULL;
printf("当前进程的结束代码\n");
return 0;
}
程序在执行完打印开始后,执行了ls命令,结束没有打印。程序替换为了全新的程序,所有代码都替换了。后面的都不会执行
执行ls命令时还可以带入参数,设置带颜色显示
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("当前进程的开始代码\n");
// execl("/usr/bin/ls", "ls", NULL);
execl("/usr/bin/ls", "ls", "-a", "--color=auto", NULL);
printf("当前进程的结束代码\n");
return 0;
}
创建子进程,用子进程来运行ls命令,父进程阻塞等待,获取退出码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t id =fork();
if (id == 0)
{
//子进程
printf("子进程开始执行\n");
execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
exit(11);
}
else
{
//父进程
printf("父进程开始执行\n");
int status = 0;
pid_t ret = wait(-1, &status, 0); //阻塞等待,一定是子进程执行完毕,父进程才推出
if (ret > 0)
{
printf("等待成功,返回码: %d\n", WEXITSTATUS(status));
exit(0);
}
}
return 0;
}
execv
int execv(const char *path, char *const argv[]);
参数用数组
//子进程
printf("子进程开始执行\n");
// execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
char* ary[10] =
{
"ls",
"-a",
"-l"
};
execv("/usr/bin/ls", ary);
execlp
int execlp(const char *file, const char *arg, …);
第一个参数只需要传入程序,会自动在环境变量中查找
//子进程
printf("子进程开始执行\n");
// execlp("ls", "ls", "-a", "-l", NULL);
char* ary[10] =
{
"ls",
"-a",
"-l"
};
exelp(("ls", "ls", "-a", "-l", NULL);
exit(11);
execvp
int execvp(const char *file, char *const argv[]);
//子进程
printf("子进程开始执行\n");
// execl("/usr/bin/ls", "ls", "-a", "-l", NULL);
char* ary[10] =
{
"ls",
"-a",
"-l"
};
execvp("/usr/bin/ls", ary);
exit(11);
execle
int execle(const char *path, const char *arg, …,char *const envp[]);
这个函数可以传入环境变量
被执行程序可以用getenv函数获取环境变量
mycmd.c的b参数函数里加一个打印环境变量
printf(“环境变量: %s\n”, getenv(“my_env”));
这个环境变量系统里是没有的,需要通过exec函数传入。修改exec.c文件
execle(file, “mycmd”, “-b”, NULL, _env);
char* _env[6] =
{
“my_env=3455”,
NULL
};
环境变量具有全局属性,父进程的环境变量子进程可以继承。所以main函数的环境变量也可以直接传递给子进程,也就是env参数
execve
这个是系统调用,其他几个是对它的封装
int execve(const char *filename, char *const argv[], char *const envp[]);
makefile构建多个源文件
.PHONY:all
all: exec mycmd
mycmd: mycmd.c
gcc -o $@ $^
exec: exec.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f exec mycmd
exec和mycmd是源文件的名字
运行自己的程序
exec运行自己写的程序,也是可以的。先写一个根据传入的参数不同执行不同功能的源文件
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
if (argc != 2)
{
printf("传入参数多少\n");
exit(1);
}
if (strcmp(argv[1], "-a") == 0)
{
printf("hello -a\n");
}
else if (strcmp(argv[1], "-b")==0)
{
printf("hello -b\n");
}
return 0;
}
然后用刚才的子进程源文件执行上面的程序,pwd获得当前路径,程序名加上全路径,用execl函数
char* file = "/home/tmp/linux/06-进程替换/mycmd";
//子进程
printf("子进程开始执行\n");
execl(file, "mycmd", "-b", NULL);
char* ary[10] =
{
"ls",
"-a",
"-l"
};
// execvp("/usr/bin/ls", ary);
exit(11);
执行exec程序,显示出了hello -b
全路径的方式也可以换为相对路径
char* file = “./mycmd”;
运行其他语言程序
写一个简单的py程序,打印hello
再写一个shell程序,输出hello
修改exec.c的执行函数
exelp(“python”, “python”, “test.py”, NULL);
execlp(“bash”, “bash”, “test.sh”, NULL);
python的运行也可以给其加上可执行权限,会自动查找python执行程序
简易shell
vim批量替换
新建一个myshell源文件,makefile也需要修改,可以批量替换
%s/要替换的/替换为/g
需要在底行模式下
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
//shell运行原理,通过让子进程执行命令,父进程等待 解析命令
int main()
{
char* ary[64];
//0. 命令解释器,是一个常驻进程,不退出
while (1)
{
//1. 打印提示信息 [tmp@VM-16-7-cento myshell]#
printf("[tmp@VM-16-7-cento myshell]# ");
fflush(stdout);
//2. 获取用户输入的指令和选项
char buff[1024];
memset(buff, '\0', sizeof buff);
if (fgets(buff, sizeof buff, stdin) == NULL)
{
continue;
}
//printf("%s\n", buff);
//缓冲区的回车去掉
buff[strlen(buff) - 1] = '\0';
//3. 分割命令行,解析
ary[0] = strtok(buff, " "); //第一次调用,传入原始字符串
int index = 1;
//ls增加颜色
if (strcmp(ary[0], "ls") == 0)
{
ary[index++] = "--color=auto";
}
while(ary[index] = strtok(NULL, " "))
{
index++;
}
//3.5 内置命令,需要父进程执行的,如cd
if (strcmp(ary[0], "cd")==0)
{
if (ary[1] != NULL)
{
chdir(ary[1]);
continue;
}
}
pid_t id = fork();
if (id == 0)
{
//4. 子进程执行任务
execvp(ary[0], ary);
exit(1);
}
//父进程等待
int status = 0;
pid_t ret = waitpid(id,&status,0 );
if (ret > 0)
{
printf("等待成功,退出码: %d\n", WEXITSTATUS(status));
}
else
{
printf("等到失败\n");
}
}
return 0;
}
思路
shell执行的命令通常有两种:
1.第三方提供的对应在磁盘中有具体二进制文件的可执行程序
2.shell内部,自己实现的方法,由父进程执行
分为下面几步:
1.shell程序必须是常驻的,不能退出,所以是一个死循环
2.打印提示信息,也就是用户版本号这些东西
3.获取用户输入
4.对用户输入内容分割,第一个是命令,后面的是参数,用strtok可以分割
5.对于有些命令特殊处理。需要父进程执行的,如cd,export等,如果是ls,可以自动设置颜色。export导环境变量,其实是向系统写入环境变量的地址,所以必须用一个字符串变量存储环境变量,写入它的地址,并保证不会随意修改
6.子进程执行得到的命令
7.父进程等待,回收资源
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
//shell运行原理,通过让子进程执行命令,父进程等待 解析命令
int main()
{
char* ary[64];
char _env[64]; //记录环境变量
//0. 命令解释器,是一个常驻进程,不退出
while (1)
{
//1. 打印提示信息 [tmp@VM-16-7-cento myshell]#
printf("[tmp@VM-16-7-cento myshell]# ");
fflush(stdout);
//2. 获取用户输入的指令和选项
char buff[1024];
memset(buff, '\0', sizeof buff);
if (fgets(buff, sizeof buff, stdin) == NULL)
{
continue;
}
//printf("%s\n", buff);
//缓冲区的回车去掉
buff[strlen(buff) - 1] = '\0';
//3. 分割命令行,解析
ary[0] = strtok(buff, " "); //第一次调用,传入原始字符串
int index = 1;
//ls增加颜色
if (strcmp(ary[0], "ls") == 0)
{
ary[index++] = "--color=auto";
}
while(ary[index] = strtok(NULL, " "))
{
index++;
}
//3.5 内置命令,需要父进程执行的,如cd
if (strcmp(ary[0], "cd")==0)
{
if (ary[1] != NULL)
{
chdir(ary[1]);
continue;
}
}
if (strcmp(ary[0], "export") == 0 && ary[1] != NULL)
{
strcpy(_env, ary[1]);
putenv(_env);
continue;
}
pid_t id = fork();
if (id == 0)
{
//4. 子进程执行任务
execvp(ary[0], ary);
exit(1);
}
//父进程等待
int status = 0;
pid_t ret = waitpid(id,&status,0 );
if (ret > 0)
{
printf("等待成功,退出码: %d\n", WEXITSTATUS(status));
}
else
{
printf("等到失败\n");
}
}
return 0;
}
环境变量具有全局属性,子进程替换进程后,环境变量也不会修改
shell的环境变量时写在配置文件里的,shell启动的时候,读取配置文件获得起始环境变量
函数和进程的相似性
exec/exit就像call/return
一个c程序有很多函数构成,一个函数可以调用另一个函数,传入一些参数。被调用的函数执行一个操作,返回一个值,每个函数都有他的局部变量,不同的函数通过call/return进行通信
这种通过参数和返回值在拥有私有数据的函数间通信是结构化程序设计的基础。linux鼓励这种程序之内的模式扩展到程序之间,如下图: