1.描述
复制文件描述符。
dup()
dup()系统调用生成文件描述符oldfd的一个副本,从未使用的文件描述符当中选取最小的文件描述符作为新的文件描述符。
当成功返回后,旧的文件描述符和新的文件描述符可以互换使用,这两个描述符指向了同一个打开的文件描述符(见系统调用open()),他们共享同一个文件偏移量和文件状态标志。比如说,当使用lseek操作某一个文件描述符,改变了该文件的偏移量的时候,使用另外一个描述符看到的是相同的偏移量。
这两个文件描述符不会共享文件描述符标志(例如 close-on-exec标志)。如果旧的文件描述符有close-on-exec标志,则新的文件描述符没有。
dup2()
dup2()系统调用完成和dup相同的功能。但是dup2并不将未使用的文件描述符的最小值作为新的文件描述符,它使用参数newfd作为新的文件描述符。如果newfd在之前已经打开了,会先将打开的文件关闭,在完成文件描述符的复制操作。
关闭newfd之前打开的文件和复制newfd新的文件描述符这个操作是原子的。这个操作的原子性至关重要,因为实现close()和dup()两个函数相同的功能涉及到竞争问题。也就是说在关闭了newfd打开的文件之后,将newfd复制为新的文件描述符之前,newfd有可能已经被重新定义成其他文件的文件描述符了。这种情况有可能会发生,比如说主程序被一个信号打断,或者在完成了close操作之后,又有一个并行执行的线程分配了这个文件描述符。
注意一下要点:
- 如果oldfd不是一个有效的文件描述符,此次调用失败,newfd打开的文件也不关闭
- 如果oldfd是一个有效的文件描述符,并且newfd=oldfd,dup2什么也不做,直接返回newfd()
dup3()
dup3实现和dup2相同的功能,但有以下差别:
-
调用这可以设定close-on-exec标志,这个操作可以通过指定flags参数里面的O_CLOEXEC。参考open系统调用里面相同的标志参数,那里面解释了为什么这样做是有效的。
-
如果oldfd等于newfd,dup3调用失败,伴随这一个错误EINVAL。
2.头文件与声明
#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);
3.参数
int dup(int oldfd);
/*
oldfd 需要复制的旧文件描述符
*/
int dup2(int oldfd, int newfd);
/*
oldfd 被复制的文件描述符
newfd 被粘贴的文件描述符
*/
int dup3(int oldfd, int newfd, int flags);
/*
oldfd 被复制的文件描述符
newfd 被粘贴的文件描述符
flags 标志
*/
4.返回值
成功后,这些系统调用将返回新的文件描述符。 如果出错,则返回-1,并正确设置errno。
5.实例
将标准输出重定向到文件。
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
int main(int argc, char* argv[]) {
int fd;
fd = open("ps.out", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror("open ps.out error");
exit(1);
}
dup2(fd, STDOUT_FILENO);
/*
stdin等是FILE *类型,属于标准I/O,在<stdio.h>。
STDIN_FILENO等是文件描述符,是非负整数,一般定义为0, 1, 2,属于没有buffer的I/O,直接调用系统调用,在<unistd.h>。
*/
execlp("ps", "ps", "ax", NULL);
perror("exec error");
close(fd);
exit(1);
}
6.注意
当newfd超过了文件描述符规定的范围的时候,dup2()的返回值和funtl(… , F_DUPFD, …)的返回值不同。在某些操作系统上,dup2也会想F_DUPFD一样返回EINVAL。
如果newfd是一个已经打开的文件,执行dup2的时候,在关闭newfd原来打开的文件的时候,如果这个时候出错,那么这些错误就都会被忽略。考虑到这种情况,除非你的程序是单线程的,并且在信号处理程序中不会分配文件描述符,否则不要在调用dup2之前调用close,因为多线程情况下存在资源竞争(译者注:如果先调用close(newfd)在调用dup2(oldfd,newfd)有可能在两步调用操作之间有另外一个线程首先给newfd分配了文件,从而产生错误。)我们可以使用以下的代码:
/* 获取一个newfd的副本,这样可以在接下来的过程中检查close中出现的错误,EBADF错误表示
newfd没有打开 */
tmpfd = dup(newfd);
if (tmpfd == -1 && errno != EBADF) {
/* 处理复制过程中的错误 */
}
/* 原子操作,复制 */
if (dup2(oldfd, newfd) == -1) {
/* Handle dup2() error */
}
/* 可以通过检查tmpfd来判断在关闭newfd的时候是否产生了错误 */
if (tmpfd != -1) {
if (close(tmpfd) == -1) {
/* Handle errors from close */
}
}
本文介绍了Linux系统调用dup、dup2和dup3的功能,详细解析了它们的区别和使用场景。dup()创建文件描述符副本,而dup2()允许指定新文件描述符,并确保操作的原子性。dup3()则增加了设置close-on-exec标志的能力。此外,文章还提供了头文件、参数、返回值等信息,并给出了重定向标准输出的实例。

378

被折叠的 条评论
为什么被折叠?



