1. 介绍
Linux 的cpu热插拔是cpu电源管理的一部分,支持系统在负载比较低的时候,拔掉一个cpu,从而省下cpu的静态功耗,并在系统需要时,重新将cpu插上。
另外,在多cpu系统启动、休眠唤醒的过程中也会涉及到non-boot cpu的拔插。
2. 数据结构
cpu hotplug数据结构
3. CPU状态
cpu的状态包括:possible、present、online、active。
- possible状态的cpu:可理解为存在这个CPU资源,但还没有纳入Kernel的管理范围。
- present状态的cpu:表示已经被kernel接管。
- online状态的cpu:表示可以被调度器使用。
- active状态的cpu:表示可以被迁移migrate。
4. 拔插流程
Linux内核在初始的时候,会创建虚拟总线cpu_subsys,每个cpu调用register_cpu注册时,都会将cpu设备挂在这个总线下。
cpu的拔插是通过操作文件节点online实现的,具体拔插操作如下(以cpu1为例):
- 拔核操作
echo 0 > /sys/devices/system/cpu/cpu1/online
- 插核操作
echo 1 > /sys/devices/system/cpu/cpu1/online
CPU的拔插流程可以概括为:
cpu hotplug流程
AP:将要被拔掉的cpu。
BP:处理拔核流程的cpu。
4.1 CPU UP
cpu up流程
- 颜色的含义:
- 绿色:系统初始化过程调用。
- 黄色:BP上做的处理。
- 橙色:AP上做的处理。
- Kernel会为每个cpu都创建一个hotplug线程,执行state中定义的状态回调函数,比如teardown/startup。
- cpu_up的时候依赖底层的具体实现,没有进一步画出cpu_boot后的动作,这后面linux会通过PSCI接口跟ATF通信,然后由ATF跟其他电源管理模块通信,最终由电源管理模块完成上电。
4.2 CPU DOWN
cpu down流程
- cpu down的流程跟cpu on相反,整个过程很类似;
- cpuhp拔插核线程创建后,由于should_run为false,所以并没有实质的运行,并处于S状态,在_cpu_down函数中,由__cpuhp_kick_ap函数将should_run置成true,然后wake_up_process cpuhp线程运行在被拔核cpu,然后等待cpuhp运行到指定状态(从CPUHP_ONLINE到CPUHP_TEARDOWN_CPU)的teardown.single调用;
- cpuhp线程(cpuhp_thread_fun)从CPUHP_ONLINE执行到CPUHP_TEARDOWN_CPU状态,做相关的teardown.single的调用,对于takedown_cpu的调用首先在处理拔核的cpu上运行,会停止每cpu线程,然后通过stop_machine_cpuslocked函数将take_cpu_down任务queue到被拔核的stopper线程上运行,在stop_machine_cpuslocked的__stop_cpus函数中,拔核cpu会通过wait_for_completion(&done.completion)等待被拔核cpu的migrate线程(cpu_stopper_thread)执行完work的回调(muti_cpu_stop,会调用到take_cpu_down),被拔核cpu执行完work的回调后,就会从过cpu_stop_signal_done函数释放信号量(complete(&done->completion)),然后拔核cpu继续运行将被拔核cpu下电;
- 对于运行在被拔核cpu上的stopper线程(migration)执行take_cpu_down,会先通过执行__cpu_disable将被拔核cpu设为offline,然后从CPUHP_TEARDOWN_CPU运行到CPUHP_AP_OFFLINE状态的teardown.single调用,然后调用stop_machine_park将stopper线程的flag置上KTHREAD_SHOULD_PARK,然后在hotplug线程循环函数smpboot_thread_fn中判断KTHREAD_SHOULD_PARK,然后将stopper线程停下来,然后kthread_parkme里会调用schedule_preempt_disabled schedule出去,然后这时会运行被拔核上的idle线程(或者将其他非rq上的task拉到被拔核cpu执行)。
- 运行被拔核cpu上的idle线程会通过函数cpu_is_offline判断当前的cpu是否offline,若offline会调用cpuhp_report_idle_dead,然后调用cpuhp_complete_idle_dead,先将state设为CPUHP_AP_IDLE_DEAD,进而complete_ap_thread,这时拔核cpu一直阻塞在takedown_cpu函数中的wait_for_ap_thread,然后拔核cpu执行最后的拔核操作;
- cpu电的关闭是通过do_idle->cpu_die->cpu_off利用PSCI向ATF发起关闭cpu的请求,然后由ATF跟其他电源管理模块通信,最终在电源管理模块将cpu的电关闭。