Linux兄弟线程唤醒和调度的实现
文章目录
- 一、SMT兄弟CPU唤醒`wake_sleeping_dependent`
- 1. 函数原型和初始检查
- 2. 锁策略和安全性说明
- 3. 获取兄弟CPU映射和锁
- 4. 排除当前CPU
- 5. 唤醒睡眠的兄弟任务
- 6. 释放兄弟CPU锁
- 7. 最终状态说明
- 二、协调兄弟线程的任务调度`dependent_sleeper`
- 1. 检查调度域标志
- 2. 释放当前运行队列锁
- 3. 初始化兄弟线程掩码
- 4. 获取当前运行队列的下一个任务
- 4.1.检查运行队列是否为空
- 4.2.从活跃队列(`active`)或过期队列(`expired`)中选择任务队列
- 4.3.通过优先级位图找到最高优先级任务
- 5. 检查兄弟线程的任务并决策
- 5.1.遍历兄弟线程(`for_each_cpu_mask(i, sibling_map)`)
- 5.2.条件1:是否需要延迟当前任务?
- 5.2.条件2:是否需要唤醒兄弟线程的任务?
一、SMT兄弟CPU唤醒wake_sleeping_dependent
static inline void wake_sleeping_dependent(int this_cpu, runqueue_t *this_rq)
{struct sched_domain *sd = this_rq->sd;cpumask_t sibling_map;int i;if (!(sd->flags & SD_SHARE_CPUPOWER))return;spin_unlock(&this_rq->lock);sibling_map = sd->span;for_each_cpu_mask(i, sibling_map)spin_lock(&cpu_rq(i)->lock);cpu_clear(this_cpu, sibling_map);for_each_cpu_mask(i, sibling_map) {runqueue_t *smt_rq = cpu_rq(i);if (smt_rq->curr == smt_rq->idle && smt_rq->nr_running)resched_task(smt_rq->idle);}for_each_cpu_mask(i, sibling_map)spin_unlock(&cpu_rq(i)->lock);}
当某个 CPU(this_cpu
)的运行队列(this_rq
)发生变化时(例如任务被唤醒或调度),检查同一物理核心的其他逻辑 CPU(如超线程中的 SMT 兄弟线程)是否有任务需要被唤醒
1. 函数原型和初始检查
static inline void wake_sleeping_dependent(int this_cpu, runqueue_t *this_rq)
{struct sched_domain *sd = this_rq->sd;cpumask_t sibling_map;int i;if (!(sd->flags & SD_SHARE_CPUPOWER))return;
参数和变量:
this_cpu
:当前CPU编号this_rq
:当前运行队列sd
:当前CPU的调度域sibling_map
:兄弟CPU的位图i
:循环计数器
SMT架构检查:
if (!(sd->flags & SD_SHARE_CPUPOWER))return;
SD_SHARE_CPUPOWER
标志:
- 表示调度域内的CPU共享执行资源(如SMT兄弟线程)
- 只有在SMT架构(如Intel Hyper-Threading)中设置
2. 锁策略和安全性说明
/** Unlock the current runqueue because we have to lock in* CPU order to avoid deadlocks. Caller knows that we might* unlock. We keep IRQs disabled.*/
spin_unlock(&this_rq->lock);
- 先释放当前CPU的锁
- 按固定顺序获取所有兄弟CPU的锁
- 防止循环等待
3. 获取兄弟CPU映射和锁
sibling_map = sd->span;for_each_cpu_mask(i, sibling_map)spin_lock(&cpu_rq(i)->lock);
sd->span
:
- 包含调度域内所有CPU的位图
- 对于SMT域,包含共享同一物理核心的所有逻辑CPU
锁定所有兄弟CPU:
for_each_cpu_mask(i, sibling_map)spin_lock(&cpu_rq(i)->lock);
- 按CPU编号顺序获取所有兄弟CPU的运行队列锁
- 建立全局一致的锁顺序
4. 排除当前CPU
/** We clear this CPU from the mask. This both simplifies the* inner loop and keps this_rq locked when we exit:*/
cpu_clear(this_cpu, sibling_map);
设计原因:
- 简化循环:不需要在循环内特殊处理当前CPU
- 保持锁定:当前CPU的锁在函数结束时仍然持有
- 避免重复:不需要唤醒自己
5. 唤醒睡眠的兄弟任务
for_each_cpu_mask(i, sibling_map) {runqueue_t *smt_rq = cpu_rq(i);/** If an SMT sibling task is sleeping due to priority* reasons wake it up now.*/if (smt_rq->curr == smt_rq->idle && smt_rq->nr_running)resched_task(smt_rq->idle);
}
条件1:smt_rq->curr == smt_rq->idle
- 兄弟CPU当前正在运行空闲任务
- 说明该CPU处于空闲状态
条件2:smt_rq->nr_running
- 兄弟CPU的运行队列中有就绪任务
- 说明有任务在等待运行
唤醒逻辑:
resched_task(smt_rq->idle);
- 设置空闲任务的
TIF_NEED_RESCHED
标志 - 强制兄弟CPU在下一个调度点重新调度
- 让等待的任务有机会运行
6. 释放兄弟CPU锁
for_each_cpu_mask(i, sibling_map)spin_unlock(&cpu_rq(i)->lock);
对称的锁操作:
- 按相同顺序释放所有兄弟CPU的锁
- 保持锁操作的对称性
- 恢复系统到正常状态
7. 最终状态说明
/** We exit with this_cpu's rq still held and IRQs* still disabled:*/
函数结束时的状态:
- 当前CPU的运行队列锁仍然持有
- 中断仍然被禁用
- 调用者可以继续操作当前运行队列
二、协调兄弟线程的任务调度dependent_sleeper
static inline int dependent_sleeper(int this_cpu, runqueue_t *this_rq)
{struct sched_domain *sd = this_rq->sd;cpumask_t sibling_map;prio_array_t *array;int ret = 0, i;task_t *p;if (!(sd->flags & SD_SHARE_CPUPOWER))return 0;spin_unlock(&this_rq->lock);sibling_map = sd->span;for_each_cpu_mask(i, sibling_map)spin_lock(&cpu_rq(i)->lock);cpu_clear(this_cpu, sibling_map);if (!this_rq->nr_running)goto out_unlock;array = this_rq->active;if (!array->nr_active)array = this_rq->expired;BUG_ON(!array->nr_active);p = list_entry(array->queue[sched_find_first_bit(array->bitmap)].next,task_t, run_list);for_each_cpu_mask(i, sibling_map) {runqueue_t *smt_rq = cpu_rq(i);task_t *smt_curr = smt_rq->curr;if (((smt_curr->time_slice * (100 - sd->per_cpu_gain) / 100) >task_timeslice(p) || rt_task(smt_curr)) &&p->mm && smt_curr->mm && !rt_task(p))ret = 1;if ((((p->time_slice * (100 - sd->per_cpu_gain) / 100) >task_timeslice(smt_curr) || rt_task(p)) &&smt_curr->mm && p->mm && !rt_task(smt_curr)) ||(smt_curr == smt_rq->idle && smt_rq->nr_running))resched_task(smt_curr);}
out_unlock:for_each_cpu_mask(i, sibling_map)spin_unlock(&cpu_rq(i)->lock);return ret;
}
dependent_sleeper()
是 Linux 内核调度器中的一个函数,主要用于 在超线程(SMT)或共享 CPU 资源的场景下,协调兄弟线程(sibling CPUs)的任务调度。其核心目标是:
- 防止低优先级任务占用过多共享资源(如超线程中的执行单元)
- 在必要时触发兄弟线程的重新调度,以平衡资源使用
该函数通常在 当前 CPU(this_cpu
)的任务调度上下文中调用,用于检查是否需要延迟当前任务的执行或唤醒兄弟线程的更高优先级任务
1. 检查调度域标志
struct sched_domain *sd = this_rq->sd;
if (!(sd->flags & SD_SHARE_CPUPOWER))return 0;
SD_SHARE_CPUPOWER
:表示当前调度域内的 CPU 共享物理资源(如超线程 SMT 兄弟线程)- 逻辑:如果当前调度域不涉及 CPU 资源共享,直接返回
0
(无需处理)
2. 释放当前运行队列锁
spin_unlock(&this_rq->lock);
- 原因:后续需要遍历并加锁兄弟线程的运行队列,为避免死锁,需先释放当前队列的锁
3. 初始化兄弟线程掩码
sibling_map = sd->span;
for_each_cpu_mask(i, sibling_map)spin_lock(&cpu_rq(i)->lock);
cpu_clear(this_cpu, sibling_map);
sd->span
:调度域覆盖的 CPU 掩码(包括this_cpu
及其兄弟线程)for_each_cpu_mask(i, sibling_map)
:遍历所有兄弟线程,并对它们的运行队列加锁cpu_clear(this_cpu, sibling_map)
:从掩码中移除当前 CPU(this_cpu
),后续只需处理兄弟线程
4. 获取当前运行队列的下一个任务
if (!this_rq->nr_running)goto out_unlock;
array = this_rq->active;
if (!array->nr_active)array = this_rq->expired;
BUG_ON(!array->nr_active);p = list_entry(array->queue[sched_find_first_bit(array->bitmap)].next,task_t, run_list);
4.1.检查运行队列是否为空
if (!this_rq->nr_running)goto out_unlock;
this_rq->nr_running
:当前运行队列(this_rq
)中的任务数量- 逻辑:如果
nr_running == 0
,说明当前 CPU 没有可运行的任务,直接跳转到out_unlock
释放锁并返回(避免后续无效操作)
4.2.从活跃队列(active
)或过期队列(expired
)中选择任务队列
array = this_rq->active;
if (!array->nr_active)array = this_rq->expired;
BUG_ON(!array->nr_active);
this_rq->active
:当前活跃的优先级数组(prio_array_t
),存储不同优先级(0-139
)的任务队列array->nr_active
:该优先级数组中非空队列的数量- 默认从
active
数组中选择任务。 - 如果
active
数组为空(nr_active == 0
),则切换到expired
数组(时间片用完的任务队列) BUG_ON(!array->nr_active)
:如果active
和expired
均为空,触发内核崩溃(因为前面已检查nr_running > 0
,这里理论上不应为空)
- 默认从
为什么需要
active
和expired
两个数组?
Linux 调度器使用 多级反馈队列,任务初始放入active
数组,时间片用完后移到expired
数组。当active
为空时,交换active
和expired
的角色,避免遍历所有优先级队列
4.3.通过优先级位图找到最高优先级任务
p = list_entry(array->queue[sched_find_first_bit(array->bitmap)].next,task_t, run_list);
这一行代码涉及 优先级位图(bitmap
)和任务队列(queue
) 的联合使用,具体步骤如下:
array->bitmap
的作用
bitmap
是一个位图(unsigned long[BITS_PER_LONG]
),每一位代表一个优先级(0-139)- 如果某位为
1
,表示该优先级对应的任务队列(queue
)非空 - 例如,
bitmap[0] & (1 << 5)
检查优先级5
是否有任务
- 如果某位为
sched_find_first_bit(array->bitmap)
- 功能:扫描
bitmap
,返回第一个被设置的位(即最高优先级的非空队列)
array->queue[prio].next
queue
是一个数组,索引是优先级(prio
),存储该优先级下的任务链表(list_head
)queue[prio].next
:指向该优先级队列的第一个任务(因为list_head
是双向链表)
list_entry(..., task_t, run_list)
- 功能:通过
container_of
宏,从链表节点(run_list
)获取包含它的任务结构体(task_t
) task_t
:内核任务控制块(struct task_struct
),存储进程/线程的所有信息
5. 检查兄弟线程的任务并决策
for_each_cpu_mask(i, sibling_map) {runqueue_t *smt_rq = cpu_rq(i);task_t *smt_curr = smt_rq->curr;/* 条件1:是否需要延迟当前任务? */if (((smt_curr->time_slice * (100 - sd->per_cpu_gain) / 100) >task_timeslice(p) || rt_task(smt_curr)) &&p->mm && smt_curr->mm && !rt_task(p))ret = 1;/* 条件2:是否需要唤醒兄弟线程的任务? */if ((((p->time_slice * (100 - sd->per_cpu_gain) / 100) >task_timeslice(smt_curr) || rt_task(p)) &&smt_curr->mm && p->mm && !rt_task(smt_curr)) ||(smt_curr == smt_rq->idle && smt_rq->nr_running))resched_task(smt_curr);
}
5.1.遍历兄弟线程(for_each_cpu_mask(i, sibling_map)
)
for_each_cpu_mask(i, sibling_map) {runqueue_t *smt_rq = cpu_rq(i);task_t *smt_curr = smt_rq->curr;
sibling_map
:当前 CPU 的所有兄弟线程的掩码(即共享同一物理核心的逻辑核心)cpu_rq(i)
:获取逻辑核心i
的运行队列(runqueue_t
)smt_rq->curr
:该逻辑核心当前正在运行的任务(task_t
)
作用:遍历所有兄弟线程,检查每个核心的当前任务(smt_curr
),决定是否需要干预调度
5.2.条件1:是否需要延迟当前任务?
if (((smt_curr->time_slice * (100 - sd->per_cpu_gain) / 100) >task_timeslice(p) || rt_task(smt_curr)) &&p->mm && smt_curr->mm && !rt_task(p))ret = 1;
逻辑:如果满足以下条件,设置 ret = 1
(表示需要延迟当前任务 p
):
-
兄弟线程的任务时间片更长或兄弟线程是实时任务
-
smt_curr->time_slice * (100 - sd->per_cpu_gain) / 100 > task_timeslice(p)
sd->per_cpu_gain
:调度域(sched_domain
)配置的参数,表示 SMT 兄弟线程之间的时间片调整比例(例如50
表示减少 50%)- 计算兄弟线程
smt_curr
的 调整后时间片,如果仍大于任务p
的时间片(task_timeslice(p)
),则认为smt_curr
占用过多资源
-
rt_task(smt_curr)
:如果兄弟线程运行的是实时任务(RT
),则必须优先保证其实时性
-
-
当前任务
p
和兄弟线程任务均为用户态任务p->mm && smt_curr->mm
:mm
字段表示用户态任务的内存管理结构,内核线程的mm
为NULL
。!rt_task(p)
:当前任务p
不是实时任务(避免干扰实时任务)
作用:
- 如果兄弟线程的任务占用了过多 CPU 时间(或优先级更高),则可能需要延迟当前任务
p
的执行
5.2.条件2:是否需要唤醒兄弟线程的任务?
if ((((p->time_slice * (100 - sd->per_cpu_gain) / 100) >task_timeslice(smt_curr) || rt_task(p)) &&smt_curr->mm && p->mm && !rt_task(smt_curr)) ||(smt_curr == smt_rq->idle && smt_rq->nr_running))resched_task(smt_curr);
逻辑:如果满足以下条件之一,触发兄弟线程的任务 smt_curr
重新调度(resched_task
):
条件2.1:当前任务 p
的时间片更长 或 p
是实时任务
-
p->time_slice * (100 - sd->per_cpu_gain) / 100 > task_timeslice(smt_curr)
- 当前任务
p
的调整后时间片比兄弟线程的任务smt_curr
更长,可能更适合运行
- 当前任务
-
rt_task(p)
- 当前任务
p
是实时任务,优先级更高
- 当前任务
且:
smt_curr->mm && p->mm
:两者均为用户态任务!rt_task(smt_curr)
:兄弟线程的任务不是实时任务(避免干扰实时任务)
条件2.2:兄弟线程当前空闲且有可运行任务
-
smt_curr == smt_rq->idle && smt_rq->nr_running
- 兄弟线程当前运行的是
idle
进程(空闲),但运行队列中有其他任务(nr_running > 0
),可以唤醒更高优先级的任务
- 兄弟线程当前运行的是
作用:
- 如果当前任务
p
更适合运行(时间片更长或优先级更高),则让兄弟线程的任务smt_curr
主动让出 CPU(通过resched_task
触发重新调度) - 如果兄弟线程空闲且有任务堆积,则唤醒其运行更高优先级的任务