[Linux]学习笔记系列 -- [kernel]cpu
title: cpu
categories:
- linux
- kernel
tags: - linux
- kernel
abbrlink: ba80502e
date: 2025-10-03 09:01:49

https://github.com/wdfk-prog/linux-study
文章目录
- include/linux/cpumask.h
- cpumask_check 验证当前cpu数量是否超过了配置的最大cpu数量,并返回cpu
- cpumask_test_and_set_cpu 在 cpumask 中对 CPU 进行原子测试和设置 CPU
- kernel/cpu.c
- set_cpu_online 设置 CPU 在线状态
- set_cpu_ possible enable present active dying 设置 CPU 状态
- boot_cpu_init 激活第一个处理器
- boot_cpu_hotplug_init 引导 CPU 热插拔初始化
- cpuhp_setup_state_nocalls 设置 CPU 热插拔状态
- kernel/smpboot.c
- __smpboot_create_thread 创建一个内核线程
- smpboot_destroy_threads 销毁所有与热插拔相关的线程
- smpboot_unpark_thread 唤醒一个停泊的线程
- smpboot_register_percpu_thread 注册一个与热插拔相关的per_cpu线程
- smpboot_thread_fn - per-cpu热插拔线程的循环函数
include/linux/cpumask.h
cpumask_check 验证当前cpu数量是否超过了配置的最大cpu数量,并返回cpu
// 验证当前cpu数量是否超过了配置的最大cpu数量
static __always_inline void cpu_max_bits_warn(unsigned int cpu, unsigned int bits)
{
#ifdef CONFIG_DEBUG_PER_CPU_MAPSWARN_ON_ONCE(cpu >= bits);
#endif /* CONFIG_DEBUG_PER_CPU_MAPS */
}/* verify cpu argument to cpumask_* operators */
//验证当前cpu数量是否超过了配置的最大cpu数量
static __always_inline unsigned int cpumask_check(unsigned int cpu)
{//small_cpumask_bits = NR_CPUS//验证当前cpu数量是否超过了配置的最大cpu数量cpu_max_bits_warn(cpu, small_cpumask_bits);return cpu;
}
cpumask_test_and_set_cpu 在 cpumask 中对 CPU 进行原子测试和设置 CPU
/*** cpumask_test_and_set_cpu - 在 cpumask 中对 CPU 进行原子测试和设置 CPU* @cpu: cpu number (< nr_cpu_ids)
* @cpumask:cpumask 指针** test_and_set_bit cpumasks 的包装器。** 返回:如果在 @cpumask 的旧位图中设置@cpu则为 true,否则返回 false*/
static __always_inline
bool cpumask_test_and_set_cpu(int cpu, struct cpumask *cpumask)
{return test_and_set_bit(cpumask_check(cpu), cpumask_bits(cpumask)); // ((maskp)->bits)
}
kernel/cpu.c
set_cpu_online 设置 CPU 在线状态
void set_cpu_online(unsigned int cpu, bool online)
{/** 需要 atomic_inc/dec() 来处理重新启动和 kexec 代码对此功能的可怕滥用,这些代码在关闭 CPU 时从 IPI/NMI 广播中调用它。* 来自常规 CPU 热插拔的调用已正确序列化。** 请注意,__num_online_cpus 属于 atomic_t 类型这一事实并不能保护未序列化的读取器免受并发热插拔作的影响。*/if (online) {if (!cpumask_test_and_set_cpu(cpu, &__cpu_online_mask)) //在 cpumask 中对 CPU 进行原子测试和设置 CPUatomic_inc(&__num_online_cpus); //设置成功增加该变量值} else {if (cpumask_test_and_clear_cpu(cpu, &__cpu_online_mask))atomic_dec(&__num_online_cpus);}
}
set_cpu_ possible enable present active dying 设置 CPU 状态
//assign_bit 为内存中的位分配值
#define assign_cpu(cpu, mask, val) \assign_bit(cpumask_check(cpu), cpumask_bits(mask), (val))#define set_cpu_possible(cpu, possible) assign_cpu((cpu), &__cpu_possible_mask, (possible))
#define set_cpu_enabled(cpu, enabled) assign_cpu((cpu), &__cpu_enabled_mask, (enabled))
#define set_cpu_present(cpu, present) assign_cpu((cpu), &__cpu_present_mask, (present))
#define set_cpu_active(cpu, active) assign_cpu((cpu), &__cpu_active_mask, (active))
#define set_cpu_dying(cpu, dying) assign_cpu((cpu), &__cpu_dying_mask, (dying))
boot_cpu_init 激活第一个处理器
/** 激活第一个处理器*/
void __init boot_cpu_init(void)
{int cpu = smp_processor_id();/* 将引导 cpu 标记为 “present”、“online” 等,以用于 SMP 和 UP 情况*/set_cpu_online(cpu, true);set_cpu_active(cpu, true);set_cpu_present(cpu, true);set_cpu_possible(cpu, true);#ifdef CONFIG_SMP__boot_cpu_id = cpu;
#endif
}
boot_cpu_hotplug_init 引导 CPU 热插拔初始化
/** 必须调用 _AFTER_ 来设置per_cpu区域*/
void __init boot_cpu_hotplug_init(void)
{
#ifdef CONFIG_SMPcpumask_set_cpu(smp_processor_id(), &cpus_booted_once_mask);atomic_set(this_cpu_ptr(&cpuhp_state.ap_sync_state), SYNC_STATE_ONLINE);
#endifthis_cpu_write(cpuhp_state.state, CPUHP_ONLINE);this_cpu_write(cpuhp_state.target, CPUHP_ONLINE);
}
cpuhp_setup_state_nocalls 设置 CPU 热插拔状态
/*** __cpuhp_setup_state_cpuslocked - 设置热插拔计算机状态的回调* @state:要设置的状态* @name:步骤名称* @invoke:如果为 true,则为其中* CPU 状态 >= @state* @startup:启动回调函数* @teardown:teardown 回调函数* @multi_instance:为多个实例设置状态,这些实例获取* 后添加。** 调用者在调用此函数时需要保持 cpus 读锁定。*返回:* 成功时:* 如果 @state 为 CPUHP_AP_ONLINE_DYN 或 CPUHP_BP_PREPARE_DYN,则为正数状态号;* 0 表示所有其他状态* 失败时:正确(负)错误代码*/
int __cpuhp_setup_state_cpuslocked(enum cpuhp_state state,const char *name, bool invoke,int (*startup)(unsigned int cpu),int (*teardown)(unsigned int cpu),bool multi_instance)
{int cpu, ret = 0;bool dynstate;lockdep_assert_cpus_held();if (cpuhp_cb_check(state) || !name)return -EINVAL;mutex_lock(&cpuhp_state_mutex);ret = cpuhp_store_callbacks(state, name, startup, teardown,multi_instance);dynstate = state == CPUHP_AP_ONLINE_DYN || state == CPUHP_BP_PREPARE_DYN;if (ret > 0 && dynstate) {state = ret;ret = 0;}if (ret || !invoke || !startup)goto out;/** Try to call the startup callback for each present cpu* depending on the hotplug state of the cpu.*/for_each_present_cpu(cpu) {struct cpuhp_cpu_state *st = per_cpu_ptr(&cpuhp_state, cpu);int cpustate = st->state;if (cpustate < state)continue;ret = cpuhp_issue_call(cpu, state, true, NULL);if (ret) {if (teardown)cpuhp_rollback_install(cpu, state, NULL);cpuhp_store_callbacks(state, NULL, NULL, NULL, false);goto out;}}
out:mutex_unlock(&cpuhp_state_mutex);/** If the requested state is CPUHP_AP_ONLINE_DYN or CPUHP_BP_PREPARE_DYN,* return the dynamically allocated state in case of success.*/if (!ret && dynstate)return state;return ret;
}/*** cpuhp_setup_state_nocalls - 设置热插拔状态回调,而不调用* @startup 回调* @state:呼叫的安装状态* @name:回调的名称。* @startup:启动回调函数或 NULL(如果不需要)* @teardown:teardown 回调函数或 NULL(如果不需要)** 与 cpuhp_setup_state() 相同,只是在安装过程中不调用 @startup 回调。如果 SMP=n 或 HOTPLUG_CPU=n,则为 NOP。*/
static inline int cpuhp_setup_state_nocalls(enum cpuhp_state state,const char *name,int (*startup)(unsigned int cpu),int (*teardown)(unsigned int cpu))
{return __cpuhp_setup_state(state, name, false, startup, teardown,false);
}
kernel/smpboot.c
__smpboot_create_thread 创建一个内核线程
- 根据一个给定的线程模板(struct smp_hotplug_thread *ht),在一个指定的CPU(unsigned int cpu)上,实际地创建一个、绑定并准备好一个内核线程。
/** 这是一个静态的内部函数,用于在一个指定的CPU上,根据一个热插拔线程模板来创建线程。* @ht: 指向smp_hotplug_thread线程模板的指针。* @cpu: 目标CPU的编号。** 返回值: 0表示成功,负的错误码表示失败。*/
static int
__smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
{/* tsk: 指向新创建任务的task_struct的指针。*/struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu);/* td: 指向一个临时的数据结构,用于向新线程传递参数。*/struct smpboot_thread_data *td;/** 检查ht->store这个per-cpu数组中,对应于当前cpu的槽位是否已经有值。* per_cpu_ptr(ht->store, cpu)获取该槽位的地址,'*'解引用获取其内容。*/if (tsk)/* 如果已经存在,说明线程已创建,直接返回成功。*/return 0;/** 为smpboot_thread_data动态分配内存。* GFP_KERNEL表示这是一个常规的、可能引起睡眠的内核内存分配。* cpu_to_node(cpu)确保内存在与目标CPU亲和的NUMA节点上分配。*/td = kzalloc_node(sizeof(*td), GFP_KERNEL, cpu_to_node(cpu));/* 如果内存分配失败,返回-ENOMEM错误。*/if (!td)return -ENOMEM;/* 将目标CPU编号和线程模板指针存入td中,以便传递。*/td->cpu = cpu;td->ht = ht;/** 调用kthread_create_on_cpu()来创建并绑定线程。* - smpboot_thread_fn: 是所有通过此框架创建的线程的通用入口函数。* - td: 是传递给smpboot_thread_fn的参数。* - cpu: 指定线程必须在这个CPU上创建和运行。* - ht->thread_comm: 是线程名的格式化字符串,如"ksoftirqd/%u"。*/tsk = kthread_create_on_cpu(smpboot_thread_fn, td, cpu,ht->thread_comm);/* 检查kthread_create_on_cpu是否返回了一个错误。IS_ERR会判断指针是否编码了错误。*/if (IS_ERR(tsk)) {/* 如果创建失败,释放之前分配的td内存。*/kfree(td);/* 使用PTR_ERR从指针中提取出负的错误码并返回。*/return PTR_ERR(tsk);}/* 调用kthread_set_per_cpu,在task_struct中设置这是一个per-cpu线程的标志。*/kthread_set_per_cpu(tsk, cpu);/** 注释:停泊该线程,以便当CPU可用时,它能立刻在该CPU上启动。*//* 调用kthread_park,向新线程发出一个“请停泊”的请求。*/kthread_park(tsk);/** 增加新线程tsk的引用计数。因为我们将要把它的指针存入一个全局的* per-cpu变量中,必须增加引用计数以防止它意外消失。*/get_task_struct(tsk);/* 将新创建的task_struct指针,存入ht->store的对应CPU槽位中。*/*per_cpu_ptr(ht->store, cpu) = tsk;/* 检查线程模板中是否提供了一个create回调函数。*/if (ht->create) {/** 注释:在调用create回调之前,确保该任务实际上已经调度出去并* 进入了停泊位置。至少,迁移线程的回调要求任务已不在运行队列上。*//** 调用wait_task_inactive,同步等待,直到tsk的状态变为TASK_PARKED。* 如果等待失败,则触发一个内核警告。*/if (!wait_task_inactive(tsk, TASK_PARKED))WARN_ON(1);else/* 在确认线程已停泊后,安全地调用用户提供的create回调函数。*/ht->create(cpu);}/* 返回0表示成功。*/return 0;
}
smpboot_destroy_threads 销毁所有与热插拔相关的线程
static void smpboot_destroy_threads(struct smp_hotplug_thread *ht)
{unsigned int cpu;/* We need to destroy also the parked threads of offline cpus */for_each_possible_cpu(cpu) {struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu);if (tsk) {kthread_stop_put(tsk);*per_cpu_ptr(ht->store, cpu) = NULL;}}
}
smpboot_unpark_thread 唤醒一个停泊的线程
static void smpboot_unpark_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
{struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu);if (!ht->selfparking)kthread_unpark(tsk);
}
smpboot_register_percpu_thread 注册一个与热插拔相关的per_cpu线程
static LIST_HEAD(hotplug_threads);
static DEFINE_MUTEX(smpboot_threads_lock);/*** smpboot_register_percpu_thread - 注册一个与热插拔相关的per_cpu线程* @plug_thread: 热插拔线程的描述符结构体指针。** 在所有在线的CPU上创建并启动这些线程。*/
int smpboot_register_percpu_thread(struct smp_hotplug_thread *plug_thread)
{/* cpu: 用于在循环中迭代CPU编号。*/unsigned int cpu;/* ret: 用于存储函数返回值,默认为0(成功)。*/int ret = 0;/** 获取cpus_read_lock读锁,以创建一个稳定的CPU视图,防止在遍历期间* CPU的在线状态发生改变。*/cpus_read_lock();/** 获取smpboot_threads_lock互斥锁,以保护对全局hotplug_threads链表的* 并发访问。*/mutex_lock(&smpboot_threads_lock);/* 使用for_each_online_cpu宏,遍历系统中所有当前处于“在线”状态的CPU。*/for_each_online_cpu(cpu) {/** 调用内部辅助函数__smpboot_create_thread,根据plug_thread模板,* 为指定的cpu创建一个内核线程。*/ret = __smpboot_create_thread(plug_thread, cpu);/* 如果线程创建失败(ret不为0)。*/if (ret) {/* 调用smpboot_destroy_threads,清理掉所有已经为这个* plug_thread模板创建成功的线程,以回滚操作。*/smpboot_destroy_threads(plug_thread);/* 跳转到out标签,准备解锁并返回错误。*/goto out;}/* 如果创建成功,则调用smpboot_unpark_thread唤醒这个新创建的、* 处于停泊状态的线程,让它开始运行。*/smpboot_unpark_thread(plug_thread, cpu);}/** 在为所有当前在线的CPU都创建了线程之后,将这个plug_thread模板* 添加到全局的hotplug_threads链表中。* 这确保了未来有新CPU上线时,也会为它创建这个线程。*/list_add(&plug_thread->list, &hotplug_threads);
out:/* 释放互斥锁。*/mutex_unlock(&smpboot_threads_lock);/* 释放读锁。*/cpus_read_unlock();/* 返回操作结果(0表示成功,否则为错误码)。*/return ret;
}
/** 将smpboot_register_percpu_thread函数符号以GPL兼容的方式导出,* 使得遵循GPL许可的可加载内核模块也能调用它。*/
EXPORT_SYMBOL_GPL(smpboot_register_percpu_thread);
smpboot_thread_fn - per-cpu热插拔线程的循环函数
/*** smpboot_thread_fn - per-cpu热插拔线程的循环函数* @data: 线程的数据指针** 检查线程的停止和停泊条件。为已注册的线程调用必要的* setup, cleanup, park和unpark函数。** 返回值: 当线程应该退出时返回1,否则返回0。* (注:根据实现,它最终返回0来退出)*/
static int smpboot_thread_fn(void *data)
{/* td: 指向本次线程实例的私有数据,包含了cpu编号和ht模板指针。*/struct smpboot_thread_data *td = data;/* ht: 指向定义了这类per-cpu线程通用行为的模板结构体。*/struct smp_hotplug_thread *ht = td->ht;/* 进入无限循环,构成线程的主体。*/while (1) {/* 将自己的状态置为可中断睡眠,准备在无事可做时睡眠。*/set_current_state(TASK_INTERRUPTIBLE);/* 在检查状态前禁用抢占,以防止在检查和睡眠之间产生竞争。*/preempt_disable();/* 检查是否有外部请求要求本线程停止并退出。*/if (kthread_should_stop()) {/* 如果需要停止,则恢复为运行状态以执行清理。*/__set_current_state(TASK_RUNNING);/* 重新使能抢占。*/preempt_enable();/* 清理必须与设置镜像对应。*//* 如果模板中定义了cleanup回调,并且本线程已初始化过,则调用它。*/if (ht->cleanup && td->status != HP_THREAD_NONE)ht->cleanup(td->cpu, cpu_online(td->cpu));/* 释放为本次实例分配的私有数据td。*/kfree(td);/* 从线程主函数返回0,这将最终导致kthread_exit被调用。*/return 0;}/* 检查是否有外部请求要求本线程暂停工作并进入停泊状态。*/if (kthread_should_park()) {/* 恢复运行状态以执行停泊前的回调。*/__set_current_state(TASK_RUNNING);preempt_enable();/* 如果模板中定义了park回调,并且线程当前是活跃的。*/if (ht->park && td->status == HP_THREAD_ACTIVE) {/* 健壮性检查:确保我们在预期的CPU上运行。*/BUG_ON(td->cpu != smp_processor_id());/* 调用park回调。*/ht->park(td->cpu);/* 更新内部状态为已停泊。*/td->status = HP_THREAD_PARKED;}/* 调用kthread框架的标准停泊函数,它会让自己睡眠。*/kthread_parkme();/* 我们可能因为stop而被唤醒。*//* 当从kthread_parkme()唤醒后,继续下一次主循环,重新评估所有状态。*/continue;}/* 健壮性检查:再次确认我们运行在正确的CPU上。*/BUG_ON(td->cpu != smp_processor_id());/* 检查状态机,以执行一次性的setup或unpark操作。*/switch (td->status) {case HP_THREAD_NONE: /* 初始状态,线程首次运行。*/__set_current_state(TASK_RUNNING);preempt_enable();/* 如果模板中定义了setup回调,则调用它。*/if (ht->setup)ht->setup(td->cpu);/* 将状态迁移到活跃状态。*/td->status = HP_THREAD_ACTIVE;/* 继续下一次主循环。*/continue;case HP_THREAD_PARKED: /* 从停泊状态被唤醒。*/__set_current_state(TASK_RUNNING);preempt_enable();/* 如果模板中定义了unpark回调,则调用它。*/if (ht->unpark)ht->unpark(td->cpu);/* 将状态迁移到活跃状态。*/td->status = HP_THREAD_ACTIVE;/* 继续下一次主循环。*/continue;}/* 如果模板中定义了thread_should_run回调,则调用它判断当前是否真的有工作要做。*/if (!ht->thread_should_run(td->cpu)) {/* 如果没有工作,则使能抢占(但不检查重新调度),然后调用schedule()主动让出CPU。*/preempt_enable_no_resched();schedule();} else {/* 如果有工作要做,则恢复运行状态并使能抢占。*/__set_current_state(TASK_RUNNING);preempt_enable();/* 调用模板中定义的核心工作函数。*/ht->thread_fn(td->cpu);}}
}
