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

Linux中CPU初始化和调度器初始化函数的实现

设置每CPU数据区setup_per_cpu_areas

static void __init setup_per_cpu_areas(void)
{unsigned long size, i;char *ptr;/* Created by linker magic */extern char __per_cpu_start[], __per_cpu_end[];/* Copy section for each CPU (we discard the original) */size = ALIGN(__per_cpu_end - __per_cpu_start, SMP_CACHE_BYTES);
#ifdef CONFIG_MODULESif (size < PERCPU_ENOUGH_ROOM)size = PERCPU_ENOUGH_ROOM;
#endifptr = alloc_bootmem(size * NR_CPUS);for (i = 0; i < NR_CPUS; i++, ptr += size) {__per_cpu_offset[i] = ptr - __per_cpu_start;memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start);}
}

1. 函数功能

为每个CPU创建独立的 per-CPU 数据区,这些数据区在内存中是连续分配的,但每个CPU访问自己的副本

2. 代码详细解释

2.1. 变量声明和链接器符号

unsigned long size, i;
char *ptr;
/* Created by linker magic */
extern char __per_cpu_start[], __per_cpu_end[];
  • size:每个CPU数据区的大小
  • i:循环计数器
  • ptr:指向当前CPU数据区的指针
  • __per_cpu_start[], __per_cpu_end[]:链接器定义的符号,标记per-CPU数据的起始和结束位置

2.2. 计算数据区大小

size = ALIGN(__per_cpu_end - __per_cpu_start, SMP_CACHE_BYTES);
  • 计算原始per-CPU数据段的大小:__per_cpu_end - __per_cpu_start
  • 使用ALIGN宏按缓存行大小(SMP_CACHE_BYTES)对齐

2.3. 模块情况下的最小大小检查

#ifdef CONFIG_MODULES
if (size < PERCPU_ENOUGH_ROOM)size = PERCPU_ENOUGH_ROOM;
#endif
  • 如果配置了模块支持(CONFIG_MODULES):
    • 检查计算的大小是否小于预定义的最小值PERCPU_ENOUGH_ROOM
    • 如果太小,使用最小值确保有足够空间供动态加载的模块使用

2.4. 分配内存

ptr = alloc_bootmem(size * NR_CPUS);
  • 使用alloc_bootmem从启动内存分配器中分配内存
  • 分配的总大小 = 每个CPU的数据区大小 × CPU数量
  • 返回指向分配内存起始位置的指针

2.5. 设置每个CPU的数据区

for (i = 0; i < NR_CPUS; i++, ptr += size) {__per_cpu_offset[i] = ptr - __per_cpu_start;memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start);
}

循环为每个CPU:

  • __per_cpu_offset[i] = ptr - __per_cpu_start
    • 设置第i个CPU的偏移量数组
    • 这个偏移量用于计算每个CPU访问自己数据区的地址
  • memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start)
    • 将原始的per-CPU数据复制到新分配的区域
    • 每个CPU都获得一份原始数据的完整副本
  • ptr += size:移动到下一个CPU的数据区

3. 内存布局示例

分配的内存区域:
+----------------+----------------+----------------+----------------+
|   CPU0数据区   |   CPU1数据区   |   CPU2数据区   |   CPU3数据区   |
|   (size字节)   |   (size字节)   |   (size字节)   |   (size字节)   |
+----------------+----------------+----------------+----------------+
^                ^                ^                ^
ptr0            ptr1            ptr2            ptr3__per_cpu_offset数组:
[0] = ptr0 - __per_cpu_start
[1] = ptr1 - __per_cpu_start  
[2] = ptr2 - __per_cpu_start
[3] = ptr3 - __per_cpu_start

设置引导CPUsmp_prepare_boot_cpu

void __devinit smp_prepare_boot_cpu(void)
{cpu_set(smp_processor_id(), cpu_online_map);cpu_set(smp_processor_id(), cpu_callout_map);
}
#define cpu_set(cpu, dst) __cpu_set((cpu), &(dst))
static inline void __cpu_set(int cpu, volatile cpumask_t *dstp)
{set_bit(cpu, dstp->bits);
}

1. 函数功能

设置引导CPU(BSP - Bootstrap Processor)在多CPU系统中的状态,将其标记为在线和可调度的

2. 代码详细解释

2.1. 函数定义

void __devinit smp_prepare_boot_cpu(void)
  • __devinit:这是一个编译器属性,表示该函数只在设备初始化阶段使用,初始化完成后可以被释放
  • 函数不接受参数,也不返回值

2.2. 设置CPU在线状态

