1 介绍
BFQ,全称 Budget Fair Queuing / 预算公平排队,是Linux基于CFQ的一个“比例共享”的IO调度算法。按照官方的说法,它具有“高吞吐,低延迟以及公平性”的特点。Linux 5.0内核中,CFQ已经被完全删除,取而代之的是诸如BFQ的多队列算法。
不同于CFQ中的时间片,BFQ使用预算(budget)分配的方式来实现公平:每个进程在一个窗口有预算的sector数量,花完所有sector(或超时)则进程的请求队列被挂起,等候调度。同时budget会不断调整,如果进程花完了budget,下一次的budget会增加;若没花完,则下一次的budget会减少。而budget越小,调度越频繁(时间敏感应用),budget越大,执行时间越长(高吞吐应用),bfq以这种自适应的方式来实现不同进程的需求,实现低延迟和高吞吐。
此外,BFQ考虑进程的IO优先级,来决定进程在何时能够被调度。BFQ“严格执行优先级,只要存在较高优先级的队列,就不会为低优先级队列提供服务”。可以通过ionice命令设定优先级。
具体参考:linux/bfq-iosched.rst at master ·torvalds/linux (github.com)
2 进程的IO优先级
ionice将IO调度分为三大类:
(1)RT(Real Time):实时调度,不考虑其他进程的IO,立即访问磁盘。
(2)BE(Best Effort):缺省调度策略,可以指定优先级(0~7),数值越小优先级越高。
(3)IDLE:空闲调度,当没有其他进程需要IO时,才能进行调度,无优先级可言。
PS: 此外还有一个(0)none 优先级。未设置优先级时默认为none,其实也就是BE,只是优先级数是通过cpu中的进程优先级自动计算出来的, io_priority = (cpu_nice +20)/5。一般的用户程序,cpu优先级是0(可查看top命令,“NI”项大多为0),因此默认的io优先级为4。
以上默认IO请求都是同步请求,因为同步IO才是针对进程而言的。
3 使用方式
3.1 更改IO调度策略
注:linux 5.0以上的版本。
查看当前调度策略,"sda"对应想要修改策略的盘:
~$ cat /sys/block/sda/queue/scheduler
[mq-deadline] none
此时这里显示并没有bfq,先检测一下你的系统中有没有bfq模块:
~$ sudo modprobe bfq
没有报错说明是有的,再次查看调度策略,已经有bfq了:
~$ cat /sys/block/sda/queue/scheduler
[mq-deadline] bfq none
修改为bfq,再次查看:
~$ sudo echo 'bfq'>/sys/block/sda/queue/scheduler
~$ cat /sys/block/sda/queue/scheduler
mq-deadline [bfq] none
如果要设置bfq随系统启动加载,参考。
3.2 BFQ调参
BFQ具有以下参数,参数列表 。
例:若要设置low_latency参数为0
~$ echo 0 > /sys/block/sda/queue/iosched/low_latency
3.3 设置IO优先级
除了使用ionice命令设置优先级外,还可以使用ioprio_set()系统调用设置进程(线程)的IO优先级,Linux手册链接。
可能会出现头文件linux/ioprio.h链接失败的问题,导致宏定义找不到,图方便可以直接把宏定义copy过来。示例代码:
#include <cstdio>
#include <iostream>
//#include <linux/ioprio.h> /* Definition of IOPRIO_* constants */
#include <sys/syscall.h> /* Definition of SYS_* constants */
#include <unistd.h>
using namespace std;
#define IOPRIO_CLASS_SHIFT (13)
#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data)
enum {
IOPRIO_WHO_PROCESS = 1, // 代表下一个参数为线程号(0代表自身)
IOPRIO_WHO_PGRP, // 代表下一个参数为组号(0代表自身所在组)
IOPRIO_WHO_USER, // 代表下一个参数为用户ID(..)
};
// 以上宏定义从linux/ioprio.h中复制
enum {
RT = 1,
BE ,
IDLE,
};
int main(){
// 获取当前线程优先级
int prio = syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, 0);
cout << (prio>>IOPRIO_CLASS_SHIFT) << "," << (prio & 0b1111) << endl;
// 设置当前线程IO优先级为7 (BE)
syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, 0, IOPRIO_PRIO_VALUE(BE, 7));
// 设置PID=80的线程IO优先级为IDLE
syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, 80, IOPRIO_PRIO_VALUE(IDLE, 0));
// 设置当前线程所在组的所有线程IO优先级为2(RT)
syscall(SYS_ioprio_set, IOPRIO_WHO_PGRP, 0, IOPRIO_PRIO_VALUE(RT, 2));
// 设置进程组号为20的所有线程IO优先级为2(BE)
syscall(SYS_ioprio_set, IOPRIO_WHO_PGRP, 20, IOPRIO_PRIO_VALUE(BE, 2));
prio = syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, 0);
cout << (prio>>IOPRIO_CLASS_SHIFT) << "," << (prio & 0b1111) << endl;
return 0;
}