Unix/Linux编程:复制文件描述符

引入

Bourne shell 的 I/O 重定向语法 2>&1,意在通知 shell 把标准错误(文件描述符 2)重定向到标准输出(文件描述符 1)。因此,下列命令将把(因为 shell 按从左至右的顺序处理 I/O 重定向语句)标准输出和标准错误写入 result.log 文件

$ ./myscrpt > results.logs 2>&1

shell 通过复制文件描述符 2实现了标准错误的重定向操作,因此文件描述符 2 与文件描
述符 1 指向同一个打开文件句柄(类似于进程 A 的描述符 1 和 20 指向同一打开文件句柄的情况)。可以通过调用 dup()和 dup2()来实现此功能。

注意,要满足shell的这一要求,仅仅简单的打开results.log文件两次是远远不够的(第一次在描述符 1 上打开,第二次在描述符 2 上打开)。首先两个文件描述符不能共享相同的文件偏移量指针,因此有可能导致相互覆盖彼此的输出。再者打开的文件不一定就是磁盘文件,比如如下命令中,标准错误就将和标准输出一起送达同一管道:

$ ./myscrpt 2>&1 | less

理论

dup()调用复制一个打开的文件描述符oldfd,并返回一个新的文件描述符,二者都指向同一打开的文件句柄。系统会保证新描述符一定是编号最低的未用文件描述符

NAME
       dup, dup2, dup3 - 复制一个现有的文件描述符

SYNOPSIS
       #include <unistd.h>

      
       int dup(int oldfd);
       int dup2(int oldfd, int newfd);

       #define _GNU_SOURCE             /* See feature_test_macros(7) */
       #include <fcntl.h>              /* Obtain O_* constant definitions */
       #include <unistd.h>

       int dup3(int oldfd, int newfd, int flags);

DESCRIPTION
		dup()系统调用使用新描述符的编号最小的未使用文件描述符创建文件描述符
		     oldfd的副本。 

             成功返回后,旧文件描述符和新文件描述符可以互换使用。它们引用相同
             的打开文件描述(请参见open(2)),因此共享文件偏移量和文件状态标志。 
             例如,如果在文件描述符之一上使用lseek(2)修改了文件偏移,则另一个
             文件的偏移也将更改。

       		 这两个文件描述符不共享文件描述符标志(close-on-exec标志).重复描述符
       		 的执行关闭标志(FD_CLOEXEC; 请参阅fcntl(2))关闭。 
      
       dup2()
       		dup2()系统调用执行与dup()相同的任务,但不是使用编号最小的未使用文件
       		描述符,而是使用newfd中指定的文件描述符号。如果文件描述符newfd先前已
       		打开,则在重用之前将其静默关闭。 

            关闭和重用文件描述符newfd的步骤是原子执行的。 这很重要,因为尝试使用
            close(2)dup()实现等效功能会受到竞争条件的影响,因此newfd可能会在
            两个步骤之间重用。 之所以会发生这种重用,是因为主程序被分配文件描述符
            的信号处理程序中断,或者因为并行线程分配了文件描述符。 

			请注意以下几点:

        		* 如果oldfd不是有效的文件描述符,则调用将失败,并且不会关闭newfd。

        		* 如果oldfd是有效的文件描述符,并且newfd与oldfd具有相同的值,则
        		  dup2()不执行任何操作,并返回newfd。 

       dup3()
			dup3()系统调用完成的工作与 dup2()相同,只是新增了一个附加参数 flag,
			这是一个可以修改系统调用行为的位掩码。具体不同点:

        		* 调用者可以通过在标志中指定O_CLOEXEC来强制为新文件描述符设置
        		  close-on-exec标志。 有关为什么可能有用的原因,请参见open(2)中
        		  相同标志的描述。

                * 如果oldfd等于newfd,则dup3()失败,错误为EINVAL。 

