什么是Trusty OS
为了应对开放系统的安全风险,Global Platform(GP)提出了可信执行环境的概念(TEE)。TEE要求在设备上有一个能够与Rich OS(例如Android)并存,并运行于独立空间的系统。这个系统的安全级别更高,可以为Rich OS提供密钥存储、加解密计算等服务。很明显,这种系统的实现需要获得从芯片到软件的整套支持。很早以前ARM就推出了基于自己核心的解决方案---TrustZone。而各个下游厂商通常会在此基础上各自实现自己的软件系统,对Rich OS的接口也各不相同。这在一定程度上造成了开发成本的上升以及软件系统的碎片化等问题。而Trusty OS就是谷歌针对这些问题提出的解决方案。
下图是Trusty的一个大致构架,可以看出Trusty OS和Android是相对独立运行的,他们之间通过特定的接口驱动进行通信。
图1 Trusty OS 框架
此外,由图中也可以看出Trusty OS可以直接接管硬件资源,这也在很大程度上提高了系统的安全性。关于这一点,在后面的应用场景中进行更详细的讨论。
有可能是因为Trusty不强制要求,目前的AOSP默认情况下似乎还没有包含这部分内容。不过我们可以通过下面的命令直接下载代码:
$ repo init -u https://android.googlesource.com/trusty/manifest
$ repo sync
启动过程
Trusty的内核与Android Bootloader有一个同样的名字---Little Kernel(LK),实际内容也差不多。我们先来看启动过程。在目录trusty/external/lk/arch/arm64中我们可以找到链接脚本system-onesegment.ld。其中的ENTRY(_start)指定了LK从_start函数开始。而_start的定义在同一目录下的文件start.S中。start.S会做一些CPU的初始化工作,然后通过bl指令跳至lk_main函数。
前面提到的lk_main在trusty/external/lk/top/main.c中,它主要完成的工作如下:
1 void lk_main(ulong arg0, ulong arg1, ulong arg2, ulong arg3) 2 { 3 … … 4 thread_init_early();//初始化thread相关的结构体上下文 5 arch_early_init();//初始化MMU、向量表之类构架相关的东西 6 lk_primary_cpu_init_level(LK_INIT_LEVEL_ARCH_EARLY, LK_INIT_LEVEL_PLATFORM_EARLY - 1); 7 platform_early_init();//初始化中断控制器、timer之类平台相关的东西 8 lk_primary_cpu_init_level(LK_INIT_LEVEL_PLATFORM_EARLY, LK_INIT_LEVEL_TARGET_EARLY - 1); 9 target_early_init();//初始化主板上的东西,比如gpio、各种外围总线之类 10 call_constructors();//构造函数相关的初始化 11 lk_primary_cpu_init_level(LK_INIT_LEVEL_TARGET_EARLY, LK_INIT_LEVEL_HEAP - 1); 12 heap_init();//堆栈初始化 13 lk_primary_cpu_init_level(LK_INIT_LEVEL_HEAP, LK_INIT_LEVEL_KERNEL - 1); 14 kernel_init();//内核初始化 15 lk_primary_cpu_init_level(LK_INIT_LEVEL_KERNEL, LK_INIT_LEVEL_THREADING - 1); 16 thread_t *t = thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);//创建一个thread,然后在调度中执行后面的初始化函数bootstrap2 17 … … 18 thread_become_idle();//将当前thread设为idle,并启动调度过程 19 … … 20 } 21
在thread_become_idle中会调用thread_yield进行一次调度,然后在idle_thread_routine中通过ARM的wfi指令进入idle。此外,我们还可以看到lk_main中还多次执行了lk_primary_cpu_init_level,它的作用是按照enum lk_init_level执行通过宏LK_INIT_HOOK加入的回调函数。例如执行LK_INIT_HOOK(libtrusty_apps, start_apps, LK_INIT_LEVEL_APPS + 1);后会将相应的信息组合成一个结构体lk_init_struct然后放入section .lk_init。在初始化程序调用lk_primary_cpu_init_level 函数时,它会通过lk_init_level将lk_init区域中的内容取出来,找出相应的lk_init_level,并加以执行。至此启动过程基本完成。
下面简单说几句调度过程。我们知道通常时间片轮询的系统都是通过硬件上的tick中断进入调度的。因此我们可以从中断处理函数着手跟踪代码。中断出现后后会首先进入trusty/ external/lk/arch/arm64/exceptions.S:
1 .macro irq_exception 2 regsave_short 3 msr daifclr, #1 /* reenable fiqs once elr and spsr have been saved */ 4 mov x0, sp 5 bl platform_irq 6 cbz x0, .Lirq_exception_no_preempt\@ 7 bl thread_preempt 8 .Lirq_exception_no_preempt\@: 9 msr daifset, #1 /* disable fiqs to protect elr and spsr restore */ 10 b arm64_exc_shared_restore_short 11 .endm 12
可以看到这会里通过bl指令跳入interrupts.c 中的platform_irq函数,它会返回下面的枚举类型中的一个,具体判断条件后面会提到:
1 enum handler_return { 2 INT_NO_RESCHEDULE = 0, 3 INT_RESCHEDULE, 4 }; 5
platform_irq执行完毕后,根据这个函数的返回值决定是否需要调度,如果需要调度就调用thread_preempt。通常情况下thread_preempt会先把当前thread放到队列尾部,然后调用thread_resched进行切换。Thread_resched在队列中找到要执行的thread,然后启动一个回调为thread_timer_tick的10毫秒的tick中断,以便再次进入platform_irq完成下一次循环。最后它会调用arch_context_switch完成新thread的上下文切换。
这里有一个非常重要的变量---remaining_quantum,这个变量实际上是标识当前thread轮询次数的计数器,每个thread都有自己的remaining_quantum,初始值为5。tick中断到达时会按照前面的描述首先调用platform_irq,platform_irq会先找到上面注册的回调函数thread_timer_tick并加以执行。thread_timer_tick每次执行时都会将remaining_quantum减1,然后判断它的值,如果大于0就返回INT_NO_RESCHEDULE,否则返回INT_RESCHEDULE。最终由platform_irq将这个结果传入exceptions.S中,完成整个调度过程。
Trusty应用程序
Trusty OS中的MMU功能是打开的,因此的每个应用都可以运行在各自独立的虚拟地址空间。整个系统采用时间片轮询调度,所有应用享有同样的优先级。开发者可以使用C/C++语言编写应用程序。虽然它看上去是一个比较完整的操作系统,但是Trusty OS是不支持动态安装第三方应用程序的。也就是说它是个封闭的系统,这也在一定程度上保障了系统的安全性。
所有的应用程序由一个不带参数的main函数开始执行。此外每个应用还包含一个manifest.c文件,这个文件中会包含一些UUID等应用程序描述信息:
1 #define TRUSTY_APP_MANIFEST_ATTRS \ 2 __attribute((aligned(4))) __attribute((section(".trusty_app.manifest"))) 3 trusty_app_manifest_t TRUSTY_APP_MANIFEST_ATTRS trusty_app_manifest = 4 { 5 /* UUID : {5f902ace-5e5c-4cd8-ae54-87b88c22ddaf} */ 6 { 0x5f902ace, 0x5e5c, 0x4cd8, 7 { 0xae, 0x54, 0x87, 0xb8, 0x8c, 0x22, 0xdd, 0xaf } }, 8 … … 9 }; 10
可以看出这部分信息在链接后会和该应用程序放在一起,并形成一个完整的trusty_app image。
链接文件trusty_apps.ld中会指定两个指针,__trusty_app_start及__trusty_app_end。这两个指针一个指向app image的头部一个指向尾部,由此系统启动时可以通过它们找到所有的应用:
1 void trusty_app_init(void) 2 { 3 trusty_app_image_start = (char *)&__trusty_app_start; 4 trusty_app_image_end = (char *)&__trusty_app_end; 5 … … 6 for (i = 0, trusty_app = trusty_app_list; i < trusty_app_count; i++, trusty_app++) { 7 … … 8 } 9
Trusty应用通过端口(ports)和通道(channels)与Android中相应的应用程序进行交互。端口用于Trusty OS中的应用向Android暴露自己的服务。也就是说每个服务都有自己的端口ID,当Android中的某个应用需要与Trusty OS中相应的服务交互的时候,就作为客户端向相应的端口ID发起请求。Trusty OS中的服务接受请求后会与客户端协商建立两个通道,用于全双工通信。
转载请注明出处: