1 简介
little kernel是一个基于线程的操作系统,是运行在AARCH32状态下的操作系统,跟uCOS类似程序不可以动态加载,程序需要在编译操作系统时一起编译,little kernel提供event、mutex、timer以及thread的支持。
little kernel现在用于安卓的bootloader,bootloader作为其一个应用程序(aboot)实现
本文分析的little kernel是dragonboard410c的源码
2 源码获取
git clone git://codeaurora.org/kernel/lk.git
git checkout -b mylk remotes/origin/master
3 编译
安装编译器 sudo apt install gcc-arm-linux-gnueabi
设置工具链 export TOOLCHAIN_PREFIX=arm-linux-gnueabi-
编译 make msm8916 EMMC_BOOT=1
4 运行流程
通过链接脚本arch/arm/system-onesegment.ld第4行ENTRY(_start),可以知道程序从_start开始执行,_start位于arch/arm/crt0.S中,此文件实现了异常向量表、堆栈初始化、数据段初始化(data、BSS)并把自己移动到合适的地址,最后跳转到kmain
4.1 crt0.S
#define DSB .byte 0x4f, 0xf0, 0x7f, 0xf5 /* ARM64的数据屏障指令 */
#define ISB .byte 0x6f, 0xf0, 0x7f, 0xf5 /* ARM64的指令屏障指令 */
.section ".text.boot"
.globl _start
/*
* 异常向量
*/
_start:
b reset //复位异常
b arm_undefined //未定义指令异常
b arm_syscall //软件中断,系统调用
b arm_prefetch_abort //指令预取异常
b arm_data_abort //数据访问异常
b arm_reserved //保留未用
b arm_irq //interrupt
b arm_fiq //fast interrupt
reset:
#ifdef ENABLE_TRUSTZONE
/*Add reference to TZ symbol so linker includes it in final image */
ldr r7, =_binary_tzbsp_tzbsp_bin_start
#endif
/* do some cpu setup */
/*
* 以下代码用于初始化SCTLR(System Control Register)
* 以下代码 清除SCTLR b15/b13/b12/b2/b1/b0
* 如果是ARMv8 置位SCTLR b5
* b15保留
* b13 0 异常基地址remap到VBAR、HVBAR(Hyp mode)、MVBAR(Monitor mode)
* 1 异常固定 地址0xFFFF0000
* b12 1 使能指令缓存
* b5 1 使能barrier指令
* b2 1 使能数据缓存
* b1 1 使能对齐检测异常
* b0 1 MMU使能
*
* 关闭指令数据缓存,关闭MMU,使能异常地在remap,使能barrier指令
*/
#if ARM_WITH_CP15
/* Read SCTLR */
mrc p15, 0, r0, c1, c0, 0
/* XXX this is currently for arm926, revist with armv6 cores */
/* new thumb behavior, low exception vectors, i/d cache disable, mmu disabled */
bic r0, r0, #(1<<15| 1<<13 | 1<<12)
bic r0, r0, #(1<<2 | 1<<0)
/* disable alignment faults */
bic r0, r0, #(1<<1)
/* Enable CP15 barriers by default */
#ifdef ARM_CORE_V8
orr r0, r0, #(1<<5)
#endif
/* Write SCTLR */
mcr p15, 0, r0, c1, c0, 0
#ifdef ENABLE_TRUSTZONE
/*nkazi: not needed ? Setting VBAR to location of new vector table : 0x80000 */
/*
* 此处设置异常向量表到0x8000
*/
ldr r0, =0x00080000
mcr p15, 0, r0, c12, c0, 0
#endif
#endif
#if WITH_CPU_EARLY_INIT
/* call platform/arch/etc specific init code */
#ifndef ENABLE_TRUSTZONE
/* Not needed when TrustZone is the first bootloader that runs.*/
bl __cpu_early_init
#endif
/* declare return address as global to avoid using stack */
.globl _cpu_early_init_complete
_cpu_early_init_complete:
#endif
/*
* 检测是否是热启动,如果是热启动复位
* 使用冷启动时,内存中warm_boot_tag为0
* 如果热启动,内存中的值不变,warm_boot_tag中的值为1,促发重启
*/
#if (!ENABLE_NANDWRITE)
#if WITH_CPU_WARM_BOOT
ldr r0, warm_boot_tag
cmp r0, #1
/* if set, warm boot */
ldreq pc, =BASE_ADDR /* 复位芯片,BASE_ADDR是芯片启示地在 */
mov r0, #1
str r0, warm_boot_tag /* 设置warm_boot_tag地在中的值为1 */
#endif
#endif
/* see if we need to relocate */
mov r0, pc
sub r0, r0, #(.Laddr - _start)//计算_start的绝对地址,即本段代码的加载位置
.Laddr:
ldr r1, =_start //加载复位地址
cmp r0, r1 //对比_start与复位是否一致,即本段代码是否加载到复位地址
beq .Lstack_setup //加载到复位地址跳转到.Lstack_setup
/* 以下代码用于将本段程序移动到复位地址 */
/* we need to relocate ourselves to the proper spot */
ldr r2, =__data_end
.Lrelocate_loop:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r1, r2
bne .Lrelocate_loop
/* 此处使用绝对跳转,跳转到正确的地址 */
/* we're relocated, jump to the right address */
ldr r0, =.Lstack_setup
bx r0
/* 检测热启动的全局变量 */
.ltorg
#if WITH_CPU_WARM_BOOT
warm_boot_tag:
.word 0
#endif
/*
* 以下代码对32位模式下的SP指针初始化
*
* CPSR
* b4 0 AARCH64
* 1 AARCH32
* b4 = 1时
* b3-b0 0000 User
* 0001 FIQ
* 0010 IRQ
* 0011 Supervisor
* 0110 Monitor
* 0111 Abort
* 1010 Hyp
* 1011 Undefined
* 1111 System
*
*/
.Lstack_setup:
/* set up the stack for irq, fiq, abort, undefined, system/user, and lastly supervisor mode */
/* 把cpsr的b0-b4清零保存到r0,便于之后切换异常模式 */
mrs r0, cpsr
bic r0, r0, #0x1f
/* 加载内存地址,堆栈向低地址增长 */
ldr r2, =abort_stack_top
orr r1, r0, #0x12 // irq
msr cpsr_c, r1
ldr r13, =irq_save_spot /* save a pointer to a temporary dumping spot used during irq delivery */
orr r1, r0, #0x11 // fiq
msr cpsr_c, r1
mov sp, r2
orr r1, r0, #0x17 // abort
msr cpsr_c, r1
mov sp, r2
orr r1, r0, #0x1b // undefined
msr cpsr_c, r1
mov sp, r2
orr r1, r0, #0x1f // system
msr cpsr_c, r1
mov sp, r2
orr r1, r0, #0x13 // supervisor
msr cpsr_c, r1
mov sp, r2
/* copy the initialized data segment out of rom if necessary */
ldr r0, =__data_start_rom
ldr r1, =__data_start
ldr r2, =__data_end
/* 如果没有静态数据跳过初始化过程.L__copy_loop */
cmp r0, r1
beq .L__do_bss
.L__copy_loop:/* 静态数据初始化 */
cmp r1, r2
ldrlt r3, [r0], #4
strlt r3, [r1], #4
blt .L__copy_loop
/* bss数据段初始化为0 */
.L__do_bss:
/* clear out the bss */
ldr r0, =__bss_start
ldr r1, =_end
mov r2, #0
.L__bss_loop:
cmp r0, r1
strlt r2, [r0], #4
blt .L__bss_loop
#ifdef ARM_CPU_CORTEX_A8
DSB /* 数据屏障 */
ISB /* 指令屏障 */
#endif
bl kmain /* 调用lk的main函数 */
b . /* 死循环 */
.ltorg
/* 堆栈 */
.bss
.align 2
/* the abort stack is for unrecoverable errors.
* also note the initial working stack is set to here.
* when the threading system starts up it'll switch to a new
* dynamically allocated stack, so we don't need it for very long
*/
abort_stack:
.skip 1024
abort_stack_top:
4.2 kmain
kmain是little kernel的主函数,操作系统初始化后创建一个线程bootstrap2
void kmain(void)
{
/*
* 系统进程相关的初始化
* 初始化全局线程链表thread_list
* 初始化运行队列run_queue
* 为当前创建创建进程标示
*/
thread_init_early();
/*
* 架构(ARM、x86、MIPS)相关初始化
* 设置异常向量基地址
* 使能MMU
* 使能cp10 cp11,这两个协处理器与FPU相关)
* 使能周期计数器(cycle count)
*/
arch_early_init();
/*
* Soc相关初始化
* msm8916执行了以下操作
* 获取platform相关信息,保存到borad全局变量中
* 子系统时钟控制句柄初始化,保存在msm_clk_list结构体中
* 中断控制其初始化
* 初始化ticks_per_sec
* 通过SCM调用,判断系统是否支持SCM(scm_arm_support)
*/
platform_early_init();
/*
* 目标板相关初始化
* dragonboard 410c执行了以下操作
* 串口初始化
*/
target_early_init();
dprintf(INFO, "welcome to lk\n\n");
bs_set_timestamp(BS_BL_START);/* 获取系统开始时钟 */
// deal with any static constructors
dprintf(SPEW, "calling constructors\n");
call_constructors();/* 编译器相关的构造,链接器输出.ctors段 */
// bring up the kernel heap
dprintf(SPEW, "initializing heap\n");
heap_init();/* 初始化栈 */
/*
* 堆栈溢出保护相关
* 在堆栈中放入一个常数
* 函数返回之前检测
* 如果发生变化,说明堆栈被破坏
* 此处使用随机数初始化此常数__stack_chk_guard
* 需要编译器配合
* 在函数入口处在堆栈放入常数
* 在函数出口处检测常数是否正确
* */
__stack_chk_guard_setup();
// initialize the threading system
dprintf(SPEW, "initializing threads\n");
thread_init();/* 定时器timer相关初始化 */
// initialize the dpc system
dprintf(SPEW, "initializing dpc\n");
/*
* dpc为一个系统服务
* 用于执行一些比较重要的任务
* 优先级仅次与highest
* 在系统级用于进程资源回收
*/
dpc_init();
// initialize kernel timers
dprintf(SPEW, "initializing timers\n");
/*
* 启动定时任务
*/
timer_init();
#if (!ENABLE_NANDWRITE)
dprintf(SPEW, "creating bootstrap completion thread\n");
thread_resume(thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
// 使能中断
exit_critical_section();
/* 标记当前线程为空闲线程 */
thread_become_idle();
#else
bootstrap_nandwrite();
#endif
}
4.2.1 堆栈保护
kmain代码中有一处比较关键的代码__stack_chk_guard_setup(),此代码与安全相关(防止栈溢出),工作比较简单只是设置全局变量__stack_chk_guard。要理解上面的代码需要看一下arm开启栈保护(-fstack-protector)生成的汇编代码。
int func(){
char b[10];
gets(b);/* 此处可能溢出 */
return 0;
}
对应汇编代码如下
/*
*
* 堆栈如下
* --------------- SP
*
* b的空间 10byte
*
* ---------------
* 对齐保留 2byte
* ---------------
* canary 4byte
* ---------------
* FP备份 4byte
* --------------- FP
* 返回地址 4byte
* ---------------
*/
func:
@ args = 0, pretend = 0, frame = 16
@ frame_needed = 1, uses_anonymous_args = 0
push {fp, lr} /* 备份上一级函数的FP,并且备份返回地址 */
add fp, sp, #4 /* 设置FP */
sub sp, sp, #16 /* 开辟堆栈空间 */
ldr r3, .L4 /* 获取__stack_chk_guard的地址 */
ldr r3, [r3] /* 获取__stack_chk_guard的值 */
str r3, [fp, #-8] /* 把__stack_chk_guard的值保存到堆栈中 */
sub r3, fp, #20 /* 计算出变量b的地址 */
mov r0, r3
bl gets /* 函数调用 */
mov r3, #0
mov r0, r3 /* 设置函数返回值 */
ldr r3, .L4 /* 获取__stack_chk_guard的地址 */
ldr r2, [fp, #-8] /* 获取堆栈中保存的__stack_chk_guard的备份 */
ldr r3, [r3] /* 获取__stack_chk_guard的值 */
cmp r2, r3 /* 判断堆栈中的值有无变化 */
beq .L3
bl __stack_chk_fail/* 堆栈中的值被破坏报错 */
.L3:
sub sp, fp, #4 /* 恢复SP */
@ sp needed
pop {fp, pc} /* 恢复FP,并退出函数 */
.L5:
.align 2
.L4:
.word __stack_chk_guard
4.3 bootstrap2
bootstrap2还有一些与设备相关的初始化工作之后启动apps_init,apps_init实现了一个应用框架,具体实现常见第6章
static int bootstrap2(void *arg)
{
dprintf(SPEW, "top of bootstrap2()\n");
arch_init();
// XXX put this somewhere else
#if WITH_LIB_BIO
bio_init();
#endif
#if WITH_LIB_FS
fs_init();
#endif
// initialize the rest of the platform
dprintf(SPEW, "initializing platform\n");
platform_init();
// initialize the target
dprintf(SPEW, "initializing target\n");
target_init();
dprintf(SPEW, "calling apps_init()\n");
apps_init();
return 0;
}
5 little kernel OS
5.1 概要
little kernel是一个基于线程(thread
)的操作系统
little kernel根据线程优先级进行调度,优先调度优先级高的线程。同一个优先级下可以有多个线程,这些线程通过时间片轮询调度。
little kernel提供定时器(timer
)功能,可以在多长时间后执行一个任务,也可以设定为周期性执行。
little kernel为线程通信提供了基本的服务event
、mutex
event
用于向其他程序发送消息,通知其他线程执行某个动作
mutex
用于多线程之间保护共享资源,防止共享资源被多个线程同时访问
5.2 线程
5.2.1 数据结构
little kernel每个线程为一个函数,函数格式如下
typedef int (*thread_start_routine)(void *arg);
little kernel每个线程对应一个线程标示符,标示符定义如下:
typedef struct thread {
int magic;/* 一个常数,用于检测结构体是否合法 */
struct list_node thread_list_node;/* 在thread_list中的节点 */
struct list_node queue_node;/* 在运行队列或者等待队列中的节点 */
int priority;/* 线程的优先级 */
enum thread_state state; /* 线程状态 */
int saved_critical_section_count;
int remaining_quantum;/* 线程剩余的时间片 */
/*
* 线程进入BLOCK状态时
* 这个指针指向等待队列
* 在带超时等待的event、mutex时有效
* 用于超时后从队列中删除后把队列中的计数器减1
*/
struct wait_queue *blocking_wait_queue;
/* 记录带超时等待的状态,超时或等到 */
status_t wait_queue_block_ret;
/* 架构与线程相关的数据 */
struct arch_thread arch;
/* 线程的栈空间 */
void *stack;
size_t stack_size;
thread_start_routine entry;/* 线程入口函数 */
void *arg;/* 线程入口函数的参数 */
int retcode;/* 线程结束的返回值 */
/*
* 线程的局部存储
* 线程创建时从父线程继承
* static inline __ALWAYS_INLINE uint32_t tls_get(uint entry)
* static inline __ALWAYS_INLINE uint32_t tls_set(uint entry, uint32_t val)
* 以上两个函数分别用来修改获取局部存储
* 在调试时,会输出线程的局部存储
* 在当前代码中此功能没有使用
*/
uint32_t tls[MAX_TLS_ENTRY];
char name[32];/* 线程名 */
} thread_t;
little kernel的线程有如下状态
enum thread_state {
THREAD_SUSPENDED = 0,/* 线程创建时的状态 */
THREAD_READY, /* 准备就绪的线程,处于run_queue中 */
THREAD_RUNNING, /* 运行中的线程,不在run_queue中 */
THREAD_BLOCKED, /* 阻塞中的线程(等待event、mutex),处于某个wait_queue中 */
THREAD_SLEEPING, /* 线程调用thread_sleep后的状态 */
THREAD_DEATH, /* 线程退出后的状态,等待dpc服务回收资源 */
};
相关的全局变量
static struct list_node thread_list; /* 线程列表 */
static thread_t bootstrap_thread; /* 系统上电的线程 */
thread_t *current_thread; /* 当前正在执行的线程 */
thread_t *idle_thread; /* 空闲线程 */
little kernel通过优先级调度程序,准备就绪的程序会被加入一个运行队列。little kernel具有多少个优先级就有多少个运行队列。运行队列是一个列表,多个运行队列组成一个数组。线程通过thread_t.priority找到对应的线程队列。其中优先级值越大等级越高。
static struct list_node run_queue[NUM_PRIORITIES];/* 多个运行队列组成的数组 */
static uint32_t run_queue_bitmap;/* 标记run_queue数组中,哪些非空 */
run_queue_bitmap用于标记那个队列非空,使用run_queue_bitmap.bit[n]== 1标示run_queue[n]非空。因为run_queue_bitmap是uint32_t类型,只有32个比特位,所以little kernel最多有32个优先级。
5.2.2 相关操作
5.2.2.1 thread_init_early
初始化线程相关的全局变量(thread_list、run_queue、current_thread),标记当前线程为开机线程bootstrap_thread
,注意开机线程具有最高的优先级HIGHEST_PRIORITY
void thread_init_early(void)
5.2.2.2 thread_init
初始化定时器preempt_timer,此定时器将用于添加周期任务thread_timer_tick
,用于处理线程的时间片。任务的维护由线程调度函数thread_resched
维护
void thread_init(void);
5.2.2.3 thread_create
此操作用于创建一个线程标示符,进行一些基本的初始化,创建线程的堆栈空间,并添加到全局线程列表thread_list。注意此时的线程处于THREAD_SUSPENDED
状态,不会立即执行,将等待thread_resume
调用把线程加入到运行队列。
thread_t *thread_create(
const char *name, /* 要创建的线程的名字 */
thread_start_routine entry, /* 是线程的入口函数 */
void *arg, /* 线程入口函数的参数 */
int priority, /* 线程的优先级*/
size_t stack_size) /* 线程的堆栈的大小 */
5.2.2.4 thread_resume
把thread_create
创建的线程加入到运行队列的开始,并触发一次线程调度。
status_t thread_resume(thread_t *t)/* t是thread_create创建的线程标示符 */
5.2.2.5 thread_exit
线程退出,会向系统dpc
复位注册一个任务,用于线程资源回收(堆栈、标示符、从全局线程列表删除任务)
dpc
是一个优先级较高的线程,作为系统复位,可以接受其他线程的任务
void thread_exit(int retcode)/* retcode为线程返回值 */
5.2.2.6 thread_resched
触发一次线程调度(执行就绪队列中优先级最高的线程),统计空闲进程的执行时间,并且根据情况初始化一个周期定时任务thread_timer_tick
。thread_timer_tick用于处理线程的时间片。
void thread_resched(void)
5.2.2.7 thread_yield
让出CPU资源,触发线程调度,优先调度同等优先级的其他线程
void thread_yield(void)
5.2.2.8 thread_preempt
处理线程的时间片,时间片用完时,调度相同优先级的下一个线程
void thread_preempt(void)
5.2.2.9 thread_block
使进程进入等待状态,在调用此函数前需要处理好进程标示符,把进程标示符加入合适的等待队列并标记进程为THREAD_BLOCK
void thread_block(void)
5.2.2.10 thread_sleep
延时函数,把当前线程标记为THREAD_SLEEPING
,添加一个定时任务(在指定时间后唤醒自己),触发线程调度
void thread_sleep(time_t delay)/* delay为要延时的时间 */
5.2.2.11 thread_set_name
修改当前线程的名字
void thread_set_name(const char *name);
5.2.2.12 thread_set_priority
void thread_set_priority(int priority);
修改当前线程的优先级
5.2.2.13 thread_become_idle
把当前进程标记为空闲进程
void thread_become_idle(void) __NO_RETURN;
5.3 定时器
5.3.1 数据结构
/* timer_queue为定时器列表
* 按时间顺序排序任务
* 所有的定时任务都将添加到这个列表中
*/
static struct list_node timer_queue;
typedef struct timer {
int magic; /* 常数用于检查结构体为合法的定时器 */
struct list_node node; /* 用于构成链表 */
time_t scheduled_time; /* 促发时间的时间 */
time_t periodic_time; /* 标记为周期时间 */
timer_callback callback;/* 要执行的任务,函数回调句柄 */
void *arg; /* 回调函数的参数 */
} timer_t;
/* 定时任务,函数类型定义 */typedef enum handler_return (*timer_callback)(struct timer *, time_t now, void *arg);
5.3.2 相关操作
5.3.2.1 timer_init
启动定时任务,创建一个周期定时任务此任务会维护定时器队列timer_queue
void timer_init(void);
5.3.2.2 timer_initialize
初始化一个timer_t结构
void timer_initialize(timer_t *);
5.3.2.3 timer_set_oneshot
添加一个一次性定时任务,指定延时delay,要执行的任务timer_callback,以及任务的参数arg
void timer_set_oneshot(timer_t *, time_t delay, timer_callback, void *arg);
5.3.2.4 timer_set_periodic
添加一个周期性定时任务,指定延时delay,要执行的任务timer_callback,以及任务的参数arg
void timer_set_periodic(timer_t *, time_t period, timer_callback, void *arg);
5.3.2.5 timer_cancel
取消一个定时器,将从timer_queue中删除
void timer_cancel(timer_t *);
5.4 等待队列
5.4.1 数据结构
等待队列,是处于阻塞THREAD_BLOCK
状态的线程的列表。为enent
和mutex
的属性,用于记录被阻塞的线程。
typedef struct wait_queue { int magic; /* 一个常数标记当前结构体为一个合法的wait_queue_t */ struct list_node list; /* 链表,被堵塞的线程标示符列表 */ int count; /* 等待队列中线程的个数 */} wait_queue_t;
5.4.2 相关操作
5.4.2.1 wait_queue_init
初始化等待队列,此时列表初始化为空
void wait_queue_init(wait_queue_t *);
5.4.2.2 wait_queue_destroy
释放等待队列的资源
void wait_queue_destroy(wait_queue_t *, bool reschedule);
所有被阻塞的线程会被添加到运行队列。如果reschedule为真,触发线程调度,当前线程将让出CPU资源,优先调度优级大于等于当前线程的线程。
5.4.2.3 wait_queue_block
把当前线程添加到等待队列,timeout
不等于INFINITE_TIME
时添加一个定时任务。此任务用于把当前线程唤醒(添加到运行队列并触发任务调度),并把当前任务从等待队列中删除。
status_t wait_queue_block(wait_queue_t *, time_t timeout);
5.4.2.4 wait_queue_wake_one
唤醒等待队列中的第一个线程,reschedule标记要不要触发一次调度,wait_queue_error是唤醒的原因
int wait_queue_wake_one(wait_queue_t *wait, bool reschedule, status_t wait_queue_error)
5.4.2.5 wait_queue_wake_all
唤醒等待队列中的所有线程,reschedule标记要不要触发一次调度,wait_queue_error是唤醒的原因
int wait_queue_wake_all(wait_queue_t *, bool reschedule, status_t wait_queue_error);
5.4.2.6 thread_unblock_from_wait_queue
把线程从等待队列中删除,添加到运行队列。reschedule标记要不要触发一次调度,wait_queue_error是唤醒的原因
status_t thread_unblock_from_wait_queue(thread_t *t, bool reschedule, status_t wait_queue_error)
5.5 event
5.5.1 数据结构
event有两种,一种需要调用event_unsignal,一种不需要调用event_unsignal。
typedef struct event { int magic; /* 一个常数用于标记当前结构体为合法的event_t */ bool signalled; /* 标记envent是否被标记 */ uint flags; /* 标记event的种类,0不会自动复位,EVENT_FLAG_AUTOUNSIGNAL会自动复位 */ wait_queue_t wait; /* 被event阻塞的线程的等待队列 */} event_t;
5.5.2 相关操作
5.5.2.1 event_init
初始化一个event
,指定其初始状态为initial
,并指定event
的复位类型
void event_init(event_t *, bool initial, uint flags);
5.5.2.2 event_destroy 销毁一个event
对象,并把等待event的线程添加到运行队列,并触发线程调度
void event_destroy(event_t *);
5.5.2.3 event_wait 等待event
status_t event_wait(event_t *);
5.5.2.4 event_wait_timeout 带超时等待event
status_t event_wait_timeout(event_t *, time_t);
5.5.2.5 event_signal 标记一个event
,reschedule指定要不要立马调度
status_t event_signal(event_t *, bool reschedule);
5.5.2.6 event_unsignal 清除event
标记
status_t event_unsignal(event_t *);
5.6 mutex
5.6.1 数据结构
typedef struct timer {
int magic; /* 常数用于检查结构体为合法的定时器 */
struct list_node node; /* 用于构成链表 */
time_t scheduled_time; /* 促发时间的时间 */
time_t periodic_time; /* 标记为周期时间 */
timer_callback callback;/* 要执行的任务,函数回调句柄 */
void *arg; /* 回调函数的参数 */
} timer_t;
5.6.2 相关操作
5.6.2.1 mutex_init 初始化一个mutex_init
void mutex_init(mutex_t *);
5.6.2.2 mutex_destroy 销毁一个mutex,把等待队列中所有的进程添加到运行队列并触发线程调度
void mutex_destroy(mutex_t *);
5.6.2.3 mutex_acquire 等待一个mutex
status_t mutex_acquire(mutex_t *);
5.6.2.4 mutex_acquire_timeout 带超时等待一个mutex
status_t mutex_acquire_timeout(mutex_t *, time_t);
5.6.2.5 mutex_release 释放mutex_release,唤醒等待队列中的一个线程
status_t mutex_release(mutex_t *);
5 little kernel APP架构
app_descriptor结构体用于记录应用程序信息,定义与include/app.h中
struct app_descriptor {
const char *name; //应用名
app_init init; //初始化函数
app_entry entry; //线程函数
unsigned int flags; //标记,现在只有两个值,1在apps_init时不启动,0在apps_init时启动
};
另外在include/app.h中定义了两个宏
struct app_descriptor {
const char *name; //应用名
app_init init; //初始化函数
app_entry entry; //线程函数
unsigned int flags; //标记,现在只有两个值,1在apps_init时不启动,0在apps_init时启动
};
通过这两个宏可以实现一个app,主要把一个struct app_descriptor变量放到.apps段 然后结合链接脚本arch/arm/system-onesegment.ld以及app\app.c中的apps_init实现app的启动 arch/arm/system-onesegment.ld把所有的.apps段放到了一起,起始符号__apps_start,结束符号__apps_end apps_init通过__apps_start、__apps_end遍列所有的app_descriptor建立线程并执行(执行完所有线程的init,然后使用entry创建线程)
6 little kernel实现了一个命令行接口
命令行接口是其中一个线程,在app/shell/shell.c中实现 命令行接口实现位于lib/console.c中 重要的数据结构在include/lib/console.h中声明 其中命令是一个函数 typedef int (console_cmd)(int argc, const cmd_args argv); 命令的参数值通过cmd_args结构体传递,结构体保存了字符串和数值形式
typedef struct {
const char *str; //字符串格式
unsigned int u; //无符号整数格式
int i; //有符号整数格式
} cmd_args;
命令信息记录在结构体cmd中
typedef struct {
const char *cmd_str; //命令名
const char *help_str; //命令的帮助信息
const console_cmd cmd_callback; //命令的回调函数
} cmd;
多条命令信息通过结构体cmd_block保存
typedef struct _cmd_block {
struct _cmd_block *next; //指向下一组命令信息
size_t count; //命令个数
const cmd *list; //指针,指向cmd数组
} cmd_block;
同时定义了两个宏用于辅助生成命令块
#define STATIC_COMMAND_START static const cmd _cmd_list[] = {
#define STATIC_COMMAND_END(name) }; \
const cmd_block _cmd_block_##name \
__SECTION(".commands")= \
{ NULL, sizeof(_cmd_list) / sizeof(_cmd_list[0]), _cmd_list }
这里命令数组是固定的名子_cmd_list,所以一个文件只能出现一次STATIC_COMMAND_START、STATIC_COMMAND_END宏 命令信息块被放到.commands段 链接脚本arch/arm/system-onesegment.ld把所有的.commands段放到了一起,起始符号__commands_start,结束符号__commands_cmd console_init函数通过__commands_start、__commands_cmd遍列所有的结构体完成命令的注册(组成cmd_block的列表),注册过程在shell_init中完成 然后通过console_start函数,等待命令行输入并调用相应的处理函数,console_start在shell_entry中被调用 经过测试little kernel命令行接口并不能正常工作,因为aboot程序在init过程中实现,所有的app线程都没有创建(包含命令行app shell)
7 aboot
一个为android设计的bootloader,作为一个little kernel的一个非标准程序(在init中完成所有的操作,没有线程入口entry)存在,aboot入口位于app/aboot/aboot.c aboot_init函数中,以下是aboot的主要流程:
flowchat
st=>start: 开始
e=>end: 结束
op1=>operation: 设置存储器信息(页大小)
op2=>operation: 获取设备信息(加锁、校验等信息)位于devinfo第一个扇区、aboot最后一个扇区或者emmc的RPMB分区
op3=>operation: 获取oem解锁信息,位于config、frp最后一个扇区最后一个字节的LSB比特位
op4=>operation: 显示相关初始化(可以配置是否闹钟唤醒跳过显示初始化)
op5=>operation: 用户强制重启直接启动boot分区
op6=>operation: 根据按键设置启动模式
op7=>operation: 读取一些重启信息(寄存器)设置启动模式
op8=>operation: 启动系统(boot或recovery,此处校验整个启动镜像)或者进入fastboot
st->op1->op2->op3->op4->op5->op6->op7->op8->e
其中,启动系统(boot、recovery)时,对系统进行签名校验。其中与签名校验相关的部分
- 签名有两个不同的方式,根据VERIFIED_BOOT配置决定
- 公钥保存在platform/msm_shared/certificate.c certBuffer[]中,x509格式
- 签名格式比较简单,即用RAS私钥加密的hash值
- hash算法可配置SHA1/SHA256
- 公钥保存在platform/msm_shared/include/oem_keystore.h或者keystone分区中,格式未知(d2i_KEYSTORE解码出keystone)
- 签名信息比较复杂,有被签名对象的名字以及数据长度
- hash算法固定SHA256
- VERIFIED_BOOT为真
- VERIFIED_BOOT为假
- 设备信息device_info(位于devinfo第一个扇区,或aboot最后一个扇区,或emmc的RPMB分区中),其中is_unlocked域为真可以跳过签名检查
- OEM解锁信息(位于config、frp最后一个扇区最后一个字节的LSB比特位),此位如果为1将跳过签名校验
启动分区放的镜像文件为一种raw,没有文件系统,格式参见bootimg.h
镜像文件包含3部分,镜像、initrd、second(作用未知),device tree可以放在两个位置:
- dt_size = 0时,device tree在内核中的后半部分,地址用tags_addr指定
- dt_size > 0时,device tree在second之后,地址紧接着second
8 前端系统输入信息
前端系统在内存中保留了一些信息,在kmain->platform_early_init->board_init->platform_detect中被解析
这些内存结构在platform/msm_shared/smem.h定义,在platform/msm_shared/smem.c中解析
8.1 内存结构
根据分析,可知内存按如下格式保存信息
smem
proc_comm[4]
version_info[32]
head_info
alloc_info[SMEM_MAX]---+
|
| +-----+ - <--- &(smem) + alloc_info[0].offset
| | | ↑
+---| | alloc_info[0].size
| | | ↓
| +-----+ -
|
| +-----+ - <--- &(smem) + alloc_info[1].offset
| | | ↑
+---| | alloc_info[1].size
| | | ↓
| +-----+ -
|
| +-----+ - <--- &(smem) + alloc_info[2].offset
| | | ↑
+---| | alloc_info[2].size
| | | ↓
| +-----+ -
|
.
.
.
.
| +-----+ - <--- &(smem) + alloc_info[n].offset
| | | ↑
+---| | alloc_info[n].size
| | ↓
+-----+ -
platform/msm_shared/smem.c主要定义了三个函数用于解析以上的内存结构
8.1.1 sem_get_alloc_entry
void* smem_get_alloc_entry(smem_mem_type_t type, uint32_t* size)
此函数用于获取第type
个内存块,大小通过size
返回,地址通过函数返回值获取
8.1.2 sem_read_alloc_entry
unsigned smem_read_alloc_entry(smem_mem_type_t type, void *buf, int len)
此函数从第type
个内存块开始处拷贝len
个字节到缓冲内存buf
中
8.1.3 sem_read_alloc_entry_offset
unsigned smem_read_alloc_entry_offset(smem_mem_type_t type, void *buf, int len, int offset)
此函数从第type
个内存块的offset
处拷贝len
个字节到buf
中
8.2 内存解析
此部分内容,主要位于platform/msm_shared/board.c中的,platform_detect中
在第SMEM_BOARD_INFO_LOCATION
内存中存放着board信息,以结构体保存在内存中,但结构体有如下几种格式
smem_board_info_v6
smem_board_info_v7
smem_board_info_v8
smem_board_info_v9
smem_board_info_v10
smem_board_info_v11
其中第一个字标示了,格式的版本
第一个字又分为高16bit、低16bit分别表示format_major、format_minor,当前format_major等于0,format_minor有6、7、8、9、10、11对应smem_board_info_v6、smem_board_info_v7……smem_board_info_v11
smem_board_info_v6、smem_board_info_v7……smem_board_info_v11解析出来的信息保存到全局变量static struct board_data board中。
8.3 board信息获取
在内存信息配置完后,format_major
、format_minor
、board
被设置,这些信息封装后在platform/msm_shared/board.c中被封装成函数,在platform/msm_shared/include/board.h中被声明
8.4 获取信息找到最合适的DT
高通定义了一种方式打包多个DT,具体参见我写的Device Tree分析[1]。在little kernel中需要根据dt_entry描述的信息,与board的信息对比找出合适的DT
struct dt_entry
{
uint32_t platform_id;
uint32_t variant_id;
uint32_t board_hw_subtype;
uint32_t soc_rev;
uint32_t pmic_rev[4];
uint32_t offset;
uint32_t size;
};
struct board_data {
uint32_t platform;
uint32_t foundry_id;
uint32_t chip_serial;
uint32_t platform_version;
uint32_t platform_hw;
uint32_t platform_subtype;
uint32_t target;
uint32_t baseband;
struct board_pmic_data pmic_info[MAX_PMIC_DEVICES];
uint32_t platform_hlos_subtype;
uint32_t num_pmics;
uint32_t pmic_array_offset;
struct board_pmic_data *pmic_info_array;
};
匹配操作在int dev_tree_get_entry_info(struct dt_table *table, struct dt_entry *dt_entry_info)
函数中完成。
作者:wxjstz
文章来源:TrustZone
推荐阅读
- arm-trusted-firmware分析(上)
- arm-trusted-firmware分析(下)
- ARM/Linux嵌入式面经(十四):ARM体系架构基础知识
- 搞DDR必懂的关键技术笔记:DDR RAS—内存中的纠错码 (ECC)
- 牛掰!这老哥用显微镜摄取芯片ROM,还原了芯片的二进制固件。
- arm-trusted-firmware分析(上)
更多物联网安全,PSA等技术干货请关注平台安全架构(PSA)专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入PSA技术交流群,请备注研究方向。