Wednesday, January 26, 2011

[精彩] 用dup2()能关闭标准输出吗?

[精彩] 用dup2()能关闭标准输出吗?


http://www.chinaunix.net 作者:cu_liang  发表于:2008-05-21 13:55:42
发表评论】 【查看原文】 【Linux讨论区】【关闭

在APUE第三章中讲到
       dup2( filedes, filedes2 );  
is equivalent to
          close( filedes2 );
          fcntl( filedes, F_DUPFP, filesdes2 );

那为什么在执行下面这段代码时,还能打印出东西呢?
#include <unistd.h>

#include <stdio.h>

int main()

{

 dup2(0, STDOUT_FILENO ); 

 printf("how!!\n" );

}




而执行下面这段代码时就没有打印出 how!!\n 了。
#include <unistd.h>

#include <stdio.h>

int main()

{

 close( STDOUT_FILENO );

 printf("how!!\n" );

}




希望高手能不吝赐教,谢了。

[ 本帖最后由 cu_liang 于 2008-4-11 11:39 编辑 ]



 swordfish.cn 回复于:2008-04-11 12:34:54

从strace的情况来看,dup2并没有实际想像那样操作。
在lighttpd里面是这样关闭stdin的。
 575     /* close stdin and stdout, as they are not needed */

 576     /* move stdin to /dev/null */

 577     if (-1 != (fd = open("/dev/null", O_RDONLY))) {

 578         close(STDIN_FILENO);

 579         dup2(fd, STDIN_FILENO);

 580         close(fd);

 581     }

 582

 583     /* move stdout to /dev/null */

 584     if (-1 != (fd = open("/dev/null", O_WRONLY))) {

 585         close(STDOUT_FILENO);

 586         dup2(fd, STDOUT_FILENO);

 587         close(fd);

 588     }


[ 本帖最后由 swordfish.cn 于 2008-4-11 12:39 编辑 ]


 zhuhefang2006 回复于:2008-04-11 12:42:47

楼上的解释我没看懂,关注ing


 swordfish.cn 回复于:2008-04-11 12:43:06

#include <string.h>

#include <fcntl.h>



int main () {

    int fd;

    fd = open("/dev/null", O_RDONLY);

    close(STDOUT_FILENO);

    dup2(fd, STDOUT_FILENO);

    close(fd);

    printf("%d\n", STDOUT_FILENO);

    return 0;

}


这样strace之后即有

引用:
$ strace ./t
...
write(1, "1\n", 2)                      = -1 EBADF (Bad file descriptor)




 MMMIX 回复于:2008-04-12 00:06:36

判断一下 dup2 的返回值。


 cu_liang 回复于:2008-04-13 11:35:30

dup2的返回值是1,说明dup2函数应该是执行成功的了,但就是不知道为什么没有关闭标准输出了。


用strace命令好象也看不出什么东西来,
有谁知道dup2()在底层是怎么实现的吗?:em11: :em12:

[ 本帖最后由 cu_liang 于 2008-4-13 11:44 编辑 ]


 swordfish.cn 回复于:2008-04-13 13:13:30

dup2是怎么实现的,看代码不就行了吗。
代码都是现成的。


 MMMIX 回复于:2008-04-13 19:31:49

引用:原帖由 cu_liang 于 2008-4-13 11:35 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6551920&ptid=991081]
dup2的返回值是1,说明dup2函数应该是执行成功的了,


dup2(2) 成功时返回 0。另外,dup2 是系统调用,而系统调用一般都是成功时返回 0,失败时返回 -1。


 swordfish.cn 回复于:2008-04-13 20:47:48

引用:原帖由 MMMIX 于 2008-4-13 19:31 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6552212&ptid=991081]

dup2(2) 成功时返回 0。另外,dup2 是系统调用,而系统调用一般都是成功时返回 0,失败时返回 -1。 



