首页/目录/全部文章

全部文章

八个专题的源码、算法与协议笔记都在这里。

笔记列表

rk3588/kernel-6.1 init 目录作用与内核启动流程分析

rk3588/kernel-6.1 init 目录作用与内核启动流程分析

扩展文档集:见 README.md(总览 + 01–07 专题,原理与实现分篇)。

1. init 目录的作用

init/ 是内核从“刚进入 C 代码”到“拉起用户态 init 进程”这段路径的核心目录,主要承担:

  • 系统早期初始化总控main.c
  • initramfs/initrd 解包与早期根文件系统准备initramfs.cdo_mounts*.c
  • 内核命令行参数解析与 init 进程参数传递main.c
  • initcall 驱动初始化框架调度do_initcalls
  • 版本/初始任务等基础对象version.cinit_task.c

一句话:init/ 负责把系统从“boot CPU 单线程内核态”推进到“多核 + 驱动可用 + 用户态 PID1 运行”。


2. 目录分层与文件职责

init/Makefile 可见构成:

  • 核心:main.o version.o mounts.o init_task.o
  • initramfs 路径:
    • 无 initrd:noinitramfs.o
    • 有 initrd:initramfs.o
  • 根挂载相关:
    • do_mounts.o
    • do_mounts_rd.o(ramdisk)
    • do_mounts_initrd.o(initrd)
  • 其他:calibrate.o(延迟校准)

关键文件职责:

  • main.cstart_kernelrest_initkernel_init_freeablekernel_init
  • do_mounts.croot= 解析、prepare_namespace、根设备挂载
  • initramfs.c:内置 cpio 的解包与文件恢复

3. 启动流程总览(主干调用链)

主链路可抽象为:

  1. start_kernel()
  2. arch_call_rest_init() -> rest_init()
  3. rest_init() 创建:
    • PID1 内核线程入口:kernel_init
    • kthreadd
  4. kernel_init()
    • kernel_init_freeable()
    • 释放 init 内存、切系统状态为 SYSTEM_RUNNING
    • run_init_process(...) 执行用户态 /sbin/init

4. 分阶段启动过程详解

4.1 早期阶段:start_kernel()

start_kernel 做的是“把最小可运行内核环境搭起来”:

  • CPU/体系结构早期初始化
    smp_setup_processor_idsetup_arch
  • 命令行处理
    setup_command_lineparse_early_paramparse_args
  • 内存子系统初始化
    mm_init(页分配、slab、vmalloc 等)
  • 调度与中断基础能力
    sched_initearly_irq_initinit_IRQsoftirq_init
  • 时间子系统
    timekeeping_inittime_inithrtimers_init
  • 控制台与锁调试
    console_initlockdep_init
  • 关键核心子系统
    fork_initcred_initvfs_caches_initcgroup_initsecurity_init

之后调用 arch_call_rest_init(),进入非 __init 的后续阶段。

4.2 任务切换阶段:rest_init()

rest_init 关键动作:

  • 启动 PID1 的内核态线程入口 kernel_init
  • 启动 kthreadd
  • 置系统状态为 SYSTEM_SCHEDULING
  • complete(&kthreadd_done),让 kernel_init 可继续
  • 当前 boot idle 线程进入 cpu_startup_entry

设计重点:先建好 kthreadd,再让后续 init 路径安全创建内核线程。

4.3 可阻塞初始化阶段:kernel_init_freeable()

这一段已经允许阻塞分配,开始完成“大规模子系统与驱动初始化”:

  • smp_prepare_cpus / smp_init / sched_init_smp
  • workqueue_init
  • do_pre_smp_initcalls(early initcall)
  • do_basic_setup()
    • driver_init
    • init_irq_proc
    • do_ctors
    • do_initcalls(按 level 执行各类 __initcall
  • wait_for_initramfs
  • console_on_rootfs
  • 若无可执行的 rdinit,走 prepare_namespace() 挂载最终根文件系统
  • integrity_load_keys

4.4 收尾并进入用户态:kernel_init()

kernel_init 在 freeable 阶段后继续:

  • async_synchronize_full
  • free_initmem 释放 __init* 内存
  • mark_readonly 打开只读保护(rodata 等)
  • system_state = SYSTEM_RUNNING
  • 根据优先级尝试执行 init:
    1. ramdisk_execute_commandrdinit=
    2. execute_commandinit=
    3. CONFIG_DEFAULT_INIT
    4. /sbin/init -> /etc/init -> /bin/init -> /bin/sh
  • 全失败则 panic:No working init found

5. 内核如何拉起用户态 init 进程

Linux 的用户态 init 不是由某个已经存在的用户进程 fork 出来的,而是由内核在启动末期把 PID1 从“内核态启动线程”转换成“用户态 init 程序”。

整体链路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
start_kernel()
-> arch_call_rest_init()
-> rest_init()
-> user_mode_thread(kernel_init, NULL, CLONE_FS) // 创建 PID1
-> kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES)
-> complete(&kthreadd_done)

PID1: kernel_init()
-> wait_for_completion(&kthreadd_done)
-> kernel_init_freeable()
-> do_basic_setup()
-> wait_for_initramfs()
-> console_on_rootfs()
-> prepare_namespace()
-> free_initmem()
-> system_state = SYSTEM_RUNNING
-> run_init_process(...)
-> kernel_execve(...)
-> bprm_execve()
-> 装载 ELF / 脚本解释器
-> 进入用户态 init

