在具体研究 io_uring 之前,有必要了解之前的 aio,包括 glib 实现的 POSIX aio 和 Linux 后来提供的只支持 O_DIRECT 的 Linux aio (不支持 socket 因为 socket 不能 O_DIRECT)。不然我实在看不懂 io_uring 目前的资料(和 epoll 铺天盖地的资料实在是没法比啊)。
首先是基本的情况,由于 Linux 早期不支持 Posix ais,所以实际 glib 是在用户态模拟了一个的,实际原理是通过多线程后台同步读写,然后自己维护一个 buffer,然而性能垃圾而且有 bug,只能说异步 IO 的实现有点复杂了属于,大牛都写不出个好用的来(M$ IOCP下面研究一下)
首先有必要搞明白 Proactor 和 Reactor 模型的到底区别在哪里?
One thread per client
- 最古老的复用是用线程/进程复用,由 kernel 来调度公平地为每个用户服务。如果要做成千上万的并发连接的话,尤其是长连接,就涉及这么多的进程线程调度的开销。
- 当然,one thread per client 也是会阻塞挂起的。
Reactor
- 通过 master + workers 的 Reactor 方案则是通过数据结构来维护所有的用户的信息,让特定的 workers 去给他服务。
- 这里就涉及要用定时器来单线程(尽管有固定数量的多个工作进程(或者线程也行),但是他们是单线程为多个用户服务的)调度每个用户了。
- 这个过程中如果涉及了阻塞的系统调用,基本的方法是再次注册一个等待事件到 epoll wait 的事件集合中,然后换一个回调函数。
- 回调函数在 Reactor 的概念是这样的,他就是一个阻塞事件就绪之后调用的东西,比如一个 fd,如果能读写了,他就会醒来,此时 Reactor 会根据 hashmap dispatch 这个回调函数给一个工作线程运行。
- 此时工作线程运行这个 read 或者 write 就不会阻塞了。
- 当然,还有一种情况,如果读了一半又要阻塞了怎么办?(由于我们必须让工作线程服务多个用户,所以这个必须是非阻塞调用),或者说用户人态定时的 expiration 到了。
- 所以一般我们绑定这个 Callback 是一个状态机来的,他必须是保留着状态的,这样才能重新注回到 epoll wait 里面(就涉及要打断正在 blocking 的 epoll wait),并且因为保留了状态(比如说用一个 Object)所以回调函数还是同一个。
Proactor 复习
- 我之前搞不明白 Proactor 这个有什么区别。其实最直观的区别就是 Proactor 的回调触发是说异步读写 Complete 了的,而 Reactor 回调的时候,读写还没有开始。所以这个东西和 DMA 是差不多的机制的。但是问题是,这样整个架构不是没什么区别吗?无非是去掉了要调用读写的部分,而数据已经准备好了而已。
- 当然一个区别是,我们不需要再自己调度了,不需要自己维护定时器了,因为事情实际已经委托给一个内核线程来进行读写了。
- 由于实际网上根本找不到具体讲这个怎么做的(只有一大堆讲个大概原理的),所以只能看各大网络库的实际实现依赖的 OS 接口了,就是微软的 IOCP I/O Completion Ports - Win32 apps | Microsoft Docs。当然,只需要学一下 boost 的 asio 就能明白了,以及 Proactor 的论文还有那本书 POSA2。不过既然都要看 IOCP,就从 IOCP 提供的 API 去看怎么实现 Proactor 吧。
- 先复习一下 asio 是什么样的架构,以及他对应的部件先吧

1491

被折叠的 条评论
为什么被折叠?