M版,这个说法不对吧。在 manpage 中如是说:
引用:
RETURN VALUE
       dup() and dup2() return the new descriptor, or -1 if an error  occurred
       (in which case, errno is set appropriately).



所以返回 1 的话,那执行应该是成功了吧。
但是事实上的效果却又有不同,所以我只好把它看作一个特例了。


 scutan 回复于:2008-04-13 22:39:16

引用:原帖由 cu_liang 于 2008-4-11 10:52 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6549545&ptid=991081]
在APUE第三章中讲到
       dup2( filedes, filedes2 );  
is equivalent to
          close( filedes2 );
          fcntl( filedes, F_DUPFP, filesdes2 );

那为什么在执行下面这段代码时,还能打印出 ... 



其实重点不是在dup2(),对于下面的代码:

#include<stdio.h>

#include<unistd.h>

#include<fcntl.h>

#include<sys/types.h>

#include<stdlib.h>



int main()

{

        close(STDOUT_FILENO);

        write(0, "hello\n", 6);

        return 0;

}



照样可以输出hello来。

而dup2()完成的功能是什么呢?它是让文件描述符的指针指向文件表。
当dup2(0, STDOUT_FILENO);时 首先关闭STDOUT_FILENO,也就是将标准输出关闭掉。此时再让 STDOUT_FILENO描述符的指针指向STDIN_FILENO所指向的那个文件表。所以说对于楼主所列的第一个代码仍然可以输出也就是这样的原 因。

[CODE]
#include <unistd.h>
#include <stdio.h>
int main()
{
        dup2(0, STDOUT_FILENO );        
        printf("how!!\n" );
}
[/CODE]
此时STDIN_FILENO,STDOUT_FILENO两个都指向的是标准输入,也即是0的那个文件表,而这个又与终端相连,所以能够输出数据来。

不信你可以这样来试试,
gcc test.c -o test

./test 1>test.out
此时你可以看到那个how!!仍然是打印到终端而没有写入到test.out中,证明不是从标准输出得到的数据。

而如果
./test 0>test.out
你则可以看到那个how!!输出到了test.out文件中。证明是从标准输入这个描述符得到的数据。

不知道我说清楚没有?有什么大家再一起讨论。


 scutan 回复于:2008-04-13 22:42:34

另外,楼主可以看看APUE2中3.12节的那个图,此时相当于是fd1与fd0都指向了fd0之前指向的那个file table。所以说你此时能够打印出东西来。


 cu_liang 回复于:2008-04-13 23:30:05

果然如此!将程序改为如下就不能输出“how!!”了
#include <stdio.h>

#include <unistd.h>

#include <fcntl.h>

int main()

{

    int fd;

    fd = open( "./1.txt", O_CREAT|O_RDWR );

    printf( "the fd is :%d\n", fd );

    dup( fd, STDOUT_FILENO );

    printf( "how!!\n" );

}
不过,奇怪的是为什么将字符写入标准输入后,这些字符会在屏幕上打印出来,难道就是因为标准输入跟终端相连了?


 scutan 回复于:2008-04-13 23:42:11

引用:原帖由 cu_liang 于 2008-4-13 23:30 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6552569&ptid=991081]
果然如此!将程序改为如下就不能输出“how!!”了#include 
#include 
#include 
int main()
{
    int fd;
    fd = open( "./1.txt", O_CREAT|O_RDWR );
    printf( "the fd is :%d\n", fd );
     ... 



我想应该就是这个原因,至于具体的实现我也不是太清楚,抱歉。


 cu_liang 回复于:2008-04-13 23:52:36

还是非常感谢你。
我想知道dup2是具体是如何实现也就是为了要搞清楚为什么会输出“how”
现在既然搞清楚了,就不需要知道dup2是如何实现的了,至少是暂时不需要了,呵呵。


 swordfish.cn 回复于:2008-04-14 11:55:49

引用:
此时STDIN_FILENO,STDOUT_FILENO两个都指向的是标准输入,也即是0的那个文件表,而这个又与终端相连,所以能够输出数据来。



Oh,原来是这样。没想到“标准输入”并不仅仅是“输入”。


 MMMIX 回复于:2008-04-14 19:30:19

引用:原帖由 swordfish.cn 于 2008-4-13 20:47 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6552357&ptid=991081]


