Linux内存管理(七十二):Linux PSI 原理更新(v5.15)

源码基于:Linux 5.15

约定:

  • 芯片架构:ARM64
  • 内存架构:UMA
  • CONFIG_ARM64_VA_BITS:39
  • CONFIG_ARM64_PAGE_SHIFT:12
  • CONFIG_PGTABLE_LEVELS3

0. 前言

本文是在之前**《PSI 详解 v5.4》**一文基础上,整理一下PSI 原理中的细节,包含 cgroup v2 关于PSI 的原理和使用。

参考:

PSI 详解 v5.4

PSI 指标

PSI 功能依赖 CONFIG_PSI ,当该 config 没有使能,psi.c 是不会被编译到 image的。

1. PSI 中涉及的重要属性

本节主要梳理 PSI 中一些重要的变量、属性值,笔者习惯把重要的属性总结到最前文,阅读PSI 原理可以暂跳过此节。

1.1 psi_enable

cpp 复制代码
kernel/sched/psi.c

#ifdef CONFIG_PSI_DEFAULT_DISABLED
static bool psi_enable;
#else
static bool psi_enable = true;
#endif

静态全局变量 psi_enable 受限于 CONFIG_PSI_DEFAULT_DISABLED,默认该 config 不会使能,即 psi_enable 为 true。

另外, 可以通过 cmdline 中属性***"psi="***修改该值:

cpp 复制代码
kernel/sched/psi.c

static int __init setup_psi(char *str)
{
	return kstrtobool(str, &psi_enable) == 0;
}
__setup("psi=", setup_psi);

当 psi_enable 为false 时,PSI 全局控制变量 psi_disabled(static key) 会被置成 true:

cpp 复制代码
kernel/sched/psi.c

void __init psi_init(void)
{
	if (!psi_enable) {
		static_branch_enable(&psi_disabled);
		return;
	}

    ...
}

1.2 psi_period

cpp 复制代码
kernel/sched/psi.c

/* Sampling frequency in nanoseconds */
static u64 psi_period __read_mostly;

1.3 psi_cgroups_enabled

cpp 复制代码
kernel/sched/psi.c

DEFINE_STATIC_KEY_TRUE(psi_cgroups_enabled);

该变量主要确认 cgroup 中是否启动了 PSI 功能,默认值为 true。

cgroup 模块通过在 cmdline 中设定 "cgroup_disable=" 属性来禁用功能,例如:

  • 设定 "cgroup_disable=cpuset" 来禁用 cpuset;
  • 设定 "cgroup_disable=pressure" 来禁用 PSI;

在 PSI 初始化 **psi_init()**函数调用时,会确定 cgroup 模块是否禁用了 pressure,如果 cgroup 中禁用了 PSI,则 psi_cgroups_enabled 的值会被置为 false。

另外,与 Linux6.6 不同之处是,该变量在 Linux6.6 中定义时加上了 static:

cpp 复制代码
static DEFINE_STATIC_KEY_TRUE(psi_cgroups_enabled);

1.4 psi_system

正常情况下,PSI 中通过 struct psi_group 结构体管理 PSI 中所有状态、数据、work 等信息。

psi_system 是 struct psi_group 结构体变量,用以管理系统 PSI 状态、数据、work 等信息。

cpp 复制代码
kernel/sched/psi.c

static DEFINE_PER_CPU(struct psi_group_cpu, system_group_pcpu);
struct psi_group psi_system = {
	.pcpu = &system_group_pcpu,
};

但,在 cgroup 中也可以实现PSI 局部管理,通过对 cpu.pressurememory.pressureio.pressure 等文件 write 操作注册 psi_trigger,实现对特定 cgroup 的资源瓶颈状态的监听和跟踪。

1.4 struct psi_group

cpp 复制代码
include/linux/psi_types.h

struct psi_group {
	//avgs_work 数据同步锁
	struct mutex avgs_lock;

	/* Per-cpu task state & time tracking */
	struct psi_group_cpu __percpu *pcpu;

	/* Running pressure averages */
	u64 avg_total[NR_PSI_STATES - 1];

    //avgs_work的时间点
	u64 avg_last_update;
	u64 avg_next_update;

	//带有定时器的avgs_work
	struct delayed_work avgs_work;

	/* Total stall times and sampled pressure averages */
	u64 total[NR_PSI_AGGREGATORS][NR_PSI_STATES - 1];
	unsigned long avg[NR_PSI_STATES - 1][3];

	//psimon 线程
	struct task_struct __rcu *poll_task;

	struct timer_list poll_timer;
	wait_queue_head_t poll_wait;
	atomic_t poll_wakeup;

	/* Protects data used by the monitor */
	struct mutex trigger_lock;

	//所有psi_trigger都存在链表中
	struct list_head triggers;

    //每个psi_states类型的psi_trigger 数量
	u32 nr_triggers[NR_PSI_STATES - 1];

