Linux任务迁移函数和空闲负载均衡函数的实现

文章目录

  • 一、调度优先级位图查找函数`sched_find_first_bit`
    • [1. `__ffs` 函数 - 查找第一个设置位](#1. __ffs 函数 - 查找第一个设置位)
    • [2. `sched_find_first_bit` 函数 - 多字位图查找](#2. sched_find_first_bit 函数 - 多字位图查找)
  • 二、任务迁移函数`move_tasks`
    • [1. 函数原型和参数](#1. 函数原型和参数)
    • [2. 变量声明和初始检查](#2. 变量声明和初始检查)
    • [3. 选择源数组策略](#3. 选择源数组策略)
    • [4. 优先级位图搜索循环](#4. 优先级位图搜索循环)
    • [5. 数组切换逻辑](#5. 数组切换逻辑)
    • [6. 任务链表遍历](#6. 任务链表遍历)
    • [7. 迁移条件检查](#7. 迁移条件检查)
    • [8. 执行任务迁移](#8. 执行任务迁移)
      • [8.1.**`pull_task` 操作**](#8.1.pull_task 操作)
    • [9. 迁移数量限制检查](#9. 迁移数量限制检查)
  • 三、双运行队列锁平衡`double_lock_balance`
    • [1. 函数原型和属性](#1. 函数原型和属性)
    • [2. 尝试获取锁的快速路径](#2. 尝试获取锁的快速路径)
    • [3. 慢速路径:地址排序防死锁](#3. 慢速路径:地址排序防死锁)
  • 四、NEWLY_IDLE状态负载均衡`load_balance_newidle`
    • [1. 函数原型和变量声明](#1. 函数原型和变量声明)
    • [2. 统计计数和查找最忙组](#2. 统计计数和查找最忙组)
    • [3. 无最忙组的处理](#3. 无最忙组的处理)
    • [4. 查找最忙运行队列](#4. 查找最忙运行队列)
    • [5. 双锁平衡和任务迁移](#5. 双锁平衡和任务迁移)
    • [6. 迁移失败统计和锁释放](#6. 迁移失败统计和锁释放)
    • [7. 返回结果](#7. 返回结果)
  • 五、空闲负载均衡层级搜索`idle_balance`
    • [1. 函数原型和目的](#1. 函数原型和目的)
    • [2. 调度域遍历](#2. 调度域遍历)
    • [3. NEWIDLE平衡标志检查](#3. NEWIDLE平衡标志检查)
    • [4. 执行负载均衡和停止条件](#4. 执行负载均衡和停止条件)

参考博客

性能分析之数据收集函数profile_hit: https://blog.csdn.net/weixin_51019352/article/details/152267042

内核堆栈跟踪函数dump_stack: https://blog.csdn.net/weixin_51019352/article/details/152317302

添加进程到队列activate_task: https://blog.csdn.net/weixin_51019352/article/details/152221394

任务迁移CPU函数pull_taskcan_migrate_task: https://blog.csdn.net/weixin_51019352/article/details/152327134

最忙CPU组查找函数和最忙运行队列查找函数: https://blog.csdn.net/weixin_51019352/article/details/15233321

一、调度优先级位图查找函数sched_find_first_bit

c 复制代码
static inline unsigned long __ffs(unsigned long word)
{
        __asm__("bsfl %1,%0"
                :"=r" (word)
                :"rm" (word));
        return word;
}
static inline int sched_find_first_bit(const unsigned long *b)
{
        if (unlikely(b[0]))
                return __ffs(b[0]);
        if (unlikely(b[1]))
                return __ffs(b[1]) + 32;
        if (unlikely(b[2]))
                return __ffs(b[2]) + 64;
        if (b[3])
                return __ffs(b[3]) + 96;
        return __ffs(b[4]) + 128;
}

这两个函数实现了高效的位图查找,用于在调度器的优先级位图中快速找到第一个设置的位

1. __ffs 函数 - 查找第一个设置位

c 复制代码
static inline unsigned long __ffs(unsigned long word)
{
        __asm__("bsfl %1,%0"
                :"=r" (word)
                :"rm" (word));
        return word;
}

汇编指令bsfl %1,%0

  • bsfl:Bit Scan Forward Long - 从低位向高位扫描第一个设置位
  • %1:输入操作数(要扫描的word)
  • %0:输出操作数(结果)

操作数约束

  • "=r" (word):输出操作数,使用寄存器,结果写入word变量
  • "rm" (word):输入操作数,可以使用寄存器或内存

功能说明

  • 返回word中从最低位开始的第一个1的位位置

2. sched_find_first_bit 函数 - 多字位图查找

c 复制代码
static inline int sched_find_first_bit(const unsigned long *b)
{
        if (unlikely(b[0]))
                return __ffs(b[0]);
        if (unlikely(b[1]))
                return __ffs(b[1]) + 32;
        if (unlikely(b[2]))
                return __ffs(b[2]) + 64;
        if (b[3])
                return __ffs(b[3]) + 96;
        return __ffs(b[4]) + 128;
}

数据结构背景

调度器优先级位图由5个unsigned long组成,支持140个优先级(0-139):

  • b[0]:位0-31
  • b[1]:位32-63
  • b[2]:位64-95
  • b[3]:位96-127
  • b[4]:位128-139(只使用前12位)

unlikely

c 复制代码
#define unlikely(x) __builtin_expect(!!(x), 0)
  • 告诉编译器这个条件很可能为假
  • 帮助CPU分支预测优化
  • 基于优先级分布的统计特性(高优先级任务较少)

这种设计使得调度器能够在常数时间内找到最高优先级任务,为系统的实时响应性提供了基础保障

二、任务迁移函数move_tasks

c 复制代码
static int move_tasks(runqueue_t *this_rq, int this_cpu, runqueue_t *busiest,
                      unsigned long max_nr_move, struct sched_domain *sd,
                      enum idle_type idle)
{
        prio_array_t *array, *dst_array;
        struct list_head *head, *curr;
        int idx, pulled = 0;
        task_t *tmp;

        if (max_nr_move <= 0 || busiest->nr_running <= 1)
                goto out;

        if (busiest->expired->nr_active) {
                array = busiest->expired;
                dst_array = this_rq->expired;
        } else {
                array = busiest->active;
                dst_array = this_rq->active;
        }

new_array:
        idx = 0;
skip_bitmap:
        if (!idx)
                idx = sched_find_first_bit(array->bitmap);
        else
                idx = find_next_bit(array->bitmap, MAX_PRIO, idx);
        if (idx >= MAX_PRIO) {
                if (array == busiest->expired && busiest->active->nr_active) {
                        array = busiest->active;
                        dst_array = this_rq->active;
                        goto new_array;
                }
                goto out;
        }

        head = array->queue + idx;
        curr = head->prev;
skip_queue:
        tmp = list_entry(curr, task_t, run_list);

        curr = curr->prev;

        if (!can_migrate_task(tmp, busiest, this_cpu, sd, idle)) {
                if (curr != head)
                        goto skip_queue;
                idx++;
                goto skip_bitmap;
        }

        schedstat_inc(this_rq, pt_gained[idle]);
        schedstat_inc(busiest, pt_lost[idle]);

        pull_task(busiest, array, tmp, this_rq, dst_array, this_cpu);
        pulled++;

        if (pulled < max_nr_move) {
                if (curr != head)
                        goto skip_queue;
                idx++;
                goto skip_bitmap;
        }
out:
        return pulled;
}

这个函数是Linux内核调度器中实际执行任务迁移的核心函数

1. 函数原型和参数

c 复制代码
static int move_tasks(runqueue_t *this_rq, int this_cpu, runqueue_t *busiest,
                      unsigned long max_nr_move, struct sched_domain *sd,
                      enum idle_type idle)

参数说明

  • this_rq:目标运行队列(当前CPU)
  • this_cpu:目标CPU编号
  • busiest:最忙CPU
  • max_nr_move:最大迁移任务数
  • sd:调度域
  • idle:空闲状态类型

返回值:实际迁移的任务数量

2. 变量声明和初始检查

c 复制代码
prio_array_t *array, *dst_array;
struct list_head *head, *curr;
int idx, pulled = 0;
task_t *tmp;

if (max_nr_move <= 0 || busiest->nr_running <= 1)
    goto out;

变量说明

  • array:源优先级数组
  • dst_array:目标优先级数组
  • head, curr:链表遍历指针
  • idx:当前优先级索引
  • pulled:已迁移任务计数
  • tmp:当前处理的任务

初始检查

  • 如果不需要迁移或源队列任务太少,直接退出

3. 选择源数组策略

c 复制代码
if (busiest->expired->nr_active) {
    array = busiest->expired;
    dst_array = this_rq->expired;
} else {
    array = busiest->active;
    dst_array = this_rq->active;
}

设计原理

优先迁移过期任务

  • 过期任务短期内不会执行
  • 缓存很可能是冷的(cache-cold)
  • 迁移对性能影响最小

4. 优先级位图搜索循环

c 复制代码
new_array:
/* Start searching at priority 0: */
idx = 0;
skip_bitmap:
if (!idx)
    idx = sched_find_first_bit(array->bitmap);
else
    idx = find_next_bit(array->bitmap, MAX_PRIO, idx);

搜索策略

  1. 首次搜索:从最高优先级(最低编号)开始
  2. 后续搜索:继续查找下一个有任务的优先级

位图操作

  • sched_find_first_bit():找到第一个设置的位(最高优先级)
  • find_next_bit():从指定位置找到下一个设置的位

5. 数组切换逻辑

c 复制代码
if (idx >= MAX_PRIO) {
    if (array == busiest->expired && busiest->active->nr_active) {
        array = busiest->active;
        dst_array = this_rq->active;
        goto new_array;
    }
    goto out;
}

条件分析

  • 如果搜索完所有优先级(idx ≥ 140)
  • 且当前在过期数组,且活跃数组有任务
  • 则切换到活跃数组重新搜索
  • 否则结束迁移

6. 任务链表遍历

c 复制代码
head = array->queue + idx;
curr = head->prev; // curr指向链表尾部
skip_queue:
tmp = list_entry(curr, task_t, run_list);
curr = curr->prev;

数据结构

c 复制代码
// 每个优先级有一个双向链表
struct list_head queue[MAX_PRIO];

// 从链表尾部开始遍历

7. 迁移条件检查

c 复制代码
if (!can_migrate_task(tmp, busiest, this_cpu, sd, idle)) {
    if (curr != head)
        goto skip_queue;
    idx++;
    goto skip_bitmap;
}

can_migrate_task 检查

  1. 任务是否正在运行
  2. CPU亲和性是否允许
  3. 任务是否缓存热(根据条件)

跳转逻辑

  • 如果不能迁移,继续遍历当前优先级链表
  • 如果遍历完当前优先级,跳到下一个优先级

8. 执行任务迁移

c 复制代码
schedstat_inc(this_rq, pt_gained[idle]);
schedstat_inc(busiest, pt_lost[idle]);

pull_task(busiest, array, tmp, this_rq, dst_array, this_cpu);
pulled++;

统计计数

  • 目标队列:增加获得任务的统计
  • 源队列:增加失去任务的统计

8.1.pull_task 操作

text 复制代码
// 实际执行任务迁移:
1. 从源队列移除任务
2. 更新任务CPU关联
3. 加入目标队列
4. 调整时间戳
5. 检查是否需要抢占

9. 迁移数量限制检查

c 复制代码
if (pulled < max_nr_move) {
    if (curr != head)
        goto skip_queue;
    idx++;
    goto skip_bitmap;
}

控制逻辑

  • 如果还没达到最大迁移数,继续迁移
  • 先继续遍历当前优先级链表
  • 当前优先级遍历完后,继续下一个优先级

三、双运行队列锁平衡double_lock_balance

c 复制代码
static void double_lock_balance(runqueue_t *this_rq, runqueue_t *busiest)
        __releases(this_rq->lock)
        __acquires(busiest->lock)
        __acquires(this_rq->lock)
{
        if (unlikely(!spin_trylock(&busiest->lock))) {
                if (busiest < this_rq) {
                        spin_unlock(&this_rq->lock);
                        spin_lock(&busiest->lock);
                        spin_lock(&this_rq->lock);
                } else
                        spin_lock(&busiest->lock);
        }
}

1. 函数原型和属性

c 复制代码
static void double_lock_balance(runqueue_t *this_rq, runqueue_t *busiest)
        __releases(this_rq->lock)
        __acquires(busiest->lock)
        __acquires(this_rq->lock)

编译器属性说明

  • __releases(this_rq->lock):函数会释放this_rq的锁
  • __acquires(busiest->lock):函数会获取busiest的锁
  • __acquires(this_rq->lock):函数会重新获取this_rq的锁

作用:帮助静态分析工具理解锁的获取/释放顺序,防止锁使用错误

2. 尝试获取锁的快速路径

c 复制代码
if (unlikely(!spin_trylock(&busiest->lock))) {

spin_trylock 策略

  • 尝试非阻塞地获取busiest运行队列的锁
  • 如果立即成功,进入快速路径
  • 如果失败,进入慢速路径

3. 慢速路径:地址排序防死锁

c 复制代码
if (busiest < this_rq) {
    spin_unlock(&this_rq->lock);
    spin_lock(&busiest->lock);
    spin_lock(&this_rq->lock);
} else
    spin_lock(&busiest->lock);

情况1:busiest < this_rq(busiest地址较小)

c 复制代码
spin_unlock(&this_rq->lock);    // 先释放当前锁
spin_lock(&busiest->lock);      // 按地址顺序获取小地址锁
spin_lock(&this_rq->lock);      // 再获取大地址锁

情况2:busiest >= this_rq(busiest地址较大或相等)

c 复制代码
spin_lock(&busiest->lock);      // 直接获取,因为this_rq锁已持有
                                // 且busiest地址较大,符合排序

四、NEWLY_IDLE状态负载均衡load_balance_newidle

c 复制代码
static int load_balance_newidle(int this_cpu, runqueue_t *this_rq,
                                struct sched_domain *sd)
{
        struct sched_group *group;
        runqueue_t *busiest = NULL;
        unsigned long imbalance;
        int nr_moved = 0;

        schedstat_inc(sd, lb_cnt[NEWLY_IDLE]);
        group = find_busiest_group(sd, this_cpu, &imbalance, NEWLY_IDLE);
        if (!group) {
                schedstat_inc(sd, lb_nobusyg[NEWLY_IDLE]);
                goto out;
        }

        busiest = find_busiest_queue(group);
        if (!busiest || busiest == this_rq) {
                schedstat_inc(sd, lb_nobusyq[NEWLY_IDLE]);
                goto out;
        }

        /* Attempt to move tasks */
        double_lock_balance(this_rq, busiest);

        schedstat_add(sd, lb_imbalance[NEWLY_IDLE], imbalance);
        nr_moved = move_tasks(this_rq, this_cpu, busiest,
                                        imbalance, sd, NEWLY_IDLE);
        if (!nr_moved)
                schedstat_inc(sd, lb_failed[NEWLY_IDLE]);

        spin_unlock(&busiest->lock);

out:
        return nr_moved;
}

这个函数处理CPU刚刚变为空闲时的负载均衡,是响应最快的负载均衡路径

1. 函数原型和变量声明

c 复制代码
static int load_balance_newidle(int this_cpu, runqueue_t *this_rq,
                                struct sched_domain *sd)
{
    struct sched_group *group;
    runqueue_t *busiest = NULL;
    unsigned long imbalance;
    int nr_moved = 0;

参数说明

  • this_cpu:当前变为空闲的CPU编号
  • this_rq:当前CPU的运行队列
  • sd:调度域

变量说明

  • group:最忙的调度组
  • busiest:最忙的运行队列
  • imbalance:需要迁移的负载量
  • nr_moved:实际迁移的任务数

2. 统计计数和查找最忙组

c 复制代码
schedstat_inc(sd, lb_cnt[NEWLY_IDLE]);
group = find_busiest_group(sd, this_cpu, &imbalance, NEWLY_IDLE);

统计计数

c 复制代码
schedstat_inc(sd, lb_cnt[NEWLY_IDLE]);
  • 记录NEWLY_IDLE负载均衡被调用的次数
  • 用于性能分析和调优

查找最忙组

c 复制代码
group = find_busiest_group(sd, this_cpu, &imbalance, NEWLY_IDLE);
  • 在调度域中找到负载最重的组
  • 计算需要迁移的负载量到imbalance
  • 使用NEWLY_IDLE空闲类型

3. 无最忙组的处理

c 复制代码
if (!group) {
    schedstat_inc(sd, lb_nobusyg[NEWLY_IDLE]);
    goto out;
}

条件:如果没有找到明显负载不均的组

统计:记录"无繁忙组"的情况

处理:直接跳到结束,返回0个迁移任务

4. 查找最忙运行队列

c 复制代码
busiest = find_busiest_queue(group);
if (!busiest || busiest == this_rq) {
    schedstat_inc(sd, lb_nobusyq[NEWLY_IDLE]);
    goto out;
}

find_busiest_queue

  • 在找到的最忙组内查找具体的繁忙CPU
  • 返回该CPU的运行队列指针

有效性检查

  1. !busiest:组内没有繁忙队列
  2. busiest == this_rq:最忙队列就是自己(不应该发生,因为如果是自己的话group会是空,看find_busiest_group的实现)

统计:记录"无繁忙队列"的情况

5. 双锁平衡和任务迁移

c 复制代码
/* Attempt to move tasks */
double_lock_balance(this_rq, busiest);

schedstat_add(sd, lb_imbalance[NEWLY_IDLE], imbalance);
nr_moved = move_tasks(this_rq, this_cpu, busiest,
                                imbalance, sd, NEWLY_IDLE);

获取双锁

c 复制代码
double_lock_balance(this_rq, busiest);
  • 安全地同时持有this_rqbusiest的锁
  • 防止死锁的地址排序算法

统计记录

c 复制代码
schedstat_add(sd, lb_imbalance[NEWLY_IDLE], imbalance);
  • 记录检测到的不平衡量

执行迁移

c 复制代码
nr_moved = move_tasks(this_rq, this_cpu, busiest,
                                imbalance, sd, NEWLY_IDLE);
  • 实际从繁忙队列迁移任务到当前队列

6. 迁移失败统计和锁释放

c 复制代码
if (!nr_moved)
    schedstat_inc(sd, lb_failed[NEWLY_IDLE]);

spin_unlock(&busiest->lock);

失败统计

c 复制代码
if (!nr_moved)
    schedstat_inc(sd, lb_failed[NEWLY_IDLE]);
  • 记录迁移失败的情况
  • 帮助诊断负载均衡问题

锁释放

c 复制代码
spin_unlock(&busiest->lock);
  • 释放繁忙队列的锁
  • this_rq锁由调用者释放

7. 返回结果

c 复制代码
out:
    return nr_moved;

返回值:实际迁移的任务数量

五、空闲负载均衡层级搜索idle_balance

c 复制代码
static inline void idle_balance(int this_cpu, runqueue_t *this_rq)
{
        struct sched_domain *sd;

        for_each_domain(this_cpu, sd) {
                if (sd->flags & SD_BALANCE_NEWIDLE) {
                        if (load_balance_newidle(this_cpu, this_rq, sd)) {
                                /* We've pulled tasks over so stop searching */
                                break;
                        }
                }
        }
}

这个函数实现了CPU空闲时的多级负载均衡策略,按照调度域层级从近到远搜索可迁移的任务

1. 函数原型和目的

c 复制代码
static inline void idle_balance(int this_cpu, runqueue_t *this_rq)

功能:在CPU进入空闲状态时,尝试从其他CPU拉取任务来利用空闲资源

参数

  • this_cpu:当前变为空闲的CPU编号
  • this_rq:当前CPU的运行队列

2. 调度域遍历

c 复制代码
struct sched_domain *sd;

for_each_domain(this_cpu, sd) {

for_each_domain

  • 遍历当前CPU所属的所有调度域
  • 从最内层(最小范围)到最外层(最大范围)依次搜索

3. NEWIDLE平衡标志检查

c 复制代码
if (sd->flags & SD_BALANCE_NEWIDLE) {

SD_BALANCE_NEWIDLE 标志

  • 表示该调度域允许在CPU刚变空闲时进行负载均衡
  • 不是所有调度域都启用此功能

4. 执行负载均衡和停止条件

c 复制代码
if (load_balance_newidle(this_cpu, this_rq, sd)) {
    /* We've pulled tasks over so stop searching */
    break;
}

load_balance_newidle 返回值

  • > 0:成功迁移了任务
  • = 0:没有迁移任务(系统已平衡或无法迁移)

停止搜索条件

c 复制代码
break;  // 一旦在某个层级成功迁移任务,就停止继续搜索
相关推荐
tt666qq3 小时前
linux文件系统学习
linux·运维·学习
七七七七073 小时前
【Linux系统】进程替换
linux·运维·服务器
skyutuzz4 小时前
vim删除文本文件内容
linux·编辑器·vim
---学无止境---4 小时前
Linux信号处理的相关数据结构和操作函数
linux
前行居士5 小时前
Sub-process /usr/bin/dpkg returned an error code (1)
linux·运维·windows
tt5555555555556 小时前
Linux 驱动开发入门:LCD 驱动与内核机制详解
linux·运维·驱动开发
行者..................6 小时前
petalinux 安装Armadillo
linux·运维·服务器
xiatianit6 小时前
【centos生产环境搭建(三)jdk环境配置】
linux
zhaotiannuo_19986 小时前
linux centos 7 解决终端提示符出现-bash-4.2的问题
linux·centos·bash