Using io_uring instead of epoll to realize high-speed polling


As mentioned in the previous articleio_uringIt’s the latest native asynchronous I/O implementation in Linux, in factio_uringIt also supports polling and is a good alternative to epoll.


Useio_uringIt’s easy to poll an fd. First initialize io_uring object (io_uring_queue_init) and get SQE (io_uring_get_sqe) as allio_uringOperations are necessary, as described above, there is no much explanation here. Once you get the sqe, initialize the SQE pointer using io_uring_prep_poll_add.

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

The first parameter is the SQE pointer obtained earlier; the second parameter is the file descriptor you want to poll; and the third parameter is the flag bit, where io_uring does not introduce a new flag (macro), but follows.poll(2)Defined flags, such as POLLIN, POLLOUT, etc.

Like other I/O requests, each SQE can set a user’s own value inside, usingio_uring_sqe_set_data

You can see that only one poll request can be added at a time. If there are more than one fd, the call is repeatedio_uring_get_sqeGet multiple SQE pointers separatelyio_uring_prep_poll_addAll right.io_uring_get_sqeIt’s not that system calls don’t go into the kernel.io_uring_prep_poll_addIt is simple structure parameter assignment, so there is no speed problem.

Use after adding the required requestio_uring_submitUniform submission and useio_uring_peek_cqeOperations such as obtaining completion are consistent with standard asynchronous I/O requests.

Useio_uringThere is a big difference between polling and epoll, poll’s default mode.io_uringPolling always works in one-shot mode (equivalent to epoll’s)EPOLLONESHOTThat is, once a poll operation is completed, the user must resubmit the poll request or no new event will be triggered, thus ensuring that each poll request has and only one response. Then, since it is one-shot mode, there is no difference between LT and ET modes in epoll.

Clear the polling request in progress using io_uring_prep_poll_remove

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

It also needs SQE and submit. You can see that this function is very specific and directly requires the user_data parameter. The kernel deletes requests with equal values by comparing the user_data submitted previously with the user_data you now specify.


The initial requirement in network programming is asynchronous listening for client access (O_NONBLOCK accept), which is also a code example of many epolls. useio_uringAs follows:

int sockfd = socket(...);

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);

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

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


Personal feeling if takeio_uringThere is no advantage in doing polling alone. takeio_uringThe most useful thing about polling is to monitor and process the completion events of polling and AIO in a unified way. Imagine using clientfd immediately after you get itio_uring_prep_readvRead the request body and reuse itio_uring_prep_poll_addAccept access from other clients, which is really asynchronous programming.