Linux 内核块设备 I/O 调度器在线切换:sysfs scheduler 接口与 block/elevator.c 源码剖析 📅 发布时间:2026/9/14 22:01:48 👁 浏览次数: Linux 内核块设备 I/O 调度器在线切换sysfs scheduler 接口与 block/elevator.c 源码剖析【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux本篇基于内核文档 switching-sched.rst 展开讲解 Linux 块层如何按设备粒度查看并在线切换 I/O 调度器mq-deadline、kyber、bfq、none。读完本文你不仅能掌握echo SCHEDNAME /sys/block/DEV/queue/scheduler这类日常运维操作的完整用法还能从 block/elevator.c 源码层面理解调度器切换的冻结、排空、回退机制以及切换之后各调度器在/sys/block/DEV/queue/iosched下暴露的调优参数。一、在哪里找到 I/O 调度器的 sysfs 接口每个 I/O 队列request queue都关联一组 I/O 调度器调优参数这些参数控制调度器的工作方式。内核文档指出这些条目位于/sys/block/device/queue/iosched前提是系统已挂载 sysfs。如果尚未挂载可以执行# mount none /sys -t sysfs从源码结构看iosched目录是由调度器框架在注册队列时创建的elv_register_queue() 通过kobject_add(e-kobj, q-disk-queue_kobj, iosched)在queuekobject 下建立名为iosched的子目录随后遍历该调度器elevator_attrs数组逐个sysfs_create_file把该调度器特有的可调参数如 deadline 的read_expire、kyber 的read_lat_nsec挂载进去完成后还会调用blk_mq_sched_reg_debugfs(q)在 debugfs 中导出调度器调试接口。因此只有当前挂载了某个调度器的设备其iosched目录里才有该调度器的参数文件调度器切换时这个目录的内容也会随之变化旧调度器属性由 elv_unregister_queue() 移除。而调度器选择本身则通过queue/scheduler属性读写读它可以看到可选列表及当前值写它则触发一次在线切换。二、查看当前调度器与可选列表对任意块设备执行# cat /sys/block/sda/queue/scheduler [mq-deadline] kyber bfq none输出中方括号[...]标注的是当前生效的调度器。文档给出的示例# cat /sys/block/sda/queue/scheduler [mq-deadline] kyber bfq none # echo none /sys/block/sda/queue/scheduler # cat /sys/block/sda/queue/scheduler [none] mq-deadline kyber bfq这个输出格式由 elv_iosched_show() 实现函数先持有q-elevator_lock若q-elevator为 NULL 则先打印[none]否则打印none然后加elv_list_lock遍历全局已注册调度器链表elv_list当前调度器用[name]形式输出其余以普通名称输出。也就是说none 是框架内建概念、恒在列表中而 mq-deadline、kyber、bfq 等则出现在它们各自的模块加载或内建编译并调用elv_register()注册之后。三、在线切换调度器文档给出的核心操作是echo SCHEDNAME /sys/block/DEV/queue/scheduler其中SCHEDNAME是某个已定义的 I/O 调度器名称mq-deadline、none、bfq、kyberDEV是设备名如sda、nvme0n1、mmcblk0。切换是逐设备生效的——不同磁盘可以使用不同调度器从而针对各自介质特性优化吞吐或延迟。3.1 sysfs 写路径模块自动加载与并发保护写scheduler属性最终进入 elv_iosched_store()。源码中有几个值得注意的环节队列状态检查若blk_queue_registered(q)不成立设备正在移除直接返回-ENOENT。模块自动加载先拷贝并strstrip去掉空白再由 elv_iosched_load_module() 检查该名称是否已注册未注册则执行request_module(%s-iosched, elevator_name)——即写入kyber会自动尝试加载kyber-iosched内核模块。代码注释特意说明这一步必须在冻结队列之前完成否则当模块文件恰好存放在本设备上的文件系统时冻结队列会导致模块无法读取。锁与串行化使用down_write_trylock(set-update_nr_hwq_lock)写锁且为 trylock 语义取不到锁返回-EBUSY。注释解释了原因既要避免与 kernfs active 引用的循环依赖update_nr_hwq_lock - kn-active与kn-active - update_nr_hwq_lock两条路径交叉又要把elevator_change()的两阶段切换elevator_lock内的核心切换 锁外的elevator_change_done()串行化。设备级禁切开关若队列设置了QUEUE_FLAG_NO_ELV_SWITCHblk_queue_no_elv_switch(q)为真写入返回-ENOENT。部分控制器或 passthrough 设备会主动禁用调度器切换写入失败且报 No such file or directory 即源于此。3.2 切换核心冻结、排空、失败回退elv_iosched_store随后调用 elevator_change()若目标不是none先通过blk_mq_alloc_sched_res()为新调度器分配每个硬件队列所需的调度资源失败则提前返回不破坏现状blk_mq_freeze_queue(q)冻结队列阻止新的 I/O 进入设备尚未 add 时无文件系统 I/O则冻结加blk_mq_cancel_work_sync()已足以排空派发动作避免 quiesce 带来的长启动延迟持有q-elevator_lock调用 elevator_switch()先elevator_match判断是否已在目标调度器上是则跳过否则blk_mq_quiesce_queue(q)排空在途派工、elevator_exit(q)退出旧调度器再blk_mq_init_sched()初始化新调度器切换到none时则清除QUEUE_FLAG_SQ_SCHED标志、把nr_requests/async_depth恢复为tag_set-queue_depth全程通过blk_add_trace_msg(q, elv switch: %s, ...)留下 trace 事件便于用 ftrace 观察切换行为失败回退若初始化失败pr_warn(elv: switch to \%s\ failed, falling back to \none\\n, ...)会打印告警队列回退到无调度器状态none而非停留在半初始化状态。elevator_change()结尾还会释放已分配但未能启用的调度资源。3.3 调度器的注册与默认选择每个调度器模块在内建或加载时通过elv_register()注册mq-deadlinereturn elv_register(mq_deadline);、kyberreturn elv_register(kyber_sched);、bfqret elv_register(iosched_bfq_mq);。elv_register() 会强制要求调度器实现finish_request、insert_requests、dispatch_request三个操作拒绝重名注册返回-EBUSY可选地按icq_size创建 io_cq 的 kmem cache并打印io scheduler %s registered到内核日志。设备上线时的默认调度器由 elevator_set_default() 决定源码注释写得很明确单硬件队列q-nr_hw_queues 1或使用共享 tag 的设备默认使用mq-deadline多硬件队列设备典型如 NVMe 的多队列默认none多队列场景下由驱动按 CPU 分发到各硬件队列软件调度器收益有限tag_set 设置了BLK_MQ_F_NO_SCHED_BY_DEFAULT的设备完全不挂载调度器。默认值只是初始状态用户随时可用本文第二、三节的 sysfs 操作改写。四、切换后各调度器在 iosched 目录下的调优参数4.1 mq-deadline 可调参数按 deadline-iosched.rst 的说明mq-deadline 尝试为每个请求保证一个开始服务时间暴露以下参数参数单位/类型含义read_expire毫秒读请求进入调度器时截止时间为当前时间 read_expire用于保证读延迟上界write_expire毫秒同 read_expire针对写请求fifo_batch请求数同方向请求按扇区递增顺序成批batch服务batch 之间才检查截止时间值越小延迟越低1 即先来先服务越大吞吐越好、延迟波动越大writes_starved派工次数读优先策略下连续优先读该次数后按相同标准派工写请求防止写饥饿front_mergesbool是否做前向合并front merge。由于文件布局特点后向合并更常见可置 0 关闭 rbtree 前向扇区查找基于 last_merge 缓存的零成本前向合并仍会保留4.2 kyber 可调参数按 kyber-iosched.rstkyber 只有两个参数且语义是目标延迟而非超时阈值——kyber 通过限流throttle来达成目标延迟read_lat_nsec读的目标延迟纳秒write_lat_nsec同步写的目标延迟纳秒。4.3 bfq 与 nonebfq 的完整参数集见 bfq-iosched.rst核心是按进程做带宽公平分配none则不经过任何软件调度请求直接进入硬件队列iosched目录中也不会有可调参数。此外queue/ioprio目录见 ioprio.rst中的 I/O 优先级可与调度器配合使用属于同一套块层调优体系。五、注意事项与常见坑elevator内核启动参数已失效。block/elevator.c 中专门保留了__setup(elevator, elevator_setup)其处理函数只打印警告Kernel parameter elevator does not have any effect anymore. Please use sysfs to set IO scheduler for individual devices.——请改用本文的 sysfs 方式按设备设置。切换粒度是设备而非全局/sys/block/DEV/queue/scheduler只影响该设备整盘同一块盘的所有分区共用其队列与调度器。写入失败的含义写后cat未见变化或收到错误时按源码排查顺序为模块名不存在-EINVAL路径→ 设备设置NO_ELV_SWITCH禁切-ENOENT→update_nr_hwq_lock竞争-EBUSY可重试→ 新调度器初始化失败回退 none 并见内核日志pr_warn。多队列设备切回 mq-deadline 等调度器时调度器内部状态与硬件队列数绑定当nr_hw_queues变化时elv_update_nr_hw_queues() 会强制重新执行elevator_switch()以重建调度资源切换路径与普通写入一致。六、小结Linux 块层的 I/O 调度器是每队列、可热切、参数随调度器动态暴露的设计查看用cat /sys/block/DEV/queue/scheduler当前值在方括号中切换用echo SCHEDNAME /sys/block/DEV/queue/scheduler切换过程由elevator_change()以冻结 quiesce 保证不丢在途 I/O失败自动回退none切换成功后在/sys/block/DEV/queue/iosched下即可调整该调度器的read_expire、fifo_batch、read_lat_nsec等参数。理解这套机制后再配合 blk-mq.rst 中关于块层多队列架构的文档即可对吞吐与延迟敏感的业务数据库、存储节点做出有据可依的调度器选型。【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考