5.1 rest_init() 创建 PID1

rest_init() 中最关键的一句是:

1
pid = user_mode_thread(kernel_init, NULL, CLONE_FS);

它的作用是创建一个将来进入用户态的任务,并让这个任务先执行内核函数 kernel_init()
因为这是系统中第一个正式创建的任务,所以它获得 PID 1。

随后 rest_init() 再创建 kthreadd

1
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

这里顺序很重要:

  • 先创建 kernel_init,保证 init 获得 PID1;
  • 再创建 kthreadd,后续内核线程都依赖它;
  • kernel_init() 会等待 kthreadd_done,避免在 kthreadd 尚未准备好时创建内核线程。

也就是说,PID1 最开始并不是 /sbin/init,而是一个执行 kernel_init() 的内核启动任务。

5.2 kernel_init_freeable() 准备用户态运行环境

kernel_init() 先等待 kthreadd 完成:

1
wait_for_completion(&kthreadd_done);

然后执行:

1
kernel_init_freeable();

这一阶段完成用户态 init 启动前必须具备的环境:

  • 调度器 SMP 初始化完成;
  • workqueue 初始化完成;
  • 各级 initcall 执行完成,驱动和子系统基本就绪;
  • initramfs 解包完成;
  • /dev/console 在 rootfs 上打开;
  • 如果没有可用的 initramfs /init,则 prepare_namespace() 挂载真实根文件系统;
  • rootfs、设备节点、控制台、基础驱动准备好后,用户态 init 才可能执行成功。

其中这段逻辑决定是否走 initramfs 的 /init

1
2
3
4
if (init_eaccess(ramdisk_execute_command) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}

默认 ramdisk_execute_command"/init"。如果 initramfs 中存在可执行 /init,内核优先执行它;否则挂载真实根文件系统,再寻找 /sbin/init 等。

5.3 kernel_init() 选择哪个 init 程序

kernel_init_freeable() 结束后,kernel_init() 做启动收尾:

1
2
3
4
5
6
7
async_synchronize_full()
free_initmem()
mark_readonly()
pti_finalize()
system_state = SYSTEM_RUNNING
rcu_end_inkernel_boot()
do_sysctl_args()

然后开始选择用户态 init。优先级如下:

  1. rdinit= 指定的 initramfs init,默认是 /init
  2. init= 指定的真实根文件系统 init;
  3. CONFIG_DEFAULT_INIT
  4. 内核硬编码 fallback:
    • /sbin/init
    • /etc/init
    • /bin/init
    • /bin/sh

源码逻辑可抽象为:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (ramdisk_execute_command)
run_init_process(ramdisk_execute_command);

if (execute_command)
run_init_process(execute_command);

if (CONFIG_DEFAULT_INIT[0] != '\0')
run_init_process(CONFIG_DEFAULT_INIT);

try_to_run_init_process("/sbin/init");
try_to_run_init_process("/etc/init");
try_to_run_init_process("/bin/init");
try_to_run_init_process("/bin/sh");

如果 init= 指定的程序存在但执行失败,内核会直接 panic;如果 fallback 路径一个都无法执行,也会 panic:

1
No working init found. Try passing init= option to kernel.

5.4 run_init_process() 如何把 PID1 变成用户态 init

run_init_process() 会设置 argv/envp,然后调用 kernel_execve()

1
2
3
4
5
6
static int run_init_process(const char *init_filename)
{
argv_init[0] = init_filename;
pr_info("Run %s as init process\n", init_filename);
return kernel_execve(init_filename, argv_init, envp_init);
}

这一步和用户态调用 execve() 的效果类似:不新建进程,而是把当前 PID1 的地址空间和执行镜像替换成目标 init 程序

因此:

  • PID 仍然是 1;
  • task_struct 仍是这个 init 任务;
  • 原来的 kernel_init() 内核态执行路径被新程序镜像替换;
  • 成功后不会再返回 kernel_init()
  • CPU 最终通过体系结构返回用户态路径,开始执行 /init/sbin/init 的入口。

kernel_execve() 内部大致做:

1
2
3
4
5
6
7
8
getname_kernel(init_filename)
-> alloc_bprm()
-> 复制 argv/envp
-> bprm_execve()
-> search_binary_handler()
-> load_elf_binary() / load_script()
-> 建立用户态 mm、栈、入口地址
-> 返回用户态执行

如果 init 是脚本,例如:

1
#!/bin/sh

则 exec 路径会继续寻找并执行解释器 /bin/sh。所以 initramfs 或 rootfs 中不仅要有 init 文件,还要有它依赖的解释器、动态链接器和库。

5.5 init=rdinit= 和根文件系统的关系

两个参数含义不同:

  • rdinit=/xxx:优先从 initramfs/rootfs 早期环境中执行,常用于 initramfs 的 /init
  • init=/xxx:挂载真实根文件系统后执行,常见为 init=/sbin/initinit=/bin/sh

典型场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
使用 initramfs:
kernel_init_freeable()
-> wait_for_initramfs()
-> 发现 /init 可执行
kernel_init()
-> run_init_process("/init")

使用真实 rootfs:
kernel_init_freeable()
-> initramfs 无 /init
-> prepare_namespace() 挂载 root=
kernel_init()
-> run_init_process("/sbin/init")

调试启动失败时,常用:

1
2
init=/bin/sh
rdinit=/bin/sh