RETURN VALUE
		成功后,这些系统调用将返回新的文件描述符。 
		如果出错,则返回-1,并正确设置errno。 

VERSIONS
		dup3()已在2.6.27版本中添加到Linux;2.9版开始提供glibc支持。 

例子1:dup

  • dup()调用复制一个打开的文件描述符 oldfd,并返回一个新描述符,二者都指向同一打开的文件句柄。系统会保证新描述符一定是编号值最低的未用文件描述符

在这里插入图片描述
假设发起如下调用

newfd = dup(1);

在假定在正常情况下,shell 已经代表程序打开了文件描述符 0、1 和 2,且没有其他描述符在用,dup()调用会创建文件描述符 1 的副本,返回的文件描述符编号值为 3

如果希望返回文件描述符 2,可以使用如下技术

close(2);
newfd = dup(1);

只有当描述符 0 已经打开时,这段代码方可工作。如果想进一步简化上述代码,同时总
是能获得所期望的文件描述符,可以调用 dup2()。

  • dup2()系统调用会为 oldfd 参数所指定的文件描述符创建副本,其编号由 newfd 参数指定。
  • 如果由 newfd 参数所指定编号的文件描述符之前已经打开,那么 dup2()会首先将其关闭。
  • dup2()调用会默然忽略 newfd 关闭期间出现的任何错误。故此,编码时更为安全的做法是:在调用dup2()之前,若 newfd 已经打开,则应显式调用 close()将其关闭。

前述调用 close()和 dup()的代码可以简化为:

dup2(1, 2);
  • 若调用 dup2()成功,则将返回副本的文件描述符编号(即 newfd 参数指定的值)
  • 如果 oldfd 并非有效的文件描述符,那么 dup2()调用将失败并返回错误 EBADF,且不关闭 newfd。
  • 如果 oldfd 是有效的文件描述符,且与 newfd 值相等,那么 dup2()将什么也不做,不关闭 newfd,并将其作为调用结果返回

例子2:
假设初始时:描述符1对应文件A,描述符4对应一个文件B,A和B的引用次数都是1
在这里插入图片描述
当调用dup2(4,1),情况如下: 两个文件描述符都指向B,文件A已经被关闭了,因为引用次数变成了0,所以它的文件表和v-node表也删除了。文件B的引用次数增加A。从此对A的操作都被重定向到了B
在这里插入图片描述

fcntl与dunp

fcntl()的F_DUPFD操作时复制文件描述符的另一接口,更具灵活性:

newfd = fcntl(oldfd, F_DUPFD, startfd);
  • 该调用为oldfd创建一个副本,而且将使用大于等于startfd的最小未用值作为描述符的编号。该调用还能保证新描述符newfd编号落在特定区间范围内。

  • 文件描述符的正、副本之间共享同一打开文件句柄所含的文件偏移量和状
    态标志。然而,新文件描述符有其自己的一套文件描述符标志,且其 close-on-exec 标志
    (FD_CLOEXEC)总是处于关闭状态(可以通过dup3直接控制新描述符的 close-on-exec标志)。

对于dup

dup()

相当于:

fcntl(fd, F_DUPFD, 0);

对于dup2

dup2(fd, fd2);

相当于

close(fd2);
fcntl(fd, F_DUPFD, fd2);

dup3

dup3()系统调用完成的工作与 dup2()相同,只是新增了一个附加参数 flag,这是一个可以修改系统调用行为的位掩码。

  • 目前,dup3()只支持一个标志 O_CLOEXEC,这将促使内核为新文件描述符设置 close-on-exec标志(FD_CLOEXEC)。
  • 设计该标志的缘由,和对 open()调用中 O_CLOEXEC 标志类似:
    • 使用 O_CLOEXEC 标志(打开文件),可以免去程序执行 fcntl()的 F_GETFD和 F_SETFD 操作来设置 close-on-exec 标志的额外工作
    • 在多线程程序中执行 fcntl() 的 F_GETFD和 F_SETFD 操作有可能导致竞争状态,而使用 O_CLOEXEC 标志则能够避免这一点。
    • 可能引发竞争的场景是:线程某甲打开一文件描述符,尝试为该描述符标记 close-on-exec 标志,于此同时,线程某乙执行 fork()调用,然后调用 exec()执行任意一个程序。(假设在某甲打开文件描述符和调用fcntl()设置 close-on-exec 标志之间,某乙成功地执行了 fork()和 exec()操作。)此类竞争可能会在无意间将打开的文件描述符泄露给不安全的程序。

