APUE编程:101---进程管理(exec、execv、execle、execve、execlp、execvp、fexecve函数)

一、什么是exec函数

  • 用fork函数创建子进程后,子进程如果想要执行另一个程序,往往要调用exec函数以执行另一个程序
  • exec函数执行的特点:
    • exec把当前进程映像替换成新的程序文件,该进程完全由新程序代换,而且新程序从其main函数开始执行
    • 因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段
    • 如果exec函数执行成功,则原程序中的exec之后的代码都不会执行
    • exec函数不会关闭原程序打开的文件描述符,除非该文件描述符被设置了类似SOCK_CLOEXEC的属性(见socket函数的type参数:https://blog.csdn.net/qq_41453285/article/details/89179532

二、exec函数

  • 有7种不同的exec函数可供使用,它们常常被统称为exec函数,我们可以使用这些中的其中一个,这些exec函数使得UNIX进程控制原语更加完善。用fork可以创建新进程,用exec可以执行新的程序
  • exit函数和两个wait函数处理终止和等待终止。这些是我们需要的基本的进程控制原语。在后面各节中将使用这些原语构造另外一些如popen和system之类的函数
#include <unistd.h>
int execl(const char *pathname, const char *arg0, ... /*(char *)0*/);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, .../*(char *)0, char *const envp[]*/);
int execve(const char *pathname, char *const argv[], char *const envp[]);

int execlp(const char *filename, const char *arg0, ... /* (char *)0 */ );
int execvp(const char *filename, char *const argv[]);

int fexecve(int fd, char *const argv[], char *const envp[]);
  • 参数:前四个函数取路径名作为参数,后两个函数则取文件名作为参数,最后一个函数取文件描述符作为参数
    • 这几个函数分别使用pathname或filename或打开的文件描述符fd来打开一个新的进程
  • 返回值:出错返回-1并errno;成功不返回(一般情况下,exec函数是不返回的,除非出错)

当指定filename作为参数时:

  • 如果filename中包含/,则就将filename作为一个路径名来打开一个程序
  • 否则就从PATH环境变量的相关目录中搜寻可执行文件(有很多出于安全性方面的考虑,要求在搜索路径中决不要包括当前目录

这7个exec函数的参数很难记忆。函数名中的字符会给我们一些帮助:

  • 字母p表示该函数取filename作为参数,并且用PATH环境变量寻找可执行文件
  • 字母l表示该函数取一个参数表,它与字母v互斥
  • v表示该函数取一个argv[]矢量
  • 母e表示该函数取envp[]数组,而不使用当前环境

  • 在很多UNIX实现中,这7个函数中只有一个execve是内核的系统调用。另外6个只是库函数,它们最终都要调用系统调用。这7个函数之间的关系如图:

三、不同exec函数的区别

区别总结:

  • ①待执行的程序文件是由文件名还是由路径名决定
  • ②新程序的参数是一一列出还是由一个指针数组来引用
  • ③把调用进程的环境传递给新程序还是给新程序重新制定新的环境 

execlp、execvp:

  • 如果execlp和execvp中的任意一个使用路径前缀中的一个找到了一个可执行文件,但是该文件不是由连接编辑程序产生的机器可执行代码文件,则就认为该文件是一个shell脚本,于是试着调用/bin/sh,并以该filename作为shell的输入

第二个区别:新进程的参数表是通过逐个参数传递还是通过一个指针数组传递(l表示列表list),v表示矢量(vector)

  • 函数execl、execlp和execle要求将新程序的每个命令行参数都说明为一个单独的参数。这种参数表以空指针((char*)0))结尾
  • 对于另外三个函数(execv,execvp和execve、fexecve),则应先构造一个指向各参数的指针数组(数组的最后一个元素还是空指针(char*)0),然后将该数组地址作为这三个函数的参数

在使用ISOC原型之前,对execl,execle和execlp三个函数表示命令行参数的一般方法是:

char *arg0, char *arg1, ..., char *argn, (char *)0
  • 这种语法显示地说明了最后一个命令行参数之后跟了一个空指针。如果用常数0来表示一个空指针,则必须将它强制转换为一个字符指针,否则它将被解释为整型参数。如果一个整型数的长度与char*的长度不同,exec函数实际参数就将出错

最后一个区别与向新程序传递环境表相关:

  • 以 e结尾的3个函数(execle和execve、fexecve) 可以传递一个指向环境字符串指针数组的指针。其他四个函数则使用调用进程中的environ变量为新程序复制现存的环境。(回忆7 . 9节及表7-2中对环境字符串的讨论。其中曾提及如果系统支持setenv和putenv这样的函数,则可更改当前环境和后面生成的子进程的环境,但不能影响父进程的环境)
  • 通常,一个进程允许将其环境传播给其子进程, 但有时也有这种情况,进程想要为子进程指定一个确定的环境。例如,在初始化一个新登录的shell时,login程序创建一个只定义少数几个变量的特殊环境,而在我们登录时, 可以通过shell启动文件,将其他变量加到环境中

在使用ISO C 原型之前, execle的参数是:

char *pathname, char *arg0, ..., char *argn, (char *)0, char *envp[]
  • 从中可见,最后一个参数是指向环境字符串的各字符指针构成的数组的指针。而在ISO C原型中,所有命令行参数,包括空指针,envp指针都用省略号(…)表示