M版,这个说法不对吧。在 manpage 中如是说:


所以返回 1 的话,那执行应该是成功了吧。
但是事实上的效果却又有不同,所以我只好把它看作一个特例了。 


嗯,我当时看的是 Mac OS X 自带的文档(BSD 的),估计和 Linux 的又差异,而且我当时试验楼主的代码,调用 dup2 的返回值也是 -1。


 MMMIX 回复于:2008-04-14 19:32:15

引用:原帖由 cu_liang 于 2008-4-13 23:30 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6552569&ptid=991081]
不过,奇怪的是为什么将字符写入标准输入后,这些字符会在屏幕上打印出来,难道就是因为标准输入跟终端相连了?


估计 Shell 打开终端设备文件的时候用的是读写方式,所以既能读,也能写。


 UCfree 回复于:2008-04-15 10:51:12

标准输入默认会在终端回显,关闭回显就不会看到 how!! 了


 bierdaci 回复于:2008-04-17 00:17:58

引用:原帖由 scutan 于 2008-4-13 22:39 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6552510&ptid=991081]


其实重点不是在dup2(),对于下面的代码:


#include
#include
#include
#include
#include

int main()
{
        close(STDOUT_FILENO);
        write(0, "hello\n", 6);
        return 0 ... 



你这个实验让我对shell重定向原理产生了疑惑,./test 1>out中的"1"和write(1, .....)中的"1"难道不一样?


 scutan 回复于:2008-04-17 10:09:28

引用:原帖由 bierdaci 于 2008-4-17 00:17 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6556623&ptid=991081]


你这个实验让我对shell重定向原理产生了疑惑,./test 1>out中的"1"和write(1, .....)中的"1"难道不一样? 



是一样的啊.


 JohnBull 回复于:2008-04-17 11:33:34

引用:原帖由 swordfish.cn 于 2008-4-11 12:34 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6549670&ptid=991081]
从strace的情况来看,dup2并没有实际想像那样操作。
在lighttpd里面是这样关闭stdin的。

 575     /* close stdin and stdout, as they are not needed */
 576     /* move stdin to /dev/null */
 577  ... 



close(xxx_FILENO)没有必要,这种写法画蛇添足,不应提倡。
在多线程环境下会有racing。


 bierdaci 回复于:2008-04-17 11:37:44

引用:原帖由 scutan 于 2008-4-17 10:09 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6557136&ptid=991081]


是一样的啊. 



如果一样的话,为什么./test 1>out没有输出到out里呢?
我原来的理解./test 1>out shell大概会在程序中有类似如下的动作
fd = open("out", ...);
dup2(fd, 1);

如果是这样的话write(1, ...)怎么会不写到out中呢?


 scutan 回复于:2008-04-17 13:54:26

引用:原帖由 bierdaci 于 2008-4-17 11:37 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6557286&ptid=991081]


如果一样的话,为什么./test 1>out没有输出到out里呢?
我原来的理解./test 1>out shell大概会在程序中有类似如下的动作
fd = open("out", ...);
dup2(fd, 1);

如果是这样的话write(1, ...)怎么会不写 ... 



因为楼主这个是特殊的情况, 此时的STDOUT_FILENO也即是1已经被关闭了.


 bierdaci 回复于:2008-04-17 15:09:30

引用:原帖由 scutan 于 2008-4-17 13:54 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6557530&ptid=991081]


因为楼主这个是特殊的情况, 此时的STDOUT_FILENO也即是1已经被关闭了. 



大概你没有看明白我的问题了

算了,还是不讨论吧


 swordfish.cn 回复于:2008-04-17 15:51:11

