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;
}
相关推荐
Lary_Rock1 小时前
RK3576 LINUX RKNN SDK 测试
linux·运维·服务器
云飞云共享云桌面3 小时前
8位机械工程师如何共享一台图形工作站算力?
linux·服务器·网络
Peter_chq4 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
一坨阿亮5 小时前
Linux 使用中的问题
linux·运维
dsywws6 小时前
Linux学习笔记之vim入门
linux·笔记·学习
幺零九零零6 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
小林熬夜学编程8 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
程思扬8 小时前
为什么Uptime+Kuma本地部署与远程使用是网站监控新选择?
linux·服务器·网络·经验分享·后端·网络协议·1024程序员节
sun0077008 小时前
拷贝 cp -rdp 和 cp -a
linux·运维·服务器
wowocpp9 小时前
ubuntu 22.04 server 安装 anaconda3
linux·运维·ubuntu