Linux系统---进程程序替换

顾得泉:个人主页

个人专栏:《Linux操作系统》  《C/C++》  《LeedCode刷题》

键盘敲烂,年薪百万!


一、进程程序替换

一、替换原理

       用fork 创建子进程后执行的是和父进程相同的程序 ( 但有可能执行不同的代码分支 ), 子进程往往要调用一种 exec 函数 以执行另一个程序。当进程调用一种exec 函数时 , 该进程的用户空间代码和数据完全被新程序替换 , 从新程序的启动 例程开始执行。调用exec 并不创建新进程 , 所以调用 exec 前后该进程的 id 并未改变。

Linux系统---进程程序替换_第1张图片 

二、替换函数

其实有六种以 exec 开头的函数 , 统称 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函数只有出错的返回值而没有成功的返回值。

四、命名理解

    这些函数原型看起来很容易混,但只要掌握了规律就很好记:

         l(list):表示参数采用列表

        v(vector):参数用数组

        p(path):有p自动搜索环境变量PATH

        e(env):表示自己维护环境变量

Linux系统---进程程序替换_第2张图片

exec调用举例如下:
#include 
int main()
{
     char *const argv[] = {"ps", "-ef", NULL};
     char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};
     execl("/bin/ps", "ps", "-ef", NULL);
     // 带p的,可以使用环境变量PATH,无需写全路径
     execlp("ps", "ps", "-ef", NULL);
     // 带e的,需要自己组装环境变量
     execle("ps", "ps", "-ef", NULL, envp);
     execv("/bin/ps", argv);
 
     // 带p的,可以使用环境变量PATH,无需写全路径
     execvp("ps", argv);
     // 带e的,需要自己组装环境变量
     execve("/bin/ps", argv, envp);
     exit(0);
}

下图exec函数族一个完整的例子:
Linux系统---进程程序替换_第3张图片


二、制作简易的shall

考虑下面这个与 shell 典型的互动:
[root@localhost epoll]# ls
client.cpp readme.md server.cpp utility.h
[root@localhost epoll]# ps
 PID TTY TIME CMD
 3451 pts/0 00:00:00 bash
 3514 pts/0 00:00:00 ps
       用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell 由标识为 sh 的方块代表,它随着时间的流逝从左 向右移动。shell 从用户读入字符串 "ls" shell 建立一个新的进程,然后在那个进程中运行 ls 程序并等待那个进程结束。

Linux系统---进程程序替换_第4张图片

       然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序并等待这个进程结束。所以要写一个shell,需要循环以下过程:

       1.获取命令行

       2.解析命令行

       3.建立一个子进程 (fork)

       4.替换子进程(execvp)

       5.父进程等待子进程退出(wait)

       根据这些思路,和我们前面的学的技术,就可以自己来实现一个shell了。

实现代码

#include 
#include 
#include 
#include 
#include 

#define MAX_CMD 1024
char command[MAX_CMD];

int do_face()
{
     memset(command, 0x00, MAX_CMD);
     printf("minishell$ ");
     fflush(stdout);
     if (scanf("%[^\n]%*c", command) == 0) 
    {
         getchar();
         return -1; 
    } 
     return 0;
}

char **do_parse(char *buff)
{
     int argc = 0;
     static char *argv[32];
     char *ptr = buff;
     while(*ptr != '\0') 
    {
         if (!isspace(*ptr)) 
         {
             argv[argc++] = ptr;
             while((!isspace(*ptr)) && (*ptr) != '\0') 
             {
                 ptr++;
             }
        }
        else 
        {
             while(isspace(*ptr)) 
             {
                 *ptr = '\0';
                 ptr++;
             }
        }
    }
    argv[argc] = NULL;
    return argv;
}

int do_exec(char *buff)
{
     char **argv = {NULL};
     int pid = fork();
     if (pid == 0) 
     {
         argv = do_parse(buff);
         if (argv[0] == NULL) 
         {
             exit(-1);
         }
         execvp(argv[0], argv);
     }
     else 
     {
         waitpid(pid, NULL, 0);
     }
     return 0;
}

int main(int argc, char *argv[])
{
     while(1) 
     {
         if (do_face() < 0)
         continue;
         do_exec(command);
     }
     return 0;
}
在继续学习新知识前,我们来思考函数和进程之间的相似性
exec/exit就像call/return

       一个C程序有很多函数组成。一个函数可以调用另外一个函数,同时传递给它一些参数。被调用的函数执行一定的操作,然后返回一个值。每个函数都有他的局部变量,不同的函数通过call/return系统进行通信。

       这种通过参数和返回值在拥有私有数据的函数间通信的模式是结构化程序设计的基础。Linux 鼓励将这种应用于程序之内的模式扩展到程序之间。如下图:
Linux系统---进程程序替换_第5张图片

 

       一个C 程序可以 fork/exec 另一个程序,并传给它一些参数。这个被调用的程序执行一定的操作,然后通过 exit(n) 来 返回值。调用它的进程可以通过wait &ret )来获取 exit 的返回值。

结语:Linux系统关于进程程序替换的分享到这里就结束了,没有进行展示的操作大家可以自行练习,希望本篇文章的分享会对大家的学习带来些许帮助,如果大家有什么问题,欢迎大家在评论区留言~~~ 

你可能感兴趣的:(Linux操作系统,linux,运维,ubuntu,服务器)