非阻塞访问文件系统

8

当编写一个非阻塞程序(处理多个套接字),在某个时刻需要使用open(2)打开文件,使用stat(2)检查文件或者使用opendir(2)打开目录时,如何确保系统调用不会阻塞?

在我看来,除了使用线程或fork(2),似乎没有其他选择。


我不认为这是可能的。为了做到这一点,您必须停止系统上的所有文件系统活动(以确保没有东西由于时间差而阻止您的下一个调用),但是没有一个理智的桌面操作系统会让您这样做。 - user541686
其他系统(例如Windows)实际上通过允许您请求I/O,然后在完成时通知您来处理此问题。就像watain一样,我从来没有弄清楚如何使Linux很好地完成这项任务。(+1) - DrC
1
你还可以设置一个计时器,如果超时,则会中断open系统调用(失败并返回errno == EINTR)。 - Basile Starynkevitch
@BasileStarynkevitch 我明白你的意思,在大多数情况下,open_是相当快的,但我仍然不想遇到可能的异常情况。设置一个定时器来中断系统调用似乎是合理的,我需要考虑一下,谢谢!(尽管这可能会增加不必要的复杂性)。 - watain
@Mehrdad:大多数操作系统(内部)都异步执行所有IO操作(因为它们必须处理许多进程同时打开文件的情况)。问题在于用户空间库-例如标准C库,它是在任何人关心线程之前设计的,POSIX异步IO库无法处理“异步打开”,标准C ++库太差了,甚至无法告诉您为什么打开失败等。 - Brendan
显示剩余3条评论
3个回答

4
作为Mel Nicholson的回复,对于所有基于文件描述符的内容,您可以使用select/poll/epoll。对于其他所有内容,您可以使用一个代理线程或线程池,使用小堆栈,通过内核调度程序将任何同步阻塞等待转换为可选择/轮询/epoll-able异步事件,使用eventfdunix pipe(需要可移植性)。
代理线程将阻塞,直到操作完成,然后写入eventfd或pipe以唤醒select/poll/epoll。

常规文件(也称描述符)总是从轮询中返回准备就绪。然后read阻塞实际读取数据(write通常不等待实际写入磁盘,但如果进行随机访问,则可能会阻塞_读取_数据)。 - Jan Hudec
@Jan Hudec - 如果需要高性能文件IO,则通常使用mmap,这可以提供确定性延迟。否则,eventfd / kicksocket(pipe)是一种选择。 - bobah
我认为不是这样的。如果你使用工作线程单独将每个操作异步化,额外的上下文切换会增加显著的开销。线程应该被分配大块可以独立完成的工作,以保持同步需求的合理性。 - Jan Hudec
文件IO mmap不能提供确定性延迟,因为它仍然需要通过磁盘访问层进行操作,而该层无法提供确定性延迟。但是它是最快的。并且只能使用线程来完成,因为阻塞在页面错误中是不可中断的。 - Jan Hudec

2

实际上,除了使用线程之外,还有一种无法解决的阻塞问题是页面故障。这可能发生在程序代码、程序数据、内存分配或从文件映射的数据中。几乎不可能避免它们(实际上,您可以将某些页面锁定到内存中,但这是特权操作,可能会通过使内核在其他地方处理内存管理时表现不佳而产生反效果)。因此:

  1. 你不能真正摆脱每个客户端的所有阻塞机会,所以不要把注意力放在像 openstat 这样的函数上。网络可能会添加比这些函数更大的延迟。
  2. 为了获得最佳性能,您应该拥有足够的线程,以便在其他线程被阻塞在页面故障或类似的困难阻塞点时可以安排一些线程。

此外,如果需要在处理网络请求期间读取和处理或处理和写入数据,则使用内存映射访问文件速度更快,但这是阻塞的,无法变成非阻塞的。因此,现代网络服务器倾向于对大多数内容使用阻塞调用,只需拥有足够的线程即可在其他线程等待 I/O 时使 CPU 处于繁忙状态。

大多数现代服务器都是多核的,这也是需要多个线程的另一个原因。


我理解你的观点。很遗憾,没有真正的非阻塞文件系统相关操作库(这可能会使事情更容易),但由于它与套接字不是完全相同的情况,我可以理解为什么使用线程是更好的选择。 - watain

0

您可以使用poll()命令在单个线程中检查任意数量的套接字数据。

请参阅此处以获取Linux详细信息,或者使用man poll命令获取您系统的详细信息。

open()stat()在所有符合POSIX标准的系统中都会在调用它们的线程中阻塞,除非通过异步策略(如在fork中)调用。


那么,如何使用 poll 来检查 openstat 是否需要等待 inode 缓存填充? - Jan Hudec

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