Linux-exec函数族和system函数

参考资料:《Linux环境编程:从应用到内核》

execve函数

execve函数接口如下:

 #include <unistd.h>
 
 int execve(const char *filename, char *const argv[],
        char *const envp[]);

参数:

  • 第一个参数:filename是可执行的新程序的路径名,可以是绝对路径,也可以是相对于当前工作目录的相对路径;
  • 第二个参数:字符串指针组成的数组,以NULL结束。argv[0]一般对应可执行文件的文件名,也就是filename中的basename(路径名最后一个/后面的部分)。当然如果argv[0]不遵循这个约定也无妨,因为execve可以从第一个参数获取到要执行文件的路径,只要不是NULL即可。
  • 第三个参数:与C语言的main函数中的第三个参数envp一样,也是字符串指针数组,以NULL结束,指针指向的字符串的格式为name=value。

一般来说,execve()函数总是紧随fork函数之后。父进程调用fork之后,子进程执行execve函数,抛弃父进程的程序段,和父进程分道扬镳,从此天各一方,各走各路。但是也可以不执行fork,单独调用execve函数:

返回值:

execve函数返回值是特殊的,如果失败,返回-1,但如果成功,永不返回。

所以无须检查execve的返回值,只要返回,就必然是-1。可以从errno判断出出错的原因。出错的可能性非常多,手册提供了19种不同的errno,罗列了22种失败的情景。很难记住,好在大部分都不常见,常见的情况有以下几种:

  • EACCESS:这个是我们最容易想到的,就是第一个参数filename,不是个普通文件,或者该文件没有赋予可执行的权限,或者目录结构中某一级目录不可搜索,或者文件所在的文件系统是以MS_NOEXEC标志挂载的。
  • ENOENT:文件不存在。
  • ETXTBSY:存在其他进程尝试修改filename所指代的文件。
  • ENOEXEC:这个错误其实是比较高端的一种错误了,文件存在,也可以执行,但是无法执行,比如说,Windows下的可执行程序,拿到Linux下,调用execve来执行,文件的格式不对,就会返回这种错误。
// 准备外部程序 main.cpp

#include <iostream>

int main(int argc, char* args[])
{
    if (argc < 3)
    {
        std::cout << "函数参数太少" << std::endl;

        return -1;
    }

    int a = std::stoi(args[1]);
    int b = std::stoi(args[2]);

    std::cout << "a + b = " << (a + b) << std::endl;

    return 0;
}
// 示例

// execve函数

#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

// 执行外部程序main
void func1()
{
    // 为什么使用const char*,之后又转一次?
    // 这是因为 "./main"、"1"、"2"在C++中都是字符串字面值,属于const char*,如果赋值给char*,就会用警告
    // 所以用const char*接收,之后通过const_cast去除const属性。
    const char* args[] = { "./main", "1", "2", nullptr };

    if (execve(const_cast<char*>(args[0]), const_cast<char**>(args), nullptr) == -1)
    {
        perror("execve");
        exit(EXIT_FAILURE);
    }
}

// 在终端执行 pwd -L 命令
void func2()
{
    const char* args[] = { "/bin/pwd", "-L", nullptr };

    if (execve(const_cast<char*>(args[0]), const_cast<char**>(args), nullptr) == -1)
    {
        perror("execve");
        exit(EXIT_FAILURE);
    }
}

int main()
{
    func1();

    // func2();

    std::cout << "never get here" << std::endl;

    return 0;
}

在这里插入图片描述

func1()和func2()不能同时测试,因为execve函数永不返回,无法执行完func1(),再执行func2()。

可以看到,我们在func1()里使用execve函数调用外部程序main,输出结果也是正确。

exec家族

从内核的角度来说,提供execve系统调用就足够了,但是从应用层编程的角度来讲,execve函数就并不那么好使了:

  • 第一个参数必须是绝对路径或是相对于当前工作目录的相对路径。习惯在shell下工作的用户会觉得不太方便,因为日常工作都是写ls和mkdir之类命令的,没有人会写/bin/ls或/bin/mkdir。shell提供了环境变量PATH,即可执行程序的查找路径,对于位于查找路径里的可执行程序,我们不必写出完整的路径,很方便,而execve函数享受不到这个福利,因此使用不便。
  • execve函数的第三个参数是环境变量指针数组,用户使用execve编程时不得不自己负责环境变量,书写大量的“key=value”,但大部分情况下并不需要定制环境变量,只需要使用当前的环境变量即可。

正是为了提供相应的便利,所以用户层提供了6个函数,当然,这些函数本质上都是调用execve系统调用,只是使用的方法略有不同,代码如下:

#include <unistd.h>
extern char **environ;

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[]);

上述6个函数分成上下两个半区。分类的依据是参数采用列表(l,表示list)还是数组(v,表示vector)。上半区采用列表,它们会罗列所有的参数,下半区采用数组。在每个半区之中,带p的表示可以使用环境变量PATH,带e的表示必须要自己维护环境变量,而不使用当前环境变量,

在这里插入图片描述

// 示例 返回值自行判断

char* const ps_argv[] = { "ps","-ax",NULL };
char* const ps_envp[] = { "PATH=/bin:/usr/bin","TERM=console",NULL };

execl("/bin/ps", "ps", "-ax", NULL);

/*带p的,可以使用环境变量PATH,无须写全路径*/
execlp("ps", "ps", "-ax", NULL);

/*带e的需要自己组拼环境变量*/
execle("/bin/ps", "ps", "-ax", NULL, ps_envp);

execv("/bin/ps", ps_argv);

/*带p的,可以使用环境变量PATH,无须写全路径*/
execvp("ps", ps_argv);

/*带e的需要自己组拼环境变量*/
execve("/bin/ps", ps_argv, ps_envp);

system函数

程序可以调用system函数,来执行任意的shell命令,可以使C程序很方便地调用其他语言编写的程序。

函数接口如下:

#include <stdlib.h>

int system(const char *command);

这里将需要执行的命令作为command参数,传给system函数,该函数就帮你执行该命令。

这样看来system最大的好处就在于使用方便。不需要自己来调用fork、exec和waitpid,也不需要自己处理错误,处理信号,方便省心。

但是system函数的缺点也是很明显的。首先是效率,使用system运行命令时,一般要创建两个进程,一个是shell进程,另外一个或多个是用于shell所执行的命令。如果对效率要求比较高,最好是自己直接调用fork和exec来执行既定的程序。

  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值