用 C语言程序调用 C++ 的程序
用 C语言程序调用 python 的程序
可以使用程序调换,调用任何后端语言对应的可执行程序!
5. execle
int execle(const char *path, const char *arg, ...,char *const envp[]);
传入自定义环境变量
传入系统的环境变量
execle 函数能够传入环境变量,但是我们发现传入系统环境变量,就不能传入自定义环境变量了。如果我们先要两个都有的话,就可以借助 putenv 函数了。
当使用 exec 函数将程序加载到内存的时候,其在调用 main 函数之前首先调用一个特殊的例程,并且将此启动例程指定为程序的起始位置。这个启动例程将从内核取得该可执行程序的命令行参数和环境变量,然后传递给 main 函数。
尽管前 4 个 exec 函数没有传环境变量,但是子进程照样能够通过 environ 拿到默认的环境变量,其是通过进程地址空间的方式让子进程拿到的!
6. execve
int execve(const char *path, char *const argv[], char *const envp[]);
在程序替换中,execve 是系统调用。其余 6 个函数都是对 execve 系统调用做的封装,以满足开发者的需求。
和 mian 函数的命令行参数结合
以上就实现了用我们的程序去执行系统的程序。如果再把前面的./myexec
去掉,就相当于我们自己写了个shell
。那么接下来,我们就模拟实现一个简易的shell
。
👉shell 的模拟实现👈
现在我们已经学习到了进程创建、进程退出、进程登台、进程程序替换等知识,那么我们理解这些知识模拟实现简易版的命令行解释器 shell。
注:本次模拟实现的 shell 并不是十全十美的,多少会有一些 BUG。对于一些常用的命令,还是能实现的。
实现思路:
- 通常来说,shell 读取一行新的输入,对输入进行命令解析。然后创建子进程并进行程序替换执行输入的命令。但是对于一些内建(内置)命令,shell 会自己执行,而不是通过创建子进程再进行程序替换的方式。
- 什么是内建(内置命令)?不需要创建子进程来执行,而是让 shell 自己执行的命令称为内建命令或者内置命令,其本质是调用相应的系统接口。
echo 和 cd 就是常见的内建命令。因为 echo 是个内建命令,命令行解释器 bash 不会创建子进程来执行 echo 命令而是自己去执行该目录,所以 echo 能够输出不具有全局属性的本地变量。
shell 的循环过程
获取命令行
解析命令行
建立一个子进程(fork)
替换子进程(execvp)
父进程等待子进程退出(wait)
关于 cd 为什么是内建命令,我们需要了解什么是当前路径!当前路径就是当前进程的工作路径。默认情况下,当前路径就是可执行程序所处的路径。进程的工作路径可以通过系统调用chdir来修改。如果我们创建子进程来执行 cd 命令的话,子进程执行 cd 命令改变子进程的工作路径。而子进程执行 cd 命令后就退出了,并不会改变父进程 bash 的工作路径。所以说,要想改变父进程 bash 的工作路径,就只能父进程 bash 来执行 cd 命令了。所以,cd 也是内建命令。
让子进程执行 cd 命令的情况
使用 chdir 修改进程的工作路径
myshell 源码
#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 lastCode = 0; int lastSignal = 0; int main() { while(1) { char* user = getenv("USER"); // 当前登录用户 // 根据用户输出对应的提示信息, get_current_dir_name函数可以获得当前的工作路径 if(strcmp(user, "root") == 0) { printf("[%s@%s %s]# ", user, getenv("HOSTNAME"), get_current_dir_name()); } else { printf("[%s@%s %s]$ ", user, getenv("HOSTNAME"), get_current_dir_name()); } fflush(stdout); // 刷新缓冲区 // 获取用户输入 char* s = fgets(lineCommand, sizeof(lineCommand) - 1, stdin); assert(s != NULL); // 清除最后一个\n, abcd\n lineCommand[strlen(lineCommand) - 1] = 0; // 字符串切割:"ls -a -l" -> "ls" "-a" "-l" myargv[0] = strtok(lineCommand, " "); int i = 1; // 因为无法执行"ll"指令, 所以这里做一下处理 if(myargv[0] != NULL && strcmp(myargv[0], "ll") == 0) { myargv[0] = "ls"; myargv[i++] = "-l"; } if(myargv[0] != NULL && strcmp(myargv[0], "ls") == 0) { myargv[i++] = "--color=auto"; } // 如果切割完毕, strtok返回NULL, myargv[end] = NULL while(myargv[i++] = strtok(NULL, " ")); // 如果是cd命令, 不需要创建子进程来执行, 让当前进程的父进程shell执行对应的命令, 本质就是调用系统接口 // 像这种不需要创建子进程来执行, 而是让shell自己执行的命令, 称为内建命令或者内置命令 // echo和cd就是一个内建命令 if(myargv[0] != NULL && strcmp(myargv[0], "cd") == 0) { // 如果cd命令没有第二个参数, 则切换到家目录 if(myargv[1] == NULL) { chdir(getenv("HOME")); // 更改到家目录 } else { if(strcmp(myargv[1], "-") == 0) // 该功能还有BUG, 因为环境变量的问题 { chdir(getenv("OLDPWD")); // 回到上一次所处的路径 } else if(strcmp(myargv[1], "~") == 0) { chdir(getenv("HOME")); // 去到家目录 } else { chdir(myargv[1]); // 更改到指定目录 } } continue; // 不创建子进程, continue回到while循环处 } // 实现echo命令, 当前的echo命令功能也不是很全 if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0) { if(strcmp(myargv[1], "$?") == 0) { printf("%d, %d\n", lastSignal, lastCode); } else { printf("%s\n", myargv[1]); } continue; } // 创建子进程来执行命令 pid_t id = fork(); assert(id != -1); // child process if(id == 0) { execvp(myargv[0], myargv); exit(1); // 进程替换失败 } int status = 0; pid_t ret = waitpid(id, &status, 0); // 阻塞等待 assert(ret > 0); lastCode = ((status >> 8) & 0xFF); lastSignal = (status & 0x7F); } return 0; }
myshell 使用演示
myshell 的源码里已经有了相应的注释,所以就不详细讲解了。我们无法做到使用 cd 命令时,使得 bash 和 myshell 的工作路径一起跟着改变。因为当你登录上 Xshell 时,操作系统已经将 bash 进程给创建好了,myshell 是 bash 的一个子进程,所以 myshell 执行 cd 命令并不会修改 bash 的工作路径。
👉补充知识👈
exec 和 exit 就像 call 和 return 一样。一个 C 语言程序有很多函数组成,一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过 call 和 return 进行通信。这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux 鼓励将这种应用于程序之内的模式扩展到程序之间。如下图:
一个 C 语言程序可以 fork 和 exec 另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过 exit 来返回值。调用它的进程可以通过 wait 来获取 exit 的返回值。
👉总结👈
本篇博客主要讲解了进程的程序替换并且综合前面学到的进程创建、进程退出和进程等待的知识模拟实现了一个简易版的命令行解释器 myshell。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️