【Linux】进程程序替换
创始人
2024-10-22 07:38:42
0

目录

一、原理

二、程序替换函数

三、模拟实现简易版shell


一、原理

我们可以通过fork方法创建一个子进程,但是我们为什么要创建子进程呢?

在程序中,我们往往需要子进程帮助我们执行另一个程序,但子进程又只能和父进程共享相同的代码和数据。此时就需要调用一些函数来将子进程的代码和数据替换为新程序的代码和数据,即程序替换,这些函数我们统称为exec函数

当子进程调用这些函数时,其物理内存中的代码和数据完全被新程序的代码和数据替换(先进行写时拷贝),并从新程序的启动例程开始执行

所以程序替换只是替换了代码和数据,并没有创建新进程,子进程的ID也并不会改变


二、程序替换函数

一共有六种以exec开头的程序替换函数,它们分别是:

#include   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。所以一旦exec函数有返回值则说明调用出错。

这些函数命名看似混乱,实际有迹可循,我们先对它们的参数进行解释:

  • path:待替换的程序路径
  • file:不需带路径,只需带文件名,会自动在环境变量中查找
  • arg...:命令行参数,格式为列表
  • argv:命令行参数,格式为数组
  • envp:环境变量表

所以函数名中的字母分别表示:

  • l(list):命令行参数的格式为列表
  • v(vector):命令行参数的格式为数组
  • p(path):会自动在环境变量中查找,不需带路径
  • e(env):需要传入环境变量表

我们以execl为例,先来测试一下:

#include  #include   int main() {     execl("/usr/bin/ls", "ls", "-l", "-a", NULL); //命令行参数最后一个必须是NULL                                                                                                                                   return 0; } 

执行这段代码,结果如下:

可以看到,我们的程序已经成功被替换为了ls命令

如果将execl函数替换为execlp(会自动在环境变量中查找),则不需要带ls命令的完整路径了,只需要传 "ls" 即可,其他函数同理。

exec系列函数体现了加载器的效果。shell本质也是一个进程,我们用shell运行程序实际上都是通过创建子进程和exec系列函数实现的

之前说过,子进程会继承父进程的环境变量,所以我们可以通过在父进程中添加环境变量的方式向子进程传递环境变量。除了这种方法,我们在execle或execve函数中还可以通过自己维护环境变量表并传参的方式来彻底覆盖子进程的环境变量内容。所以向函数中传参实际上并不是对子进程的环境变量进行追加,而是覆盖

实际上,这些函数中只有execve是真正的系统调用,其他五个函数都是对其的封装。


三、模拟实现简易版shell

我们汇总以前学过的知识和本文中学到的程序替换,就可以自己实现一个丐版的shell

虽然功能远远比不上真正的shell,但是其原理我们已经掌握了

完整代码:

#include  #include  #include  #include  #include  #include  #include   #define LABLE "#" #define LINE_SIZE 1024 #define QUIT 0 #define ARGC_SIZE 32 #define EXIT_CODE 4  char commandline[LINE_SIZE]; char* argv[ARGC_SIZE]; int argc; int last_code; char pwd[LINE_SIZE]; char myenv[LINE_SIZE];  const char* gethostname() //获取主机名 {     return getenv("HOSTNAME"); }  const char* getusername() //获取用户名 {     return getenv("USER"); }  void getpwd() //获取工作路径 {      getcwd(pwd, sizeof(pwd)); }  void printinfo() //打印命令行信息 {     getpwd();     printf("[%s@%s %s]"LABLE" ", getusername(), gethostname(), pwd); }  void getcommand() //获取输入的命令 {     char* s = fgets(commandline, sizeof(commandline), stdin);     assert(s != NULL);     (void)s;     commandline[strlen(commandline)-1] = '\0'; //去掉\n }  int parsecommand() //解析命令:将长串字符串切割为一个个选项 {     int i = 0;     argv[i++] = strtok(commandline, " \t");     while(argv[i++] = strtok(NULL, " \t"));     return i-1; }  int ExecuteBuiltinCommands() //执行内建命令 {     if(argc == 2 && strcmp(argv[0], "cd") == 0) //cd命令     {         chdir(argv[1]);         getpwd();         sprintf(getenv("PWD"), "%s", pwd);         return 1;     }     else if(argc == 2 && strcmp(argv[0], "export") == 0) //export命令     {         strcpy(myenv, argv[1]);         putenv(myenv);         return 1;     }     else if(argc == 2 && strcmp(argv[0], "echo") == 0) //echo命令     {         if(strcmp(argv[1], "$?") == 0) //打印上一次的退出码         {             printf("%d\n", last_code);             last_code = 0;         }         else if(*argv[1] == '$') //判断是否要输出环境变量         {             char* s = getenv(argv[1]+1);             if(s) printf("%s\n", s);         }         else              printf("%s\n", argv[1]);         return 1;     }     //...     if(strcmp(argv[0], "ls") == 0) //特殊处理     {         argv[argc++] = "--color"; //给不同文件加上颜色         argv[argc] = NULL;     }     return 0; }  void ExecuteRegularCommands() //执行普通命令 {     pid_t id = fork(); //创建子进程执行命令     if(id < 0)     {         perror("fork error");         return;     }     else if(id == 0)     {         //程序替换         execvp(argv[0], argv);         exit(EXIT_CODE);     }     else      {         int status = 0;         pid_t ret = waitpid(id, &status, 0); //阻塞式等待         if(ret == id) //等待成功         {             last_code = WEXITSTATUS(status); //保留退出码                }     } }  int main() {     while(!QUIT)     {         printinfo(); //打印命令行信息         getcommand(); //获取输入的命令         argc = parsecommand(); //解析命令         if(argc == 0) continue;         int flag = ExecuteBuiltinCommands(); //判断是否为内建命令         if(!flag) ExecuteRegularCommands(); //执行普通命令     }     return 0; }

