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
变量,表示系统中所有在线的CPUset_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启动过程中的作用
- 引导CPU准备:在启动其他APs之前,先设置BSP的状态
- 建立基础:为后续的CPU热插拔和调度建立基础框架
- 一致性保证:确保BSP在调度器和进程管理看来是"合法"的CPU
5. 关键点总结
- 引导CPU初始化:专门为系统启动时的BSP设置状态
- 双重状态设置:同时设置在线状态和启动响应状态
- 位图操作:使用高效的位操作来管理CPU集合
- SMP基础:为多处理器系统的正常运作奠定基础
- 启动序列:这是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负载,初始为0active_balance
:主动负载平衡标志push_cpu
:推送迁移的目标CPUmigration_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. 初始化后的系统状态
- 所有运行队列:锁已初始化,数据结构就绪
- 优先级数组:所有进程链表为空,位图清除
- 当前线程:成为CPU 0的空闲线程
- 调度器:准备就绪,可以开始调度进程
初始化空闲任务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的IDrq = 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. 空闲任务的角色和特性
空闲任务的作用
- CPU占用者:当没有其他任务运行时执行
- 节能角色:可能执行HLT指令让CPU进入低功耗状态
- 调度后备:确保调度器始终有任务可运行
空闲任务的特殊属性
- 最低优先级:不会与正常任务竞争CPU
- 不在运行队列:
array = NULL
,由调度器特殊处理 - 始终可运行:
state = TASK_RUNNING
- CPU绑定:每个CPU有自己的空闲任务