如果能进入 shell,说明内核已基本完成启动,问题多半在 rootfs、init 程序、动态库或启动脚本。


6. 命令行参数在启动中的作用

init/main.c 里,命令行参数分几类:

  • early_param:超早期生效(如 logleveldebugquietrodata
  • __setup:常规启动参数(如 init=rdinit=
  • "--" 后参数:作为用户态 init 的参数传递

典型参数:

  • init=/path/to/init:指定 PID1 可执行文件
  • rdinit=/path/to/init:指定 initramfs 内早期 init
  • ro/rw:根文件系统挂载只读/读写(在 do_mounts.c

7. initramfs/initrd 与根文件系统切换

7.1 initramfs 解包

initramfs.c 负责解析 cpio 流并在 rootfs 中创建文件/目录/设备节点。
这一步让系统在没有真实块设备根分区前也能获得最小用户空间。

7.2 根文件系统准备

do_mounts.c 负责:

  • 解析 root=(支持设备名、PARTUUID=PARTLABEL= 等)
  • 处理 ro/rw/rootwait 等策略
  • prepare_namespace() 完成真实根挂载与切换准备

若使用 initramfs,通常会先跑早期用户空间脚本再切换到真实根。


8. initcall 机制在启动中的位置

do_initcalls() 是内核驱动/子系统初始化总调度器:

  • 按 initcall level 依次执行(early/core/postcore/arch/subsys/fs/device/late)
  • 每个 level 会重新解析命令行片段
  • 可配 initcall_debug 观测耗时与失败

这套机制决定了绝大多数驱动何时“上线”。


9. init 目录在系统稳定性中的关键点

  1. start_kernel 顺序极其敏感
    中断、调度、内存初始化次序错误会导致早期崩溃。

  2. PID1 启动失败直接致命
    run_init_process 全部失败后系统 panic。

  3. 根文件系统参数错误常见导致卡启动
    root= 不正确、设备未就绪、initrd 路径错误都会阻塞启动。

  4. initcall 卡住会表现为“开机停在某阶段”
    借助 initcall_debug 和 early console 可定位。


10. 调试建议(面向启动问题)

  1. 查看早期日志

    • 使用 earlycon、提高 loglevel
    • 关注 Kernel command lineRun ... as init process 输出
  2. 验证 PID1 可执行路径

    • 检查 init= / rdinit= 是否存在并可执行
    • 检查 initramfs 是否包含所需解释器/依赖
  3. 定位根挂载问题

    • 核对 root= 语法与设备可见性
    • 必要时加 rootwait
  4. 定位驱动初始化卡顿

    • 开启 initcall_debug
    • 根据最后一条 initcall 输出追踪对应驱动

11. 一句话总结

init/ 的本质是内核启动编排器:
start_kernel 完成核心基础初始化,rest_init 创建 PID1 的 kernel_init 启动任务,kernel_init_freeable 执行驱动与根文件系统准备,最后 run_init_process() 通过 kernel_execve() 把 PID1 替换成用户态 /init/sbin/init

init 文档索引

init 文档索引

Linux 6.1(RK3588)内核 init/ 目录源码分析与启动流程说明。

总览文档

文档 说明
init内核启动机制与原理详解.md 总览:启动阶段、与 arch/mm/drivers 边界、RK3588 关联
源码目录与模块索引.md init/ 文件清单、Makefile/Kconfig 对照
rk3588-kernel-6.1-init目录作用与内核启动流程分析.md 补充:早期整理的启动流程速览

功能组专题(原理 + 实现)

每篇文档结构:原理 → 实现方式 → 数据结构/接口 → RK3588 → 调试 → 附录

编号 文档 源码重点
01 start_kernel早期初始化机制与实现详解.md start_kernel()、命令行、早期子系统
02 rest_init与kernel_init机制与实现详解.md rest_initkernel_initexec init
03 initcall驱动初始化机制与实现详解.md do_initcalls、七级 initcall、异步
04 根文件系统挂载机制与实现详解.md do_mounts.cprepare_namespaceroot=
05 initramfs与早期用户态机制与实现详解.md initramfs.cinitrdramdisk_execute_command
06 init_task版本与校准机制与实现详解.md init_task.cversion*.ccalibrate.c
07 RK3588启动优化与平台关联详解.md 长 cmdline、Thunder Boot、HW 解压

源码路径

1
rk3588/kernel-6.1/init/

关联路径(不在 init/ 内但属启动链):

1
2
3
rk3588/kernel-6.1/arch/arm64/          # setup_arch、head.S
rk3588/kernel-6.1/mm/memblock.c # defer_free_memblock
rk3588/kernel-6.1/drivers/ # device initcall probe

阅读顺序建议

总览 init内核启动机制与原理详解01 start_kernel02 rest_init / kernel_init03 initcall05 initramfs04 根挂载06 init_task / version07 RK3588

核心机制速查

机制 主要文件 关键入口
内核 C 入口总控 main.c start_kernel()
调度与 PID1 线程 main.c rest_init()kernel_init()
驱动/子系统注册初始化 main.c do_initcalls()do_one_initcall()
挂真实根 do_mounts.c prepare_namespace()mount_root()
内置 cpio initramfs.c populate_rootfsunpack_to_rootfs
0 号任务模板 init_task.c init_taskinit_stack
BogoMIPS calibrate.c calibrate_delay()
RK 延迟 memblock main.c + mm/memblock.c defer_free_memblock

与 RK3588 / SeagullYpcEncode 场景

  • 启动耗时prepare_namespacewait_for_device_probe()04
  • 快启 memblockCONFIG_ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK07linuxDoc/mm/01
  • initramfs 硬件解压CONFIG_ROCKCHIP_HW_DECOMPRESS05
  • 模块签名密钥integrity_load_keys()kernel_init_freeable 末尾)→ linuxDoc/certs

init/ 源码目录与模块索引

init/ 源码目录与模块索引

源码路径rk3588/kernel-6.1/init/
内核版本:Linux 6.1(RK3588 / ARM64)
文档目录linuxDoc/init/


一、目录文件清单

文件 链接对象 职责摘要
main.c main.o start_kernelrest_initkernel_init、initcall、bootconfig、执行 init
init_task.c init_task.o 静态 init_task / init_stack、信号与 cred 模板
do_mounts.c mounts.o 之一 root=prepare_namespacemount_root
do_mounts_rd.c 可选 块 ramdisk 加载
do_mounts_initrd.c 可选 initrd 镜像处理
do_mounts.h 挂载相关内部声明
initramfs.c initramfs.o 内置 cpio 解包到 rootfs(需 CONFIG_BLK_DEV_INITRD
noinitramfs.c noinitramfs.o 无 initrd 时的最小 rootfs 占位
calibrate.c 可选 calibrate_delay() / lpj=
version.c version.o UTS_VERSION 编译期字符串
version-timestamp.c 构建生成 最终构建时间戳写入 UTS_VERSION
build-version 脚本输入 可由 KBUILD_BUILD_VERSION 覆盖版本串
Makefile 对象选择、UTS_VERSION 生成规则
Kconfig 大量全局构建/工具链选项 + 部分 initramfs 相关项

文件数量:约 15 个(含 KconfigMakefile)。


二、Makefile 对象映射

1
2
3
4
5
6
7
8
obj-y := main.o version.o mounts.o init_task.o
# CONFIG_BLK_DEV_INITRD=n → noinitramfs.o
# CONFIG_BLK_DEV_INITRD=y → initramfs.o
# CONFIG_GENERIC_CALIBRATE_DELAY → calibrate.o

mounts-y := do_mounts.o
mounts-$(CONFIG_BLK_DEV_RAM) += do_mounts_rd.o
mounts-$(CONFIG_BLK_DEV_INITRD) += do_mounts_initrd.o

mounts.omounts-y 中多个 .o 链接而成。


三、main.c 内主要符号(按启动顺序)

符号 阶段
start_kernel 单线程早期,至 arch_call_rest_init()
rest_init 创建 kernel_init 线程、kthreadd,boot CPU idle
kernel_init 等待 kthreadd → freeable → exec init
kernel_init_freeable SMP、initcall、initramfs、挂根、integrity
do_pre_smp_initcalls __initcall_start .. __initcall0_start(early)
do_basic_setup driver_init + do_initcalls
do_initcalls / do_initcall_level pure .. late 七级
do_one_initcall 单个 initcall 执行与检查
run_init_process kernel_execve 用户态 init
parse_early_param / unknown_bootoption 启动参数

四、Kconfig 说明

init/Kconfig 体积很大,多数条目属于:

  • 编译器版本探测(GCC/Clang/LD)
  • CONFIG_RUSTBUILDTIME_TABLE_SORT 等全局构建选项

启动行为 更相关的配置通常分散在:

配置 典型位置
INITRAMFS_SOURCE init/Kconfig 后部
DEFAULT_INIT init/Kconfig
BLK_DEV_INITRD drivers/block/Kconfig
ROCKCHIP_THUNDER_BOOT* Rockchip 平台 Kconfig
ARCH_ROCKCHIP arch/arm64/Kconfig

阅读 RK3588 defconfig 时需结合 archdrivers,不能只看 init/Kconfig


五、平台相关代码位置(init 内)

宏/符号 文件 说明
CONFIG_ARCH_ROCKCHIP main.c 分段打印 saved_command_line
CONFIG_ROCKCHIP_THUNDER_BOOT_DEFER_FREE_MEMBLOCK main.c kthread_run(defer_free_memblock)
CONFIG_ROCKCHIP_THUNDER_BOOT + CONFIG_ROCKCHIP_HW_DECOMPRESS initramfs.c wait_initrd_hw_decom_done()

实现体 defer_free_memblockmm/memblock.c


六、专题文档对照

源码主题 文档
start_kernel 01
rest_init / kernel_init 02
initcall 03
do_mounts* 04
initramfs / initrd 05
init_task / version / calibrate 06
Rockchip 启动优化 07

核心接口与环机制与实现详解

核心接口与环机制与实现详解

源码路径rk3588/kernel-6.1/io_uring/io_uring.cinclude/linux/io_uring_types.h
文档目录linuxDoc/io_uring/


目录


一、原理

1.1 共享环模型

生产者 消费者 内容
SQ 应用写 tail,内核读 内核更新 head 索引 → SQE 数组
CQ 内核写 tail 应用读 head struct io_uring_cqe

内核通过 mmapstruct io_rings 与 SQE 数组映射到用户空间,减少每次 I/O 的拷贝与 syscall。

1.2 请求对象 io_kiocb

每个 SQE 在提交时分配 io_kiocb(slab req_cachep),承载 opcode、file、链接关系、req->cqe 预填字段、async 数据等,完成后回收或链式触发下一个。


二、三个系统调用

2.1 io_uring_setup

1
2
3
4
5
static long io_uring_setup(u32 entries, struct io_uring_params __user *params)
{
// 校验 p.flags、p.resv
return io_uring_create(entries, &p, params);
}
  • entries:SQ 深度(向上取 2 幂,上限 IORING_MAX_ENTRIES 32768)
  • 返回 匿名文件 fdio_uring_fops),private_data 指向 io_ring_ctx
  • 可向用户写回实际 sq_entriescq_entriesIORING_SETUP_CQSIZE 可放大 CQ)

2.2 io_uring_enter

1
SYSCALL_DEFINE6(io_uring_enter, ...)
flags 行为
提交 to_submit 非 SQPOLL:mutex + io_submit_sqes
IORING_ENTER_GETEVENTS io_cqring_waitio_iopoll_check
IORING_ENTER_SQ_WAKEUP 唤醒 SQPOLL 线程
IORING_ENTER_REGISTERED_RING 使用 tctx->registered_rings[fd] 免 fdget

IORING_SETUP_R_DISABLED 时 enter 返回 -EBADFD,需 IORING_REGISTER_ENABLE_RINGS 启用。

2.3 io_uring_register

__io_uring_register 处理缓冲区/文件/eventfd/probe/限制/IOWQ 亲和性/PBUF_RING 等(见 06)。


三、io_ring_ctx 与 mmap 布局

io_uring_types.hstruct io_ringssq.head/tailcq.head/tailsq_ring_maskcq_ring_masksq_droppedsq_flagscq_overflow 等。

struct io_ring_ctx(核心字段概念):

字段类 作用
ringssq_array 用户可见环与 SQE 索引表
uring_lock 提交、注册、部分 poll 路径互斥
completion_lock CQ 写入
submit_state 批量提交缓存
flags 复制自 IORING_SETUP_*
io_wq / sq_data 异步池 / SQPOLL 共享数据
restrictions 沙箱化环(限制 register/op)

io_ring_ctx_alloc 初始化 refcount、hash table(inflight)、cqe 缓存指针等。


四、setup 标志

标志 含义
IORING_SETUP_IOPOLL 内核轮询完成(块设备忙轮询)
IORING_SETUP_SQPOLL 内核线程提交 SQ
IORING_SETUP_SQ_AFF SQPOLL 绑 CPU
IORING_SETUP_CQSIZE 自定义 CQ 长度
IORING_SETUP_SINGLE_ISSUER 单提交者优化
IORING_SETUP_DEFER_TASKRUN 延迟 task_work
IORING_SETUP_SQE128 / CQE32 扩展 SQE/CQE
IORING_SETUP_COOP_TASKRUN 协作式 task run

五、RK3588

无平台专用分支;绑核与 SQPOLL 对 大小核调度 敏感,生产环境建议实测 latency。


六、调试

1
2
3
# 需 CONFIG_IO_URING 与 tracepoints
perf trace -e io_uring:*
cat /proc/<pid>/fdinfo/<ring_fd>

fdinfo.c 导出环深度、内存注册量等。


附录

符号 文件
io_uring_create io_uring.c
io_ring_ctx_alloc ~260
io_uring_fops 同文件

下一篇02 提交与完成

提交与完成路径机制与实现详解

提交与完成路径机制与实现详解

源码路径rk3588/kernel-6.1/io_uring/io_uring.cio_uring.h
文档目录linuxDoc/io_uring/


目录


一、原理

提交路径把用户 SQE 转为 io_kiocb 并尽量 同步 issue;若返回 -EAGAIN 或 op 需要阻塞,则转入 io-wqpoll。完成路径将结果写入 CQ,必要时 batch flush 并 signal eventfd


二、提交流程

2.1 io_submit_sqes

1
2
3
4
5
6
7
8
9
10
11
12
int io_submit_sqes(struct io_ring_ctx *ctx, unsigned int nr)
{
left = min3(nr, ctx->sq_entries, io_sqring_entries(ctx));
io_get_task_refs(left);
do {
req = io_alloc_req(ctx);
sqe = io_get_sqe(ctx);
io_submit_sqe(ctx, req, sqe);
} while (--left);
io_commit_sqring(ctx);
return ret;
}
  • 持有 ctx->uring_lock(非 SQPOLL 的 enter 路径)
  • io_get_sqe:校验 index、从 sq_array 取 SQE 指针
  • io_submit_sqe:parse opcode、prep、链式/延迟/直发 io_queue_sqe
  • IORING_SETUP_SUBMIT_ALL:单个 SQE 失败仍继续提交

2.2 io_issue_sqe

1
2
3
4
5
6
7
ret = def->issue(req, issue_flags);
if (ret == IOU_OK)
io_req_complete_post(req);
else if (ret != IOU_ISSUE_SKIP_COMPLETE)
return ret;
if (IOPOLL && def->iopoll_queue)
io_iopoll_req_issued(req, issue_flags);
  • io_assign_file:普通 fdget 或 固定文件表
  • IOU_ISSUE_SKIP_COMPLETE:异步进行中(如 -EIOCBQUEUED
  • IOU_OK:同步完成,立即 post CQE

三、issue 与完成

3.1 完成写入

  • io_get_cqe:使用 cqe_cached 快速路径,耗尽则 __io_get_cqe
  • __io_commit_cqring_flush:更新 cq.tail、memory barrier
  • io_req_complete_post:链式触发、REQ_F_LINK 失败传播

3.2 io_wq_submit_work

worker 中再次 io_issue_sqe-EAGAIN 时可 arm poll 或 iopoll 重试(见 io_uring.c ~1773)。

3.3 IOPOLL

io_do_iopoll / io_iopoll_check:对支持 iopoll 的块请求轮询 ->iopoll_completed


四、链接与批量

SQE 标志 行为
IOSQE_IO_LINK 顺序执行,前一失败则跳过后续
IOSQE_IO_HARDLINK 更强链接语义
IOSQE_BUFFER_SELECT 由内核从 provide buffer 选缓冲区(kbuf.c

io_submit_state 可对同一批请求 plug 块层(opdef.plug)。


五、overflow 与 task_work

  • CQ 满:io_req_cqe_overflow → overflow 链表,cq_overflow 计数
  • IORING_SETUP_DEFER_TASKRUN:完成工作进 work_llistio_run_local_work 处理
  • tctx_task_work:与 task_work 集成,保证在进程上下文完成 UAPI 可见操作

六、调试

  • initcall_debug 不适用;使用 trace_io_uring_completetrace_io_uring_submit
  • 观察 sq_dropped:用户 SQ 索引无效

附录

返回值 含义
IOU_OK (0) 同步完成
IOU_ISSUE_SKIP_COMPLETE 已排队,勿重复 complete
-EIOCBQUEUED 同 SKIP_COMPLETE 语义(历史)

相关03 opdef04 io-wq

操作码与 opdef 分派机制与实现详解

操作码与 opdef 分派机制与实现详解

源码路径rk3588/kernel-6.1/io_uring/opdef.copdef.h、各 *.c 处理器
文档目录linuxDoc/io_uring/


目录


一、原理

所有 IORING_OP_*io_op_defs[] 中注册元数据与函数指针;io_issue_sqe 仅通过 req->opcode 索引,实现 表驱动分派,新增 opcode 主要改 opdef.c 与对应 *.c

io_uring_optable_init()(initcall)校验表长等于 IORING_OP_LAST 且每项有 prep/issue


二、io_op_def 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct io_op_def {
unsigned needs_file : 1;
unsigned plug : 1;
unsigned hash_reg_file : 1; // 常规文件 hash 到 io-wq
unsigned unbound_nonreg_file : 1;
unsigned pollin / pollout / poll_exclusive;
unsigned buffer_select : 1;
unsigned iopoll / iopoll_queue : 1;
unsigned async_size;
const char *name;
int (*prep)(struct io_kiocb *, const struct io_uring_sqe *);
int (*issue)(struct io_kiocb *, unsigned int);
...
};

issue_flags 常见位:IO_URING_F_NONBLOCKIO_URING_F_UNLOCKEDIO_URING_F_MULTISHOTIO_URING_F_COMPLETE_DEFER


三、操作码分类

类别 操作码示例 源文件
基础 NOP nop.c
读写 READVWRITEVREADWRITEREAD_FIXEDWRITE_FIXEDREADV_FIXEDWRITEV_FIXED rw.c
向量扩展 READ_MULTISHOTWRITE_MULTISHOT 等(若 6.1 已启用) rw.c
同步元数据 FSYNCSYNC_FILE_RANGE sync.c
结构 STATXOPENATCLOSERENAMEATMKDIRAT statx.copenclose.cfs.c
管道/切分 SPLICETEE splice.c
建议 FADVISEMADVISE advise.c
扩展属性 GETXATTRSETXATTR xattr.c
网络 SENDRECVACCEPTCONNECTSENDMSGRECVMSGSEND_ZC net.cCONFIG_NET
轮询 POLL_ADDPOLL_REMOVE poll.c
超时 TIMEOUTTIMEOUT_REMOVELINK_TIMEOUT timeout.c
epoll EPOLL_CTL epoll.c
取消 ASYNC_CANCEL cancel.c
环间 MSG_RING msg_ring.c
驱动命令 URING_CMD uring_cmd.c
不支持 not_supportedio_eopnotsupp_prep

IORING_REGISTER_PROBE:遍历 io_op_defsIO_URING_OP_SUPPORTED


四、实现文件映射

SQE opcodeio_op_defsprepissuerw.cnet.curing_cmd.cpoll.c

读写路径 rw.c

  • 使用内嵌 struct io_rw(含 kiocb
  • 支持 fixed bufferbuffer selectnowaitioprio
  • 块文件可走 io_read/io_writecall_read_iter / 直 IO

五、RK3588

  • 编解码场景常用:文件 READ/WRITE(码流落盘)、SOCKET(RTSP/私有协议)、URING_CMD(若存储栈支持)
  • 裁剪内核时关闭 CONFIG_NET 将导致网络 opcode 在 probe 中不可用

附录

API 作用
io_uring_get_opcode 根据 opcode 返回名称字符串
io_prep_rw 通用读写 prep

相关06 URING_CMD

异步执行与 SQPOLL 机制与实现详解

异步执行与 SQPOLL 机制与实现详解

源码路径rk3588/kernel-6.1/io_uring/io-wq.csqpoll.ctctx.c
文档目录linuxDoc/io_uring/


目录


一、原理

issue() 不能在内核提交上下文立即完成(阻塞、-EAGAIN、显式 async),请求进入 io_wq,由 bound/unbound worker 执行。
SQPOLL 则用 内核线程 代替用户态反复 io_uring_enter 提交 SQ,适合 极高 IOPS 且可牺牲一核的场景。


二、io-wq

2.1 结构(io-wq.c

概念 说明
struct io_wq 每 ring 或 attach 的共享工作队列
struct io_worker 内核线程,IO_WORKER_F_BOUND 区分绑核池
hash bucket 常规文件按 inode hash,减少锁竞争
IO_WQ_WORK_CANCEL 退出或取消时标记

2.2 入队路径

io_queue_iowqio_uring.c)根据 opdef

  • hash_reg_file → hash wq
  • unbound_nonreg_file → unbound wq(socket、pipe 等)

io_uring_register 可设置 IORING_REGISTER_IOWQ_AFFIOWQ_MAX_WORKERS

2.3 生命周期

  • worker 空闲 5*HZ 超时回收(WORKER_IDLE_TIMEOUT
  • 任务退出:io_uring_cancel_generic 取消 inflight

三、SQPOLL

3.1 struct io_sq_data

多个 ring 可共享 sqpoll 线程IORING_SETUP_ATTACH_WQ 相关路径)。

API 作用
io_sq_thread_park / unpark 注册变更时停线程
io_sq_thread_stop 销毁

3.2 io_uring_enter 行为

IORING_SETUP_SQPOLL

  • 用户 通常只 wakeupIORING_ENTER_SQ_WAKEUP)或 SQ_WAIT
  • 实际 io_submit_sqessqpoll 线程 中执行

IORING_SQ_NEED_WAKEUP:应用更新 tail 后需唤醒(memory barrier 后检查)。


四、tctx 每任务上下文

struct io_uring_taskcurrent->io_uring):

成员 作用
io_wq 任务级工作队列 attach
registered_rings[] IORING_REGISTER_RING_FDS,加速 enter
cached_refs 批量提交引用计数缓存
task_list / task_work DEFER_TASKRUN 本地工作

io_uring_alloc_task_context:首次使用 ring 时分配。


五、RK3588

  • SQPOLL + SQ_AFF:建议绑 大核(Cortex-A76/A77),避免小核导致抖动
  • IOWQ 线程数:默认随 CPU;嵌入式可限制以免与 媒体线程 争用
  • 无 Rockchip 专用 sqpoll 逻辑

附录

文件 关键符号
io-wq.c io_wq_submit_work(入口在 io_uring.c 包装)
sqpoll.c sqpoll 主循环
tctx.c ring fd 注册

相关02 提交完成

轮询、超时与取消机制与实现详解

轮询、超时与取消机制与实现详解

源码路径rk3588/kernel-6.1/io_uring/poll.ctimeout.ccancel.cepoll.c
文档目录linuxDoc/io_uring/


目录


一、原理

io_uring 把 等待就绪 也纳入环:应用提交 POLL_ADD 代替 poll() syscall;TIMEOUTLINK_TIMEOUT 控制链式请求截止时间;ASYNC_CANCEL 按 user_data/flags 取消 inflight。与 IOPOLL(块层忙等)不同,poll 面向 file->f_op->poll 语义。


二、Poll

2.1 核心(poll.c

  • io_poll_table:嵌入 poll_table_struct,在 vfs_poll 中登记 waitqueue
  • multishotIORING_OP_POLL_ADD + multishot 标志可多次 CQ 而不注销
  • IO_POLL_REF_BIAS:引用计数防并发卸载
  • io_poll_issue:multishot 就绪时从 task_work 调用 io_issue_sqe

2.2 更新与删除

  • POLL_REMOVE:按 user_data 移除
  • IORING_OP_POLL_UPDATE(若启用):io_poll_update 结构更新 events/user_data

2.3 与 read/write 协作

io_wq_submit_work-EAGAIN 时对 pollin/pollout op arm poll,就绪后重试 issue。


三、Timeout

timeout.c

操作码 作用
TIMEOUT 绝对/相对超时,可产生 CQE
TIMEOUT_REMOVE 按 user_data 删除
LINK_TIMEOUT 链接序列的截止时间

REQ_F_ARM_LTIMEOUTio_arm_ltimeout 配合,在 worker 或提交路径启动链接超时。

io_cqring_wait 比较 cq_timeouts 判断 wait 是否因超时唤醒。


四、Cancel

cancel.c + IORING_REGISTER_SYNC_CANCEL

  • 按 fd、user_data、opcode 过滤 inflight 请求
  • 进程退出:io_uring_cancel_generic 清理 task 上所有 ring
  • io_match_task_safe 判断请求归属

五、Epoll

epoll.cIORING_OP_EPOLL_CTL 将 epoll 管理纳入 io_uring(减少 epoll_ctl syscall),需 CONFIG_EPOLL


六、调试

  • multishot poll:确认应用处理 多次 CQE 且最终 REMOVE
  • 超时链:检查 IOSQE_IO_LINK 顺序

附录

文件 行级规模
poll.c ~1060
timeout.c 中等
cancel.c 中等

相关02 完成路径

资源注册与 URING_CMD 机制与实现详解

资源注册与 URING_CMD 机制与实现详解

源码路径rk3588/kernel-6.1/io_uring/rsrc.cfiletable.ckbuf.curing_cmd.c
文档目录linuxDoc/io_uring/


目录


一、原理

高频 I/O 避免每次 fdget 与用户指针 copy_from_user

  • 固定文件表:SQE 中 IOSQE_FIXED_FILE + index
  • 固定缓冲区:pin 用户页,READ_FIXED/WRITE_FIXED 直 DMA 友好
  • URING_CMD:块设备/NVMe 等驱动自定义命令,类似 ioctl 但纳入环

注册通过 io_uring_register,受 RLIMIT_MEMLOCK 约束(__io_account_mem)。


二、文件与缓冲区注册

2.1 经典接口

opcode 函数
IORING_REGISTER_FILES io_sqe_files_register
IORING_REGISTER_BUFFERS io_sqe_buffers_register
IORING_REGISTER_FILES_UPDATE 增量更新

2.2 v2(rsrc.c

  • IORING_REGISTER_FILES2 / BUFFERS2
  • struct io_uring_rsrc_register:支持 tag、范围更新
  • 上限:IORING_MAX_FIXED_FILES (1<<20)、IORING_MAX_REG_BUFFERS (1<<14)

filetable.cio_fixed_filefile*FFS_* 标志。


三、Provide buffer 与 PBUF_RING

kbuf.c

机制 说明
PROVIDE_BUFFERS 应用提交缓冲区池,读路径 IOSQE_BUFFER_SELECT 由内核选 bid
IORING_REGISTER_PBUF_RING 内核与用户共享 buffer ring(更新式提供缓冲)
io_kbuf_recycle_legacy 完成后回收 buffer 供下次使用

bgid(buffer group id)索引 io_buffer_list


四、IORING_OP_URING_CMD

uring_cmd.c

1
2
3
4
5
6
7
8
void io_uring_cmd_done(struct io_uring_cmd *ioucmd, ssize_t ret, ...)
{
io_req_set_res(req, ret, 0);
if (IOPOLL)
smp_store_release(&req->iopoll_completed, 1);
else
__io_req_complete(req, issue_flags);
}

驱动流程:

  1. issue 调用 file->f_op->uring_cmd(或块层桥接)
  2. 若异步完成,返回 -EIOCBQUEUED,之后 io_uring_cmd_doneio_uring_cmd_complete_in_task
  3. IORING_SETUP_SQE128 时 PDU 更大(uring_cmd_pdu_size

导出符号:供 NVMe、SCSI、自定义块驱动使用。


五、RK3588

  • eMMC/UFS/NVMe 若启用 blk-uring 或 NVMe passthrough,URING_CMD 可降低 ioctl 开销
  • 注册大块 DMA buffer 时注意 memlock ulimitCMA(见 linuxDoc/mm/09
  • 无平台专用 rsrc 代码

附录

限制
IO_RINGFD_REG_MAX 16(每任务注册 ring fd)
memlock __io_account_mem

相关01 register03 opdef

RK3588 平台关联与使用场景详解

RK3588 平台关联与使用场景详解

源码路径rk3588/kernel-6.1/io_uring/(通用实现,无 Rockchip 补丁)
文档目录linuxDoc/io_uring/


目录


一、平台结论

说明
源码差异 grep rockchip io_uring/ 无匹配
架构 ARM64,与 x86 io_uring 同源,性能特征受 内存带宽、存储控制器、网卡驱动 影响
默认配置 CONFIG_IO_URING=yinit/Kconfig

RK3588 上使用 io_uring = 标准 Linux 6.1 行为 + BSP 存储/网络栈质量。


二、硬件与内核配置

组件 与 io_uring 关系
eMMC/UFS READV/WRITEV + 可选 IORING_SETUP_IOPOLL(设备需支持 non-blocking 完成路径)
NVMe URING_CMD、高队列深度受益明显
千兆/2.5G 网卡 net.c 中 SEND/RECV/ZC;驱动需支持 MSG_ZEROCOPY
big.LITTLE IORING_SETUP_SQ_AFFIORING_REGISTER_IOWQ_AFF 建议绑大核
内存 固定 buffer 注册消耗 locked_vm

三、SeagullYpcEncode 相关场景

场景 建议用法
码流写盘 WRITE/WRITE_FIXED + 注册文件表;批量 IOSQE_IO_LINK 写元数据
高帧率网络拉流 RECV/RECVMSG + POLL_ADD multishot 减 syscall
与 VPU/RGA 协同 io_uring 不直接操作媒体 IP;DMA buffer 若经 dmabuf,注意与 registered buffer 的 pin 范围
用户态框架 liburing、tokio-uring 等,均基于本目录 syscall

不宜期望:io_uring 替代 内核驱动 ioctl 配置 VPU;媒体管线仍走 V4L2/MPP,存储/网络可用 io_uring 加速。


四、性能与调优

手段 说明
IORING_SETUP_SQPOLL 省提交 syscall,占用 1 核
IORING_SETUP_SINGLE_ISSUER 无锁提交优化前提
增大 entries 减少背压;注意内存
REGISTER_FILES / REGISTER_BUFFERS2 热路径免 fdget/pin
避免 memlock 过小 ulimit -l / systemd LimitMEMLOCK
IOPOLL 仅低延迟块场景;CPU 占用高

五、观测与工具

1
2
3
4
5
grep CONFIG_IO_URING /boot/config-$(uname -r)
perf trace -e 'syscalls:sys_enter_io_uring_*'
# 应用层
./io_uring-bench # 需自行编译 liburing 测试
strace -e io_uring_setup,io_uring_enter -c <app>
接口 信息
/proc/<pid>/fdinfo/<fd> 环参数(fdinfo.c
IORING_REGISTER_PROBE 当前内核支持的 op

关联文档