部分功能测试:

完.

相关内容

热门资讯

我来教你/游戏推荐牛牛房卡出售... 复仇者联盟是一款非常受欢迎的棋牌游戏,咨询房/卡添加微信:【3329006910】或QQ:33290...
微信炸金花链接怎么买房卡/牛牛... 炸金花是一款非常受欢迎的棋牌游戏,咨询房/卡添加微信:8488009许多玩家在游戏中会购买房卡来享受...
科技实测!牛牛房卡怎么购买先锋... 科技实测!牛牛房卡怎么购买先锋大厅/新道游/正规房卡链接在哪购买Sa9Ix苹果iPhone 17手机...
推荐一款!金花房卡是正规的青鸟... 您好!微信青鸟大厅大厅链接获取房卡可以通过以下几种方式购买: 1.微信渠道:(青鸟大厅)大厅介绍:...
正规平台有哪些,斗牛房间怎么创... 今 日消息,海贝之城房卡添加微信33549083 苹果今日发布了 iOS 16.1 正式版更新,简单...
安卓系统不升级的坏处,安卓系统... 亲爱的手机用户们,你是不是也遇到了这样的烦恼:安卓系统的更新通知总是跳出来,但你却总是犹豫不决,担心...
正规平台有哪些,金花房卡专卖店... 正规平台有哪些,金花房卡专卖店乐游联盟/正规房卡平台有哪些乐游联盟是一款非常受欢迎的游戏,咨询房/卡...
微信群上炸金花房间链接怎么开/... 炸金花是一款非常受欢迎的棋牌游戏,咨询房/卡添加微信:33903369许多玩家在游戏中会购买房卡来享...
科技实测!游戏微信牛牛房卡龙马... 微信游戏中心:龙马大厅房卡在哪里买打开微信,添加客服微信【88355042】,进入游戏中心或相关小程...
重大通报,牛牛房卡代理天龙大厅... 重大通报,牛牛房卡代理天龙大厅/房卡在哪里购买Sa9Ix苹果iPhone 17手机即将进入量产阶段。...
微信金花房卡链接使用方法/微信... 牛牛是一款非常受欢迎的棋牌游戏,咨询房/卡添加微信:15984933许多玩家在游戏中会购买房卡来享受...
微信群炸金花房间怎么开/神牛大... 炸金花是一款非常受欢迎的棋牌游戏,咨询房/卡添加微信:55051770许多玩家在游戏中会购买房卡来享...
我来教你/金花房卡专卖店豌豆互... 今 日消息,豌豆互娱房卡添加微信33549083 苹果今日发布了 iOS 16.1 正式版更新,简单...
玩家攻略,牛牛充值房卡九游联盟... 九游联盟房卡更多详情添加微:33549083、 2、在商城页面中选择房卡选项。 3、根...
IA解析/游戏推荐牛牛房卡出售... IA解析/游戏推荐牛牛房卡出售新全游/飞鹰互娱/微信链接房卡批发价新全游/飞鹰互娱是一款非常受欢迎的...
微信链接炸金花房卡怎么买/新海... 炸金花是一款非常受欢迎的棋牌游戏,咨询房/卡添加微信:8488009许多玩家在游戏中会购买房卡来享受...
在哪里买炸金花房卡便宜又好玩/... 金花是一款非常受欢迎的棋牌游戏,咨询房/卡添加微信:160470940许多玩家在游戏中会购买房卡来享...
科技实测!金花房卡是正规的九哥... 微信游戏中心:九哥联盟房卡在哪里买打开微信,添加客服微信【88355042】,进入游戏中心或相关小程...
推荐一款!金花微信链接市场价格... 推荐一款!金花微信链接市场价格表乐乐大厅/微信链接房卡充值购买Sa9Ix苹果iPhone 17手机即...
推荐一款!金花房间怎么创建新超... 您好!微信新超圣/樱花大厅大厅链接获取房卡可以通过以下几种方式购买: 1.微信渠道:(新超圣/樱花...