Linux进程程序替换

前文中我们讲述了进程的退出码,和通过位运算获得进程退出码的方法。本文中我们继续讲述通过宏的方法获得子进程退出码,以及进程程序替换的相关内容。

可以使用下面的一些宏方法获得进程退出码的内容。

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

进程程序替换

创建子进程的目的:为了让子进程执行特定的任务(让子进程执行部分父进程的代码;让子进程想执行一个全新的程序代码),因此需要有程序替换这样的操作。

替换原理

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

替换函数

通过查看man手册可以看到替换函数的基本信息:Linux进程程序替换_第1张图片

上图中的...被称为可变参数列表,可以让我们像一个C函数传递任意个数的参数。除了上述六种之外还有一个替换函数:

int execve(const char *path, char *const argv[], char *const envp[]);

函数解释

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值

命名理解

这些函数原型虽然看起来比较的混乱,但是都有着规律:

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量

下面就来简单地举一下例子:

#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);
}

上述替换函数中在手册中会发现只有execve在手册第二节,因为这个函数是真正的函数调用,其余的五个函数都是调用的execve,通过这些封装我们可以更灵活的去使用程序替换函数。

我们可以简单地使用之前学习过的内容来实现一个简易的shell:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAX 1024
#define ARGC 64
#define SEP " "

int split(char* commandstr, char* argv[])
{
    assert(commandstr);
    assert(argv);

    argv[0] = strtok(commandstr, SEP);
    if (argv[0] == NULL) return -1;
    int i = 1;
    while ((argv[i++] = strtok(NULL, SEP)));
    //int i = 1;
    //while(1)
    //{
    //    argv[i] = strtok(NULL, SEP);
    //    if(argv[i] == NULL) break;
    //    i++;
    //}
    return 0;
}

void debugPrint(char* argv[])
{
    for (int i = 0; argv[i]; i++)
    {
        printf("%d: %s\n", i, argv[i]);
    }
}

int main()
{
    while (1)
    {
        char commandstr[MAX] = { 0 }; // 命令行
        char* argv[ARGC] = { NULL };
        printf("[zyq@mymachine currpath]# "); // 这里的每一个字段都可以通过系统调用获取 [用户名@主机名当前路径]
        fflush(stdout); // 没有换行,需要对缓冲区刷新使上述的命令行字段能够显示出来
        
        char* s = fgets(commandstr, sizeof(commandstr) - 1, stdin); // 获取当前的以空格为间隔的字符串 // 在这里最好对sizeof减去1;fgets本来会添加\0,但是最好要添加上,因为编写系统代码的时候,有的是C的接口,有的是操作系统的接口,C接口会减但是系统接口不会,代码会比较割裂
        assert(s); // 为NULL时获取失败
        (void)s; // 保证在release方式发布的时候,因为去掉assert了,所以s就没有被使用,而带来的编译告警, 什么都没做,但是充当一次使用
        
        // abcd\n\0
        commandstr[strlen(commandstr) - 1] = '\0'; // 在我们输入的时候会自动读取回车,但是这是我们不需要的,因此需要去除
        // "ls -a -l" -> "ls" "-a" "-l"
        int n = split(commandstr, argv); // 通过空格分隔指令
        if (n != 0) continue; // 如果切失败了什么都不做
        //debugPrint(argv);
         // version 1
        pid_t id = fork();
        assert(id >= 0);
        (void)id;

        if (id == 0)
        {
            //child
            execvp(argv[0], argv);
            exit(1);
        }
        int status = 0;
        waitpid(id, &status, 0);
        //printf("%s\n", commandstr);
    }
}

你可能感兴趣的:(Linux,linux)