io_submit
系统调用及示例
1. 函数介绍
在使用 io_setup
创建了异步 I/O 上下文之后,下一步就是向这个上下文提交实际的 I/O 请求。
提交后,内核会接管这些请求,并在后台(可能使用专门的线程或机制)执行这些 I/O 操作。调用 io_submit
的进程可以立即继续执行,无需等待 I/O 完成。
简单来说,io_submit
就是把写好的"异步任务清单"(iocb
结构体)交给之前创建的"任务管理器"(io_context_t
),让它开始执行这些任务。
2. 函数原型
c
// 需要定义宏来启用 AIO 相关定义
#define _GNU_SOURCE
#include <linux/aio_abi.h> // 包含 iocb 等定义
#include <sys/syscall.h> // 包含系统调用号
#include <unistd.h> // 包含 syscall 函数
// io_submit 系统调用的实际接口
long syscall(SYS_io_submit, io_context_t ctx_id, long nr, struct iocb **iocbpp);
注意 :这也是一个底层系统调用,通常需要通过 syscall()
函数调用。
3. 功能
将 nr
个异步 I/O 请求(由 iocbpp
指向的数组描述)提交到由 ctx_id
标识的异步 I/O 上下文中。内核会尝试立即开始处理这些请求。
4. 参数
ctx_id
:io_context_t
类型。- 由
io_setup
返回的、有效的异步 I/O 上下文的标识符。
nr
:long
类型。- 指定要提交的异步 I/O 请求数量。这个值应该与
iocbpp
数组的大小相对应。
iocbpp
:struct iocb **
类型。- 一个指针数组,数组中的每个元素都指向一个
struct iocb
结构体。struct iocb
描述了一个单独的异步 I/O 请求。 - 数组的大小至少为
nr
。
5. struct iocb
结构体 (关键部分)
这是描述单个异步 I/O 请求的核心结构体。
c
// 简化版,实际定义在 linux/aio_abi.h
struct iocb {
__u64 aio_data; // 用户定义的数据,用于匹配请求和完成事件
__u32 aio_key, aio_reserved1;
__u16 aio_lio_opcode; // 操作类型 (IOCB_CMD_PREAD, IOCB_CMD_PWRITE, ...)
__s16 aio_reqprio; // 请求优先级 (通常为 0)
__u32 aio_fildes; // 文件描述符
__u64 aio_buf; // 用户空间缓冲区地址
__u64 aio_nbytes; // 传输字节数
__s64 aio_offset; // 文件偏移量
// ... 其他字段用于高级功能
};
关键字段:
aio_lio_opcode
: 指定操作类型。IOCB_CMD_PREAD
: 异步预读(指定偏移量的读取)。IOCB_CMD_PWRITE
: 异步预写(指定偏移量的写入)。IOCB_CMD_FSYNC
: 异步文件数据和元数据同步。IOCB_CMD_FDSYNC
: 异步文件数据同步。
aio_fildes
: 进行 I/O 操作的目标文件描述符。aio_buf
: 用户空间缓冲区的地址(读取时存放数据,写入时提供数据)。aio_nbytes
: 要传输(读取或写入)的字节数。aio_offset
: 文件中的偏移量(类似pread
/pwrite
)。aio_data
: 用户自定义数据。当这个请求完成后,对应的完成事件 (io_event
) 会包含这个值,方便程序识别是哪个请求完成了。
6. 返回值
- 成功 : 返回实际成功提交的请求数 (一个非负整数,可能小于或等于
nr
)。 - 失败 : 返回 -1,并设置
errno
。如果返回一个 0 到nr
之间的正数m
,则表示只有数组中前m
个请求被成功提交,后面的提交失败了。
7. 错误码 (errno
)
EAGAIN
: 资源暂时不可用,例如内核的提交队列已满。EBADF
:ctx_id
无效,或者iocbpp
中某个iocb
的aio_fildes
是无效的文件描述符。EINVAL
:ctx_id
无效,或者iocbpp
中某个iocb
的参数无效(例如aio_lio_opcode
未知,或nr
为负数)。ENOMEM
: 内存不足。
8. 相似函数或关联函数
io_setup
: 创建异步 I/O 上下文,是io_submit
的前置步骤。io_getevents
: 用于获取已提交请求的完成状态(事件)。io_cancel
: 尝试取消一个已提交但尚未完成的 I/O 请求。struct iocb
: 描述单个异步 I/O 请求的结构体。
9. 示例代码
下面的示例演示如何使用 io_setup
创建上下文,然后使用 io_submit
提交异步写入请求。
c
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <linux/aio_abi.h>
#include <sys/syscall.h>
// 封装 io_setup 系统调用
static inline int my_io_setup(unsigned nr_events, io_context_t *ctxp) {
return syscall(__NR_io_setup, nr_events, ctxp);
}
// 封装 io_destroy 系统调用
static inline int my_io_destroy(io_context_t ctx) {
return syscall(__NR_io_destroy, ctx);
}
// 封装 io_submit 系统调用
static inline int my_io_submit(io_context_t ctx, long nr, struct iocb **iocbpp) {
return syscall(__NR_io_submit, ctx, nr, iocbpp);
}
// 辅助函数:初始化一个异步写入的 iocb 结构
void prep_pwrite(struct iocb *iocb, int fd, const void *buf, size_t count, __u64 offset) {
memset(iocb, 0, sizeof(*iocb)); // 清零结构体
iocb->aio_lio_opcode = IOCB_CMD_PWRITE; // 设置操作类型为异步写
iocb->aio_fildes = fd; // 设置文件描述符
iocb->aio_buf = (__u64)(unsigned long)buf; // 设置缓冲区地址
iocb->aio_nbytes = count; // 设置写入字节数
iocb->aio_offset = offset; // 设置文件偏移量
iocb->aio_data = (__u64)(unsigned long)buf; // 设置用户数据 (这里用 buf 地址)
}
int main() {
const char *filename = "io_submit_test_file.txt";
const int num_writes = 3;
const size_t chunk_size = 1024;
int fd;
io_context_t ctx = 0; // 必须初始化为 0
struct iocb iocbs[num_writes];
struct iocb *iocb_ptrs[num_writes];
char buffers[num_writes][chunk_size];
int ret, i;
printf("--- Demonstrating io_submit ---\n");
// 1. 初始化要写入的数据
for (i = 0; i < num_writes; ++i) {
memset(buffers[i], 'A' + i, chunk_size); // Fill with 'A', 'B', 'C'
}
// 2. 创建并打开文件
fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
printf("1. Opened/created file '%s' (fd=%d)\n", filename, fd);
// 3. 初始化异步 I/O 上下文
ret = my_io_setup(num_writes, &ctx);
if (ret < 0) {
perror("io_setup");
close(fd);
exit(EXIT_FAILURE);
}
printf("2. Initialized AIO context (ctx_id=%llu)\n", (unsigned long long)ctx);
// 4. 准备 I/O 请求 (iocb)
printf("3. Preparing %d asynchronous write requests...\n", num_writes);
for (i = 0; i < num_writes; ++i) {
prep_pwrite(&iocbs[i], fd, buffers[i], chunk_size, i * chunk_size);
iocb_ptrs[i] = &iocbs[i];
printf(" Prepared write %d: offset=%zu, size=%zu, data='%c'...\n",
i+1, i * chunk_size, chunk_size, 'A' + i);
}
// 5. 提交 I/O 请求
printf("4. Submitting %d write requests using io_submit...\n", num_writes);
ret = my_io_submit(ctx, num_writes, iocb_ptrs);
if (ret != num_writes) {
fprintf(stderr, " io_submit failed: submitted %d requests, expected %d\n", ret, num_writes);
if (ret < 0) {
perror(" io_submit error");
} else {
printf(" Only the first %d requests were submitted successfully.\n", ret);
}
// 清理并退出
my_io_destroy(ctx);
close(fd);
unlink(filename);
exit(EXIT_FAILURE);
}
printf(" io_submit succeeded. All %d requests submitted.\n", ret);
// 6. 注意:此时写入操作可能仍在进行中,我们需要用 io_getevents 来等待完成
// 这个例子只演示提交,不等待完成。
printf("5. Note: io_submit returned immediately. The writes are happening in the background.\n");
printf(" To get the results, you need to call io_getevents().\n");
// 7. 清理资源 (在真实程序中,你应该在 io_getevents 确认完成后再关闭文件)
printf("6. Cleaning up resources...\n");
my_io_destroy(ctx);
printf(" Destroyed AIO context.\n");
close(fd);
printf(" Closed file descriptor.\n");
unlink(filename); // 删除测试文件
printf(" Deleted test file '%s'.\n", filename);
printf("\n--- Summary ---\n");
printf("1. io_submit(ctx_id, nr, iocb_ptrs) submits 'nr' AIO requests to the context 'ctx_id'.\n");
printf("2. Each request is described by an 'iocb' struct, pointed to by elements in 'iocb_ptrs'.\n");
printf("3. It returns the number of requests successfully submitted (may be < nr on partial failure).\n");
printf("4. It returns immediately; the I/O happens asynchronously in the background.\n");
printf("5. Use io_getevents() afterwards to check for completion and get results.\n");
return 0;
}
10. 编译和运行
bash
# 假设代码保存在 io_submit_example.c 中
gcc -o io_submit_example io_submit_example.c
# 运行程序
./io_submit_example
11. 预期输出
--- Demonstrating io_submit ---
1. Opened/created file 'io_submit_test_file.txt' (fd=3)
2. Initialized AIO context (ctx_id=123456789)
3. Preparing 3 asynchronous write requests...
Prepared write 1: offset=0, size=1024, data='A'...
Prepared write 2: offset=1024, size=1024, data='B'...
Prepared write 3: offset=2048, size=1024, data='C'...
4. Submitting 3 write requests using io_submit...
io_submit succeeded. All 3 requests submitted.
5. Note: io_submit returned immediately. The writes are happening in the background.
To get the results, you need to call io_getevents().
6. Cleaning up resources...
Destroyed AIO context.
Closed file descriptor.
Deleted test file 'io_submit_test_file.txt'.
--- Summary ---
1. io_submit(ctx_id, nr, iocb_ptrs) submits 'nr' AIO requests to the context 'ctx_id'.
2. Each request is described by an 'iocb' struct, pointed to by elements in 'iocb_ptrs'.
3. It returns the number of requests successfully submitted (may be < nr on partial failure).
4. It returns immediately; the I/O happens asynchronously in the background.
5. Use io_getevents() afterwards to check for completion and get results.