【问题标题】:Why do operating systems limit file descriptors?为什么操作系统会限制文件描述符?
【发布时间】:2023-03-04 07:24:07
【问题描述】:

在尽我最大的努力研究实现消息队列服务器的最佳方法后,我提出了这个问题。为什么操作系统会限制一个进程和全局系统可以拥有的打开文件描述符的数量? 我当前的服务器实现使用 zeromq,并为每个连接的 websocket 客户端打开一个订阅者套接字。显然,单个进程只能处理客户端到 fds 的限制。 当我研究这个主题时,我发现了很多关于如何将系统限制提高到高达 64k fds 的信息,但它从未提到它如何影响系统性能以及为什么它一开始是 1k 或更低? 我目前的方法是尝试使用协程在其自己的循环中向所有客户端发送消息,以及所有客户端及其订阅通道的映射。但我很想听到关于文件描述符限制的可靠答案,以及它们如何影响尝试在每个客户端级别上通过持久连接使用它们的应用程序?

【问题讨论】:

  • 好的,所以我从所有这些答案中收集到 1)归结为可用 RAM 的问题 2)如果可移植性是关键,Web 服务器应用程序不应依赖使用大量动态分配的文件描述符.因为那些实现服务器的人必须调整他们的服务器 FD 限制。

标签: websocket operating-system message-queue zeromq file-descriptor


【解决方案1】:

这可能是因为文件描述符值是文件描述符表的索引。因此,可能的文件描述符的数量将决定表的大小。普通用户不希望他们的一半内存被一个文件描述符表占用,该表可以处理他们永远不需要的数百万个文件描述符。

【讨论】:

  • 我喜欢这个答案,因为它特别说明它归结为可用 RAM。因此,我知道我的应用程序要消耗大量文件描述符并且我的服务器专门针对该应用程序进行了调整,这取决于我。谢谢!
【解决方案2】:

出于性能考虑,打开文件表需要静态分配,因此它的大小需要固定。文件描述符只是该表的偏移量,因此所有条目都需要是连续的。您可以调整表的大小,但这需要停止进程中的所有线程并为文件表分配一个新的内存块,然后将所有条目从旧表复制到新表。这不是您想要动态执行的操作,尤其是当您这样做的原因是因为旧表已满时!

【讨论】:

  • 这在 unix 平台上是正确的。在windows上使用文件句柄,一个进程默认可以分配1600万个句柄。句柄表是动态分配的,因此与句柄相比,您更有可能耗尽内存。但是,如果您确实用完了句柄,就会发生奇怪的事情。见blogs.technet.com/b/markrussinovich/archive/2009/09/29/…
  • 还有其他东西会占用更多的空间(可能还有时间),使用更多的 FD - 例如,用于 select() 的 FD 掩码。
  • 谢谢。我可以看到提高限制不是一件动态的事情。我可以看到这个答案导致建议为 FD 分配更大的静态表将是更多的专用内存?
【解决方案3】:

当您拥有大量潜在文件描述符时,某些操作会减慢速度。一个示例是“关闭除stdinstdoutstderr 之外的所有文件描述符”的操作——唯一可移植的*方法是尝试关闭除这三个之外的所有可能的文件描述符,这可能变成如果您可能打开数百万个文件描述符,那么操作会很慢。

*:如果你愿意不便携,你可以查看/proc/self/fd——但这不是重点。

这不是一个特别好的理由,但它是一个的理由。另一个原因只是为了防止有缺陷的程序(即“泄漏”文件描述符的程序)消耗过多的系统资源。

【讨论】:

  • 我不认为这是主要原因。 TMN下面的答案更好。
【解决方案4】:

在 unix 系统上,进程创建 fork() 和 fork()/exec() 习惯用法需要遍历所有潜在的进程文件描述符,试图关闭每一个,通常只留下几个文件描述符,例如 stdin、stdout、 stderr 未触及或重定向到其他地方。

由于此用于启动进程的 unix api,因此必须在任何时候创建新进程时执行此操作,包括执行在 shell 脚本中调用的每个非内置命令。 p>

其他需要考虑的因素是,虽然某些软件可能使用sysconf(OPEN_MAX) 来动态确定进程可能打开的文件数量,但很多软件仍然使用C 库的默认FD_SETSIZE,即通常是 1024 个描述符,因此无论管理上定义的上限如何,打开的文件都不会超过那么多。

Unix 有一个基于文件描述符集的传统异步 I/O 机制,这些文件描述符集使用位偏移来表示要等待的文件以及准备好或处于异常条件的文件。它不适用于数千个文件,因为每次运行循环时都需要设置和清除这些描述符集。更新的非标准 api 出现在主要的 unix 变体上,包括 *BSD 上的 kqueue() 和 Linux 上的 epoll(),以解决处理大量描述符时的性能缺陷。

重要的是要注意select()/poll() 仍然被很多软件使用,因为长期以来它一直是用于异步 I/O 的 POSIX api。现代 POSIX 异步 IO 方法现在是 aio_* API,但它可能无法与 kqueue()epoll() API 竞争。我没有在愤怒中使用过 aio,它当然不会具有本地方法提供的性能和语义,因为它们可以聚合多个事件以获得更高的性能。 *BSD 上的 kqueue() 为事件通知提供了非常好的边缘触发语义,允许它替换 select()/poll() 而不会强制对您的应用程序进行大的结构更改。 Linux epoll() 遵循 *BSD kqueue() 的领先优势并对其进行了改进,而后者又遵循了 Sun/Solaris evports 的领先优势。

结果是,增加系统中允许打开的文件的数量会增加系统中每个进程的时间和空间开销,即使它们无法根据所使用的 api 使用这些描述符。对于允许的打开文件的数量,也有聚合系统限制。这个older but interesting tuning summary for 100k-200k simultaneous connections using nginx on FreeBSD 提供了一些关于维护开放连接和another one covering a wider range of systems 开销的见解,但“仅”将 10K 连接视为珠穆朗玛峰。

可能最好的 unix 系统编程参考是 W. Richard Stevens Advanced Programming in the Unix Environment

【讨论】:

  • 嗯。哪里有支持要求 fork() 遍历所有文件描述符以尝试关闭每个文件描述符的文档? fork() 手册页说子进程继承了父文件描述符表的副本。这是否意味着如果一个 想要 关闭 fork() 上的文件描述符,它可能 意味着在父进程中允许更多文件描述符的情况下迭代一个更大的列表?
  • 另外,我注意到您正在引用更多历史/遗留 Unix 情况。这些是否仍然与现代 Linux 发行版相关,它们仍然会影响默认限制?例如,在我的 OSX 机器上,默认文件句柄 ulimit 非常低(256),但它是 BSD 并且支持 kqueue 很长时间了。
  • 我想我永远不会知道这是什么原因。这仅适用于 Unix,没有任何参考。
  • @jdi fork() 本身不会关闭文件描述符,它是 fork()/exec() 的 unix 习惯用法,导致需要新分叉的孩子(仍然使用相同的程序运行text 作为父级)关闭所有文件描述符,只保留基本的 stdin/stdout/stderr 在调用 exec() 加载和替换程序文本之前打开。
  • @AseemBansal 是的,这是 unix,因为该问题专门指的是文件描述符限制,而不是 Windows 文件句柄或 Mach 微内核端口。
猜你喜欢
  • 2012-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-24
  • 2013-05-30
  • 2011-06-18
  • 1970-01-01
  • 2014-01-02
相关资源
最近更新 更多