Unix/Linux编程:文件描述符与exec()

默认情况下,由exec()的调用程序所打开的文件描述符在exec()的执行过程中会保持打开状态,而且在新进程中依然有效。这通常很实用,因为调用程序可能会以特定的描述符来打开文件,而在新程序中这些文件将自动有效,无需再去了解文件名或是把它们重新打开。

shell 利用这一特性为其所执行的程序处理 I/O 重定向。例如,假设键入如下的 shell 命令:

$ ls /tmp > dir.txt
$ ls
dir.txt

shell运行该命令时,执行了以下步骤:

  • 调用fork()创建子进程,子进程也会运行shell的一份拷贝(包括命令行)
  • 子shell以描述符1(标准输出)打开文件dir.txt用于输出。可能会采取以下任一方式
    • 子 shell 关闭描述符 1(STDOUT_FILENO)后,随即打开文件 dir.txt。因为 open()在为描述符取值时总是取最小值,而标准输入(描述符 0)又仍处于打开状态,所以会以描述符 1 来打开文件
    • shell打开文件dir.txt,获取一个新的文件描述符。之后,如果该文件描述符不是标准输
      出,那么 shell 会使用 dup2()强制将标准输出复制为新描述符的副本,并将此时已然无用的新描述符关闭。(这种方法较之前者更为安全,因为它并不依赖于打开文件描述符的低值取数原则。)源代码顺序大体如下
fd = open("dir.txt", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | SZ-IRGRP | S_IROTH | S_IWOTH);
if(fd != STDOUT_FILENO){
	dup2(fd, STDOUT_FILENO);
	close(fd);
}
  • 子进程执行ls。ls将其结果输出到标准输出,亦即文件 dir.txt 中

执行时关闭(close-on-exec)标志(FD_CLOEXEC)

在执行exec()之前,程序有时需要确保关闭某些特定的文件描述符。尤其是在特权进程中调用 exec()来启动一个未知程序时(并非自己编写),亦或是启动程序并不需要使用这些已打开的文件描述符时,从安全编程的角度出发,应当在加载新程序之前确保关闭那些不必要的文件描述符。对所有此类描述符施以 close()调用就可达到这一目的,然而这一做法存在如下局限性

  • 某些描述符可能是由库函数打开的。但库函数无法使主程序在执行exec()之前关闭相应的文件描述符。作为基本原则,库函数应总是为其打开的文件设置执行时关闭(close-on-exec)标志
  • 如果 exec()因某种原因而调用失败,可能还需要使描述符保持打开状态。如果这些描述符已然关闭,将它们重新打开并指向相同文件的难度很大,基本上不太可能

为此,内核为每个文件描述符提供了执行时关闭标志。如果设置了这一标志,那么在成功执行exec()时,会自动关闭该文件描述符。如果exec()失败,文件描述符会保持打开状态。

可以通过系统调用fcntl()来访问执行时关闭标志。fcntl()的F_GETFD操作可以获取文件描述符标志的一份拷贝。

int flags;

flags = fcntl(fd, F_GETFD);
if(flags == -1){
	perror("fcntl");
	exit(1);
}

获取这些标志之后,可以对FD_CLOEXEC 位进行修改,再调用 fcntl()的 F_SETFD 操作令其生效:

flags != FD_CLOEXEC;
if(fcntl(fd, F_SETFD, flags) == -1){
	perror("fcntl");
	exit(1);
}

包括 Linux 在内的许多 UNIX 实现,还允许以另外两种非标准的 ioctl()调用来修改执行时关闭标志:以 ioctl(fd, FIOCLEX)为 fd 设置此标志,以 ioctl(fd, FIONCLEX)来清除此标志

当使用 dup()、dup2()或 fcntl()为一文件描述符创建副本时,总是会清除副本描述符的执行时关闭标志.

看个例子:

//为一文件描述符设置执行时关闭标志
#include <cstring>
#include <stdio.h>
#include <cstdlib>
#include <zconf.h>
#include <fcntl.h>

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

    if (argc > 1) {
        flags = fcntl(STDOUT_FILENO, F_GETFD);              /* Fetch flags */
        if (flags == -1){
            perror("fcntl - F_GETFD");
            exit(EXIT_FAILURE);
        }


        flags |= FD_CLOEXEC;                    /* Turn on FD_CLOEXEC */
        if (fcntl(STDOUT_FILENO, F_SETFD, flags) == -1)     /* Update flags */
        {
            perror("fcntl - F_SETFD");

            exit(EXIT_FAILURE);
        }
    }

    execlp("ls", "ls", "-l", argv[0], (char *) NULL);
    perror("execlp");
    exit(EXIT_FAILURE);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值