文章目录
- 1 简介
- 2 源码获取
- 3 编译
- 4 运行流程
- 4.1 crt0.S
- 4.2 kmain
- 4.2.1 堆栈保护
- 4.3 bootstrap2
- 5 little kernel OS
- 5.1 概要
- 5.2 线程
- 5.2.1 数据结构
- 5.2.2 相关操作
- 5.2.2.1 thread_init_early
- 5.2.2.2 thread_init
- 5.2.2.3 thread_create
- 5.2.2.4 thread_resume
- 5.2.2.5 thread_exit
- 5.2.2.6 thread_resched
- 5.2.2.7 thread_yield
- 5.2.2.8 thread_preempt
- 5.2.2.9 thread_block
- 5.2.2.10 thread_sleep
- 5.2.2.11 thread_set_name
- 5.2.2.12 thread_set_priority
- 5.2.2.13 thread_become_idle
- 5.3 定时器
- 5.3.1 数据结构
- 5.3.2 相关操作
- 5.3.2.1 timer_init
- 5.3.2.2 timer_initialize
- 5.3.2.3 timer_set_oneshot
- 5.3.2.4 timer_set_periodic
- 5.3.2.5 timer_cancel
- 5.4 等待队列
- 5.4.1 数据结构
- 5.4.2 相关操作
- 5.4.2.1 wait_queue_init
- 5.4.2.2 wait_queue_destroy
- 5.4.2.3 wait_queue_block
- 5.4.2.4 wait_queue_wake_one
- 5.4.2.5 wait_queue_wake_all
- 5.4.2.6 thread_unblock_from_wait_queue
- 5.5 event
- 5.5.1 数据结构
- 5.5.2 相关操作
- 5.5.2.1 event_init
- 5.6 mutex
- 5.6.1 数据结构
- 5.6.2 相关操作
- 5 little kernel APP架构
- 6 little kernel实现了一个命令行接口
- 7 aboot
- 8 前端系统输入信息
- 8.1 内存结构
- 8.1.1 sem_get_alloc_entry
- 8.1.2 sem_read_alloc_entry
- 8.1.3 sem_read_alloc_entry_offset
- 8.2 内存解析
- 8.3 board信息获取
- 8.4 获取信息找到最合适的DT
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 //interruptb arm_fiq //fast interruptreset:#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_V8orr 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, =0x00080000mcr 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_BOOTldr r0, warm_boot_tagcmp r0, #1/* if set, warm boot */ldreq pc, =BASE_ADDR /* 复位芯片,BASE_ADDR是芯片启示地在 */mov r0, #1str r0, warm_boot_tag /* 设置warm_boot_tag地在中的值为1 */
#endif
#endif/* see if we need to relocate */mov r0, pcsub 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], #4str r3, [r1], #4cmp r1, r2bne .Lrelocate_loop/* 此处使用绝对跳转,跳转到正确的地址 *//* we're relocated, jump to the right address */ldr r0, =.Lstack_setupbx 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, cpsrbic r0, r0, #0x1f/* 加载内存地址,堆栈向低地址增长 */ldr r2, =abort_stack_toporr r1, r0, #0x12 // irqmsr cpsr_c, r1ldr r13, =irq_save_spot /* save a pointer to a temporary dumping spot used during irq delivery */orr r1, r0, #0x11 // fiqmsr cpsr_c, r1mov sp, r2orr r1, r0, #0x17 // abortmsr cpsr_c, r1mov sp, r2orr r1, r0, #0x1b // undefinedmsr cpsr_c, r1mov sp, r2orr r1, r0, #0x1f // systemmsr cpsr_c, r1mov sp, r2orr r1, r0, #0x13 // supervisormsr cpsr_c, r1mov sp, r2/* copy the initialized data segment out of rom if necessary */ldr r0, =__data_start_romldr r1, =__data_startldr r2, =__data_end/* 如果没有静态数据跳过初始化过程.L__copy_loop */cmp r0, r1beq .L__do_bss.L__copy_loop:/* 静态数据初始化 */cmp r1, r2ldrlt r3, [r0], #4strlt r3, [r1], #4blt .L__copy_loop/* bss数据段初始化为0 */
.L__do_bss:/* clear out the bss */ldr r0, =__bss_startldr r1, =_endmov r2, #0
.L__bss_loop:cmp r0, r1strlt r2, [r0], #4blt .L__bss_loop#ifdef ARM_CPU_CORTEX_A8DSB /* 数据屏障 */ISB /* 指令屏障 */
#endifbl 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 constructorsdprintf(SPEW, "calling constructors\n");call_constructors();/* 编译器相关的构造,链接器输出.ctors段 */// bring up the kernel heapdprintf(SPEW, "initializing heap\n");heap_init();/* 初始化栈 *//** 堆栈溢出保护相关* 在堆栈中放入一个常数* 函数返回之前检测* 如果发生变化,说明堆栈被破坏* 此处使用随机数初始化此常数__stack_chk_guard* 需要编译器配合* 在函数入口处在堆栈放入常数* 在函数出口处检测常数是否正确* */__stack_chk_guard_setup();// initialize the threading systemdprintf(SPEW, "initializing threads\n");thread_init();/* 定时器timer相关初始化 */// initialize the dpc systemdprintf(SPEW, "initializing dpc\n");/* * dpc为一个系统服务* 用于执行一些比较重要的任务* 优先级仅次与highest* 在系统级用于进程资源回收*/dpc_init();// initialize kernel timersdprintf(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();
#elsebootstrap_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 = 0push {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, r3bl gets /* 函数调用 */mov r3, #0mov 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 neededpop {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_BIObio_init();
#endif
#if WITH_LIB_FSfs_init();
#endif// initialize the rest of the platformdprintf(SPEW, "initializing platform\n");platform_init();// initialize the targetdprintf(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 mutex {/* 常数用于检测一个结构体为合法的mutex_t */int magic; /* * 每获取一次mutex count加1* 如果count大于1进入等待状态* count不大于1获取mutex成功,继续执行*/int count; /* 获取到互斥锁的进程标志 */thread_t *holder; /* 没有获取到互斥锁的进程的等待队列 */wait_queue_t wait;
} mutex_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中定义了两个宏
#define APP_START(appname) \struct app_descriptor _app_##appname \__SECTION(".apps") = { .name = #appname,
#define APP_END };
通过这两个宏可以实现一个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的主要流程:
其中,启动系统(boot、recovery)时,对系统进行签名校验。其中与签名校验相关的部分
- 签名有两个不同的方式,根据VERIFIED_BOOT配置决定
- VERIFIED_BOOT为真
- 公钥保存在platform/msm_shared/include/oem_keystore.h或者keystone分区中,格式未知(d2i_KEYSTORE解码出keystone)
- 签名信息比较复杂,有被签名对象的名字以及数据长度
- hash算法固定SHA256
- VERIFIED_BOOT为假
- 公钥保存在platform/msm_shared/certificate.c certBuffer[]中,x509格式
- 签名格式比较简单,即用RAS私钥加密的hash值
- hash算法可配置SHA1/SHA256
- 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 内存结构
根据分析,可知内存按如下格式保存信息
smemproc_comm[4]version_info[32]head_infoalloc_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分析。在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)
函数中完成。