编译penglai-enclave-driver
进入penglai-enclave-driver目录:
highlighter- reasonml
cd ~/dev/penglai-enclave-driver #modify source path sed -i 's|make -C ../openeuler-kernel/ ARCH=riscv M=$(PWD) modules|make -C /usr/lib/modules/$(shell uname -r)/build ARCH=riscv M=$(PWD) modules|' Makefile > /dev/null 2>&1 make -j$(nproc) insmod penglai.ko
oe中编译sdk目录中相关库和demo
进入~/dev/sdk目录编译penglai-sdk:
highlighter- vim
cd ~/dev/sdk ./replace_compiler_prefix.sh PENGLAI_SDK=$(pwd) make -j8
运行demo测试
highlighter- gradle
cd ~/dev/sdk/demo ./host/host count/count
oe中编译secGear程序
进入~/dev/secGear目录
highlighter- routeros
cd ~/dev/secGear && source environment && mkdir -p debug && cd debug cmake -DENCLAVE=PL -DSDK_PATH=/root/dev/sdk .. && make && make install
运行demo测试
highlighter- awk
cd ~/dev/secGear/debug ./bin/secgear_helloworld ./bin/secgear_calculation
内核启动到用户程序启动的流程:
highlighter- mipsasm
main ├── uart_init ├── mm_init ├── arch_interrupt_init ├── create_root_thread │ ├── create_root_cap_group │ ├── __create_root_thread │ └── switch_to_thread └── eret_to_thread └── switch_context
- Chcore 启动后会依次初始化
uart
模块、内存管理模块、中断模块 - 然后调用
create_root_thread
创建一个根进程,创建进程的cap_group
结构体并初始化,包括分配一块虚拟地址空间vmspace
和slot_table
的初始化,__create_root_thread
会先从磁盘中载入 ELF 文件,为进程创建一个主线程,最后将线程root_thread放入根进程中切换到线程执行; eret_to_thread
则switch_context
完成从内核模式到用户模式的切换,并在用户模式下开始运行用户代码。
sys_create_cap_group()
这段代码是一个系统调用函数,名为 sys_create_cap_group,用于创建一个新的能力组(cap_group),并将其分配给指定的进程(pid)。
在这个函数中,首先检查当前的能力组是否为 ROOT_PID(即根能力组),如果不是,则返回错误码 -EPERM。
接下来,通过 obj_alloc 函数分配一个新的能力组结构体(new_cap_group),如果分配失败,则返回错误码 -ENOMEM。
然后,通过 cap_group_init 函数初始化新的能力组,将其与基本对象编号(BASE_OBJECT_NUM)和指定的进程 ID(pid)关联起来。
接着,通过 cap_alloc 函数在当前的能力组中分配一个新的能力(cap),并将其与新的能力组(new_cap_group)关联起来。如果分配失败,则返回错误码 -1。
然后,通过 cap_copy 函数将新的能力组(new_cap_group)复制到当前线程的能力组中,并将新的能力组设为第一个能力(cap[0])。
接下来,通过 obj_alloc 函数分配一个新的虚拟内存空间结构体(vmspace),如果分配失败,则返回错误码 -ENOMEM。
然后,通过 vmspace_init 函数初始化新的虚拟内存空间,并将其与指定的进程 ID(pid)关联起来。
接着,通过 cap_alloc 函数在新的能力组中分配一个新的能力(cap),并将其与新的虚拟内存空间(vmspace)关联起来。如果分配失败,则返回错误码 -1。
最后,通过 copy_from_user 函数将指定的能力组名称(cap_group_name)复制到新的能力组结构体(new_cap_group)中,并返回新的能力(cap)。
如果出现任何错误,则会释放之前分配的对象并返回相应的错误码。
Capability
Capability 可以理解为 Linux 下的文件描述符。它把一个资源对象和访问权限封装到了一起,并对外提供一个整形 cap 做访问的句柄(句柄就是对资源对象的指针或者引用的一种抽象)
ChCore 中每个 capability 都属于一个进程。cap 的值实际上就是对象在所属的 process
的 slot_table
中的下标。
示例代码:
c
// 仅为演示,删掉了部分异常处理的代码 // 分配 cap int sys_create_pmo(u64 size, u64 type) { int cap; struct pmobject *pmo; pmo = obj_alloc(TYPE_PMO, sizeof(*pmo)); // 分配对象 pmo_init(pmo, type, size, 0); // 初始化 cap = cap_alloc(current_process, pmo, 0); // 挂载到进程上,分配 cap 编号 return cap; } // 使用 cap int sys_map_pmo(u64 target_process_cap, u64 pmo_cap, u64 addr, u64 perm) { struct pmobject *pmo; // 根据 cap 获取对象的指针 pmo = obj_get(current_process, pmo_cap, TYPE_PMO); // 操作对象,省略之 // ...... // 声明自己操作结束,为了并发安全准备的。 obj_put(pmo); }
下面可以看几个例子感受一下cap是如何被使用的。
创建object并分配cap
在thread.h
的 create_thread
函数中,我们需要创建线程,然后把线程加入到进程的slot_table
中管理起来,同时要返回cap作为索引。其中核心的一句如下:
c
thread = obj_alloc(TYPE_THREAD, sizeof(*thread));
任何需要通过cap来管理的资源都是通过object
来抽象的,所以需要先创建一个object
对象。该函数的第二个参数是线程的大小,这是因为我们需要用这个大小来初始化object
,使其能容纳我们需要的资源。
仔细看一下这个函数的定义:
c
void *obj_alloc(u64 type, u64 size) { u64 total_size; struct object *object; total_size = sizeof(*object) + size; object = kmalloc(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); return object->opaque; }
https://www.iqiyi.com/v_21is04yum94.html
https://m.iqiyi.com/v_21is04yum94.html
https://www.iqiyi.com/v_2clubpj56m4.html
https://m.iqiyi.com/v_2clubpj56m4.html
在分配object
的时候,kmalloc
的大小为sizeof(*object)+size
,最后返回的是opaque
字段。此时object
的内存布局如下:
object
语句:thread = obj_alloc(TYPE_THREAD, sizeof(*thread));
,相当于是thread = malloc(sizeof(struct thread))
,并且额外地,在头部加上了点别的信息,这样组成了一个object
。这样一个函数调用完成了thread空间的分配和object的初始化。
这里最精妙的地方就是这个opaque
的类型,是个数组,因此最后返回这个数组名的时候,实际上返回的是指向第一个元素的指针,即第二个参数size
分配的额外的内存空间的起始地址。如果换成u64*
指针类型的话就没有这种效果了。
然后我们通过cap = cap_alloc(process, thread, 0);
,将线程加入到进程的slot_table
中,并且返回cap,最终返回给用户,
根据cap获取对应的被管理的对象(线程,pmo等)
如何根据线程的cap来获取线程本身?在cap_group
函数中有如下语句:root_thread = obj_get(root_process, thread_cap, TYPE_THREAD);
PMO(Physical Memory Object)
物理内存对象