Linux psi_task_change任务状态切换PSI计算
Linux psi_task_change任务状态切换PSI计算
psi_task_change是PSI(Pressure Stall Information)子系统的核心函数,负责在任务状态发生切换时更新per-CPU的stall追踪状态。任务状态的变化直接决定了cgroup当前是否处于IO、内存或CPU资源压力的stall状态,以及stall的严重程度(some vs full)。
函数核心逻辑
psi_task_change接收当前任务、新旧任务状态flags以及是否睡眠等参数,根据这些信息判断stall状态是否发生变化,并更新per-CPU的统计累积时间。
```c
void psi_task_change(struct task_struct *task, int clear, int set)
{
struct psi_group *group;
int cpu = task_cpu(task);
struct psi_group_cpu *groupc;
u64 now;
int state_mask;
if (!task->pid)
return;
group = task_psi_group(task);
if (!group)
return;
now = cpu_clock(cpu);
groupc = per_cpu_ptr(group->pcpu, cpu);
raw_spin_lock(&groupc->lock);
/*
* 计算新状态掩码,先清除旧flags再设置新flags,
* 排除TASK_NONIDLE等非PSI状态位
*/
state_mask = (groupc->state_mask & ~clear) | set;
state_mask &= PSI_TASK_STATE_MASK;
if (state_mask != groupc->state_mask) {
u64 delta;
/*
* 计算从上次状态改变到现在的stall持续时间,
* 累加到对应状态的times数组中
*/
delta = now - groupc->state_start;
if (delta > 0) {
int s;
for (s = 0; s < NR_PSI_STATES; s++) {
if (groupc->state_mask & (1 << s))
groupc->times[s][groupc->state_mask] += delta;
}
}
groupc->state_mask = state_mask;
groupc->state_start = now;
/*
* 如果新状态包含非空闲标志,需要设置
* nonidle_start时间戳,用于非idle时间跟踪
*/
if (state_mask & TASK_NONIDLE) {
if (!groupc->nonidle_start)
groupc->nonidle_start = now;
} else {
if (groupc->nonidle_start) {
delta = now - groupc->nonidle_start;
groupc->nonidle_time += delta;
groupc->nonidle_start = 0;
}
}
}
raw_spin_unlock(&groupc->lock);
/*
* 如果状态变化涉及可运行状态(running/runnable)的切换,
* 需要通知调度器更新psi_sched统计
*/
if (task->psi_flags & TSK_RUNNING) {
if (!(set & TSK_RUNNING)) {
psi_sched_account(task, true);
}
}
}
```
PSI状态的分类体系
PSI定义了三种资源类型(IO、Memory、CPU)和两种stall严重程度(Some、Full)。Some表示至少有一个任务处于stall状态但非全部,Full表示所有非idle任务都处于stall状态。
```c
enum psi_states {
PSI_IO_SOME,
PSI_IO_FULL,
PSI_MEM_SOME,
PSI_MEM_FULL,
PSI_CPU_SOME,
PSI_CPU_FULL,
NR_PSI_STATES,
};
```
psi_task_switch调度器入口
调度器在进程切换时调用psi_task_switch,该函数包装了psi_task_change并提供额外的上下文信息,是调度器与PSI子系统的主要接口。
```c
void psi_task_switch(struct task_struct *prev, struct task_struct *next,
bool sleep)
{
struct psi_group *group, *prev_group;
if (!static_branch_likely(&psi_disabled))
return;
if (prev->pid) {
prev_group = task_psi_group(prev);
if (prev->state == TASK_RUNNING) {
psi_task_change(prev, TSK_RUNNING, 0);
} else if (sleep) {
/*
* 任务进入睡眠状态,设置TSK_SLEEP标志,
* 此时该任务不再贡献活跃度
*/
psi_task_change(prev, TSK_RUNNING, TSK_SLEEP);
if (prev->in_iowait)
psi_task_change(prev, 0, TSK_IOWAIT);
}
}
if (next->pid) {
group = task_psi_group(next);
if (next->state == TASK_RUNNING) {
psi_task_change(next, 0, TSK_RUNNING);
}
}
/*
* 如果任务迁移到不同cgroup,需要同步
* per-CPU的psi_group_cpu统计
*/
if (prev_group != group && prev->pid && next->pid)
psi_group_change_sync(prev, prev_group, next, group, cpu);
}
```
memstall与iowait的处理
当任务因缺页等待IO时,内核标记PF_MEMSTALL标志。psi_memstall_enter和psi_memstall_leave通过psi_task_change切换TSK_MEMSTALL状态,使PSI能够追踪内存stall导致的任务等待。
```c
void psi_memstall_enter(unsigned long *flags)
{
struct task_struct *task = current;
if (!static_branch_likely(&psi_disabled))
return;
*flags = task->flags & PF_MEMSTALL;
task->flags |= PF_MEMSTALL;
if (!(*flags & PF_MEMSTALL))
psi_task_change(task, 0, TSK_MEMSTALL);
}
void psi_memstall_leave(unsigned long *flags)
{
struct task_struct *task = current;
if (!static_branch_likely(&psi_disabled))
return;
if (*flags & PF_MEMSTALL)
return;
task->flags &= ~PF_MEMSTALL;
psi_task_change(task, TSK_MEMSTALL, 0);
}
```
通过psi_task_change的精细状态追踪,PSI能够准确区分不同资源类型的stall,并为系统管理员提供精确的拥塞度量,这是cgroup v2资源隔离能力的关键组成部分。
