Skip to content

Commit fd22477

Browse files
committed
add io_uring
1 parent a2c00e0 commit fd22477

File tree

6 files changed

+860
-3
lines changed

6 files changed

+860
-3
lines changed

docs/_sidebar.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
- [非阻塞式 I/O 模型](/socket/none_block)
1515
- [I/O 多路复用模型](/socket/multiplexing)
1616
- [信号式驱动式 I/O 模型](/socket/signal)
17-
- [异步 I/O 模型](/socket/async)
17+
- [异步 I/O 模型: io_uring](/socket/async)
1818

1919
- 逻辑处理模型
2020

docs/socket/async.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# 异步 I/O 模型
1+
# 异步 I/O 模型 io_uring
22

33
此处所说的异步 I/O 模型指的是 UNIX 规范中所描述的通用异步 I/O 机制。区别于信号驱动式的 I/O 模型,此种模型是真正的异步 I/O 实现。这种模型与信号驱动模型的主要区别在于:信号驱动 I/O 是由内核通知应用程序何时启动一个 I/O 操作,而异步 I/O 模型是由内核通知应用程序 I/O 操作何时完成。目前 Linux 中有三种异步 I/O 模式,分别如下
44

@@ -8,7 +8,23 @@
88

99
然而前两种模式均不支持在套接字方面的应用,并且并没有被广泛应用和采纳,而且它们已经逐渐被新的 io_uring 模式取代,所以本文不再描述这两种模式。
1010

11-
io_uring 是 linux 内核 5.X 时代加入的全新异步 I/O 模型,大概在 5.4 版本正式可用,在 5.7 和 5.12 版本逐渐完善,io_uring 普遍被认为是 linux 下对标 windows 的 IOCP 的、真正的异步 I/O 模型将来的趋势。
11+
io_uring 是 linux 内核 5.X 时代加入的全新异步 I/O 模型,大概在 5.4 版本正式可用,在 5.7 和 5.12 版本逐渐完善,io_uring 普遍被认为是 linux 下对标 windows 的 IOCP 的、真正的异步 I/O 模型将来的趋势。异步 I/O 不仅在等待数据阶段是非阻塞的,同时在读取数据阶段也是非阻塞的,这是其他模型没办法做到的,接收到通知后,可以直接在用户空间取得数据,而不是还需要进行从内核空间到用户空间的拷贝。
12+
13+
到目前为止,我们介绍了阻塞 IO,非阻塞 IO,信号驱动式 IO,IO 复用,我们打个形象的比方,来对这几种 IO 做下区分。我们去网上买东西,下完单之后,你可以有如下几种处理方式:
14+
15+
- 下完单之后,在门口一直等待快递小哥把快递送上门,这就是同步阻塞 IO;
16+
- 下完单之后不停的,或者隔几秒种就下楼看看快递到没到,这就是同步非阻塞 IO;
17+
- 下完单之后就不管了,直到快递小哥打电话给你通知你去取快递,这就是同步非阻塞 IO 里面的信号驱动式 IO;
18+
- 下完单之后,你定时的去物流 app 上面查看你所有快递的状态,只要有快递送到了寄存点,你就去取,这就是同步非阻塞 IO 里面的 IO 复用;
19+
- 下完单之后,你就不管了,直到快递小哥给你送上门,你直接拿到了快递,你不用出门就可以拿到快递了,这就是异步 IO。
20+
21+
---
22+
23+
如下是一个使用 io_uring 的简单回声服务端。使用`gcc io_uring_echo.c -luring`进行编译。可以看到,其流程部分和 I/O 复用中的回声服务器流程类似,但是也有不同。
24+
25+
不同主要体现在得到 cqe 后的处理方式。由于 io_uring 是真正的异步 I/O 模型,比如在得到 ACCEPT 类型的 cqe 后,accept 动作实际已经进行完毕,可以直接对返回的 conn_fd 进行添加准备读的操作。并且在接收到 ACCEPT 类型后,还需要重新添加对 ACCEPT 类型的准备。比如在接收到 READ 类型的 cqe 后,由于 READ 操作已经完成,继续需要进行的即为写操作。接收到 WRITE 类型的 cqe 后同理。
26+
27+
[io_uring_echo](../src/network/io_uring/io_uring_echo.c ':include')
1228

