考虑一个CPU绑定的应用程序,但也有高性能的I / O要求。
我将Linux文件I / O与Windows比较,我看不出epoll会如何帮助Linux程序。 内核会告诉我文件描述符是“准备好读取”的,但是我仍然需要调用阻塞read()来获取我的数据,如果我想读取兆字节,那么很明显,这将阻塞。
在Windows上,我可以使用OVERLAPPED集创build一个文件句柄,然后使用非阻塞I / O,并在I / O完成时得到通知,并使用该完成函数中的数据。 我不需要在应用程序级的挂钟时间等待数据,这意味着我可以精确地调整我的线程数量到我的核心数量,并获得100%的CPU利用率。
如果我必须在Linux上模拟asynchronousI / O,那么我必须分配一定数量的线程来完成这个任务,而这些线程将花费一点时间来处理CPU的事情,并且很多时间阻塞I / O,加上这些线程的消息传递会有开销。 因此,我将要么过度订阅,要么没有充分利用我的CPU核心。
我将mmap()+ madvise()(WILLNEED)看作是一个“穷人的asynchronousI / O”,但它仍然不能完全实现,因为在完成时我无法得到通知 – 到“猜测”,如果我猜“错误”,我将最终阻止内存访问,等待数据来自磁盘。
Linux似乎在io_submit中启动了asynchronousI / O,似乎也有一个用户空间的POSIX aio实现,但是一段时间就这样,我知道没有人会为这些系统担保,高性能的应用程序。
Windows模型大致如下所示:
步骤1/2通常作为一件事完成。 步骤3/4通常使用工作线程池来完成,而不是与发出I / O的线程相同的线程。 这个模型有点类似于boost :: asio提供的模型,除了boost :: asio实际上并没有给你asynchronous的基于块(磁盘)的I / O。
与Linux中的epoll不同的是,在步骤4中,没有I / O还没有发生 – 如果你确切地知道你需要什么,它会在第4步之后升级第1步,这是“倒退”。
在编写了大量的embedded式,桌面和服务器操作系统之后,我可以说这种asynchronousI / O模式对于某些types的程序是非常自然的。 这也是非常高吞吐量和低开销。 我认为这是Linux I / O模型在API层面的其余缺陷之一。
Peter Teoh间接指出的真实答案是基于io_setup()和io_submit()。 具体来说,Peter指出的“aio_”函数是基于线程的glibc用户级仿真的一部分,这不是一个有效的实现。 真正的答案在于:
io_submit(2) io_setup(2) io_cancel(2) io_destroy(2) io_getevents(2)
请注意,2012年8月的手册页说,这个实现还没有成熟到可以取代glibc用户空间模拟的地步:
http://man7.org/linux/man-pages/man7/aio.7.html
这个实现还没有成熟到POSIX AIO实现可以使用内核系统调用完全重新实现的程度。
所以,根据我能找到的最新的内核文档,Linux还没有一个成熟的,基于内核的异步I / O模型。 而且,如果我假设记录的模型实际上是成熟的,它仍然不支持recv()和read()的意义上的部分I / O。
如下所述:
http://code.google.com/p/kernel/wiki/AIOUserGuide
和这里:
http://www.ibm.com/developerworks/library/l-async/
Linux在内核级别提供异步块I / O,如下所示:
aio_read Request an asynchronousous read operation aio_error Check the status of an asynchronousous request aio_return Get the return status of a completed asynchronousous request aio_write Request an asynchronousous operation aio_suspend Suspend the calling process until one or more asynchronousous requests have completed (or failed) aio_cancel Cancel an asynchronousous I/O request lio_listio Initiate a list of I/O operations
如果你问谁是这些API的用户,它就是内核本身 – 只是一个小子集显示在这里:
./drivers/net/tun.c (for network tunnelling): static ssize_t tun_chr_aio_read(struct kiocb *iocb, const struct iovec *iv, ./drivers/usb/gadget/inode.c: ep_aio_read(struct kiocb *iocb, const struct iovec *iov, ./net/socket.c (general socket programming): static ssize_t sock_aio_read(struct kiocb *iocb, const struct iovec *iov, ./mm/filemap.c (mmap of files): generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov, ./mm/shmem.c: static ssize_t shmem_file_aio_read(struct kiocb *iocb,
等等
在用户空间级别,还有io_submit()等API(来自glibc),但下面的文章提供了使用glibc的替代方法:
http://www.fsl.cs.sunysb.edu/~vass/linux-aio.txt
它直接实现像io_setup()这样的函数的API作为直接系统调用(绕过glibc依赖),通过相同的“__NR_io_setup”签名的内核映射应该存在。 在以下位置搜索内核源代码:
http://lxr.free-electrons.com/source/include/linux/syscalls.h#L474 (网址适用于最新版本3.13),您将会在内核中直接实现这些io _ *()API :
474 asmlinkage long sys_io_setup(unsigned nr_reqs, aio_context_t __user *ctx); 475 asmlinkage long sys_io_destroy(aio_context_t ctx); 476 asmlinkage long sys_io_getevents(aio_context_t ctx_id, 481 asmlinkage long sys_io_submit(aio_context_t, long, 483 asmlinkage long sys_io_cancel(aio_context_t ctx_id, struct iocb __user *iocb,
glibc的更高版本应该使这些“syscall()”的用法不必调用sys_io_setup(),但是如果没有最新版本的glibc,你可以随时自己调用这些调用,如果你正在使用后面的内核,具有“sys_io_setup ()”。
当然,还有其他的异步I / O的用户空间选项(例如,使用信号?):
http://personal.denison.edu/~bressoud/cs375-s13/supplements/linux_altIO.pdf
或者perhap:
什么是POSIX异步I / O(AIO)的状态?
“io_submit”和朋友在glibc中仍然不可用(请参阅io_submit手册页),我已经在我的Ubuntu 14.04中验证过这个API,但是这个API是linux特有的。
其他像libuv,libev和libevent也是异步API:
http://nikhilm.github.io/uvbook/filesystem.html#reading-writing-files
http://software.schmorp.de/pkg/libev.html
所有这些API旨在跨BSD,Linux,MacOSX甚至Windows进行移植。
在性能方面,我没有看到任何数字,但怀疑libuv可能是最快的,由于其轻量级?
对于网络套接字I / O,当它“准备好”时,它不会被阻塞。 这就是O_NONBLOCK
和“ready”的意思。
对于磁盘I / O,我们有posix aio , linux aio , sendfile和朋友。