本篇博客主要讲解 linux 下程序替换函数
邮箱:blbagony@163.com
参考资料:UNIX 环境高级编程
感谢观看,请多多指教
exec 函数
一般我们在一个进程中调用 fork 创建出子进程,该子进程往往需要执行另一个程序,此时我们就需要调用 exec 函数。当进程调用一种 exec 函数时,该进程的执行程序完全替换了新进程,而新程序则从其 main 函数开始执行。
注意:exec 并不创建新进程,所以前后子进程的 ID 并没有改变。exec 只是用一个新的程序替换了当前进程的正文、数据、堆和栈段。
linux 下提供了六种不同的 exec 函数:
6 个函数成功不返回,失败返回 -1
- 函数替换成功后就去执行新的程序,即 path 指定的程序,并且不再返回。当可执行文件不存在,或者没有权限执行会错误返回,返回值为 -1。
参数解析:不带字母 p(表示 path)
的 exec 函数,第一个参数必须是程序的绝对路径或相对路径
绝对路径:计算机中的绝对路径是你的主页上的文件在硬盘上的真正的路径(URL和物理路径)
相对路径:相对与某个基准的目录的路径.
例如 "/bin/ls" 就是绝对路径"./a.out" 就是相对路径
。
对于带字母 p
的函数:如果参数中包含了/
,则将其视为路径名。否则视为不带路径的程序名 ,在 PATH 环境环境变量的目录表中搜索这个程序。
带有字母 l(表示list)
的 exec 函数要求新程序的每个命令行参数都当作一个独立的参数出递给他,命令行参数的个数可变,所以函数原型中有...
,...
中的最后一个参数必须为NULL
,然后将该数组地址作为这三个函数的参数。
函数调用
注意 execve
是系统调用,而其他五个函数都调用 execve
。
实现简易 shell
思路:
1. 父进程通过不断从标准输入读取命令行参数。
2. 将命令行参数解保存到指针数组中。
3. fork 一个子进程,让子进程通过调用 execvp 程序替换。执行用户输入命令。
4. 父进程等待子进程退出,继续读取标准输出。
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <assert.h>
#include <string.h>
#include <sys/wait.h>
const size_t BUFF_SIZE = 256;
void parse(char* str, char* argv[])
{
assert(str);
char *token = strtok(str, " ");
int i = 0;
argv[i++] = token;
while (token != NULL) {
if ( 0 == strncasecmp(token, "quit", 4) ) {
exit(0);
}
token = strtok(NULL, " ");
argv[i++] = token;
}
}
void Exec(char* const argv[])
{
pid_t id = fork();
if (id == 0) {
execvp(argv[0], argv);
perror("execvp");
} else if (id > 0) {
wait(NULL);
} else {
perror("fork");
}
}
void run(){
char *outbuf[BUFF_SIZE];
char inbuf[BUFF_SIZE];
while (1) {
printf("[blb]# ");
fflush(stdout);
bzero(inbuf, sizeof (inbuf));
bzero(outbuf, sizeof (outbuf));
/* 1. 从输入流读取字符串 */
size_t ret = read(0, inbuf, sizeof (inbuf));
inbuf[ret - 1] = 0;//注意需要把 '\n' 去掉
/* 2. 解析字符串 */
parse(inbuf, outbuf);
/* 3. 执行程序替换 */
Exec(outbuf);
}
}
int main()
{
run();
return 0;
}
简易的 bash 不能实现内建指令,不支持 > | 等运算符,后面改进时可以在 prase 函数中遇到内建指令就需要父进程自己去亲自执行
什么是内建指令