四、参数表和环境表的限制

  • 概念:每个系统对参数表和环境表的总长度都有一个限制
  • 案例在表2-7中,这种限制是ARG_MAX。 在POSIX.1系统中,此值至少是4096字节。当使用shell的文件名扩充功能产生一个文件名表时, 可能会受到此值的限制。
  • 例如,命令:
grep getrlimit /usr/share/man/*/*
  • 在某些系统上可能产生下列形式的shell错误:Argument list too long
  • 备注:由于历史原因,System V中此限制是5120字节。早期BSD系统的此限制是20480字节
  • xargs命令:
    • 为了摆脱对参数表长度的限制。我们可以使用xargs命令,将长参数表断分为几部分。为了寻找在我们所用系统手册页中的getrlimit,我们可以使用:find /usr/share/man -type f -print | xargs grep getrlimit
    • 如果所用的系统手册是压缩过的,则可使用:find /usr/share/man -type f -print | xargs bzgrep getrlimit
    • 对于find命令,我们使用-type f,以限制输出列表质只包含普通文件。这样的原因是:
      • grep命令不能在目录中进行模式搜索,我们也想避免不必要的出错消息

五、新程序的特点

在执行exec后,进程ID没有改变。但新程序从调用进程继承了的下列属性:

  • 进程ID和父进程ID
  • 实际用户ID和实际组ID
  • 附属组ID
  • 进程组ID
  • 对话ID
  • 控制终端
  • 闹钟尚余留的时间
  • 当前工作目录
  • 根目录
  • 文件模式创建屏蔽字
  • 文件锁
  • 进程信号屏蔽
  • 未处理信号
  • 资源限制
  • nice值(遵循XSI的系统)
  • tms_utime、tms_stime、tms_cutime以及tms_ustime值

六、执行时关闭(close-on-exec)

  • 对打开文件的处理与每个描述符的执行时关闭(close-on-exec)标志值有关
  • 进程中每个打开描述符都有一个执行时关闭标志。若设置了此标志,则在执行exec时关闭该描述符,否则该描述符仍打开。除非特地用fcntl设置了该标志,否则系统的默认操作是在exec后仍保持这种描述符打开
  • POSIX.1明确要求在exec时关闭打开目录流(见opendir函数)。这通常是由opendir函数实现的,它调用fcntl函数为对应于打开目录流的描述符设置exec关闭标志

七、用户ID与实际组ID

  • 注意,在exec前后实际用户ID和实际组ID保持不变
  • 而有效ID是否改变则取决于所执行程序的文件的设置用户ID位和设置组ID位是否设置
  • 如果新程序的设置用户ID位已设置,则有效用户ID变成程序文件所有者的ID,否则有效用户ID不变。对组ID的处理方式与此相同

八、演示案例1

#include <sys/wait.h>
char *env_init[] ={"USER=unknown", "PATH=/tmp", NULL };

int main(void)
{
    pid_t pid;
    
    if ((pid = fork()) < 0) {
        perror("fork error");
    } 
    else if (pid == 0) { 
        if (execle("/home/sar/bin/echoall", "echoall", "myarg1","MY ARG2", (char *)0,             env_init) < 0)
        perror("execle error");
    }
    
    if (waitpid(pid, NULL, 0) < 0)
        perror("wait error");
    
    if ((pid = fork()) < 0) {
        perror("fork error");
    } 
    else if (pid == 0) { 
    if (execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0)
        perror("execlp error");
    }
    exit(0);
}
//图8-16

int main(int argc, char *argv[])
{
    int i;
    char **ptr;
    extern char **environ;

    1for (i = 0; i < argc; i++)
        printf("argv[%d]: %s\n", i, argv[i]);

    for (ptr = environ; *ptr != 0; ptr++)
        printf("%s\n", *ptr);
    exit(0);
}
//图8-17

运行结果

九、演示案例2

  • exec程序使用execl函数打开一个newcode进程
  • 父进程调用execl前cat读取text.txt文件的内容,然后子进程调用execl函数打开newcode进程向text.txt文件中写入一个字符串。之后子进程退出,父进程再读取一个text.txt文件的内容
//exec程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
int main()
{
    int fd,status;
    pid_t pid;
    if((fd=open("text.txt",O_RDWR|O_APPEND|O_CREAT,0644))==-1){
        perror("open");
        exit(1);
    }

    printf("before execl cat text.txt:\n");
    system("cat text.txt");
    fflush(stdout);//刷新标准输出

    if((pid=fork())==-1){
        perror("fork");
        exit(2);
    }
    else if(pid==0){
        char buf[10];
        sprintf(buf,"%d",fd);
        //参数2,3为传递给newcode程序的参数
        if(execl("./newcode","newcode",buf,(char*)0)==-1){
            perror("execl");
            exit(3);
        }
    }
    else{
        wait(&status);
        printf("after execl cat text.txt:\n");
        system("cat text.txt");
        //因为text.txt中有换行符,所以不需要fflush()
    }
    
    exit(0);
}
//newcode程序
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main(int argc,char *argv[])
{
    int fd=atoi(argv[1]);
    char buff[]="newcode write\n";
    int n=strlen(buff);
    if(write(fd,buff,strlen(buff))!=n){
        perror("write");
        exit(1);
    }
    close(fd);
    exit(0);
}

提示:

  • 如果想要使用execv函数,可以更改exec程序的下面这部分代码

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

董哥的黑板报

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值