替换原理
目前,我们使用fork创建子进程,为了用if,else让子进程执行父进程代码的一部分。如果想让子进程执行一个全新的程序,进程不变,仅仅替换当前进程的代码和数据,就叫做 进程替换。
子进程往往要调用一种exec函数,执行另一个程序,当进程调用一种exec函数时。进程的用户空间代码和数据被新程序完全替换。调用exec函数前后进程的id不变。
程序替换的本质:把指定的代码和数据,加载到特定进程的上下文中。
C/C++程序要运行,必须通过 加载器先加载到内存。
方式:exec*
程序替换函数。
替换函数
在Linux下使用man 3 exec
查看:
六种函数都是以exec
开头的吗,称为exec函数。
exec函数的返回值:
成功时,函数不返回。失败时,函数返回-1。
-> 所以exec函数只有出错的返回值而没有成功的返回值。只要exec*返回了,就一定时因为调用失败了。
替换函数的使用
execl
:
int execl(const char *path, const char *arg, ...);
path:要执行的目标程序的全路劲,所在路径/文件名形式,来表示你要执行谁
arg:表示你要执行的目标程序在命令行上怎么执行,这里的参数就怎么一个一个的传递进去,和命令行上的形式一样!
… :因为时可变参数列表,所以必须用NULL
结尾!
接下来看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 execvpe(const char *file, char *const argv[], char *const envp[]);
int execve(const char* path, char* const argv[], char* const envp[]);
简单的理解方法:
l
:表示arg参数用列表传,const char* arg, ...
v
:arg参数用数组传,char* const argv[]
p
:有p自动搜索环境变量 PATH,不需要自己写路径。
e
:自己维护环境变量(自定义环境变量)
所有的接口,差别并不大,只是参数不同。设置这么多接口,是为了满足不同的应用场景。
只有execve时真正的系统调用,其他五个函数最终都调用execve,在man手册用 man 2 exec
可以查看,其他函数在man 3 exec
可以查看。
execve的demo:
char *env[] = {
"MYENV1_xxxxxxxxxxxxxxxxxxxxxxxxxx",
NULL
};
execle("./myload", "myload", NULL, env);
简易shell实现程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string.h>
#define NUM 128
#define CMD_NUM 64
int main()
{
char command[NUM];
for(; ;)
{
char *argv[CMD_NUM] = {NULL};
// 1、打印提示符
command[0] = 0; //用这种方式,可以做到O(1)时间复杂度,清空字符串
printf("[zjy@myhostname mydir]# ");
fflush(stdout);
// 2、获取命令字符串
fgets(command, NUM, stdin);
command[strlen(command)-1] = '\0';
printf("echo: %s\n", command);
// ls -a -l -d\0
// 3、解析命令字符串 strtok
const char *sep = " ";
argv[0] = strtok(command, sep);
int i = 1;
while(argv[i] = strtok(NULL, sep))
{
++i;
}
// 4、检测命令是否是需要shell本身执行的,内建命令
if(strcmp(argv[0], "cd") == 0)
{
if(argv[1] != NULL)
chdir(argv[1]);
continue;
}
// 5、执行第三方命令
if(fork() == 0)
{
// child
execvp(argv[0], argv);
exit(1);
}
// father
waitpid(-1, NULL, 0);
// for(i=0; argv[i]; ++i)
// {
// printf("%s\n", argv[i]);
// }
}
return 0;
}
注意点:
fork()执行的命令是第三方命令(独立的),但是cd不是,cd回退只是子进程回退,但是当子进程结束后,父进程还在原来的路径下,所以这时,不能创建子进程,要让父进程自己执行,可以调用 chdir
函数。不能用程序替换,如果使用,会导致整个代码发生改变,不能继续循环输入了。