替换原理
fork
创建子进程后执行的是和父进程相同的程序(但又可能执行不同的代码分支),如果想让子进程执行不同的程序,可以通过exec
函数启动另一程序并替换执行;
当调用exec
函数后,该进程的用户空间代码和数据完全被新程序替换;
调用exec
函数并不创建新进程,所以调用前后的进程 id 不改变;
替换函数
-
execl
(执行文件名 + 变长数) 格式:int execl(const char* path, const char* arg, ..., NULL);
用法:该函数接受程序路径和一系列变长参数来执行新的程序,注意最后的参数必须以
NULL
结尾; 示例:
execl("/usr/bin/ls", "ls", "-l", "-a", "/home", (char*)NULL);
// 注意,这里的"/home"是一个可选选项,用于指示其具体的目录,如果没有,则默认是当前目录
execv
(执行文件名 + 参数数组) 格式:int execv(const char* path, char* const argv[]);
用法:与execl
类似,但参数通过数组传递,适合动态构建参数列表的场景; 示例:
char *args[] = { "ls", "-l", "/home", NULL };
execv("/bin/ls", args);
execle
(执行文件名 + 变长参数 + 环境变量) 格式:int execv(const char* path, char* const argv[]);
用法:与execl
类似,但最后允许指定一个环境变量数组 envp[],以控制新程序的环境。 示例:
char *env[] = { "USER=guest", NULL };
execle("/bin/ls", "ls", "-l", "/home", (char *)NULL, env);
execve
(执行文件名 + 变长参数 + 环境变量) 格式:int execve(const char* path, char* const )
用法:该函数是最底层的exec
的变体,接受路径、参数数组和环境变量数组;所有其他的exec
函数都基于它实现; 示例:
char *args[] = { "ls", "-l", "/home", NULL };
char *env[] = { "USER=guest", NULL };
execve("/bin/ls", args, env);
execlp
(执行可搜索路径 + 变长参数) 格式:int execlp(const char *file, const char *arg, ..., NULL);;
用法:与 execl 相似,但 file 是可在环境变量 PATH 中搜索的可执行文件名,而不必指定完整路径; 示例:
execlp("ls", "ls", "-l", "/home", (char *)NULL);
execvp
(执行可搜索路径 + 参数数组) 格式:int execvp(const char *file, char *const argv[]);
用法:与execv
相似,但file
是可在PATH
环境变量中搜索的文件名; 示例:
char *args[] = { "ls", "-l", "/home", NULL };
execvp("ls", args);
总结:
l
和v
的区别:带有l
,只需提供变长参数即可;而v
则需要提供参数数组;e
的含义:需要提供自定义的环境变量;p
的作用:可以直接使用可执行文件名,其会自动在PATH
变量中查找相关文件,无需提供完整路径;
六大函数总结图如下:
正如上面所述,真正的系统调用是execve
,所有其他的函数都依赖于其实现,所以execve
在man
手册的第二节,而其他的函数位于man
手册的第三节;
自制简易shell
有了上面一系列知识的铺垫,我们可以自己实现一个简易的shell
程序,用于执行系统的部分可执行文件;
初步认识
首先,我们需要了解一般的shell
的运作机理,简单来说,shell
在执行用户命令时,会生成一个子进程用于执行,执行结束后,子进程自动销毁,而shell
作为父进程则继续运行,接受用户的下一个命令,如此循环往复;
框架实现
有了上面的认识,我们就可以使用之前介绍的exec
函数来替换子进程的程序以运行用户的命令,所以一个shell
程序,需要循环一下过程:
- 获取命令行
- 解析命令行
- 建立子进程(fork)
- 替换子进程(exec)
- 父进程等待子进程退出(wait) 下面是其简单实现:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <string.h>
#define NUM 1024
#define OPT_NUM 64
char lineCommand[NUM];
char* myargv[OPT_NUM];
int main(){
while(1){
printf("[用户名@主机名 当前路径] # ");
fflush(stdout);
char* s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin);
assert(s != NULL);
(void)s;
lineCommand[strlen(lineCommand) - 1] = 0;
myargv[0] = strtok(lineCommand, " ");
int i = 1;
while((myargv[i++] = strtok(NULL, " ")));
if(strcmp(myargv[0], "cd") == 0){
chdir(myargv[1]);
continue;
}
#ifdef DEBUG
for(int j = 0; myargv[j]; ++j){
printf("%d : %s\n", j, myargv[j]);
}
#endif
pid_t id = fork();
assert(id != -1);
if(id == 0){
execvp(myargv[0], myargv);
exit(0);
}
waitpid(id, NULL, 0);
}
return 0;
}
具体的代码解释以后再更新...
总结
以上就是Linux
中进程程替换,介绍了几个替换函数以及进程替换的原理,并通过这些知识实现了一个简易的shell
程序,用于执行用户的命令;需要注意的是,程序替换本身没有开启新进程,所以前后的进程ID是一致的;