本章主要描述进程、线程和上下文切换相关内容。
1. vmspace

每个进程都需要有自己的虚拟地址空间,这个虚拟地址空间用vmspace来描述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| struct vmspace { struct list_head vmr_list; vaddr_t *pgtbl;
struct vmregion *heap_vmr; vaddr_t user_current_heap; };
struct vmregion { struct list_head node; vaddr_t start; size_t size; vmr_prop_t perm; struct pmobject *pmo; };
|
- 虚拟地址空间翻译需要有页表基地址,vmspace中的pgtbl就是该进程的页表基地址
- 一个vmspace可能由多个vmregion组成,因此使用链表进行管理
1.1 初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #define HEAP_START (0x600000000000)
int vmspace_init(struct vmspace *vmspace) { init_list_head(&vmspace->vmr_list); vmspace->pgtbl = get_pages(0); BUG_ON(vmspace->pgtbl == NULL); memset((void *)vmspace->pgtbl, 0, PAGE_SIZE);
vmspace->user_current_heap = HEAP_START;
return 0; }
|
1.2 内存映射
假设我们已经分配好了一个物理内存对象(pmo),我们需要将虚拟地址和物理地址进行映射。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| int vmspace_map_range(struct vmspace *vmspace, vaddr_t va, size_t len, vmr_prop_t flags, struct pmobject *pmo) { struct vmregion *vmr; int ret;
va = ROUND_DOWN(va, PAGE_SIZE); if (len < PAGE_SIZE) len = PAGE_SIZE; vmr = alloc_vmregion(); if (!vmr) { ret = -ENOMEM; goto out_fail; } vmr->start = va; vmr->size = len; vmr->perm = flags; vmr->pmo = pmo;
ret = add_vmr_to_vmspace(vmspace, vmr);
if (ret < 0) goto out_free_vmr; BUG_ON((pmo->type != PMO_DATA) && (pmo->type != PMO_ANONYM) && (pmo->type != PMO_DEVICE) && (pmo->type != PMO_SHM)); if (pmo->type == PMO_DATA) fill_page_table(vmspace, vmr); return 0; out_free_vmr: free_vmregion(vmr); out_fail: return ret; }
|
- 分配vmregion对象
- vmr添加到vmspace中,就是添加到链表中
- 调用
fill_page_table()填充页表,这是内核提供的page_table的相关函数(参考memory章节)
1.3 内存去映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| int vmspace_unmap_range(struct vmspace *vmspace, vaddr_t va, size_t len) { struct vmregion *vmr; vaddr_t start; size_t size;
vmr = find_vmr_for_va(vmspace, va); if (!vmr) return -1; start = vmr->start; size = vmr->size;
if ((va != start) && (len != size)) { printk("we only support unmap a whole vmregion now.\n"); BUG_ON(1); }
del_vmr_from_vmspace(vmspace, vmr);
unmap_range_in_pgtbl(vmspace->pgtbl, va, len);
return 0; }
|
1.4 切换虚拟地址空间
不同的进程使用不同的虚拟地址空间,切换虚拟地址空间就是设置pgtbl到ttbr0_el1寄存器即可。
1 2 3 4
| void switch_vmspace_to(struct vmspace *vmspace) { set_page_table(virt_to_phys(vmspace->pgtbl)); }
|
2 capability
ChChor基于capability对内核资源进行访问控制。
所有内核资源(如物理内存等)均被抽象为了内核对象。应用通过整型的标识符cap访问从属于该进程的内核对象。

capability的代码不复杂,这里不分析代码了,主要是理解它的数据结构的设计。
- 真正的内核对象用object进行表示,一个object就是一个内核对象
- 内核对象的obj,藏在object->opaque中,它可以是物理内存,也可以是其他
- 一个object可以被多个进程共享,因此,object->copies_head就是用来把所有引用了该对象的object_slot用链表管理起来
- 一个进程所有的内核对象是通过slot_table进行管理的,它是一个数组,用bitmap标识,一个数组元素指向一个object_slot
- 通过slot_id即可找到该进程的内核对象
3 process

4 thread


5 跳转到thread执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| void main(void *addr) { uart_init(); mm_init(); exception_init(); kernel_lock_init(); lock_kernel();
sched_init(&rr); enable_smp_cores(addr); process_create_root(TEST);
sched();
eret_to_thread(switch_context());
BUG("[FATAL] Should never be here!\n"); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| u64 switch_context(void) { struct thread *target_thread; struct thread_ctx *target_ctx;
target_thread = current_thread; BUG_ON(!target_thread); BUG_ON(!target_thread->thread_ctx);
target_ctx = target_thread->thread_ctx;
if (target_thread->thread_ctx->type != TYPE_IDLE && target_thread->thread_ctx->type != TYPE_KERNEL && target_thread->thread_ctx->type != TYPE_TESTS) { BUG_ON(!target_thread->vmspace); switch_thread_vmspace_to(target_thread); } return (u64)target_ctx->ec.reg; }
|
1 2 3 4 5 6
| BEGIN_FUNC(eret_to_thread) mov sp, x0 exception_return END_FUNC(eret_to_thread)
|