目录
1. 关机重启软件流程框图
1.1 用户层
1.2 Linux内核层
1.3 ATF层
1.4 SCP层
2. Busybox中的关机重启命令
3. Linux内核中的处理
3.1 系统调用实现
3.2 内核关机函数分析
3.3 关闭所有设备处理
3.4 多CPU调度相关处理
3.5 内核核心关闭
3.6 硬件平台的关闭
3.7 内核PSCI相关操作
4. ATF Bl31中的处理
4.1 ATF 软件流程框图
4.2 内存布局bl31_entrypoint
4.3 runtime服务程序初始化
4.4 SMC异常处理入口分析
4.5 smc服务处理分析
4.5 硬件平台相关处理
5. SCP中的处理
当我们接触电源管理的时候,最简单的流程就是关机重启,但是仔细分析其涉及的所有源代码就会发现,关机重启虽然简单,但是“麻雀虽小,五脏俱全”,涉及到的软件模块非常的多,涉及的流程:Linux应用(busybox)-》Linux内核-》BL31-》SCP-》PMIC/CRU等硬件。所以是一个入门学习,特别是还没接触过Linux内核代码的好机会,下面进入代码的海洋遨游,超级干货!
1. 关机重启软件流程框图
在Linux系统上的处理分为用户态空间、内核空间、ATF、SCP四个阶段(ATF是ARM独有的,SCP在复杂SoC上才有应用)来处理:
1.1 用户层
利用reboot、poweroff等命令进行关机,在应用层会执行:
-
发送SIGTERM给所有进程,让进程正常退出
-
发送SIGKILL给所有进程,将其杀掉,并等待一段时间
-
调用reboot系统调用让系统关机/重启
1.2 Linux内核层
reboot系统调用会进入内核,具体流程为:
-
reboot系统调用根据参数找到kernel_power_off/reset
-
向关心reboot事件的进程发送消息--blocking_notifier_call_chain
-
内核Kobject状态发生改变不通知用户空间--usermodehelper_disable
-
关闭所有的设备--device_shutdown
-
禁止CPU热插拔,设置当前CPU为第一个在线CPU,把新任务转移到当前CPU上--migrate_to_reboot_cpu
-
关闭syscore设备--syscore_shutdown
-
提示用户空间系统将要关闭--pr_emerg
-
禁止cpu硬件中断--local_irq_disable
-
其他cpu处于非工作状态--smp_send_stop
-
调用psci接口,执行smc指令,关闭arm cpu--pmm_power_off/
rese->psci_sys_poweroff/reset->invoke_psci_fn->
arm_smccc_smc->SMCCC SMCCC_SMC
1.3 ATF层
执行SMC指令后会触发异常,进入ATF的BL31中继续执行:
-
进入异常向量处理的入口sync_exception_aarch64
-
跳转执行rt_svc_desc_t结构体保存的服务std_svc_smc_handler
-
执行psci相关处理。通用psci的处理函数psci_system_off和psci_system_reset,通过调用平台提供的system_off、 system_reset接口将psci消息转化为scmi消息发给SCP模块,实现最终的关机、重启。如果如果没有SCP固件的系统,会在ATF里面操作硬件寄存器进行关机重启处理。
1.4 SCP层
ATF通过scim消息发送给MHU硬件并产生中断,SCP接受到中断后内部依次进行处理的模块为:
mhu-->transport-->scmi-->scmi_system_power-->power_domain-->ppu/system_power-->i2c/cru,最后SCP固件通过控制PMIC/CRU的硬件寄存器实现对系统的关机重启设置。
2. Busybox中的关机重启命令
执行关机重启的系统命令,例如shutdown/poweroff/halt/reboot/init命令
进程及服务如果提前会被正确的中止,我们就说其是安全的退出。
通常关机重启命令需要管理员权限执行,所在系统目录为/sbin/*,如下为shutdown命令:
命令格式
[root@localhost ~]# shutdown [选项] 时间 [警告信息]
选项:
-c:取消已经执行的 shutdown 命令;
-h:关机;
-r:重启;
init命令相关执行:
[root@localhost~]# init 0
#关机,也就是调用系统的 0 级别
[root@localhost ~】# init 6
#重启,也就是调用系统的 6 级别
现在Linux里面这些命令基本都使用busybox实现的,代码参考:
https://busybox.net/downloads/
busybox启动的时候,会注册reboot的处理信号
init\init.c中init_main函数在初始化的时候调用
sigaddset(&G.delayed_sigset, SIGUSR1); /* halt */
sigaddset(&G.delayed_sigset, SIGTERM); /* reboot */
sigaddset(&G.delayed_sigset, SIGUSR2); /* poweroff */
/* Now run the looping stuff for the rest of forever */
while (1) {
/* (Re)run the respawn/askfirst stuff */
run_actions(RESPAWN | ASKFIRST);
/* Wait for any signal (typically it's SIGCHLD) */
check_delayed_sigs(NULL); /* NULL timespec makes it wait */
.....
}
check_delayed_sigs()函数会收到reboot的信号
运行busybox reboot的时候,reboot —> halt_main,可知会执行 halt_main()函数,在init\halt.c中
static const smallint signals[] = { SIGUSR1, SIGUSR2, SIGTERM };
# define RB_HALT_SYSTEM 0xcdef0123
# define RB_ENABLE_CAD 0x89abcdef
# define RB_DISABLE_CAD 0
# define RB_POWER_OFF 0x4321fedc
# define RB_AUTOBOOT 0x01234567
flags = getopt32(argv, "d:+nfwi", &delay);
if (!(flags & 4)) { /* no -f */
rc = kill(pidlist[0], signals[which]);
}
else{
rc = reboot(magic[which]);
}
这里可以看出来,分为两个流程:
-
当reboot命令没有加-f的时候,直接使用kill发送信号到busybox执行halt_reboot_pwoff函数
-
直接使用-f的话,直接使用reboot系统调用接口,通知内核,让内核执行重启操作,简单粗暴
如果1中发送kill命令的SIGTERM 信号后,在busybox的轮询处理函数中会接收信号进行处理,如下:
static void check_delayed_sigs(struct timespec *ts)