Posted on 

blkio 源码分析

背景

之前在发现线上 blkio cgroup 层级关系中 io_merged 文件内容不符合预期,所以才有这一篇源码分析。

4.9 kernel

初始化

我们都知道 blkio 是基于 IO 的 scheduler 的实现的, 基于线上的唯一的一块SATA盘的且调度算法是 cfq, 读一下 cfq 实现 blkio 的相关代码,首先到 cfq 的模块的入口函数 cfq_init()移除了一些错误处理相关的代码.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static int __init cfq_init(void)
{
int ret;

#ifdef CONFIG_CFQ_GROUP_IOSCHED
ret = blkcg_policy_register(&blkcg_policy_cfq); // 如果 CONFIG_CFQ_GROUP_IOSCHED , 也就是启动 blkio 需要的内核选项, 这个变量的位置就是
if (ret)
return ret;
#else
cfq_group_idle = 0;
#endif

ret = -ENOMEM;
cfq_pool = KMEM_CACHE(cfq_queue, 0);
if (!cfq_pool)
goto err_pol_unreg;

ret = elv_register(&iosched_cfq); // ...实现调度算法的 callback func 即可
if (ret)
goto err_free_pool;
...

blkcg_policy_register(&blkcg_policy_cfq) 中的 blkcg_policy_cfq 变量的初始化如下. 其中的 cfq_blkcg_legacy_files 就是需要特别关注点的他在用户态以文件形式体现出来.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifdef CONFIG_CFQ_GROUP_IOSCHED
static struct blkcg_policy blkcg_policy_cfq = {
.dfl_cftypes = cfq_blkcg_files, // 这里变量是个只有只有一个 .name = "weight",
.legacy_cftypes = cfq_blkcg_legacy_files, // 这些是主要的用户态 blkio 文件

.cpd_alloc_fn = cfq_cpd_alloc, // 有 fn 是回调函数, 没有的就是用户态文件
.cpd_init_fn = cfq_cpd_init,
.cpd_free_fn = cfq_cpd_free,
.cpd_bind_fn = cfq_cpd_bind,

.pd_alloc_fn = cfq_pd_alloc,
.pd_init_fn = cfq_pd_init,
.pd_offline_fn = cfq_pd_offline,
.pd_free_fn = cfq_pd_free,
.pd_reset_stats_fn = cfq_pd_reset_stats,
};
#endif

其实我们在 Linux 中看的 blkio.io_merged 就是这里的这个变量产生的, 其中 blkio 是 sub system 名字. 在下面的代码中会拼凑出.

1
2
3
4
5
6
7
8
9
static struct cftype cfq_blkcg_legacy_files[] = {
/* on root, weight is mapped to leaf_weight */
...
{
.name = "io_merged",
.private = offsetof(struct cfq_group, stats.merged),
.seq_show = cfqg_print_rwstat,
},
...

看一下 blkiocg policy 是如何注册的这些信息中, 虽然对其一部分逻辑还没有完全搞懂,但是我知道了这个随着 CFQ 一起初始化了,在内核空间中维护一些变量.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* blkcg_policy_register - register a blkcg policy
* @pol: blkcg policy to register
*
* Register @pol with blkcg core. Might sleep and @pol may be modified on
* successful registration. Returns 0 on success and -errno on failure.
*/
int blkcg_policy_register(struct blkcg_policy *pol)
{
struct blkcg *blkcg;
int i, ret;
....
/* register @pol */
pol->plid = i;
blkcg_policy[pol->plid] = pol;

/* allocate and install cpd's */
if (pol->cpd_alloc_fn) {
list_for_each_entry(blkcg, &all_blkcgs, all_blkcgs_node) {
struct blkcg_policy_data *cpd;

cpd = pol->cpd_alloc_fn(GFP_KERNEL);
...
blkcg->cpd[pol->plid] = cpd; // 看到现在对这些不是很明白
cpd->blkcg = blkcg;
cpd->plid = pol->plid;
pol->cpd_init_fn(cpd);
}
}
..
/* everything is in place, add intf files for the new policy */
if (pol->dfl_cftypes)
// cgroup_add_dfl_cftypes 函数描述 add an array of cftypes for default hierarchy
// Similar to cgroup_add_cftypes() but the added files are only used for the default hierarchy.
WARN_ON(cgroup_add_dfl_cftypes(&io_cgrp_subsys,
pol->dfl_cftypes));
if (pol->legacy_cftypes)
// cgroup_add_legacy_cftypes - add an array of cftypes for legacy hierarchies
// Similar to cgroup_add_cftypes() but the added files are only used for the legacy hierarchies.
// 其实到这里我们知道这个地方就是处理文件初始化的地方, 这里说的是文件初始化不包含内容正确.
// 这里在 cgroup fs blkio 中创建文件的地方, 拼接名字也是在这里完成的.
WARN_ON(cgroup_add_legacy_cftypes(&io_cgrp_subsys,
pol->legacy_cftypes));
.... // 移除一些错误处理的代码,主要是内核分配内存失败释放内存的逻辑
EXPORT_SYMBOL_GPL(blkcg_policy_register);

下面这个就是按照内核提供的 cgroup 框架实现的一个 controller groupsub system 名字叫 blkio, 其实 cgroup 框架的抽象也能猜测行为, 如果猜的不对可以看 kernel/cgroup.c 中的调用方式.

1
2
3
4
5
6
7
8
9
10
11
12
struct cgroup_subsys io_cgrp_subsys = {
.css_alloc = blkcg_css_alloc, // css alloc = cgroup subsystem 的 内存分配
.css_offline = blkcg_css_offline, // 就是下掉当前 sub system, 其实没有怎么在生产中见过下掉 sub system
.css_free = blkcg_css_free, // 释放内存
.can_attach = blkcg_can_attach, // 从 https://elixir.bootlin.com/linux/v4.9.10/source/kernel/cgroup.c#L5179 主要是和管理当前`层级`task 的
.bind = blkcg_bind, // 文件系统里面的 mount bind
.dfl_cftypes = blkcg_files, // 涉及的用户态文件
.legacy_cftypes = blkcg_legacy_files, // 涉及的用户态文件
.legacy_name = "blkio",
...
};
EXPORT_SYMBOL_GPL(io_cgrp_subsys);

以上基本上将 blkio 在 cfq 实现的初始化.

回头看 io_merged 存在内容为空问题

cgroup blkio
cgroup blkio

看一下 blkio.io_merged 这个文件写的过程.

linux vfs
linux vfs

1. [腾讯 Tlinux 的一个优化](https://github.com/Tencent/TencentOS-kernel/commit/e7636c1b8b5e4214b68d048ae3d3442045ea757c#diff-aa274e206b462ae50089a5a3e16d6926e2001404a1876032316f7c37e08cd111)  可以在 github 上看到,做 pre blkcg 的隔离