    //统计poll_work 需要处理哪些psi_states类型的 trigger
	u32 poll_states;

    //记录poll_work 最小的周期
	u64 poll_min_period;

	/* Total stall times at the start of monitor activation */
	u64 polling_total[NR_PSI_STATES - 1];
	u64 polling_next_update;
	u64 polling_until;
};

2. PSI module 初始化

在module init 时调用psi_proc_init() 进行 PSI 的初始化,创建了proc 虚拟文件目录 pressure,并在其下面创建了三个子文件:pressure/io、pressure/memory、pressure/cpu

与 Linux5.4 版本不同之处是 ,这些节点的创建是在 psi_enable为true 条件下:

cpp 复制代码
kernel/sched/psi.c

static int __init psi_proc_init(void)
{
	if (psi_enable) {
		proc_mkdir("pressure", NULL);
		proc_create("pressure/io", 0, NULL, &psi_io_proc_ops);
		proc_create("pressure/memory", 0, NULL, &psi_memory_proc_ops);
		proc_create("pressure/cpu", 0, NULL, &psi_cpu_proc_ops);
	}
	return 0;
}
module_init(psi_proc_init);

更多关于 psi_enable 变量信息可以查看上文第1.1节。

3. PSI 初始化 psi_init()

cpp 复制代码
start_kernel()
    ---->sched_init()
        ---->psi_init()

与 Linux5.4 不同之处是 ,这里开始多了 cgroup 的控制,主要是通过控制变量 psi_cgroups_enabled(static key),默认为true。

cpp 复制代码
void __init psi_init(void)
{
	if (!psi_enable) {
		static_branch_enable(&psi_disabled);
		return;
	}

	if (!cgroup_psi_enabled())
		static_branch_disable(&psi_cgroups_enabled);

	psi_period = jiffies_to_nsecs(PSI_FREQ);
	group_init(&psi_system);
}

与 Linux6.6 不同之处是,当 psi_enable 为false 时,也会将 psi_cgroups_enabled 置为 false。

更多关于 psi_cgroups_enabledpsi_periodpsi_system 可以查看上文第 1.2节。

3.1 group_init()

cpp 复制代码
kernel/sched/psi.c

static void group_init(struct psi_group *group)
{
	int cpu;

	for_each_possible_cpu(cpu)
		seqcount_init(&per_cpu_ptr(group->pcpu, cpu)->seq);

    //初始化avgs_work 的两个时间点
	group->avg_last_update = sched_clock();
	group->avg_next_update = group->avg_last_update + psi_period;

    //初始化带定时器的work,并指定处理函数为psi_avgs_work()
	INIT_DELAYED_WORK(&group->avgs_work, psi_avgs_work);

    //初始化avgs_work 数据同步的 avgs_lockk
	mutex_init(&group->avgs_lock);

	/* Init trigger-related members */
	atomic_set(&group->poll_wakeup, 0);
	mutex_init(&group->trigger_lock);

    //初始化psi_trigger list
	INIT_LIST_HEAD(&group->triggers);

    //初始化每个psi_states类型的psi_trigger 数量
	memset(group->nr_triggers, 0, sizeof(group->nr_triggers));

    //初始化poll_work 需要处理哪些psi_states类型的 trigger
	group->poll_states = 0;

    //初始化poll_work 的时间间隔,对于psi_system 这个group只记录最小值
	group->poll_min_period = U32_MAX;

	memset(group->polling_total, 0, sizeof(group->polling_total));

    //初始化下一次poll_work触发的时间
	group->polling_next_update = ULLONG_MAX;


	group->polling_until = 0;

    //初始化poll_work的等待队列
	init_waitqueue_head(&group->poll_wait);
	timer_setup(&group->poll_timer, poll_timer_fn, 0);
	rcu_assign_pointer(group->poll_task, NULL);
}

4. psi_trigger_create()

