zoukankan      html  css  js  c++  java
  • 关于UNIX的exec函数

    在UNIX系统中,系统为进程相关提供了一系列的控制原语,包括:进程fork,进程exit,进程exec,进程wait等服务。

    该篇文章主要与进程exec服务有关,并记录了几个需要注意留意的点。

    照例给出其头文件及函数原型如下:

     1 #include <unistd.h>
     2 
     3 int execl(const char *pathname, const char *arg0, ..., (char *)0);
     4 int execv(const char *pathname, char *const argv[]);
     5 int execle(const char *pathname, const char *arg0, ..., (char *)0, char *const envp[]);
     6 int execve(const char *pathname, char *const argv[], char *const envp[]);
     7 int execlp(const char *filename, const char *arg0, ..., (char *)0);
     8 int execvp(const char *filename, char *const argv[]);
     9 int fexecve(int fd, char *const argv[], char *const envp[]);
    10 
    11 // Linux
    12 int execvpe(const char *file, char *const argv[], char *const envp[]);

    上面总计 7+1=8 个函数,前面7个exec函数有些UNIX实现可能都会实现,也有可能只实现其中的几个,对于最后一个是GNU Linux系统的特有实现。

    记忆方式为:exec开头 + l(list列表)/v(vector向量) + e(环境变量env传递)/p(环境变量path遍历)

    上面的函数中都不应该有返回值,因为一旦exec( )函数执行成功,那么进程的内容,包括代码和数据会被全部替换掉,旧的进程的代码执行流程就不再存在,因此不该有返回值,但是exec( )函数们可能会执行失败,因此该exec( )函数是有返回值的,其返回值为固定的-1。因此,通常可以在exec函数后面进行错误的输出和程序终止,如下:

    execl("/bin/your_program",argv_list);
    cerr << "errno occurred, error number: " << errno << "
    ";

    如果exec返回,那么一定发生了错误,紧随其后的代码报告错误,并打印具体错误number至标准错误。

    上面7个exec( )函数中前四个加载新程序的方式是通过绝对路径path来指定,这四个函数之间的区别是形参的传递形式,是以数组形式,还是以一个一个参数的形式传递。还有区别是否传递环境变量和形参是否以空指针结尾。

    第五和第六个exec( )函数是通过环境变量 $PATH+file文件名来指定,而第七个则是通过已经打开的文件描述符来指定,通过文件描述符来指定可以避免欲加载的程序二进制文件被替换,从而阻止安全问题。

    第一个注意点:前面给出的7个exec( )函数都是系统调用吗?

    虽然各个UNIX实现可能提供了好几个exec( )函数,但是只有其中的execve( )函数是系统API,其他的exec( )函数都是在exece( )基础上进行包装的。

    第二个注意点:exec( )函数给后续加载的程序传递参数时是否需要指定传入的argv的数组长度?

    回忆UNIX系统中那些系统调用API,在很多形参中涉及指针的函数时,我们通常都要指定指针所指缓冲区字节数或者数组的长度,比如read函数要指定字节数,比如poll函数中要指定数组的元素个数。对于这个问题,回答肯定是不需要,因为我们无法指定数组长度,API接口形参列表中不接受数组长度。既然API接口不接受数组长度,那么exec( )函数怎么知道数组的长度呢?方法是靠参数格式约定,也即:传递给exec( )函数列表形式的形参最后一个参数是(char *)NULL,数组形式的形参argv[]最后一个元素是(char *)NULL。这样当exec( )函数遇到了(char *)NULL则表示数组已经遍历到结尾了。

    如果不遵守上述行为,不同的实现可能会有不同的行为,比如CentOS会报错,而Mac OS X则会得到意外的形参,代码如下:

    1 char *const argv[4] = {(char *) "ping", (char *) "-c", (char *) "5", (char *) "www.baidu.com"};
    2 char *const argv2[4] = {(char *) "ping", (char *) "-c", (char *) "5", (char *) "www.baidu.com"};
    3 execv("/Users/mac/Develop/cpp/test", argv);

    假设在调用execv( )之前先构造execv( )的形参argv,之后又构造了一个无用的argv2,然后调用execv( )函数,而execv( )函数中加载的test个人程序会一一打印传递给它的参数,在Mac OS X上会得到如下的结果:

    从结果可以看到,由于第一个argv没有以null空指针结尾,因此exec会一直读argv,甚至把argv2的内容也读取到了,而同样的代码在CentOS上编译后运行,却能得到正常结果。虽然对于个人写的遍历打印参数的程序能正常工作,但对于ping这样的工具则会调用失败。因此这些行为是不确定的,不能对它们做出某种假设,要遵守null指针结尾的规则。

    第三个注意点:exec( )函数中第一个形参参数名的意义是什么?

    回忆main( )函数的参数,其函数原型为

    int main(int argc, char const *argv[]);

     其中的argv数组保存了传递给main函数的命令行参数,argv总是以这样的形式构成:

    argv[0](main程序自身名) + argv[...](传递给main程序的形参) + argv[argv](null指针)

    虽然argv最后以空指针结尾,但是该空指针并不计入argv大小中。

    对于exec( )系列函数,当给其传递参数时,第一个参数按照惯例,总是传入该程序不带路径的纯名字,当然也可以为空,但不能省略。对于Mac OS X系统来说,会用该名字作为系统进程中的活跃进程名,而CentOS则不会。其意义其实就是一个约定。

  • 相关阅读:
    Python格式化输出%s和%d
    操作数据库
    协议类介绍
    并发和并行和压测 、对带宽的理解解释
    悠悠大神的 并发当前目录下所有文件的方法(还没试过)
    post参数的方法 json data 和特别的传参
    接口测试简介
    appium的三种等待方式 (还没实践过,记录在此)
    人生进步目标
    保持一个会话 添加 HTTP Cookie管理器
  • 原文地址:https://www.cnblogs.com/pluse/p/8013239.html
Copyright © 2011-2022 走看看