cpu_set(smp_processor_id(), cpu_online_map);

展开过程:

cpu_set(smp_processor_id(), cpu_online_map);
↓ 宏展开
__cpu_set(smp_processor_id(), &cpu_online_map);
↓ 函数调用
set_bit(smp_processor_id(), cpu_online_map.bits);
  • smp_processor_id():获取当前CPU的ID(对于引导CPU,通常是0)
  • cpu_online_map:全局的cpumask_t变量,表示系统中所有在线的CPU
  • set_bit(cpu, bits):将cpumask中对应CPU的位设置为1

2.3. 设置CPU可调用状态

cpu_set(smp_processor_id(), cpu_callout_map);

展开过程:

cpu_set(smp_processor_id(), cpu_callout_map);
↓ 宏展开  
__cpu_set(smp_processor_id(), &cpu_callout_map);
↓ 函数调用
set_bit(smp_processor_id(), cpu_callout_map.bits);

3. 关键数据结构

cpumask_t 结构

struct {long unsigned int bits[1];
}
  • 用位图表示CPU集合
  • 每个bit代表一个CPU

4. 在多CPU启动过程中的作用

  1. 引导CPU准备:在启动其他APs之前,先设置BSP的状态
  2. 建立基础:为后续的CPU热插拔和调度建立基础框架
  3. 一致性保证:确保BSP在调度器和进程管理看来是"合法"的CPU

5. 关键点总结

  1. 引导CPU初始化:专门为系统启动时的BSP设置状态
  2. 双重状态设置:同时设置在线状态和启动响应状态
  3. 位图操作:使用高效的位操作来管理CPU集合
  4. SMP基础:为多处理器系统的正常运作奠定基础
  5. 启动序列:这是CPU启动序列中的关键一步,确保BSP在管理其他CPU之前自身已正确初始化

内核调度器的初始化sched_init

