dup,dup2

来源:互联网 发布:面试算法100题 编辑:程序博客网 时间:2024/05/21 17:11

dup() 和 dup2() 都可以用来复制一个现有的文件描述符,这两个函数声明如下:

#include <unistd.h>int dup(int oldfd);int dup2(int oldfd, int newfd);

dup() 函数返回的新文件描述符一定是当前可用文件描述符中的最小值。

dup2() 函数可以将第 1 个参数 oldfd 指定的文件描述符复制到第 2 个参数 newfd 中,newfd 是由用户指定的一个整数,并不一定是可用文件描述符中最小的那个。需要注意的是:
1. 如果 newfd 已经打开,那么会先将其关闭。
2. 如果 oldfd 是个无效的文件描述符,则调用失败,而且 newfd 在已经打开的情形下也不会被关闭。
3. 如果 oldfd 有效,且等同于 newfd ,那么函数什么都不做,直接返回 newfd 。

在函数成功返回后,老的和新的文件描述符可以互换使用,它们共享同一个文件表项(文件偏移,文件状态标志等)。例如,如果使用 lseek() 函数在一个描述符上修改了文件偏移,那么对于另外一个描述符来说这个偏移也是同样被修改了的。

每个文件描述符都有它自己的一套文件描述符标志(注意,不是文件表项;比如 close-on-exec 文件描述符标志 FD_CLOEXEC)。

另外,复制一个描述符的另一种方法是通过 fcntl() 函数,比如 dup2(oldfd, newfd) 等同于 close(newfd); fcntl (oldfd, F_DUPFD, newfd);

测试代码

 #include <sys/types.h>    #include <sys/stat.h>    #include <stdio.h>    #include <fcntl.h>    #include <unistd.h>    #include <assert.h>    #include <string.h>    print_line(int n)    {        char buf[32];        const char *ptr = "hello";        snprintf(buf, sizeof(buf), "Line #%d\n", n);        write(1, buf, strlen(buf));     }    int main()    {        int fd;         print_line(1);        print_line(2);        print_line(3);        /*重定向标准输出 stdout 到文件 dup.out 中*/        fd = open("dup.out", O_WRONLY|O_CREAT,0666);        assert(fd>=0);        printf("fd=%d\n", fd);        dup2(fd, 1);        print_line(4);        print_line(5);        print_line(6);        close(fd);        close(1);        return 0;    }


程序运行以及输出结果

beyes@linux-beyes:~/C/call> ./dup.exe 
Line #1
Line #2
Line #3
fd=3
beyes@linux-beyes:~/C/call> cat dup.out 
Line #4
Line #5
Line #6

说明
在 print_line() 函数中,snprintf() 把格式好的字符串复制到 buf 指向的空间中;然后用 write 把 buf 缓冲区中的数据写到标准输出。

27行:经过 dup2() 函数后,标准输出就成为了 fd 的一个副本,也就是说,操作标准输出描述符 1 也就相当于操作到了 fd 上( 新旧文件描述符指向同一个文件,共享所有的锁定、读写指针和各项权限或标志位)。效果如像上面所示,标准输出被的重定向到文件中去了。


dup 与 管道
dup 与 管道
#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <stdlib.h>#include <string.h>#include <sys/wait.h> int main(int argc, char **argv, char **environ){        pid_t   pid;        int     fd[2];          if (pipe(fd)) {                printf("create pipe failed!\n");                exit(1);        }         pid = fork();        switch(pid) {            case -1:                 printf("fork error!\n");                 exit(1);            case 0:                close(fd[0]);                close(1);                dup(fd[1]);                sleep(5);                system("ls -l");                exit(0);        default:                close(fd[1]);                char message[4096];                read(fd[0], message, 4096);                printf("hello world\n");                printf("%", message);        }         return 0;}



说明
上面程序的目的是子进程通过管道向父进程发送 "ls -l" 命令输出的内容。因为管道为半双工通信,而现在的情形是子进程写,父进程读,所以在子进程里需要先将 fd[0] 读端关闭,而在父进程里要将 fd[1] 写端关闭。因为 system() 函数执行的 "ls -l" 命令后的内容是向标准输出,而我们又要将标准输出的内容输出到管道中,所以这里要进行标准输出重定向。重定向的办法就是使用 dup() 函数为标准输出建立一个副本。因此,在子进程中,我们先将标准输出的描述符 1 关掉,此时最小可用的描述符自然为 1,所以 dup(fd[1]) 后,子进程的管道写入端 fd[1] 成为了标准输出的副本。这样,写标准输出,也就是写管道。当然,也可以使用 dup2() 来完成,如 dup2(fd[1], 1);

程序执行输出如下:
beyes@linux-beyes:~/C/pipe> ./dup2.exe 
hello world
总计 84
-rw-r--r-- 1 beyes users  1446 07-23 17:38 dual_pipe.c
-rwxr-xr-x 1 beyes users 12407 07-23 17:38 dual_pipe.exe
-rw-r--r-- 1 beyes users   736 07-23 22:03 dup2.c
-rwxr-xr-x 1 beyes users 11932 07-23 22:03 dup2.exe
-rw-r--r-- 1 beyes users   769 07-22 01:40 pipe.c
-rwxr-xr-x 1 beyes users 12266 07-22 01:41 pipe.exe
-rw-r--r-- 1 beyes users   153 07-22 01:17 strlen.c
-rwxr-xr-x 1 beyes users 11373 07-22 01:17 strlen.exe

在子进程中睡眠 5 秒和在父进程中故意输出 hello world ,这是为了测试管道读写的阻塞性质。由于建立了管道,在子进程里如果没有向标准输出内容(已经调用 dup() 后)或者子进程没有退出,那么父进程会被阻塞,直到能从管道里读到些什么东西为止。如果子进程并没有向管道里写东西,而是直接退出了,那么父进程检测到子进程已经退出,则管道不再阻塞,直接从自定义的缓冲区里读些东西(遇到‘\0‘为止)也就跟着退出了。


如果将上面程序的子进程的
close(1);
dup(fd[1]);
改成:
dup2(1, fd[1]);
会出现什么情况呢(注意,不是 dup2(fd[1], 1)? 在这种方式下,我们错误的进行描述符复制,也就是使得标准输出描述符 1 变成了管道描述符 fd[1] 的一个副本。在这种情况下,父进程会先打印出 hello world 字串,然后调用 read() 试图去读管道(此时子进程睡眠),但实际上它读取的是子进程的标准输出,然而睡眠中的子进程并不会有什么输出,所以父进程在无堵塞的情况下推出了!注意,默认情况下,读标准输出是不会阻塞的,而读管道则会堵塞。当父进程退出,子进程则变成了孤儿进程,但是它马上会找到新的父亲,即 pid 为 1 的 init 进程。这个过程可以在程序的父进程程序段通过 getpid() 和在子进程程序段使用 getppid() 的打印看到。在被 init 领养后,它接着执行 system() 函数,然后 exit() 退出。这里有个小问题,就是在父进程退出时,出现终端提示符(如 [beyes@localhost syscall]$),但接着被子进程的 "ls -l" 输出给冲洗掉,此时需要再按一下回车换行才能刷出新的中断提示符。