Linux进程程序替换

文章讲述了如何通过宏WIFEXITED和WEXITSTATUS获取进程的退出码,以及进程程序替换的概念和原理,包括exec系列函数的使用,如execve、execl、execlp等,它们用于替换进程的执行程序。并提供了一个简易shell的实现示例,展示了如何处理用户输入并执行命令。
摘要由CSDN通过智能技术生成

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

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

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

进程程序替换

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

替换原理

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

替换函数

通过查看man手册可以看到替换函数的基本信息:

上图中的...被称为可变参数列表,可以让我们像一个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 <unistd.h>
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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#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);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值