引用:原帖由 JohnBull 于 2008-4-17 11:33 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6557280&ptid=991081]


close(xxx_FILENO)没有必要,这种写法画蛇添足,不应提倡。
在多线程环境下会有racing。 




哈哈,这个是 lighttpd 的代码,没有多线程。
不过多线程情况下确实会有这个问题。


 7islands 回复于:2008-04-17 21:10:05

你这个例子一点问题都没有啊! 
dup2 之后原来的旧的fd和新fd都指向同一个文件了。
After a successful return from dup() or dup2(), the old and new file descriptors may be used interchangeably. They refer to the same open file description (see open(2)) and thus share file offset and file status flags; for example, if the file offset is modified by using lseek(2) on one of the descriptors, the offset is also changed for the other.

仔细看看man dup2


 parco 回复于:2008-04-18 15:57:01

引用:原帖由 scutan 于 2008-4-13 22:39 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6552510&ptid=991081]


其实重点不是在dup2(),对于下面的代码:


#include
#include
#include
#include
#include

int main()
{
        close(STDOUT_FILENO);
        write(0, "hello\n", 6);
        return 0 ... 



厉害啊。看到这样的回复,我恨不得拜师:mrgreen:


 zeroooo 回复于:2008-04-21 16:26:36

应该是标准输入和终端是相连的。

这几天我也正在看APUE,看下APUE的程序1-2,从标准输入复制数据到标准输出。
运行程序后,输入asdfgh,再回车
就会再显示一行   asdfgh.

也就是说第一行asdfgh是输入的,由于标准输入和终端相连,所以会回显在终端上,第二行的asdfgh才是从程序从标准输入复制到终端上的数据.


 maxxfire 回复于:2008-04-22 14:24:15

打开/dev/fd ,查看文件0 1 2,全部指向了同一个终端 symbolic link to `/dev/pts/n'


 源方 回复于:2008-04-28 11:02:17

可是为什么标准输入与终端项链就把结果输出都终端。它毕竟是输入,是要接收终端的数据的。
难道还能像终端写???不是只读方式打开的?

[ 本帖最后由 源方 于 2008-4-28 11:18 编辑 ]


 源方 回复于:2008-04-28 11:16:38

scutan    (冬日夜雨) 
 帮忙解释一下吧


 scutan 回复于:2008-04-28 11:42:38

引用:原帖由 源方 于 2008-4-28 11:02 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6571282&ptid=991081]
可是为什么标准输入与终端项链就把结果输出都终端。它毕竟是输入,是要接收终端的数据的。
难道还能像终端写???不是只读方式打开的? 



我想描述符0是与终端相连, 它是读/写的方式打开的, 所以说它既能够接收到输入(这是它本来就该做的), 同时也能够作为输出.
我想可能有两点比较重要:
1. 它与终端相连.
2. 它是读/写.


 源方 回复于:2008-04-28 13:51:10

引用:原帖由 scutan 于 2008-4-28 11:42 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6571324&ptid=991081]


我想描述符0是与终端相连, 它是读/写的方式打开的, 所以说它既能够接收到输入(这是它本来就该做的), 同时也能够作为输出.
我想可能有两点比较重要:
1. 它与终端相连.
2. 它是读/写. 


多谢!只能这么理解了。


 r2r4 回复于:2008-04-28 23:56:28

bash
[table=95%][tr][td][font=FixedSys]$uname -a
Linux fc8 2.6.23.1-42.fc8 #1 SMP Tue Oct 30 13:55:12 EDT 2007 i686 i686 i386 GNU/Linux
$file /dev/fd
/dev/fd: symbolic link to `/proc/self/fd'
$ls /proc/self/fd/
0  1  2  3
$ls /proc/self/fd/*
ls: cannot access /proc/self/fd/255: No such file or directory
ls: cannot access /proc/self/fd/3: No such file or directory
/proc/self/fd/0  /proc/self/fd/1  /proc/self/fd/2
$echo $$
32581
$ls /proc/32581/fd/
0  1  2  255
$ls /proc/32581/fd/*
ls: cannot access /proc/32581/fd/3: No such file or directory
/proc/32581/fd/0  /proc/32581/fd/1  /proc/32581/fd/2  /proc/32581/fd/255
$echo $BASH_VERSION
3.2.33(1)-release
[/font][/td][/tr][/table]
第一次找不到3和255的情况,是ls的么?他们是做什么的呢?
后来的有pid的, 这里的3,是否类似open之后立即unlink的情况?
如果是的,bash的这个3是做什么用的呢?

[ 本帖最后由 r2r4 于 2008-4-29 00:24 编辑 ]


 njutbkk 回复于:2008-05-06 11:21:18

应该可以这样用的,如:
dup2(socket_fd, STDOUT_FILENO );


 ratc 回复于:2008-05-07 22:45:24

正如scutan所说的,这个问题跟dup2()确实没有关系
你的代码:
#include <unistd.h>

#include <stdio.h>

int main()

{

        dup2(0, STDOUT_FILENO );       

        printf("how!!\n" );

}
使用的dup2仅仅是把标准输出dup到了标准输入而已,之后之所以能够在终端显示how是因为你的标准输入使用了终端/dev /pts或者/dev/tty*,而这两种设备的驱动程序在处理对它们的写入操作时,采用了连接显示器输出这样的机制,所以你能看到终端显示的how;

另外你的代码:
#include <unistd.h>

#include <stdio.h>

int main()

{

        close( STDOUT_FILENO );

        printf("how!!\n" );

}
显示不了how是因为你事先关闭了标准输出,那么标准输出就变成了无效的文件描述符,当利用printf向标准输出打印时,实际上是对一个无效的文件描述符进行了写操作,利用strace跟踪一下这个程序会得到
write(1, "how!!\n", 6)                  = -1 EBADF (Bad file descriptor)
这样的结果,也正是说明了程序向一个无效文件描述符进行写的事实,当然终端就显示不了how了。

最后swordfish.cn的代码:
583     /* move stdout to /dev/null */

584     if (-1 != (fd = open("/dev/null", O_WRONLY))) {

585         close(STDOUT_FILENO);

586         dup2(fd, STDOUT_FILENO);

587         close(fd);

588     }
之所以能关闭标准输出的显示是因为它把标准输出重定向到了/dev/null设备,而这个设备的驱动程序在处理对它的写入操作时是丢弃写入的内容,所以看不到输出。

所以整个问题的关键在于不同的设备(/dev/pts or /dev/tty* or /dev/null)它们的驱动程序在处理对设备的写入操作时的方式不同,跟dup2这个系统调用没有关系。


 aple_smx 回复于:2008-05-12 10:24:54

引用:原帖由 ratc 于 2008-5-7 22:45 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6580568&ptid=991081]
正如scutan所说的,这个问题跟dup2()确实没有关系
你的代码:#include 
#include 
int main()
{
        dup2(0, STDOUT_FILENO );       
        printf("how!!\n" );
}使用的dup2仅仅是把标准输出dup ... 


学习了。


 coneagoe 回复于:2008-05-20 18:13:00

学习了,刚知道原来自己肚子里也是一笔糊涂账


 anders0913 回复于:2008-05-21 13:55:42

引用:原帖由 ratc 于 2008-5-7 22:45 发表 [url=http://linux.chinaunix.net/bbs/redirect.php?goto=findpost&pid=6580568&ptid=991081]
正如scutan所说的,这个问题跟dup2()确实没有关系
你的代码:#include 
#include 
int main()
{
        dup2(0, STDOUT_FILENO );       
        printf("how!!\n" );
}使用的dup2仅仅是把标准输出dup ... 




学习了,谢谢




原文链接:http://linux.chinaunix.net/bbs/viewthread.php?tid=991081
转载请注明作者名及原文出处

No comments:

Post a Comment