利用reboot、poweroff等命令进行关机,在应用层会执行:
调用reboot系统调用让系统关机/重启
1.2Linux内核层
reboot系统调用会进入内核,具体流程为:
内核Kobject状态发生改变不通知用户空间--usermodehelper_disable
关闭所有的设备--device_shutdown
关闭syscore设备--syscore_shutdown
提示用户空间系统将要关闭--pr_emerg
禁止cpu硬件中断--local_irq_disable
其他cpu处于非工作状态--smp_send_stop
arm_smccc_smc->SMCCCSMCCC_SMC1.3ATF层
执行SMC指令后会触发异常,进入ATF的BL31中继续执行:
进入异常向量处理的入口sync_exception_aarch64
跳转执行rt_svc_desc_t结构体保存的服务std_svc_smc_handler
1.4SCP层
ATF通过scim消息发送给MHU硬件并产生中断,SCP接受到中断后内部依次进行处理的模块为:
2.Busybox中的关机重启命令执行关机重启的系统命令,例如shutdown/poweroff/halt/reboot/init命令
进程及服务如果提前会被正确的中止,我们就说其是安全的退出。
通常关机重启命令需要管理员权限执行,所在系统目录为/sbin/*,如下为shutdown命令:
initinit.c中init_main函数在初始化的时候调用
check_delayed_sigs()函数会收到reboot的信号
运行busyboxreboot的时候,reboot—>halt_main,可知会执行halt_main()函数,在inithalt.c中
这里可以看出来,分为两个流程:
当reboot命令没有加-f的时候,直接使用kill发送信号到busybox执行halt_reboot_pwoff函数
直接使用-f的话,直接使用reboot系统调用接口,通知内核,让内核执行重启操作,简单粗暴
如果1中发送kill命令的SIGTERM信号后,在busybox的轮询处理函数中会接收信号进行处理,如下:
在busybox内部解析后会执行halt_reboot_pwoff()函数
halt_reboot_pwoff()分为三步:
发送SIGTERM给所有进程,让进程正常退出
发送SIGKILL给所有进程,将其杀掉
让系统重启
发送SIGTERM和SIGKILL信号给其他进程
调用子进程进行reboot系统调用:
reboot系统调用根据参数找到kernel_power_off/reset
向关心reboot事件的进程发送消息--blocking_notifier_call_chain
禁止CPU热插拔,设置当前CPU为第一个在线CPU,把新任务转移到当前CPU上--migrate_to_reboot_cpu
调用psci接口,执行smc指令,关闭armcpu--pmm_power_off/rese->psci_sys_poweroff/reset->invoke_psci_fn->
arm_smccc_smc->SMCCCSMCCC_SMC3.1系统调用实现
其中cmd就是系统调用传进来的magic值,其他的值定义为:
reboot系统调用实现过程:
3.2内核关机函数分析
这里我们以关机poweroff命令为例,进行代码分析,重启流程相似。
3.3关闭所有设备处理
系统中所有的设备都在“/sys/devices/”目录下,这些设备是一个链表结构串起来的,devices_kset是链表头,里面都是structdevice,然后找到对应的structbus_type和structdevice_driver等,然后按照优先级例如:class>bus>driver执行对应的shutdown回调函数。
对于多CPU的机器,无论哪个CPU触发了当前的系统调用,代码都可以运行在任意的CPU上。这个接口将代码分派到一个特定的CPU上,并禁止调度器分派代码到其它CPU上。也就是说,这个接口被执行后,只有一个CPU在运行,用于完成后续的reboot动作。
3.5内核核心关闭
systemcore的shutdown和设备的shutdown类似,也是从一个链表中,遍历所有的systemcore,并调用它的shutdown接口。
3.6硬件平台的关闭
PSCI(PowerStateCoordinationInterface)电源状态协调接口,是ARM定义的电源管理接口规范。
PSCI初始化流程:
start_kernel()->setup_arch()->psci_dt_init()->psci_0_2_init()->psci_probe()->psci_0_2_set_functions()
设备树里面的信息如下里标记的版本是psci-0.2,method是使用smc。
psci_0_2_set_functions会给处理函数赋值
PSCI关机流程:
PSCI_0_2_FN_SYSTEM_OFF的值计算为:0x84000000+8,查看ARMPSCI手册:
invoke_psci_fn()在smc模式下对应__invoke_psci_fn_smc()函数:
SMCCC宏如下,smc指令触发一个安全监视器异常后,将栈上的数据存到x0~x3上,回头看__invoke_psci_fn_smc函数实际是返回x0的结果。
由于smccc_smc函数的入参有9个参数,按照约定,前4个参数存在r0-r3,其他参数从右向左入栈。
r0=a0,r1=a1,r2=a2,r3=a3,r4=a4,r5=a5,r6=a6,r7=a7
进入ATF中EL3模式执行:
smc指令是arm-v8手册中定义的一个指令,这个安全监视器触发一个异常,然后进入到EL3。EL3:安全监控异常级别。异常级别,用于执行安全监视器代码,用于处理非安全状态和安全状态之间的转换。EL3始终处于Secure状态.4.ATFBl31中的处理4.1ATF软件流程框图
BL31中smc异常触发流程图
在Linux侧调用smc异常之后,会根据中断向量表触发cpu的同步异常sync_exception_aarch64/32
然后跳转执行到handle_sync_exception->smc_handler64/32中
根据_RT_SVC_DESCS_START_+RT_SVC_DESC_HANDLE的位置,跳转执行rt_svc_desc_t结构体保存的服务std_svc_smc_handler
最后跳转到el3_exit返回Linux侧。
SMC异常触发执行流程:
进入ATF的方式触发异常:同步异常SMC、异步异常(irq,fiq)
如果是同步异常,那么一定是在linux或tee中发生了smc调用,此时进入跳转ATF中异常向量表中的同步异常程序smc_handler64或smc_handler32在该程序中,解析smcid,来选择跳转到具体哪一个rt-svc(runtimeservice)
如果是异步异常,那么一定是触发了irq或fiq或serror中断等,此时进入跳转ATF中异常向量表中的异步异常程序,进而跳转到响应的中断处理函数.
4.2内存布局bl31_entrypoint
编译使用的lds文件是arm-trusted-firmware/bl31/bl31.ld.S,开头就可以看到入口是bl31_entrypoint:
bl31_entrypoint在bl31/aarch64/bl31_entrypoint.S中定义
可以看到设置_exception_vectors为runtime_exceptions函数的:
在bl31/aarch64/runtime_exceptions.S中
同样其他宏经过转化如下:
4.3runtime服务程序初始化
bl31_entrypoint入口向下执行首先是bl31_setup,然后是bl31_main
bl31_main()函数:
runtime_svc_init()函数
RT_SVC_DECS_NUM表示svc数量
RT_SVC_DESCS_START开始的位置已经存入了结构体数据数组,因为在ld文件中进行了内存布局说明
在bl31/bl31.ld.S中:
在include/common/bl_common.ld.h中
rt_svc_descs段存放的内容是通过DECLARE_RT_SVC宏来定义的:
//其中__setion("rt_svc_descs")的意思就是注册到rt_svc_descs段中
例如在services/std_svc/std_svc_setup.c中
staticconstrt_svc_desc_t__svc_desc_std_svc服务。其服务id为SMC_TYPE_FAST<<6+OEN_STD_START,结束服务的id为SMC_TYPE_FAST<<6+OEN_STD_END
service->init()会执行std_svc_setup()函数
->psci_setup((constpsci_lib_args_t*)svc_arg)
(void)plat_setup_psci_ops((uintptr_t)lib_args->mailbox_ep,
&psci_plat_pm_ops);
plat_setup_psci_ops()的定义根据平台,我们使用的是qemu,对应plat/qemu/qemu_sbsa/sbsa_pm.c文件中:
*psci_ops=&plat_qemu_psci_pm_ops;
4.4SMC异常处理入口分析SMC命令执行后,CPU会根据异常向量表找到sync_exception_aarch64的入口
会执行handle_sync_exception,在bl31/aarch64/runtime_exceptions.S中
x30里面存储的是esr_el3的26-32位,里面是什么判断了smc64
当前平台架构是aarch64的,看一下sync_handler64这个处理,在bl31/aarch64/runtime_exceptions.S中
sync_handler64里找rt_svc_desc_t结构体类型里面的handle处理函数,而这些处理函数在rt_svc_descs节中
一个问题:怎么找到index?
例如发的一个smc消息id是0x84000000+8
bit31决定是fastcall,还是stdcall(yield对应的就是stdcall)
bit30表示是以32位传参,还是以64位传参,注意我们看了optee在linux的driver,都是以32位方式
bit29:24决定服务的类型
bit23:16reserved
bit15:0每种call类型下,表示range
这个地方值为4,
系统启动的时候会把index信息存入到rt_svc_descs_indices里面,根据4取出来就可以了。
base+index<
handler=(base+off)+(index<
w15=(__RT_SVC_DESCS_START__+RT_SVC_DESC_HANDLE)+w15<
4.5smc服务处理分析
std_svc_smc_handler()中,可以看到会调用psci_smc_handler函数。
而在psci_smc_handler函数中,就可以看到上面传入的psci传入的PSCI_SYSTEM_OFF指令
在qemu平台上的实现如下:
psci_plat_pm_ops系统初始化的时候会赋值.system_off=qemu_system_off,
semihosting_exit:
对应重启,qemu_system_reset()函数设置GPIO实现
如果不是ATF里面自己处理,有SCP,见下章节分析。5.SCP中的处理
mhu模块:mhu_isr收到中断status=smt_channel->api->signal_message(smt_channel->id);
signal_message是smt模块里面提供的,对共享内存的数据进行处理