深入,并且广泛
-沉默犀牛
此篇博客原博客来自freebuf,原作者SetRet。原文链接:https://www.freebuf.com/news/135084.html
写这篇文章之前,我只好假定你所知道的跟我一样浅薄(针对本文这一方面),所以如果你看到一些实在是太小儿科的内容,请你多加担待,这确实就是我目前的水平,谢谢。
首先要说的一点是,之前我总以为 LK = Uboot = Bootloader,其实它们的关系是这样的:
Bootloader是linux的启动引导程序,是一种概念。LK 和 Uboot 是两个实实在在的程序
它们的关系可以类比为:“主食(Bootloader)中有大米(LK)和白面(Uboot)”
LK是高通平台通用的Bootloader,并且根据平台的不同,LK的执行也稍有不同
(比如A平台走了A函数,而B平台可能不会走A函数)
这一系列分为四个部分(Kmain之前的部分不讲了):
Kmain函数路径为bootloader/lk/kernel/main.c
在Kmain之中所做的事情可以分为两类:
1. 偏向于硬件的初始化,这是后续运行的基本要求
1.1 thread_init_early (完成早期线程初始化工作)
1.2 arch_early_init (这个函数的代码也主要是针对 CPU 的一些特性做设置)
1.3 platform_early_init (初始化平台的相关硬件设备包括主板,时钟,中断控制器,scm 等为 lk 的启动和
运行提供硬件环境)
1.4 target_early_init (为开启调试 uart 调试接口)
2. 偏向于软件环境,和具体的硬件信息没有很强的关联性
2.1 bt_set_timestamp (读取当前的 TimeTick 然后保存到 bootstate 中)
2.2 call_constructors (主要是为了调用 c++ 代码的构造函数,单纯的 lk 中这个函数并没有作用)
2.3 heap_init (这个函数的作用就是初始化堆空间)
2.4 thread_init (关于线程的初始化在 thread_init_early 中已经完成,thread_init 只是一个空接口)
2.5 dpc_init (就是创建并启动一个名为 dpc 的线程)
2.6 timer_init (主要的作用创建 lk 中的定时器链表和定时器处理函数)
3. 创建bootstrap2
void thread_init_early(void)
{
int i;
for (i=0; i < NUM_PRIORITIES; i++) //初始化run_queue(运行队列)
list_initialize(&run_queue[i]);
list_initialize(&thread_list); //初始化线程链表
thread_t *t = &bootstrap_thread;
init_thread_struct(t, "bootstrap"); //创建第一个线程bootstrap
t->priority = HIGHEST_PRIORITY; //为这个线程赋值,下同
t->state = THREAD_RUNNING;
t->saved_critical_section_count = 1;
list_add_head(&thread_list, &t->thread_list_node);
current_thread = t; //current_thread赋值为当前thread
}
这里面涉及到的几个重要结构体:
1.运行队列 run_queue
:作为多线程的调度中心存在,数组不同的下标对应不同的 运行优先级
2.线程链表 thread_list
:全局的线程链表,保存了所有创建的线程信息
3.线程结构体thread_t
:每个线程的上下文信息都通过 thread_t 结构体保存
4.current_thread
:是一个全局变量,保存了当前运行的线程的信息
这个函数太偏向于硬件,设置方法都是按住 CPU 手册来进行,所以只需要知道开启了以下几个功能即可:
1.设置异常向量基地址为 0x8F600000
2.开启 cpu mmu(memory manager unit) 内存管理单元。
3.开启一些协处理器特性
整个 platform_early_init 的作用就是初始化平台的相关硬件设备
包括主板(board),时钟(clk),通用中断控制器(qgic),安全(scm) 等,为 lk 的启动和运行提供硬件环境
void platform_early_init(void)
{
board_init(); // 主要工作是获取主板的相关信息并填充到相关结构中
platform_clock_init(); //就是初始化平台的一系列时钟
qgic_init(); //主要的作用就是初始化 QGIC
qtimer_init(); //
scm_init(); //检查 scm 是否能够使用
}
接下来分别再进一步解释以上五个函数
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;
};
填充的就是这样的以上结构体,获取信息的具体来源涉及到 sbl1 等其他模块。
这个函数根据定义好的数据结构定义一系列时钟,数据结构体(在msm8953平台下)形如:
static struct clk_lookup msm_clocks_8953[] =
{
CLK_LOOKUP("sdc1_iface_clk", gcc_sdcc1_ahb_clk.c),
CLK_LOOKUP("sdc1_core_clk", gcc_sdcc1_apps_clk.c),
CLK_LOOKUP("sdc2_iface_clk", gcc_sdcc2_ahb_clk.c),
CLK_LOOKUP("sdc2_core_clk", gcc_sdcc2_apps_clk.c),
CLK_LOOKUP("uart1_iface_clk", gcc_blsp1_ahb_clk.c),
CLK_LOOKUP("uart1_core_clk", gcc_blsp1_uart1_apps_clk.c),
CLK_LOOKUP("uart2_iface_clk", gcc_blsp1_ahb_clk.c),
CLK_LOOKUP("uart2_core_clk", gcc_blsp1_uart2_apps_clk.c),
CLK_LOOKUP("usb30_iface_clk", gcc_pc_noc_usb30_axi_clk.c),
CLK_LOOKUP("usb30_master_clk", gcc_usb30_master_clk.c),
CLK_LOOKUP("usb30_pipe_clk", gcc_usb30_pipe_clk.c),
CLK_LOOKUP("usb30_aux_clk", gcc_usb30_aux_clk.c),
CLK_LOOKUP("usb2b_phy_sleep_clk", gcc_usb2a_phy_sleep_clk.c),
CLK_LOOKUP("usb30_phy_reset", gcc_usb30_phy_reset.c),
CLK_LOOKUP("usb30_mock_utmi_clk", gcc_usb30_mock_utmi_clk.c),
CLK_LOOKUP("usb_phy_cfg_ahb_clk", gcc_usb_phy_cfg_ahb_clk.c),
CLK_LOOKUP("usb30_sleep_clk", gcc_usb30_sleep_clk.c),
CLK_LOOKUP("mdp_ahb_clk", mdp_ahb_clk.c),
CLK_LOOKUP("mdss_esc0_clk", mdss_esc0_clk.c),
CLK_LOOKUP("mdss_esc1_clk", mdss_esc1_clk.c),
CLK_LOOKUP("mdss_axi_clk", mdss_axi_clk.c),
CLK_LOOKUP("mdss_vsync_clk", mdss_vsync_clk.c),
CLK_LOOKUP("mdss_mdp_clk_src", mdss_mdp_clk_src.c),
CLK_LOOKUP("mdss_mdp_clk", mdss_mdp_clk.c),
CLK_LOOKUP("ce1_ahb_clk", gcc_ce1_ahb_clk.c),
CLK_LOOKUP("ce1_axi_clk", gcc_ce1_axi_clk.c),
CLK_LOOKUP("ce1_core_clk", gcc_ce1_clk.c),
CLK_LOOKUP("ce1_src_clk", ce1_clk_src.c),
};
qgic 是Qualcomm generic interrupt controller 的简写 ,也就是高通通用中断控制器
其中做了两步:
1.qgic 分配器的初始化
2.qgic cpu 控制器的初始化
为timer设置频率,在很多平台下都是19200000
这个函数检查了scm是否能够使用,scm 是 secure channel manager的简称,负责normal world 和 secure world 之间的通信 , secure world 就是trustzone。
scm_init -> scm_arm_support_available() -> is_scm_call_available() -> scm_call2()
最终会调用到scm_call2()这个函数,它其实就是 TrustZone 开放给普通世界的 API 接口,这个函数的两个输入参数很重要:
scmcall_arg ( 这个结构想当于一个数据包,负责携带需要传递给 TrustZone 的参数信息)
typedef struct {
uint32_t x0;/* command ID details as per ARMv8 spec :
0:7 command, 8:15 service id
0x02000000: SIP calls
30: SMC32 or SMC64
31: Standard or fast calls*/
uint32_t x1; /* # of args and attributes for buffers
* 0-3: arg #
* 4-5: type of arg1
* 6-7: type of arg2
* :
* :
* 20-21: type of arg8
* 22-23: type of arg9
*/
uint32_t x2; /* Param1 */
uint32_t x3; /* Param2 */
uint32_t x4; /* Param3 */
uint32_t x5[10]; /* Indirect parameter list */
uint32_t atomic; /* To indicate if its standard or fast call */
} scmcall_arg;
scmcall_ret (这个结构则存储着 TrustZone 返回的数据信息,但是只有数据小于 12 字节才用这个结构返回,其他的数据应该在参数中放入一个返回用的 buffer 和长度)
typedef struct
{
uint32_t x1;
uint32_t x2;
uint32_t x3;
} scmcall_ret;
这个函数的主要作用是为平台开启调试 uart 调试接口
void target_early_init(void)
{
#if WITH_DEBUG_UART
uart_dm_init(1, 0, BLSP1_UART0_BASE);
#endif
}
可见定义了WITH_DEBUG_UART宏,就可以开启uart调试接口,比如可以开启串口打印
这个函数的调用过程有些复杂,不好贴代码
描述一下函数功能好了:就是读取当前的 TimeTick 然后保存到 bootstate 中。
读取TimeTick的地址为:
#define MPM2_MPM_SLEEP_TIMETICK_COUNT_VAL 0x004A3000 //平台不同地址可能不同
保存到bootstate的地址为:
#define MSM_SHARED_IMEM_BASE 0x08600000
#define BS_INFO_OFFSET (0x6B0)
#define BS_INFO_ADDR (MSM_SHARED_IMEM_BASE + BS_INFO_OFFSET)
bootstate 的地址其实就是一个数据结构,每个成员存储了对应的时间信息,具体的成员对应的含义有以下定义:
enum bs_entry {
BS_BL_START = 0,
BS_KERNEL_ENTRY,
BS_SPLASH_SCREEN_DISPLAY,
BS_KERNEL_LOAD_TIME,
BS_KERNEL_LOAD_START,
BS_KERNEL_LOAD_DONE,
BS_DTB_OVERLAY_START,
BS_DTB_OVERLAY_END,
BS_MAX,
};
当前设置的就是 BS_BL_START
的时间,代表了 bootloader 的启动时间
这个函数是 lk 和 c++ 联合编译使用的特性,主要是为了调用 c++ 代码的构造函数,单纯的 lk 中这个函数并没有作用
static void call_constructors(void)
{
void **ctor;
ctor = &__ctor_list;
while(ctor != &__ctor_end) {
void (*func)(void);
func = (void (*)())*ctor;
func();
ctor++;
}
}
这个函数的作用就是初始化堆空间
void heap_init(void)
{
LTRACE_ENTRY;
// set the heap range
theheap.base = (void *)HEAP_START;
theheap.len = HEAP_LEN;
LTRACEF("base %p size %zd bytes\n", theheap.base, theheap.len);
// initialize the free list
list_initialize(&theheap.free_list);
// create an initial free chunk
heap_insert_free_chunk(heap_create_free_chunk(theheap.base, theheap.len));
// dump heap info
// heap_dump();
// dprintf(INFO, "running heap tests\n");
// heap_test();
}
其中涉及到的最重要的全局变量就是 theheap
, 这个变量保存了 lk 所使用的堆空间的信息,其结构如下:
struct list_node {
struct list_node *prev;
struct list_node *next;
};
struct heap {
void *base;
size_t len;
struct list_node free_list;
};
static struct heap theheap;
theheap.base
和 theheap.len
对应的宏定义如下:
// end of the binary
extern int _end;
// end of memory
extern int _end_of_ram;
#define HEAP_START ((unsigned long)&_end)
#define HEAP_LEN ((size_t)&_end_of_ram - (size_t)&_end)
_end 和 _end_of_ram 这两个符号都是在链接的时候由链接器来确定的。_end 表示程序代码尾地址, _end_of_ram 表示 lk 内存尾地址,也就是说 lk 堆空间就是程序代码尾部到内存尾部所有空间。
theheap.free_list 维护着一个堆链表,其中保存着堆中所有空闲的堆块,现在的初始化阶段,只有一块完整的堆空间。
thread_init 函数位于 kernel/thread.c 文件中,关于线程的初始化在 thread_init_early 中已经完成, thread_init 只是一个空接口
创建并启动一个名为 dpc 的线程, dpc 的全称是 deferred procedure call, 就是延期程序调用的意思
它的作用是可以在其中注册函数,然后在触发 event 时调用函数
void dpc_init(void)
{
thread_t *thr;
event_init(&dpc_event, false, 0);
thr = thread_create("dpc", &dpc_thread_routine, NULL, DPC_PRIORITY, DEFAULT_STACK_SIZE);
if (!thr)
{
panic("failed to create dpc thread\n");
}
thread_resume(thr);
}
主要的作用创建 lk 中的定时器链表和定时器处理函数
void timer_init(void)
{
list_initialize(&timer_queue); //创建 lk 中的定时器链表
/* register for a periodic timer tick */
platform_set_periodic_timer(timer_tick, NULL, 10); /* 10ms */
}
其中全局链表 timer_queue
的作用就是存储定时器,而 timer_tick
函数的作用则是遍历 timer_queue
来处理其中注册的定时器回调函数
每个定时器都存储在 struct timer_t 类型的结构体中:
typedef struct timer {
int magic;
struct list_node node;
time_t scheduled_time;
time_t periodic_time;
timer_callback callback;
void *arg;
} timer_t;
dprintf(SPEW, "creating bootstrap completion thread\n");
thr = thread_create("bootstrap2", &bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
if (!thr)
{
panic("failed to create thread bootstrap2\n");
}
thread_resume(thr);
就是创建了线程bootstrap2
原文中有关使用线程的部分,我单开一篇博客来写。
最后再强调一遍,此博客只是再次整理加工了一下别人的文章,作为记录,方便自己和正在看文章的你以后查看,原文作者:SetRet 原文链接:https://www.freebuf.com/news/135084.html