1329
---
1430

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#include <fcntl.h>
2+
#include <stdio.h>
3+
#include <string.h>
4+
#include <sys/stat.h>
5+
#include <sys/ioctl.h>
6+
#include <liburing.h>
7+
#include <stdlib.h>
8+
9+
#define QUEUE_DEPTH 1
10+
#define BLOCK_SZ 1024
11+
12+
struct file_info {
13+
off_t file_sz;
14+
struct iovec iovecs[]; /* Referred by readv/writev */
15+
};
16+
17+
/*
18+
* Returns the size of the file whose open file descriptor is passed in.
19+
* Properly handles regular file and block devices as well. Pretty.
20+
* */
21+
22+
off_t get_file_size(int fd) {
23+
struct stat st;
24+
25+
if(fstat(fd, &st) < 0) {
26+
perror("fstat");
27+
return -1;
28+
}
29+
if (S_ISBLK(st.st_mode)) {
30+
unsigned long long bytes;
31+
if (ioctl(fd, BLKGETSIZE64, &bytes) != 0) {
32+
perror("ioctl");
33+
return -1;
34+
}
35+
return bytes;
36+
} else if (S_ISREG(st.st_mode))
37+
return st.st_size;
38+
39+
return -1;
40+
}
41+
42+
/*
43+
* Output a string of characters of len length to stdout.
44+
* We use buffered output here to be efficient,
45+
* since we need to output character-by-character.
46+
* */
47+
void output_to_console(char *buf, int len) {
48+
while (len--) {
49+
fputc(*buf++, stdout);
50+
}
51+
}
52+
53+
/*
54+
* Wait for a completion to be available, fetch the data from
55+
* the readv operation and print it to the console.
56+
* */
57+
58+
int get_completion_and_print(struct io_uring *ring) {
59+
struct io_uring_cqe *cqe;
60+
int ret = io_uring_wait_cqe(ring, &cqe);
61+
if (ret < 0) {
62+
perror("io_uring_wait_cqe");
63+
return 1;
64+
}
65+
if (cqe->res < 0) {
66+
fprintf(stderr, "Async readv failed.\n");
67+
return 1;
68+
}
69+
struct file_info *fi = io_uring_cqe_get_data(cqe);
70+
int blocks = (int) fi->file_sz / BLOCK_SZ;
71+
if (fi->file_sz % BLOCK_SZ) blocks++;
72+
for (int i = 0; i < blocks; i ++)
73+
output_to_console(fi->iovecs[i].iov_base, fi->iovecs[i].iov_len);
74+
75+
io_uring_cqe_seen(ring, cqe);
76+
return 0;
77+
}
78+
79+
/*
80+
* Submit the readv request via liburing
81+
* */
82+
83+
int submit_read_request(char *file_path, struct io_uring *ring) {
84+
int file_fd = open(file_path, O_RDONLY);
85+
if (file_fd < 0) {
86+
perror("open");
87+
return 1;
88+
}
89+
off_t file_sz = get_file_size(file_fd);
90+
off_t bytes_remaining = file_sz;
91+
off_t offset = 0;
92+
int current_block = 0;
93+
int blocks = (int) file_sz / BLOCK_SZ;
94+
if (file_sz % BLOCK_SZ) blocks++;
95+
struct file_info *fi = malloc(sizeof(*fi) +
96+
(sizeof(struct iovec) * blocks));
97+
98+
/*
99+
* For each block of the file we need to read, we allocate an iovec struct
100+
* which is indexed into the iovecs array. This array is passed in as part
101+
* of the submission. If you don't understand this, then you need to look
102+
* up how the readv() and writev() system calls work.
103+
* */
104+
while (bytes_remaining) {
105+
off_t bytes_to_read = bytes_remaining;
106+
if (bytes_to_read > BLOCK_SZ)
107+
bytes_to_read = BLOCK_SZ;
108+
109+
offset += bytes_to_read;
110+
fi->iovecs[current_block].iov_len = bytes_to_read;
111+
112+
void *buf;
113+
if( posix_memalign(&buf, BLOCK_SZ, BLOCK_SZ)) {
114+
perror("posix_memalign");
115+
return 1;
116+
}
117+
fi->iovecs[current_block].iov_base = buf;
118+
119+
current_block++;
120+
bytes_remaining -= bytes_to_read;
121+
}
122+
fi->file_sz = file_sz;
123+
124+
/* Get an SQE */
125+
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
126+
/* Setup a readv operation */
127+
io_uring_prep_readv(sqe, file_fd, fi->iovecs, blocks, 0);
128+
/* Set user data */
129+
io_uring_sqe_set_data(sqe, fi);
130+
/* Finally, submit the request */
131+
io_uring_submit(ring);
132+
133+
return 0;
134+
}
135+
136+
int main(int argc, char *argv[]) {
137+
struct io_uring ring;
138+
139+
if (argc < 2) {
140+
fprintf(stderr, "Usage: %s [file name] <[file name] ...>\n",
141+
argv[0]);
142+
return 1;
143+
}
144+
145+
/* Initialize io_uring */
146+
io_uring_queue_init(QUEUE_DEPTH, &ring, 0);
147+
148+
for (int i = 1; i < argc; i++) {
149+
int ret = submit_read_request(argv[i], &ring);
150+
if (ret) {
151+
fprintf(stderr, "Error reading file: %s\n", argv[i]);
152+
return 1;
153+
}
154+
get_completion_and_print(&ring);
155+
}
156+
157+
/* Call the clean-up function. */
158+
io_uring_queue_exit(&ring);
159+
return 0;
160+
}

0 commit comments

Comments
 (0)