RTFSC (3)
note
此为代码导读的第三部分,请仔细阅读。同之前的章节相同,本节不包含习题。
本次代码导读主要聚焦从main函数开始自上而下讲解Lab2 Lab3内核态的资源管理机制以及用户态和内核态的互相调用
hint
你可能需要重新结合Lab2/Lab3的开放代码来理解本章
内核初始化
/*
* @boot_flag is boot flag addresses for smp;
* @info is now only used as board_revision for rpi4.
*/
void main(paddr_t boot_flag, void *info)
{
u32 ret = 0;
/* Init big kernel lock */
ret = lock_init(&big_kernel_lock);
kinfo("[ChCore] lock init finished\n");
BUG_ON(ret != 0);
/* Init uart: no need to init the uart again */
uart_init();
kinfo("[ChCore] uart init finished\n");
/* Init per_cpu info */
init_per_cpu_info(0);
kinfo("[ChCore] per-CPU info init finished\n");
/* Init mm */
mm_init(info);
kinfo("[ChCore] mm init finished\n");
void lab2_test_buddy(void);
lab2_test_buddy();
void lab2_test_kmalloc(void);
lab2_test_kmalloc();
void lab2_test_page_table(void);
lab2_test_page_table();
#if defined(CHCORE_KERNEL_PM_USAGE_TEST)
void lab2_test_pm_usage(void);
lab2_test_pm_usage();
#endif
/* Mapping KSTACK into kernel page table. */
map_range_in_pgtbl_kernel((void*)((unsigned long)boot_ttbr1_l0 + KBASE),
KSTACKx_ADDR(0),
(unsigned long)(cpu_stacks[0]) - KBASE,
CPU_STACK_SIZE, VMR_READ | VMR_WRITE);
/* Init exception vector */
arch_interrupt_init();
timer_init();
kinfo("[ChCore] interrupt init finished\n");
/* Enable PMU by setting PMCR_EL0 register */
pmu_init();
kinfo("[ChCore] pmu init finished\n");
/* Init scheduler with specified policy */
#if defined(CHCORE_KERNEL_SCHED_PBFIFO)
sched_init(&pbfifo);
#elif defined(CHCORE_KERNEL_RT)
sched_init(&pbrr);
#else
sched_init(&rr);
#endif
kinfo("[ChCore] sched init finished\n");
init_fpu_owner_locks();
/* Other cores are busy looping on the boot_flag, wake up those cores */
enable_smp_cores(boot_flag);
kinfo("[ChCore] boot multicore finished\n");
#ifdef CHCORE_KERNEL_TEST
kinfo("[ChCore] kernel tests start\n");
run_test();
kinfo("[ChCore] kernel tests done\n");
#endif /* CHCORE_KERNEL_TEST */
#if FPU_SAVING_MODE == LAZY_FPU_MODE
disable_fpu_usage();
#endif
/* Create initial thread here, which use the `init.bin` */
create_root_thread();
kinfo("[ChCore] create initial thread done\n");
kinfo("End of Kernel Checkpoints: %s\n", serial_number);
/* Leave the scheduler to do its job */
sched();
/* Context switch to the picked thread */
以下为Chcore内核初始化到运行第一个用户线程的主要流程图
flowchart TD lock["lock_init() 锁初始化"] uart["uart_init() uart初始化"] cpu["init_per_cpu_info() cpu结构体初始化"] mm["mm_init() 内存管理初始化"] sched["sched_init() 调度初始化"] fpu["init_fpu_owner_locks() fpu初始化"] root_thread["create_root_thread() 创建原始线程"] eret["eret_to_thread()"] pmo["create_pmo() pmo创建"] vmspace["vmspace_map_range() vm映射"] cap_group["create_root_cap_group()"] thread_alloc["thread_alloc"] memory_mapping["memory_mapping"] subgraph main lock-->uart-->cpu-->mm-->sched-->fpu-->root_thread-.->eret end subgraph thread_init root_thread-->pmo-->vmspace-->cap_group-->thread_alloc-->memory_mapping-->eret end
我们在Lab2
中主要完成mm_init以及内存管理器与vmspace和pmo的互联,现在我们再从第一个线程创建的数据流来梳理并分析
Chcore微内核的资源管理模式。
内核对象管理
在Chcore中所有的系统资源都叫做object(对象),用面向对象的方法进行理解的话,object即为不同内核对象例如vmspace, pmo, thread(等等)的父类, Chcore通过能力组机制管理所有的系统资源,能力组本身只是一个包含指向object的指针的数组
- 所有进程/线程都有一个独立的能力组,拥有一个全局唯一ID (Badge)
- 所有对象(包括进程或能力组本身)都属于一个或多个能力组当中,也就是说子进程与线程将属于父进程的能力组当中,在某个能力组的对象拥有一个能力组内的能力ID(cap)。
- 对象可以共享,即单个对象可以在多个能力组中共存,同时在不同cap_group中可以有不同的cap
- 对所有对象的取用和返还都使用引用计数进行追踪。当引用计数为0后,当内核垃圾回收器唤醒后,会自动回收.
- 能力组内的能力具有权限,表明该能力是否能被共享(CAP_RIGHT_COPY)以及是否能被删除(CAP_RIGHT_REVOKE)
struct object {
u64 type;
u64 size;
/* Link all slots point to this object */
struct list_head copies_head;
/* Currently only protect copies list */
struct lock copies_lock;
/*
* refcount is added when a slot points to it and when get_object is
* called. Object is freed when it reaches 0.
*/
volatile unsigned long refcount;
/*
* opaque marks the end of this struct and the real object will be
* stored here. Now its address will be 8-byte aligned.
*/
u64 opaque[];
};
const obj_deinit_func obj_deinit_tbl[TYPE_NR] = {
[0 ... TYPE_NR - 1] = NULL,
[TYPE_CAP_GROUP] = cap_group_deinit,
[TYPE_THREAD] = thread_deinit,
[TYPE_CONNECTION] = connection_deinit,
[TYPE_NOTIFICATION] = notification_deinit,
[TYPE_IRQ] = irq_deinit,
[TYPE_PMO] = pmo_deinit,
[TYPE_VMSPACE] = vmspace_deinit,
#ifdef CHCORE_OPENTRUSTEE
[TYPE_CHANNEL] = channel_deinit,
[TYPE_MSG_HDL] = msg_hdl_deinit,
#endif /* CHCORE_OPENTRUSTEE */
[TYPE_PTRACE] = ptrace_deinit
};
void *obj_alloc(u64 type, u64 size)
{
u64 total_size;
struct object *object;
total_size = sizeof(*object) + size;
object = kzalloc(total_size);
if (!object)
return NULL;
object->type = type;
object->size = size;
object->refcount = 0;
/*
* If the cap of the object is copied, then the copied cap (slot) is
* stored in such a list.
*/
init_list_head(&object->copies_head);
lock_init(&object->copies_lock);
return object->opaque;
}
void __free_object(struct object *object)
{
#ifndef TEST_OBJECT
obj_deinit_func func;
if (object->type == TYPE_THREAD)
clear_fpu_owner(object);
/* Invoke the object-specific free routine */
func = obj_deinit_tbl[object->type];
if (func)
func(object->opaque);
#endif
BUG_ON(!list_empty(&object->copies_head));
kfree(object);
}
所有的对象都有一个公共基类,并定义了虚构函数列表,当引用计数归零即完全被能力组移除后内核会执行deinit代码完成销毁工作。
note
你可以根据上述的描述来梳理根进程创建以及普通进程创建的异同,最后梳理出创建进程的标准模式。
用户态构建
我们在Lab1
的代码导读阶段说明了kernel
目录下的代码是如何被链接成内核镜像的,我们在内核镜像链接中引入了procmgr
这个预先构建的二进制文件。在Lab3
中,我们引入了用户态的代码构建,所以我们将procmgr
的依赖改为使用用户态的代码生成。下图为具体的构建规则图。
flowchart LR topcmake["CMakeLists.txt"] chcorelibc["chcore-libc"] libcso["libc.so"] procmgr["procmgr"] ramdisk["ramdisk"] ramdisk_cpio["ramdisk.cpio"] tmpfs["ramdisk/tmpfs.srv"] procmgr_tool["procmgr_tool"] kernel["kernel"] kernel_img["kernel.img"] subgraph libc chcorelibc-->|autotools|libcso end subgraph system_services ramdisk-->|cpio|ramdisk_cpio ramdisk_cpio-->tmpfs tmpfs-->procmgr libcso-->procmgr procmgr-->procmgr_tool procmgr_tool-->procmgr end topcmake-->system_services topcmake-->libc procmgr-->kernel_img kernel-->kernel_img
procmgr
是一个自包含的ELF
程序,其代码在procmgr
中列出,其主要包含一个ELF
执行器以及作为Chcore微内核的init
程序启动,其构建主要依赖于fsm.srv
以及tmpfs.srv
,其中fsm.srv
为文件系统管理器其扮演的是虚拟文件系统的角色用于桥接不同挂载点上的文件系统的实现,而tmpfs.srv
则是Chcore
的根文件系统其由ramdisk
下面的所有文件以及构建好libc.so
所打包好的ramdisk.cpio
构成。当构建完tmpfs.srv
后其会跟libc.so
进行动态链接,最终tmpfs.srv
以及fsm.srv
会以incbin脚本的形式以二进制的方式被连接至procmgr
的最后。在构建procmgr
的最后一步,cmake
会调用read_procmgr_elf_tool
将procmgr
这个ELF
文件的缩略信息粘贴至procmgr
之前。此后procmgr
也会以二进制的方式进一步嵌套进入内核镜像之后,最终会在create_root_thread
的阶段通过其elf
符号得以加载。 最终,Chcore的Kernel镜像的拓扑结构如下
flowchart LR kernel_img("kernel.img") kernel_objects("kernel/*.o") procmgr("procmgr") chcore_libc("libc.so") ramdisk("ramdisk") ramdisk_cpio("ramdisk.cpio") tmpfs("tmpfs.srv") fsm("fsm.srv") kernel_img-->kernel_objects kernel_img-->procmgr procmgr-->fsm procmgr-->tmpfs tmpfs-->ramdisk_cpio ramdisk_cpio-->ramdisk ramdisk_cpio-->chcore_libc