dup/dup2函数用来实现文件描述符之间的拷贝。对此,先来看看函数的声明:
#include <unistd.h> int dup(int oldfd); int dup2(int oldfd, int newfd);
dup函数
dup函数传入一个文件描述符,oldfd必须是已打开的文件描述符,否则dup函数调用失败。返回值为当前系统可用的最小文件描述符。测试程序如下:
#include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> int main() { int fd = open("./data.txt", O_RDWR | O_CREAT); int newfd = 0; /* test oldfd argv */ //close(fd); newfd = dup(fd); if (newfd < 0) { printf("dup error\n"); return -1; } char buff[] = "Hello world"; write(newfd, buff, sizeof(buff)); }
程序的运行结果为文件data.txt正确写入了"Hello world"。但是如果在dup之前先执行close(fd),那么dup函数调用是失败的。dup函数实现的功能类似fork函数之后父子进程共享文件描述符。可用下图(盗的图)来理解:
新旧描述符共享文件的偏移量(位置)、标志和锁,但是不共享close-on-exec标志。所以,都可以通过它们对文件进行操作。关于close-on-exec标志的解释:如果设置该文件描述符标志,那么通过fork函数产生的子进程中调用exec族函数,该文件描述符会被自动关闭。这样做可以用来保护文件描述符资源,防止其泄露。
dup2函数
dup2函数与dup函数起到相同的作用。不同的是dup2使用newfd来代替“当前最小可用的文件描述符”。那么与 dup类似,oldfd必须是已经打开的文件描述符,否则出错。关于newfd的说明:
- newfd可以仅仅是一个整数,而不是当前打开的文件描述符,这种情况下,dup2函数与dup函数类似,仅仅是使用newfd作为返回的文件描述符,而不是当前可用最小的文件描述符。
- newfd是当前已经打开的文件描述符,为了要对此文件描述符进行复用,dup2函数会先close该文件。相当于执行了close()-->dup()过程。不过需要注意的是,这个过程必须是原子操作。
note:
- 如果oldfd不是一个正确的正确的文件描述符,那么dup2调用失败,并且newfd不会被close。
- 如果oldfd是一个正确的文件描述符,并且newfd与oldfd具有相同的值,那么dup2函数什么都不做,只是返回newfd。
dup/dup2的使用
在单个进程中使用dup2函数可以起到重定向的作用。比如将标准输出重定向到指定的文件中。示例代码如下:
#include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> int main() { int fd = open("./data.txt", O_RDWR | O_CREAT); if (fd < 0) { printf("open error\n"); printf("error is %s\n", strerror(errno)); return -1; } int newfd = 0; newfd = dup2(fd, STDOUT_FILENO); if (newfd < 0) { printf("dup error\n"); return -1; } printf("Hello world\n"); }
运行结果为通过调用printf函数可以直接将Hello world打印到文件中。可见dup2函数可以实现了重定向功能。为了在重定向之后恢复之前的文件描述符,通常使用下面的技巧:先保存后恢复,即先将STDOUT_FILENO通过dup函数保存起来,使用完成之后再利用dup2函数恢复。
#include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> int main() { char buff[] = "Hello world\n"; int fd = open("./data.txt", O_RDWR | O_CREAT); if (fd < 0) { printf("open error\n"); printf("error is %s\n", strerror(errno)); return -1; } int newfd, savefd; // save savefd = dup(STDOUT_FILENO); if (savefd < 0) { printf("dup to savefd error\n"); return -1; } newfd = dup2(fd, STDOUT_FILENO); if (newfd < 0) { printf("dup error\n"); return -1; } write(STDOUT_FILENO, buff, sizeof(buff)); // recovery if (dup2(savefd, STDOUT_FILENO) < 0) { printf("dup2 savefd error\n"); return -1; } write(STDOUT_FILENO, buff, sizeof(buff)); }
需要注意的一个地方是,这里并没有使用标准库函数,而是使用了write系统调用。如果使用printf等函数,则最终结果为屏幕上输出两行"Hello world",而文件data.txt中为空,原因就是缓冲区作怪,由于最终的目标是屏幕,所以程序最后将缓冲区的内容都输出到屏幕。
上面的使用都是在一个进程内,现在这么考虑,如果涉及到两个进程,其中一个进程将标准输出重定向到某个文件描述符,第二个进程将此文件描述符重定向到自己的标准输出,那么这样的效果就是一个进程的输出在另一个进程的标准输出上显示出来。这就有点远程登录的意思了。试着来实现以下,为了问题的简单化,先在父子进程之间实现,其原因在于父子进程可以共享文件描述符。
#include <stdio.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> int main() { int fd = open("./data.txt", O_RDWR | O_CREAT); int pid; if (fd < 0) { printf("open error\n"); printf("error is %s\n", strerror(errno)); return -1; } int newfd, savefd; if ((savefd = dup(STDOUT_FILENO)) < 0) { printf("dup savefd error\n"); return -1; } newfd = dup2(fd, STDOUT_FILENO); if (newfd < 0) { printf("dup error\n"); return -1; } printf("Hello world\n"); if ((pid = fork()) == 0) { // child process // chang fd to stdout if (dup2(savefd, STDOUT_FILENO) < 0) { printf("dup2 savefd error\n"); return -1; } if (dup2(STDOUT_FILENO, fd) < 0) { printf("child dup2 fd error\n"); return -1; } printf("child hello world\n"); sleep(2); close(fd); return; } else if (pid > 0) { // don‘t show in child stdout printf("parent hello world\n"); close(fd); } }
看上去代码的实现是对的,但是从运行的结果看却没有起到实际的效果。关于多进程之间使用dup/dup2函数还有待研究。