void __init sched_init(void)
{runqueue_t *rq;int i, j, k;for (i = 0; i < NR_CPUS; i++) {prio_array_t *array;rq = cpu_rq(i);spin_lock_init(&rq->lock);rq->active = rq->arrays;rq->expired = rq->arrays + 1;rq->best_expired_prio = MAX_PRIO;#ifdef CONFIG_SMPrq->sd = &sched_domain_dummy;rq->cpu_load = 0;rq->active_balance = 0;rq->push_cpu = 0;rq->migration_thread = NULL;INIT_LIST_HEAD(&rq->migration_queue);
#endifatomic_set(&rq->nr_iowait, 0);for (j = 0; j < 2; j++) {array = rq->arrays + j;for (k = 0; k < MAX_PRIO; k++) {INIT_LIST_HEAD(array->queue + k);__clear_bit(k, array->bitmap);}// delimiter for bitsearch__set_bit(MAX_PRIO, array->bitmap);}}/** The boot idle thread does lazy MMU switching as well:*/atomic_inc(&init_mm.mm_count);enter_lazy_tlb(&init_mm, current);/** Make us the idle thread. Technically, schedule() should not be* called from this thread, however somewhere below it might be,* but because we are the idle thread, we just pick up running again* when this runqueue becomes "idle".*/init_idle(current, smp_processor_id());
}

1. 函数功能

初始化系统的进程调度器,为每个CPU创建运行队列(runqueue),设置调度数据结构,并将当前线程设置为空闲线程

2. 代码详细解释

2.1. 变量声明

runqueue_t *rq;
int i, j, k;
  • rq:指向运行队列的指针
  • i:CPU循环计数器
  • j:优先级数组循环计数器(0=active, 1=expired)
  • k:优先级级别循环计数器

2.2. 遍历所有CPU初始化运行队列

for (i = 0; i < NR_CPUS; i++) {
  • 为每个可能的CPU初始化运行队列
  • NR_CPUS:系统支持的最大CPU数量

2.3. 获取CPU运行队列并初始化锁

rq = cpu_rq(i);
spin_lock_init(&rq->lock);
  • cpu_rq(i):获取第i个CPU的运行队列指针
  • spin_lock_init:初始化运行队列的自旋锁,用于保护运行队列的并发访问

2.4. 设置活动/过期队列指针

rq->active = rq->arrays;
rq->expired = rq->arrays + 1;
rq->best_expired_prio = MAX_PRIO;
  • rq->arrays:包含两个优先级数组的结构体数组
  • active指向第一个数组(当前运行的进程)
  • expired指向第二个数组(时间片用完的进程)
  • best_expired_prio设置为最大优先级值(最低优先级)

2.5. SMP相关初始化

#ifdef CONFIG_SMP
rq->sd = &sched_domain_dummy;
rq->cpu_load = 0;
rq->active_balance = 0;
rq->push_cpu = 0;
rq->migration_thread = NULL;
INIT_LIST_HEAD(&rq->migration_queue);
#endif
  • sd:调度域,初始化为虚拟调度域
  • cpu_load:CPU负载,初始为0
  • active_balance:主动负载平衡标志
  • push_cpu:推送迁移的目标CPU
  • migration_thread:迁移线程指针
  • migration_queue:迁移队列链表头初始化

2.6. 初始化I/O等待计数

atomic_set(&rq->nr_iowait, 0);
  • 原子操作设置I/O等待进程数为0
  • 用于统计在I/O等待的进程数量

2.7. 初始化优先级数组

for (j = 0; j < 2; j++) {array = rq->arrays + j;for (k = 0; k < MAX_PRIO; k++) {INIT_LIST_HEAD(array->queue + k);__clear_bit(k, array->bitmap);}// delimiter for bitsearch__set_bit(MAX_PRIO, array->bitmap);
}
  • 外层循环:遍历两个优先级数组(active和expired)
  • 内层循环:遍历每个优先级级别(0到MAX_PRIO-1)
  • INIT_LIST_HEAD(array->queue + k):初始化每个优先级的进程链表头
  • __clear_bit(k, array->bitmap):清除位图中对应优先级的位,表示该优先级没有就绪进程
  • __set_bit(MAX_PRIO, array->bitmap):设置边界位,作为位搜索的分隔符

2.8. 内存管理相关初始化

atomic_inc(&init_mm.mm_count);
enter_lazy_tlb(&init_mm, current);
  • atomic_inc(&init_mm.mm_count):增加初始内存管理结构的引用计数
  • enter_lazy_tlb(&init_mm, current):设置当前线程使用懒惰TLB切换模式

2.9. 初始化空闲线程

init_idle(current, smp_processor_id());
  • 将当前线程(内核启动线程)初始化为空闲线程
  • smp_processor_id():获取当前CPU的ID
  • 空闲线程在CPU没有其他任务可运行时执行

3. 关键概念解释

运行队列(runqueue)

  • 每个CPU都有自己的运行队列
  • 包含当前可运行的进程信息
  • 使用两个优先级数组实现O(1)调度算法

优先级数组

  • active数组:包含当前时间片未用完的进程
  • expired数组:包含时间片已用完的进程
  • 当active数组为空时,交换active和expired指针

位图(bitmap)

  • 快速查找最高优先级就绪进程
  • 每个位代表对应优先级是否有就绪进程
  • __set_bit(MAX_PRIO, array->bitmap)作为搜索边界

4. 初始化后的系统状态

  1. 所有运行队列:锁已初始化,数据结构就绪
  2. 优先级数组:所有进程链表为空,位图清除
  3. 当前线程:成为CPU 0的空闲线程
  4. 调度器:准备就绪,可以开始调度进程

初始化空闲任务init_idle

void __devinit init_idle(task_t *idle, int cpu)
{runqueue_t *rq = cpu_rq(cpu);unsigned long flags;idle->sleep_avg = 0;idle->interactive_credit = 0;idle->array = NULL;idle->prio = MAX_PRIO;idle->state = TASK_RUNNING;set_task_cpu(idle, cpu);spin_lock_irqsave(&rq->lock, flags);rq->curr = rq->idle = idle;set_tsk_need_resched(idle);spin_unlock_irqrestore(&rq->lock, flags);/* Set the preempt count _outside_ the spinlocks! */
#ifdef CONFIG_PREEMPTidle->thread_info->preempt_count = (idle->lock_depth >= 0);
#elseidle->thread_info->preempt_count = 0;
#endif
}

1. 函数功能

将指定的任务初始化为某个CPU的空闲任务,设置其调度属性并将其设置为该CPU的当前运行任务

2. 代码详细解释

2.1. 函数定义和变量声明

void __devinit init_idle(task_t *idle, int cpu)
{runqueue_t *rq = cpu_rq(cpu);unsigned long flags;
  • __devinit:表示该函数只在设备初始化阶段使用
  • idle:要初始化为空闲任务的任务指针
  • cpu:目标CPU的ID
  • rq = cpu_rq(cpu):获取指定CPU的运行队列
  • flags:用于保存中断状态的变量

2.2. 设置任务基本属性

idle->sleep_avg = 0;
idle->interactive_credit = 0;
idle->array = NULL;
idle->prio = MAX_PRIO;
idle->state = TASK_RUNNING;
set_task_cpu(idle, cpu);
  • sleep_avg = 0:睡眠平均值为0,表示没有睡眠历史
  • interactive_credit = 0:交互性信用为0,不是交互式进程
  • array = NULL:不在任何优先级数组中(空闲任务特殊处理)
  • prio = MAX_PRIO:优先级设置为最大值(最低优先级)
  • state = TASK_RUNNING:状态设置为运行中
  • set_task_cpu(idle, cpu):将任务绑定到指定的CPU

2.3. 锁保护下的运行队列更新

spin_lock_irqsave(&rq->lock, flags);
rq->curr = rq->idle = idle;
set_tsk_need_resched(idle);
spin_unlock_irqrestore(&rq->lock, flags);
  • spin_lock_irqsave(&rq->lock, flags):获取运行队列锁并保存中断状态
  • rq->curr = rq->idle = idle关键操作
    • rq->curr:设置当前运行任务为空闲任务
    • rq->idle:设置空闲任务指针
  • set_tsk_need_resched(idle):设置需要重新调度标志
  • spin_unlock_irqrestore(&rq->lock, flags):释放锁并恢复中断状态

2.4. 抢占计数设置

/* Set the preempt count _outside_ the spinlocks! */
#ifdef CONFIG_PREEMPTidle->thread_info->preempt_count = (idle->lock_depth >= 0);
#elseidle->thread_info->preempt_count = 0;
#endif
  • 在自旋锁外部设置抢占计数,因为自旋锁获取时会禁用抢占,释放时会解除抢占,操作会冲突
  • CONFIG_PREEMPT:配置了内核抢占
    • idle->thread_info->preempt_count = (idle->lock_depth >= 0)
    • 如果lock_depth >= 0(通常为0,表示可能持有锁),抢占计数设为1,否则为0
  • 没有配置内核抢占:
    • idle->thread_info->preempt_count = 0

3. 空闲任务的角色和特性

空闲任务的作用

  1. CPU占用者:当没有其他任务运行时执行
  2. 节能角色:可能执行HLT指令让CPU进入低功耗状态
  3. 调度后备:确保调度器始终有任务可运行

空闲任务的特殊属性

  • 最低优先级:不会与正常任务竞争CPU
  • 不在运行队列array = NULL,由调度器特殊处理
  • 始终可运行state = TASK_RUNNING
  • CPU绑定:每个CPU有自己的空闲任务
http://www.dtcms.com/a/490039.html

相关文章:

  • MATLAB基于ST-CNN-SVM的轴承故障诊断,S变换和卷积神经网络结合支持向量机
  • 在优豆云免费云服务器上初探SSH与SCP的便捷操作
  • MySQL数据库:软件、相关知识和基本操作
  • Bahdanau注意力
  • 重生之我在大学自学鸿蒙开发第七天-《AI语音朗读》
  • Spring AI 1.0 GA 深度解析:Java生态的AI革命已来
  • Linux网络之----TCP网络编程
  • 【零基础学习CAPL语法】——writeLineEx() 函数
  • 计算机网络数据链路层
  • 做网站选什么专业门户网站开发步骤博客
  • 论文写作 24: 全文保持同样的节奏
  • 洛谷 P1438 无聊的数列 题解
  • iOS混淆与IPA加固实战手记,如何构建苹果应用防反编译体系
  • 想抓PostgreSQL里的慢SQL?pg_stat_statements基础黑匣子和pg_stat_monitor时间窗,谁能帮你更准揪出性能小偷?
  • 把 iOS 26 的「Liquid Glass」带进 React Native
  • 基于物理信息的神经网络求解偏微分方程反问题的综合优化策略
  • 工地佩戴安全帽检测-目标检测数据集
  • 广东网站备案查询系统企业网站带后台
  • 知名的集团门户网站建设费用我要自学网网站建设
  • 2025 年 10 月科技前沿全景:从量子跃迁到生命重构的文明拐点
  • scene graph generation 用到的vg150数据集groundtruth数据预处理,展示和保存
  • 【Qt开发】多元素类控件(一)-> QListWidget
  • 【Mybatis从入门到入土】ResultMap映射、多表查询与缓存机制全解析
  • Springboot整合MinIO文件服务(windows版本)
  • HarmonyOS Next 项目完整学习指南
  • vscode离线下载依赖
  • Python 高效清理 Excel 空白行列:从原理到实战
  • 算法11.0
  • 工业级串口通信设计
  • 盐山网站建设广西网上办事大厅