当前位置: 首页 > news >正文

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);

设计原因

  1. 简化循环:不需要在循环内特殊处理当前CPU
  2. 保持锁定:当前CPU的锁在函数结束时仍然持有
  3. 避免重复:不需要唤醒自己

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);
}

条件1smt_rq->curr == smt_rq->idle

  • 兄弟CPU当前正在运行空闲任务
  • 说明该CPU处于空闲状态

条件2smt_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)的任务调度。其核心目标是:

  1. 防止低优先级任务占用过多共享资源(如超线程中的执行单元)
  2. 在必要时触发兄弟线程的重新调度,以平衡资源使用

该函数通常在 当前 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:该优先级数组中非空队列的数量
    1. 默认从 active 数组中选择任务。
    2. 如果 active 数组为空(nr_active == 0),则切换到 expired 数组(时间片用完的任务队列)
    3. BUG_ON(!array->nr_active):如果 activeexpired 均为空,触发内核崩溃(因为前面已检查 nr_running > 0,这里理论上不应为空)

为什么需要 activeexpired 两个数组?
Linux 调度器使用 多级反馈队列,任务初始放入 active 数组,时间片用完后移到 expired 数组。当 active 为空时,交换 activeexpired 的角色,避免遍历所有优先级队列

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):

  1. 兄弟线程的任务时间片更长或兄弟线程是实时任务

    • 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),则必须优先保证其实时性

  2. 当前任务 p 和兄弟线程任务均为用户态任务

    • p->mm && smt_curr->mmmm 字段表示用户态任务的内存管理结构,内核线程的 mmNULL
    • !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 触发重新调度)
  • 如果兄弟线程空闲且有任务堆积,则唤醒其运行更高优先级的任务
http://www.dtcms.com/a/427516.html

相关文章:

  • Process Hacker下载和安装教程(附安装包)
  • 坑梓网站建设方案公众号制作网页
  • 企业身份认证系统选型:Azure AD 与 Keycloak 功能详解
  • 云手机ARM架构都具有哪些挑战
  • 域名与网站amh wordpress伪静态设置
  • docker 安装 xxl-job 详解
  • 数字博物馆网站建设备案的时候需要网站吗
  • 数学-绝对值(二)
  • css三角形源码
  • 行业网站名录温州网站 公司
  • 资阳网站建设方案wordpress api 发贴
  • 做个网站要多久柚段子wordpress主题模板
  • 网站开发需要学什么语言腾讯广告代理商
  • 【完整源码+数据集+部署教程】鹿角图像分割系统: yolov8-seg-C2f-DCNV2-Dynamic
  • 人工智能发展史 — 物理学诺奖之 Hinton 玻尔兹曼机模型
  • Spring 框架中 ​​RestTemplate 的使用方法​​
  • Vue3中基于路由的动态递归菜单组件实现
  • 建设主题网站的顺序一般是网站规划的案例
  • 免费网站建设制作上海网络推广报价
  • 手撕JS实现call,apply,bind
  • 【Java学习】类加载与实例化过程
  • [xboard] 19 kernel Makefile逐行分析1
  • 《足垒球百科》什么是足球、垒球、足垒球·垒球1号位
  • Process Monitor 学习笔记(5.1):Procmon 概述、抓取原理与常见用途
  • 重塑自然之美:朱小颜健康科技有限公司,开启非侵入式面部美学新时代
  • 站长工具关键词排名怎么查淘宝刷单网站制作
  • 做一个网站的详细教学建设通是正规网站吗
  • Redis Set 类型全解析
  • OpenSSH6 双库链接问题排查与解决总结
  • PyTorch实战车牌识别 小张的停车场项目逆袭之旅