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的指针的数组

  1. 所有进程/线程都有一个独立的能力组,拥有一个全局唯一ID (Badge)
  2. 所有对象(包括进程或能力组本身)都属于一个或多个能力组当中,也就是说子进程与线程将属于父进程的能力组当中,在某个能力组的对象拥有一个能力组内的能力ID(cap)。
  3. 对象可以共享,即单个对象可以在多个能力组中共存,同时在不同cap_group中可以有不同的cap
  4. 对所有对象的取用和返还都使用引用计数进行追踪。当引用计数为0后,当内核垃圾回收器唤醒后,会自动回收.
  5. 能力组内的能力具有权限,表明该能力是否能被共享(CAP_RIGHT_COPY)以及是否能被删除(CAP_RIGHT_REVOKE)

Capability

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_toolprocmgr这个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
Last change: 2024-10-21, commit: 8044d70