如何检查存储在变量中的文件描述符是否仍然有效?

64

我有一个文件描述符存储在变量中,比如说var。如何在以后的阶段检查该描述符是否有效?

  fdvar1= open(.....);
  fdvar2 = fdvar1;       // Please ignore the bad design

  ....
  // lots of loops , conditionals and threads. It can call close(fdvar2) also.  
  ....

  if(CheckValid(fdvar1)) // How can I do this check  ?
    write(fdvar1, ....);

现在,我想检查 var1 是否仍然有效(持有已打开的描述符)。 有没有相应的API可以用?


2
为什么在C级别而不是操作系统级别复制FD? - Ignacio Vazquez-Abrams
1
请参阅dup的手册页面。 - Some programmer dude
2
即使您可以检查有效性,结果大多是没有价值的,因为在原始文件关闭后,可能会有另一个打开的文件接收到相同的描述符。 - interjay
这在《Linux程序设计》练习5.4中非常明确地提到了。 - Eric
6个回答

101

fcntl(fd, F_GETFD) 是检查 fd 是否是一个有效的打开文件描述符的标准且最便宜的方法。如果您需要批量检查很多文件描述符,则使用 poll,将超时设置为零,并将 events 成员设置为 0,然后在其返回后检查 revents 中是否包含 POLLNVAL 更加高效。

话虽如此,“检查给定资源句柄是否仍然有效”的操作几乎总是根本上不正确的。在释放资源句柄(例如关闭 fd)后,其值可能会被重新分配给您分配的下一个资源。如果仍有任何可能使用的引用,它们将错误地操作新资源而不是旧资源。因此,真正的答案可能是:如果您还没有通过程序逻辑知道这一点,则需要修复重大的基本逻辑错误。


1
假设我正在尝试实现一个处理FD的库,例如pipe库,并且我是将FD参数传递给将使用我的库的程序的人,那么测试FD验证是否仍然是不良实践?还是必须的? - Ravid Goldenberg
1
@petric:我不理解你的问题。一般而言,我会说是的,“测试FD验证”是不好的(也没有意义的)。但如果你对自己正在做的事情有具体的担忧,你应该发表一个新的问题来询问它(并参考这个问题/答案),而不是在评论中提问。 - R.. GitHub STOP HELPING ICE
如果我有一个父进程和子进程共享一个管道。在父进程中,我关闭了管道的读端和写端。那么子进程仍然有一个有效的读端。这是如何工作的? - Helping Bean
很有趣。但是,如果我正在对包含一些文件描述符的结构进行单元测试,并且我想要测试一个关闭文件描述符并回收内存的函数。在这种情况下检查文件描述符是否已关闭是否正确? - St.Antario
有一个原因可能会想这样做:你的程序被一个脚本调用,该脚本执行"exec 0<&-; exec 1>&-; ./foobar"。在这种情况下,您可能需要检查FD是否打开。例如,在启动守护进程的脚本中可以设置这种类型的事情(如daemontools,它们没有保持这些FD打开的理由)。 - user3459474
显示剩余3条评论

38
你可以使用 fcntl() 函数:
int fd_is_valid(int fd)
{
    return fcntl(fd, F_GETFD) != -1 || errno != EBADF;
}

3
原文意思:F_GETFD 原则上更便宜,因为它只在内核空间中解引用(进程本地的)文件描述符,而不是其所指向的基础开放文件描述符(进程共享)。通俗易懂翻译:原则上讲,F_GETFD 更加节约成本,因为它只在内核空间中解析(属于该进程的)文件描述符,而不是所引用的基础开放文件描述符(属于多个进程)。 - R.. GitHub STOP HELPING ICE
2
在调用 fcntl 之前,您应该将 errno 设置为零。在调用函数之前,errno 可能等于 EBADF,而 fcntl 只会在出现错误的情况下更改它。 - Bilow
6
@Bilow已经检查了返回值是否为-1,这只会在“错误情况”下发生,因此在调用之前设置errno为0只是多余的。 - Craig Barnes
3
@CraigBarnes 确实。 - Bilow

7
我认为没有任何函数可以告诉您描述符是否仍然有效。描述符通常只是一个类似于6的小整数,如果您关闭文件并稍后再次打开新文件,则您的libc可能会选择重新使用该数字。
相反,您应该考虑使用dup()复制文件描述符。通过复制文件描述符而不是在多个位置使用同一描述符,您可能更容易知道文件描述符是否仍然有效。您只需记住在完成后同时关闭原始描述符和复制的描述符即可。

4
David的观点是“正确”的,即你无法确定描述符是否“仍然”有效。你可以确定它是否有效,但这并不意味着它仍然指向相同的资源。 - R.. GitHub STOP HELPING ICE
3
是的。我对有效的定义是它仍然指向它以前指向的东西。我认为这是一个好的定义,因为通常这就是你想要的,并且你可以通过查看代码来确定描述符是否仍然有效;它不依赖于未定义的行为。 - David Grayson
2
此外,在 OP 的特定问题中,使用 dup 是不错的建议。这将避免整个问题。更好的方法可能是在一个单独的变量中保留标志,指示文件描述符是否已经关闭;这将避免额外的内核空间访问(和稀缺资源消耗)。 - R.. GitHub STOP HELPING ICE
大家好,我更新了我的答案(超过2年后),以使其更清晰。我并不建议您调用dup仅仅是为了检查文件描述符是否有效;我试图提供一些设计建议,以便在第一次遇到这种情况时更容易避免它。 - David Grayson

7

参考此篇论坛文章:

int is_valid_fd(int fd)
{
    return fcntl(fd, F_GETFL) != -1 || errno != EBADF;
}

fcntl(GETFL) 可能是您可以在文件描述符上执行的最便宜且最不可能失败的操作。特别地,该规范建议它不能被信号打断,也不受任何持有的锁影响。


3
如何给你的来源进行归因:http://cboard.cprogramming.com/networking-device-communication/93683-checking-if-file-descriptor-valid.html#post678998 - Kev

1

在我看来,如果你想知道它是否仍然指向相同的资源,一个(不完美的)方法是在打开后立即使用fstat()描述符,然后稍后再次执行并比较结果。从.st_mode&S_IFMT开始,然后继续--它是文件系统对象吗?查看.st_dev / .st_ino.它是套接字吗?尝试getsockname()getpeername()。它不会100%确定,但它可以告诉你它肯定不是相同的。


-1

我已经解决了这个问题。我不知道它是否可以用于通用目的,但对于串行连接来说它很好用(例如 /dev/ttyUSB0)!

struct stat s;
fstat(m_fileDescriptor, &s);

// struct stat::nlink_t   st_nlink;   ... number of hard links 
if( s.st_nlink < 1 ){
 // treat device disconnected case
}

详情请参见 man 手册 http://linux.die.net/man/2/fstat

祝好, Flo


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接