little-kernel分析

server/2024/9/25 8:03:20/

文章目录

  • 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为线程通信提供了基本的服务eventmutex

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状态的线程的列表。为enentmutex的属性,用于记录被阻塞的线程。

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的主要流程:

Created with Raphaël 2.3.0 开始 设置存储器信息(页大小) 获取设备信息(加锁、校验等信息)位于devinfo第一个扇区、aboot最后一个扇区或者emmc的RPMB分区 获取oem解锁信息,位于config、frp最后一个扇区最后一个字节的LSB比特位 显示相关初始化(可以配置是否闹钟唤醒跳过显示初始化) 用户强制重启直接启动boot分区 根据按键设置启动模式 读取一些重启信息(寄存器)设置启动模式 启动系统(boot或recovery,此处校验整个启动镜像)或者进入fastboot 结束

其中,启动系统(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
  • 设备信息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_majorformat_minorboard被设置,这些信息封装后在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)函数中完成。


http://www.ppmy.cn/server/121721.html

相关文章

Android Studio开发发布教程

本文讲解Android Studio如何发布APP。 在Android Studiobuild菜单栏下点击Generate Singed Bundle/APK…打开对话框。 选择APK点击Next 点击Create New...进行创建

智能手机表面缺陷识别检测数据集 yolo数据集 1300张

智能手机表面缺陷识别检测数据集 yolo数据集 1300张 数据集名称 智能手机表面缺陷识别检测数据集&#xff08;Smartphone Surface Defect Recognition Dataset&#xff09; 数据集概述 该数据集是针对智能手机表面常见缺陷进行自动检测而专门构建的&#xff0c;主要应用于生…

滚雪球学SpringCloud[6.3讲]: 分布式日志管理与分析

全文目录&#xff1a; 前言1. 分布式日志管理的核心挑战2. ELK Stack&#xff08;Elasticsearch、Logstash、Kibana&#xff09;的使用2.1 什么是ELK Stack&#xff1f;2.2 安装与配置ELK Stack2.3 配置Logstash2.4 使用Kibana进行日志可视化 3. Spring Boot与ELK的集成3.1 配置…

Oracle 19c通过cdb的service name连接后为pdb库

Oracle 19c通过cdb的service name连接后为pdb库 现在数据库版本为19.19&#xff0c;库名为oemdb&#xff0c;有1个容器数据库pdb为empdbrepos&#xff0c;如下&#xff1a; [oracleoem13c ~]$ sqlplus / as sysdbaSQL*Plus: Release 19.0.0.0.0 - Production on Thu Sep 19 09:…

适合金融行业的银行级别FTP替代升级方案

在数字化办公日益普及的今天&#xff0c;金融领域对数据传输的需求日益增长&#xff0c;场景也变得更加多样化和复杂。这不仅包括内部协作&#xff0c;还涉及金融服务、外部合作以及跨境数据流动等方面。因此&#xff0c;金融行业对数据传输系统的要求越来越高&#xff0c;传统…

3d可视化图片:通过原图和深度图实现

1、depthy 在线体验demo: https://depthy.stamina.pl/#/ 也可以docker安装上面服务: docker run --rm -t -i -p 9000:9000 ndahlquist/depthy http://localhost:90001)首先传原图 2)再传对应深度图 3)效果 </ifra

动态住宅IP的多元化应用

在现代网络环境中&#xff0c;动态住宅IP以其灵活、隐蔽性强和全球范围覆盖的特点&#xff0c;逐渐成为各行业不可或缺的工具。本文将从多个角度解析动态住宅IP的多元化应用。 1. 跨境电商中的账号管理 在跨境电商平台上运营&#xff0c;通常需要多个账号来管理不同市场和区域…

【计算机基础】用bat命令将Unity导出PC包转成单个exe可执行文件

Unity打包成exe可执行文件 上边连接是很久以前用过的方法&#xff0c;发现操作有些不一样了&#xff0c;并且如果按上述操作比较麻烦&#xff0c;所以写了个bat命令。 图1、导出的pc程序 如图1是导出的pc程序&#xff0c;点击exe文件可运行该程序。 添加pack_project.bat文件 …