该函数用以创建 psi_trigger,系统中两个地方会触发:

  • 写 /proc/pressure/*****文件
  • 写 /sys/fs/cgroup/***.**pressure 文件

创建好的 psi_trigger 都会被存在对应的 psi_group 中,或是系统的 psi_system,或是 cgroup 中的psi_group。

另外,在第一次创建 psi_trigger 的时候会创建 task "psimon" ,**与 Linux5.4 不同之处是,**这里用 kthread_create() 直接创建 task_struct,而不再是 kthread_worker。

cpp 复制代码
kernel/sched/psi.c

struct psi_trigger *psi_trigger_create(struct psi_group *group,
			char *buf, size_t nbytes, enum psi_res res)
{
	struct psi_trigger *t;
	enum psi_states state;
	u32 threshold_us;
	u32 window_us;

	if (static_branch_likely(&psi_disabled))   //psi 是否enable
		return ERR_PTR(-EOPNOTSUPP);

	if (sscanf(buf, "some %u %u", &threshold_us, &window_us) == 2)  //解析是否为写 some 数据
		state = PSI_IO_SOME + res * 2;
	else if (sscanf(buf, "full %u %u", &threshold_us, &window_us) == 2) //解析是否为写 full 数据
		state = PSI_IO_FULL + res * 2;
	else
		return ERR_PTR(-EINVAL);   //其他数据都认为无效

	if (state >= PSI_NONIDLE)   //pis_states 不能超过PSI_NONIDLE
		return ERR_PTR(-EINVAL);

	if (window_us < WINDOW_MIN_US ||  //窗口时长设置在500ms 至 10s
		window_us > WINDOW_MAX_US)
		return ERR_PTR(-EINVAL);

	/* Check threshold */
	if (threshold_us == 0 || threshold_us > window_us)  //检测阈值不能为0,也不能大于窗口时长
		return ERR_PTR(-EINVAL);

	t = kmalloc(sizeof(*t), GFP_KERNEL);  //创建一个psi_trigger
	if (!t)
		return ERR_PTR(-ENOMEM);

	t->group = group;   //记录psi_trigger的 group
	t->state = state;   //记录psi_trigger的 psi_states
	t->threshold = threshold_us * NSEC_PER_USEC;  //记录psi_trigger的检测时间,us 单位
	t->win.size = window_us * NSEC_PER_USEC;      //记录psi_trigger的窗口时长,us 单位
    window_reset(&t->win, 0, 0, 0);

	t->event = 0;
	t->last_event_time = 0;
	init_waitqueue_head(&t->event_wait);

	mutex_lock(&group->trigger_lock);

	if (!rcu_access_pointer(group->poll_kworker)) { //psi 系统相当关键的地方,创建psimon 工作线程
		struct task_struct *task;

		task = kthread_create(psi_poll_worker, group, "psimon");
		if (IS_ERR(task)) {
			kfree(t);
			mutex_unlock(&group->trigger_lock);
			return ERR_CAST(task);
		}
		atomic_clear_bit(POLL_WAKEUP, &group->poll_wakeup);
        wake_up_process(task);
        rcu_assign_pointer(group->poll_task, task); //存放task到poll_task中
	}

	list_add(&t->node, &group->triggers);  //将新建的psi_trigger 存入group 中
	group->poll_min_period = min(group->poll_min_period,
		div_u64(t->win.size, UPDATES_PER_WINDOW)); //确认最新的poll 周期
	group->nr_triggers[t->state]++;    //计数当前psi 资源psi_trigger数量
	group->poll_states |= (1 << t->state);  //更新group 中资源状态

	mutex_unlock(&group->trigger_lock);

	return t;
}

4.1 psi_poll_worker()

该函数是 psimon 线程的执行函数,在 group->poll_wakup 被置为 POLL_WAKUP 或 线程停止时被唤醒,如果是 POLL_WAKEUP 唤醒则调用核心处理函数 psi_poll_work()

cpp 复制代码
kernel/sched/psi.c

static int psi_poll_worker(void *data)
{
	struct psi_group *group = (struct psi_group *)data;

    //设置psimon 的优先级
	sched_set_fifo_low(current);

    //等待唤醒,并执行psi_poll_work()
	while (true) {
		wait_event_interruptible(group->poll_wait,
				atomic_fetch_and_clear_bit(POLL_WAKEUP, &group->poll_wakeup) ||
				kthread_should_stop());
		if (kthread_should_stop())
			break;

        //psi poll 的核心处理函数
		psi_poll_work(group);
	}
	return 0;
}
相关推荐
cominglately2 小时前
centos单机部署seata
linux·运维·centos
魏 无羡2 小时前
linux CentOS系统上卸载docker
linux·kubernetes·centos
CircleMouse2 小时前
Centos7, 使用yum工具,出现 Could not resolve host: mirrorlist.centos.org
linux·运维·服务器·centos
木子Linux3 小时前
【Linux打怪升级记 | 问题01】安装Linux系统忘记设置时区怎么办?3个方法教你回到东八区
linux·运维·服务器·centos·云计算
mit6.8243 小时前
Ubuntu 系统下性能剖析工具: perf
linux·运维·ubuntu
鹏大师运维3 小时前
聊聊开源的虚拟化平台--PVE
linux·开源·虚拟化·虚拟机·pve·存储·nfs
watermelonoops3 小时前
Windows安装Ubuntu,Deepin三系统启动问题(XXX has invalid signature 您需要先加载内核)
linux·运维·ubuntu·deepin
滴水之功4 小时前
VMware OpenWrt怎么桥接模式联网
linux·openwrt
ldinvicible5 小时前
How to run Flutter on an Embedded Device
linux
YRr YRr5 小时前
解决Ubuntu 20.04上编译OpenCV 3.2时遇到的stdlib.h缺失错误
linux·opencv·ubuntu