用 io_uring 替代 epoll 實現高速 polling

前面的文章說到 io_uring 是 Linux 中最新的原生異步 I/O 實現,實際上 io_uring 也支持 polling,是良好的 epoll 替代品。linux

API

使用 io_uring 來 poll 一個 fd 很簡單。首先初始化 io_uring 對象(io_uring_queue_init),拿到 sqe(io_uring_get_sqe)是全部 io_uring 操做都必要的,前文已經介紹這裏不作過多說明。拿到 sqe 以後,使用 io_uring_prep_poll_add 初始化 sqe 指針。git

static inline void io_uring_prep_poll_add(struct io_uring_sqe *sqe, int fd,
                      short poll_mask);

第一個參數就是前面得到的 sqe 指針;第二個參數是你要 poll 的文件描述符;第三個是標誌位,這裏 io_uring 沒有引入新的標誌(宏),而是沿用了 poll(2) 定義的標誌,如 POLLIN、POLLOUT 等。編程

如其餘 I/O 請求同樣,每一個 sqe 均可以設置一個用戶本身的值在裏面,使用 io_uring_sqe_set_datasegmentfault

能夠看到一次只能添加一個 poll 請求。若是有多個 fd,那麼重複調用 io_uring_get_sqe 獲取多個 sqe 指針分別 io_uring_prep_poll_add 便可。io_uring_get_sqe 不是系統調用不會進入內核,io_uring_prep_poll_add 則是簡單的結構體參數賦值,因此沒有速度問題。網絡

添加完須要的請求後使用 io_uring_submit 統一提交、使用 io_uring_peek_cqe 獲取完成狀況等操做與標準異步 I/O 請求一致。異步

使用 io_uring 作 polling 與 epoll、poll 的默認模式有一個很大的區別就是 io_uring 的 polling 始終工做在 one-shot 模式下(等同於 epoll 的 EPOLLONESHOT),即一旦某個 poll 操做完成,用戶必須從新提交 poll 請求不然不會觸發新的事件,這樣保證每一個 poll 請求有且只有一個響應。而後既然是 one-shot 模式,也就沒有相似 epoll 中的 LT、ET 模式之分socket

清除進行中的 polling 請求使用 io_uring_prep_poll_remove異步編程

static inline void io_uring_prep_poll_remove(struct io_uring_sqe *sqe,
                         void *user_data);

也是須要 sqe 而後 submit。能夠看到這個函數很特別的直接須要 user_data 參數。內核是在用以前提交的 user_data 和你如今指定的 user_data 作對比,刪除值相等的請求。函數

示例

在網絡編程中最開始的需求就是異步監聽客戶端接入(O_NONBLOCK accept),這也是好多 epoll 的代碼示例。用 io_uring 以下:.net

int sockfd = socket(...);
bind(...);
listen(...);

struct io_uring ring;
io_uring_queue_init(32, &ring, 0);

struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_poll_add(sqe, sockfd, POLLIN);
io_uring_submit(&ring);

struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);

int clientfd = accept(sockfd, ...);

我的感受若是拿 io_uring 純作 polling 的話沒有什麼優點。拿 io_uring 作 polling 最有用的一點是把 polling 和 aio 的完成事件作統一監聽和處理。想象拿到 clientfd 以後就能夠當即使用 io_uring_prep_readv 讀取請求體,同時又能夠再使用 io_uring_prep_poll_add 接受其餘客戶端接入,這樣纔是真正的異步編程。

相關文章
相關標籤/搜索