dup3()系统调用始见于 Linux 2.6.27,为 Linux 所特有。

Linux 从 2.6.24 开始支持 fcntl()用于复制文件描述符的附加命令:F_DUPFD_CLOEXEC。该标志不仅实现了与 F_DUPFD 相同的功能,还为新文件描述符设置 close-on-exec 标志。同样,此命令之所以得以一显身手,其原因也类似于 open()调用中的 O_CLOEXEC 标志。SUSv3 并未论及 F_DUPFD_CLOEXEC 标志,但 SUSv4 对其作了规范。

实践

1、如果用dup2将标准输入重定向的文件描述符5

dup2(5, 0);
或者:
dup2(5, STDIN_FILENO)

2、输出

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <zconf.h>
#include <wait.h>


int main(int argc, char **argv)
{
    int fd1, fd2;
    char c;

    // b.txt ---> foobar
    fd1 = open("b.txt", O_RDONLY, 0);
    fd2 = open("b.txt", O_RDONLY, 0);

    read(fd2, &c, 1);
    dup2(fd2, fd1); //复制描述符表项fd2到fd1,原来newfd会被覆盖。
    read(fd1, &c, 1);
    printf("%c", c);  // o

    exit(0);
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unix/Linux系统编程是指使用C语言编写程序,能够调用操作系统提供的系统调用和库函数来完成系统级任务的程序设计过程。Unix/Linux系统编程的目的是编写高效、可靠、安全、移植性好的应用程序或系统程序。 Unix/Linux系统编程的核心代码包括使用系统调用,文件操作(读写文件、目录操作等),进程控制(fork、exec等),信号处理,网络编程等。 在Unix/Linux中,系统调用是与内核进行通讯的标准方式。程序中使用系统调用来请求内核完成某个任务。例如,open()系统调用用于打开一个文件,并返回文件描述符。read()和write()系统调用用于读写文件。 文件操作是Unix/Linux系统编程中的一个重要部分。文件操作包括打开文件、读写文件、删除文件、重命名文件等操作。另外还有目录操作,如创建目录、删除目录、遍历目录等。 进程控制是Unix/Linux系统编程中最为复杂的部分之一。进程控制包括创建新进程、执行新进程、等待进程结束、发送信号给进程等等。其中最常见的系统调用是fork()和exec()。fork()用于创建新进程,而在创建新进程之后,exec()则用于在新进程中执行新的程序。 信号处理是Unix/Linux系统编程中的一个重要概念。信号是由系统发出的一个异步事件,可以从进程内部或外部发出。进程可以对信号进行相应操作。常见的信号包括SIGINT(Ctrl+C中断信号)、SIGTERM(终止进程信号)和SIGKILL(强制终止进程信号)。 网络编程Unix/Linux系统编程中的另一个重要部分。Unix/Linux提供了许多网络编程API,例如socket()、bind()、listen()和accept()等。使用这些API可以编写服务器端和客户端程序,进行网络通信。 总之,Unix/Linux系统编程涉及到许多重要的概念和操作,涉及到操作系统底层的各种操作。因此,需要开发人员有扎实的C编程能力、熟悉Unix/Linux系统调用和库函数、了解进程控制和信号处理的概念、熟悉网络编程API以及充分了解操作系统内部的机制。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值