进程控制块

进程控制块 PCB 是一个很重要的内容。

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
40
41
42
43
/* include/trap.h 文件内容 */
struct Trapframe { // 陷阱帧,用于在陷入内核时保存进程上下文。
/* 保存32个寄存器 */
unsigned long regs[32];

/* 保存特殊寄存器 */
unsigned long cp0_status;
unsigned long hi;
unsigned long lo;
unsigned long cp0_badvaddr;
unsigned long cp0_cause;
unsigned long cp0_epc;
};

/* include/env.h 文件内容 */

// env_status 的三种取值
#define ENV_FREE 0
#define ENV_RUNNABLE 1
#define ENV_NOT_RUNNABLE 2

struct Env {
struct Trapframe env_tf; // 用于保存上下文
LIST_ENTRY(Env) env_link; // 构建空闲进程链表
u_int env_id; // 独特的进程id
u_int env_asid; // 进程的asid,填入TLB表项中
u_int env_parent_id; // 进程的父进程id
u_int env_status; // 进程的运行状态
Pde *env_pgdir; // 进程的页目录内核虚拟地址
TAILQ_ENTRY(Env) env_sched_link; // 进程的调度队列
u_int env_pri; // 进程的优先级

u_int env_ipc_value; // data value sent to us
u_int env_ipc_from; // envid of the sender
u_int env_ipc_recving; // env is blocked receiving
u_int env_ipc_dstva; // va at which to map received page
u_int env_ipc_perm; // perm of page mapping received

u_int env_user_tlb_mod_entry; // user tlb mod handler

u_int env_runs; // number of times been env_run'ed
char env_path[256];
};

asid 之所以要单独拎出来而不能直接使用 envid(明明他们都是独特的),是因为我们要将 asid 塞入 TLB 表项中,这对 asid 的位长提出了限制。我们对于 asid 位长的限制是 6 位,也就是说我们最多有 64 个可供分配的 asid。我们需要为 asid 建立分配回收机制。

在实验中,存放进程控制块的物理内存在系统启动后就已经分配好,就是 envs 数组。为快速分配进程控制块,我们同样需要使用链表来管理进程块。

  • env_init:初始化进程块链表,并进行了段地址映射。
  • env_setup_vm:初始化进程地址空间。
  • env_alloc:分配一个进程块。
  • load_icode:将进程二进制映像装入内存。
  • load_icode_mapper:作为 elf_load_seg 的回调函数,为进程内容分配物理页面并建立映射。
  • env_create:创建一个进程。
  • env_free:释放一个进程。
  • env_destroy:摧毁一个进程。
  • env_run:让一个进程运行起来。

真正创建进程的过程:分配一个 Env 结构体,设置进程控制块,将程序载入到该进程的地址空间。

异常处理

在产生异常时,我们会进入到 kern/entry.S 内进行异常分发处理。

异常处理函数向量表在 kern/trap.c 中设置。在 kern/genex.S 中设置了一些异常处理函数。

推荐阅读 include/asm/asm.h 中的宏,有助于理解汇编代码。

进程调度

进程调度的函数位于 kern/sched.c 中。课程组进行调度的逻辑比较简单,在课上上机时出的题可能是需要实现更加复杂的调度逻辑。