lab4-challenge

lab4-challenge

任务分析

概述

  • 一种异步的通知机制:当一个信号发送给一个进程,操作系统会打断进程正常的控制流程,此时,任何非原子操作都将被打断。如果进程注册了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。信号由内核发出并由用户程序处理

具体要求

信号注册

  • 64 个信号编号 1-64,每个信号包括一个处理函数和一个 2 个 int 构成的 64 位信号集作为各个信号的掩码,有默认处理函数,忽略或终止。这些信息存储在进程的信号结构体中
  • 可以通过信号注册来修改 当前进程 中每个信号的处理函数和掩码,进程也具有自己的掩码,可被修改,均需要系统调用
  • 有五个对信号集操作的函数,用户态即可

信号发送

  • 用户信号发送函数 kill,用户可以使用该函数向任何进程发送信号,进程使用系统调用接受信号,调整进程信号结构体中的信息
  • 内核中也可以直接发送信号给进程,如 SIGSEGV 和 SIGKILL 信号,调用内核信号发送函数

信号处理

  • 当进程 被调度在之后继续运行 时,首先需要处理 自身收到的所有信号,后来的信号优先级更高,可能发生信号重入,在处理过程中被打断。对于被阻塞信号,保留其信号信息
    • 进程被调度即将运行时,即 env_run 中,准备设置 PC 跳回 epc 运行前,需在内核调用信号处理函数,处理所有收到的信号。
    • 有信号发送时,如果发送给当前正在运行的进程,需要直接调用信号处理函数检查是否可以处理
    • 修改进程信号结构体信息时,如 sigactionsigprocmask时,调用到 syscall_set_signal 后也需要重新调用信号处理函数检查
  • 三个特殊信号,默认处理动作为结束程序运行
    • SIGKILL 9 号,强制结束程序的运行(不能被阻塞),本实现中设置其对应掩码与处理函数可以被修改,但在信号处理时始终强制结束程序运行
    • SIGSEGV 11 号,用户程序访问了页表中未映射且地址严格小于 0x3FE000 的虚拟页
      • 该段非法地址一定不存在于 tlb 中,访问时会触发 tlb 异常,进入 tlbex.c 中的发送处理 **_do_tlb_refill异常处理函数,在此处寻找改地址对应页面之前,增加对地址的判断,如果地址小于 0x3FE000,说明是非法地址,触发 SIGSEGV 信号,调用kern_kill** 内核处理信号函数,发送 SIGSEGV 信号结束进程
    • SIGTERM 15 号,用于终止进程,但允许目标进程通信号处理函数拦截
  • fork 后子进程继承父进程的信号处理函数。sigaction 和 mask 是父进程自身的内容,需要复制到子进程,但接受到的信号是根据 envid 针对特定进程的,子进程不应继承。此时如果正在处理信号,则直接退出,回到原程序被信号中断的位置
  • 信号处理结束后,需要返回用户态,要注意对用户态 tf 的保存恢复

实现思路

信号处理流程

用户函数处理流程
  • 当前进程接收到信号时,即需要通过一个系统函数 syscall_sig_kill 进入内核,进程接受信号后,向进程结构体加入新信号,开始调度处理
  • 默认处理在内核态中就可以完成,但用户自定义的信号处理函数需要多次在用户态和内核态跳转,其中函数跳转地址需要特殊设计,设计参考 Linux 信号机制的实现以及 ljh 的指导
  • kern_do_signal 中检查调度完毕后找到可以处理的最新信号,准备进入用户态处理中的自定义的信号处理函数时,需要类似于 env_run 中最后一步 env_pop_tf,将当前 tf 替换为用户态中的 tf,进而进入其中的 tf->cp0_epc ,即从内核返回继续运行的返回地址,此时在 pop_tf 之前将 epc 设为当前信号的用户自定义处理函数地址 void (*sa_handler)(int),即可跳转去处理
  • 该函数处理完毕后需要回到内核态,去继续处理可处理的信号或返回当前进程,所以需要跳转到一个系统调用。根据 MIPS 的函数调用规范,通过 jal 跳转到子函数时,会把返回地址放进 ra,子函数运行结束后使用 jr ra 返回,所以可以和设置 epc 一起,提前将 ra,即 tf->regs[31]设置为中提前保存在进程结构体中的当前进程的 syscall_sig_return 函数的地址,跳转其中之后,再进入 kern_do_signal,继续检查处理,直到没有可以处理的信号,返回 sys_sig_return,再返回到当前进程
  • 通过如此重复检查,即可一次处理队列中所有信号,提高效率

信号重入

  • 信号重入即在用户态进行信号处理函数时被新来的信号再次打断,此时掩码和 tf 会被覆盖,如果没有保存,则重入信号结束后无法重新处理被打断的信号

  • 需要增加三个栈保存重入层次信息,一个记录当前信号在第几层,一个记录各层的处理背景中的掩码,一个记录各层处理时的 tf,一个 sig_layer 保存当前信号重入层数,处理的时候只处理层数大于等于当前层数的,以避免用当前环境的掩码处理到更早来到的,因为其需要更换到进程掩码进行判断

  • 信号层次在新信号到来时设为 cur_layer,在开始处理当前层信号时 cur_layer+1,在处理完毕后 -1

  • 当前信号处理背景中的掩码和 tf 在进入信号处理函数之前保存到两个栈的第 cur_layer 层

信号重入

实现方法

进程结构体添加信号结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
\include\env.h
struct Env {
\\...

signal env_signal;

\include\sig.h
typedef struct env_signal{
struct sigaction sigactions[64]; //64 信号处理结构体
sigset_t mask; // 进程掩码
u_int sig_stack[64]; // 信号堆栈
u_int count; // 待处理信号数量
u_int sig_layer[64]; // 信号到达的层数
u_int cur_layer; // 当前进程层数
struct sigset_t mask_layer[64]; // 信号处理层各层信号的掩码
struct Trapframe* sig_tf[64]; // 每个信号返回用户态的上下文保存的栈帧地址
u_int sig_return; // 信号处理结束后返回的地址,为一个 syscall 函数的地址,参考 ljh 的实现
}signal;
  • 其中要注意页面大小为 4K,堆栈大小不能过大,否则会导致文件系统访问地址地址错误

初始化信号及进程调度

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
\kern\env.c
int env_alloc(struct Env **new, u_int parent_id) {

...

signal *init_signal = &(e->env_signal);
for(int i = 0; i < 64; i++){
init_signal->sigactions[i].sa_handler = 0;
init_signal->sigactions[i].sa_mask.sig[0] = 0;
init_signal->sigactions[i].sa_mask.sig[1] = 0;
}
for(int i = 0; i < 64; i++) {
init_signal->sig_stack[i] = 0;
init_signal->sig_layer[i] = 0;
}
init_signal->mask.sig[0] = init_signal->mask.sig[1] = 0;
init_signal->cur_layer = 0;
init_signal->count = 0;
init_signal->sig_return = NULL;

LIST_REMOVE(e, env_link);
*new = e;
return 0;
}

void env_run(struct Env *e) {
assert(e->env_status == ENV_RUNNABLE);
pre_env_run(e);
if (curenv) { // 寄存器状态保存的地方是 KSTACKTOP 以下的一个 sizeof(TrapFrame)大小的区域中
curenv->env_tf = *((struct Trapframe *)KSTACKTOP - 1);
}
curenv = e;
curenv->env_runs++; // lab6
cur_pgdir = curenv->env_pgdir;
// 进程被调度在之后将要继续运行时,首先需要处理自身收到的所有信号
kern_do_signal(&(curenv->env_tf));
env_pop_tf(&(curenv->env_tf), curenv->env_asid);
}

fork

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
\kern\syscall_all.c
int sys_exofork(void) {
struct Env *e;

try(env_alloc(&e, curenv->env_id));

if(curenv->env_signal.cur_layer != 0) e->env_tf = *(curenv->env_signal.sig_tf[0]);
// 在信号处理函数中 fork 时,直接回到父进程被信号中断的地方继续执行,(sig_tf[0]是原程序流的 tf 地址)
else e->env_tf = *((struct Trapframe*)KSTACKTOP-1);

e->env_tf.regs[2] = 0;
e->env_status = ENV_NOT_RUNNABLE;
e->env_pri = curenv->env_pri;

return e->env_id;
}


\user\lib\fork.c
int fork(void) {

...

try(syscall_set_tlb_mod_entry(child, cow_entry));

signal child_signal = env->env_signal;
child_signal.count = 0;
for(int i=0; i < 64; i++){
// 清空所有父进程的未决信号!
child_signal.sig_stack[i] = 0;
child_signal.sig_layer[i] = 0;
}
if(child_signal.cur_layer != 0){
child_signal.mask = child_signal.mask_layer[0]; // 回到第 0 层进程 mask
child_signal.cur_layer = 0;
}
try(syscall_set_signal(child, &child_signal));

try(syscall_set_env_status(child, ENV_RUNNABLE));
return child;
}

SIGSEGV

1
2
3
4
5
6
7
8
9
10
11
12
13
14
\kern\tlbex.c
Pte _do_tlb_refill(u_long va, u_int asid) {
Pte *pte;
if(va < 0x3FE000 && page_lookup(cur_pgdir, va, &pte)==NULL){ // 地址过低、没有映射
sys_sig_kill(0, SIGSEGV); // 发送信号 SIGSEGV
}

/* Exercise 2.9: Your code here. */
while(page_lookup(cur_pgdir, va, &pte)==NULL) { //cur_pgdir 实参
passive_alloc(va, cur_pgdir, asid);
}

return *pte;
}

用户接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
\user\include\lib.h
int syscall_set_signal(u_int envid, signal *signal);
int syscall_sig_kill(u_int envid, u_int sig);
int syscall_sig_return();

\user\include\signal.h
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); // 正常执行则返回 0,否则返回异常码 -1.

void sigemptyset(sigset_t *set); // 清空信号集,将所有位都设置为 0
void sigfillset(sigset_t *set); // 设置信号集,即将所有位都设置为 1
void sigaddset(sigset_t *set, int signum); // 向信号集中添加一个信号,即将指定信号的位设置为 1
void sigdelset(sigset_t *set, int signum); // 从信号集中删除一个信号,即将指定信号的位设置为 0
int sigismember(const sigset_t *set, int signum); // 检查一个信号是否在信号集中,如果在则返回 1,否则返回 0

int kill(u_int envid, int sig);
  • sigaction 与 sigprocmask 即调用 syscall_set_signal 修改当前进程的 signal 结构体,其中注意 sigaction 在最初创建 signal 时需要设置其 signal.sig_return = syscall_sig_return,使信号处理到自定义函数后返回地址可以设为当前进程该系统调用
  • kill 即调用 syscall_sig_kill 系统调用
  • 五个处理信号集的函数即用位运算处理
  • 实现中可以对 SIGKILL 信号对应的掩码位以及其处理函数进行修改,但在后续处理中始终保持默认处理,直接结束运行

内核信号处理

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
\kern\signal_asm.S
.text
LEAF(sig_pop_tf)
.set reorder
.set at
move sp, a0
j ret_from_exception
END(sig_pop_tf)

LEAF(set_asid)
.set reorder
.set at
sll a0, a0, 6
mtc0 a0, CP0_ENTRYHI
jr ra
END(set_asid)

\kern\sigex.c
// 类比拆分的 env_pop_tf
extern void sig_pop_tf(struct Trapframe *tf) __attribute__((noreturn));
extern void set_asid(u_int asid);

int kern_kill(u_int envid, int sig, struct Trapframe *tf){
struct Env *env;

if(sig > 64 || sig < 1) return -1;
try(envid2env(envid, &env, 0));
signal *cur_signal = &(env->env_signal);
cur_signal->sig_stack[cur_signal->count] = sig;
cur_signal->sig_layer[cur_signal->count++] = cur_signal->cur_layer;
if(curenv == env && tf != NULL){ // 给当前运行的发信号,直接去执行
tf->regs[2] = 0; // 处理信号后的 kill 的返回值
kern_do_signal(tf);
}
return 0;
}

void kern_do_signal(struct Trapframe *tf){
set_asid(curenv->env_asid);
// 由于 kern_do_signal 有访问用户空间之处(进入信号处理函数之前把 tf 暂存到用户异常栈)
// 但仍未在 env_run 里运行 env_pop_tf 设置 asid,所以必须拆开 env_pop_tf 的步骤,把 tlb 的 asid 提前设置
sigset_t mask = curenv->env_signal.mask;
signal *cur_signal = &(curenv->env_signal);
int i = cur_signal->count - 1;

int signum;
int count;
while (i >= 0) {
// printk("kern_do_signal with i = %d\n",i);
// 一个栈,后来先处理
for (; i >= 0; i--) {
signum = cur_signal->sig_stack[i];
// printk("signum = %d\n", signum);
// 只处理该层和该层之上的,本轮遍历不涉及更早来的,下一轮再降低层数,保证重入时掩码正确
if (cur_signal->sig_layer[i] < cur_signal->cur_layer)
continue;
if (signum == SIGKILL
|| (signum >= 1 && signum <= 32 && !(cur_signal->mask.sig[0] >> signum - 1))
|| (signum >= 33 && signum <= 64 && !(cur_signal->mask.sig[1] >> signum - 33)))
break;
}
if (i < 0)
break;

// 已选出本次处理的 signum,在信号栈中清除
cur_signal->sig_stack[i] = 0;
cur_signal->sig_layer[i] = 0;

if (!cur_signal->sigactions[signum - 1].sa_handler || signum == SIGKILL) { // 默认处理函数
if(signum == SIGKILL || signum == SIGSEGV || signum == SIGTERM) env_destroy(curenv);
else continue;
} else { // 用户自定义处理程序.
// 马上要离开内核态,整理信号栈,得到当前的未处理信号数量 count
count = 0;
while(count < cur_signal->count && cur_signal->sig_stack[count]) count++; // 找到第一个空位
for (i = count; i > cur_signal->count; i++) {
if (cur_signal->sig_stack[i] == 0)
continue;
cur_signal->sig_stack[count] = cur_signal->sig_stack[i];
cur_signal->sig_layer[count++] = cur_signal->sig_layer[i];
}
cur_signal->count = count;
// 设置 trapframe 并且 env_pop_tf! 由于可能是 env_run 调用,所以必须 env_pop_run 修改 asid
struct Trapframe tmp_tf = *tf; // 原本的真正的程序控制流的 tf, 要放到 UXSTACK
if (tf->regs[29] < USTACKTOP || tf->regs[29] >= UXSTACKTOP) {
// 本来不在用户异常栈中,分到栈顶.
tf->regs[29] = UXSTACKTOP;
}
tf->regs[29] -= sizeof(struct Trapframe);
cur_signal->sig_tf[cur_signal->cur_layer] = tf->regs[29]; // 存入当前层 tf 地址
*(struct Trapframe*)tf->regs[29] = tmp_tf; // 返回时回到的 tf 地址
tf->regs[4] = signum; // 传入信号处理函数的参数,压入
tf->regs[31] = cur_signal->sig_return; // 设置 ra,让用户程序能直接返回到 syscall_sig_return
tf->regs[29] -= sizeof(tf->regs[4]);

tf->cp0_epc = cur_signal->sigactions[signum - 1].sa_handler;
cur_signal->mask_layer[cur_signal->cur_layer++] = cur_signal->mask;
// 当前层有效 mask 保存如栈,即将进入该信号处理,层数 +1
cur_signal->mask = cur_signal->sigactions[signum - 1].sa_mask;
// 替换当前有效 mask 为当前即将处理的信号的 mask
sig_pop_tf(tf);
panic("should goto user signal handler but return in ksigcheckout\n");
}
}
// 从 while 中退出,没有可以处理的信号或默认处理完毕,马上要离开内核态,整理信号栈,得到当前的未处理信号数量 count
count = 0;
while(count < cur_signal->count && cur_signal->sig_stack[count]) count++;
for (i = count; i > cur_signal->count; i++) {
if (cur_signal->sig_stack[i] == 0)
continue;
cur_signal->sig_stack[count] = cur_signal->sig_stack[i];
cur_signal->sig_layer[count++] = cur_signal->sig_layer[i];
}
cur_signal->count = count;
return; // 返回到原本的程序流
}
  • kern_kill 即添加新到达的信号,调用 kern_do_signal 处理
  • kern_do_signal 即在当前 tf 环境下,对待处理信号进行选择并处理
  • 由于 kern_do_signal 有访问用户空间之处(进入信号处理函数之前把 tf 暂存到用户异常栈),所以必须先把 tlb 的 asid 设置正确,所以需要新建 signal_asm.S,拆开源汇编为两个函数,在 kern_do_signal 最开始处即设置 asid
  • 类比 tlbex.c,对原进程的 tf 进行存放,之后进入用户态信号处理函数时的 tf 不会干扰覆盖原进程的 tf,注意进入用户态信号处理函数时需要多存放一个 tf->regs[4] = signum,作为参数传入
  • 注意信号处理函数最终返回至 kill 时,需要一个返回值 0 表示正常处理完成,类比 fork 中的设置子进程的返回值 0,需要在每次需要处理时将当前 tf->regs[2] = 0,在完毕返回到 sys_sig_return 时,用该寄存器的值作返回值

系统调用

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
\kern\syscall_all.c
int sys_set_signal(u_int envid, signal *signal){
int r;
struct Env *env;
try(envid2env(envid, &env, 1));
env->env_signal = *signal;
if(env == curenv){
// printk("sys_set_signal kern_do_signal\n");
struct Trapframe *tf = (struct Trapframe*)KSTACKTOP - 1; // 类似 env_run,保存当前进程的上下文
tf->regs[2] = 0; // 设置进入 kern_do_signal 后在其中 sys_sigreturn 系统调用中返回的返回值
kern_do_signal(tf); // 发信号给当前进程,直接去处理
}
return 0;
}

int sys_sig_kill(u_int envid, u_int sig) {
struct Trapframe *tf = (struct Trapframe*)KSTACKTOP - 1;
return kern_kill(envid, sig, tf);
}

int sys_sig_return(){
/*
* 从用户信号处理函数返回
* 从 curenv->env_sigblock.sig_tf 取出用户态栈中的 tf 地址,复制到内核栈上
* 如果从 kern_do_signal 返回了,就直接带着正确的栈离开内核态,返回 sig_tf->regs[2]
* 任何调用 kern_do_signal 的函数都应该提前在用户态 tf 的 regs[2] 里提前设置返回值!
*/
signal *cur_signal = &(curenv->env_signal);
cur_signal->mask = cur_signal->mask_layer[--cur_signal->cur_layer]; // 完成一个信号,降低一层,取得该层信号的 mask
struct Trapframe *tf = cur_signal->sig_tf[cur_signal->cur_layer]; // 取得该层信号对应的用户上下文 tf
struct Trapframe *ktf = (struct Trapframe *)KSTACKTOP - 1; // 上一层信号处理过程的 tf
*ktf = *tf; // 替换为这一层的上下文,如果层数到了 0,则变成原用户进程的上下文
kern_do_signal(ktf);
// 如果回来了,return 直接恢复原进程的上下文
return ktf->regs[2];
}

程序测试

编译文件

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
\kern\include.mk
lab-ge = $(shell ["$$(echo $(lab)_ | cut -f1 -d_)" -ge $(1) ] && echo true)

targets := console.o printk.o panic.o

ifeq ($(call lab-ge,2), true)
targets += pmap.o tlb_asm.o tlbex.o
endif

ifeq ($(call lab-ge,3), true)
targets += env.o env_asm.o sched.o entry.o genex.o kclock.o traps.o
endif

ifeq ($(call lab-ge,4), true)
targets += syscall_all.o sigex.o signal_asm.o
endif

\user\include.mk
USERLIB := entry.o \
syscall_wrap.o \
debugf.o \
libos.o \
fork.o \
syscall_lib.o \
ipc.o \
signal.o
  • 新增内核态两个文件 sigex.c signal_asm.S 的编译的.o 的链接
  • 新增用户态 lib/signal.c 的编译.o 的链接

测试程序与结果

  • lab4_sig_basic (基本信号测试)
1
2
3
4
5
6
7
8
9
10
11
-------------------------------------------------------------------------------

init.c: mips_init() is called
Memory size: 65536 KiB, number of pages: 16384
to memory 80430000 for struct Pages.
pmap.c: mips vm init success
Reach handler, now the signum is 2!
global = 1.
[00000800] destroying 00000800
[00000800] free env 00000800
i am killed ...
  • lab4_sig_SIGSEGV (空指针测试)
1
2
3
4
5
6
7
8
9
10
11
-------------------------------------------------------------------------------

init.c: mips_init() is called
Memory size: 65536 KiB, number of pages: 16384
to memory 80430000 for struct Pages.
pmap.c: mips vm init success
Segment fault appear!
test = 1.
[00000800] destroying 00000800
[00000800] free env 00000800
i am killed ...
  • lab4_sig_cow (写时复制测试)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
-------------------------------------------------------------------------------

init.c: mips_init() is called
Memory size: 65536 KiB, number of pages: 16384
to memory 80430000 for struct Pages.
pmap.c: mips vm init success
Father: 1.
[00000800] destroying 00000800
[00000800] free env 00000800
i am killed ...
Child: 0.
[00001001] destroying 00001001
[00001001] free env 00001001
i am killed ...
  • lab4_sig_SIGKILL (SIGKILL 信号特性测试)
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
#include <lib.h>

int *test = NULL;
void sgv_handler(int num) {
debugf("sig num is %d\n", num);
exit();
}

int main(int argc, char **argv) {
sigset_t set;
sigfillset(&set); // 全部设为 1,用于阻塞信号
struct sigaction sig;
sig.sa_handler = sgv_handler;
sig.sa_mask = set;
sigaction(9, &sig, NULL);
kill(0, 8); // 阻塞
kill(0, 9); // 不阻塞,不会被修改执行 sgv_handler
debugf("should not be here");

return 0;
}

-------------------------------------------------------------------------------

init.c: mips_init() is called
Memory size: 65536 KiB, number of pages: 16384
to memory 80430000 for struct Pages.
pmap.c: mips vm init success
[00000800] free env 00000800
i am killed ...
  • lab4_sig_stuck (信号阻塞以及两个进程之间发送信号,以及处理信号队列)
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#include <lib.h>
const int PP1 = 0x800, PP2 = 0x1001;
int finished = 0;
void handler(int num){
debugf("into sighandler to %d.\n",num);
int envid=syscall_getenvid();
debugf("%x received sig%d.\n",envid,num);
if(num == 5){
finished++;
debugf("adding finished!\n");
}
}
int main() {
u_int me, who, i=0;
me = syscall_getenvid();
struct sigaction sig;
struct sigset_t set;
struct sigset_t shed;
sigemptyset(&set);
sigemptyset(&shed);
sigaddset(&shed, 2); // 首先屏蔽 2/3 号
sigaddset(&shed, 3);
sig.sa_handler=handler;
sig.sa_mask=set;
sigaction(2,&sig,NULL); //2 号被屏蔽,不应该优先处理。
sigaction(3,&sig,NULL); //3 号被屏蔽,不应该优先处理。
sigaction(5,&sig,NULL); //5 号设置 finished 加一
// 由于屏蔽位被替换,PP2 应当能收到 5、2、3
debugf("i am %x\n", me);
sigprocmask(2, &shed, NULL);
if (me == PP1) {
kill(PP2, 5);
debugf("@@@@PP1 sent 5 to %x@@@@\n", PP2);
debugf("PP1 start wait finished sig\n");
while(finished <1){
i++;
if((i%10000000) == 0){
debugf("%x now finished is %d\n", me,finished);
}
}
debugf("pp1received start sig and send!\n");
who = PP2;
kill(who, 5);
kill(who, 3);
kill(who, 2);
kill(who, 5);
kill(who, 5); // 共发送 4 次 5 信号,PP2 应当均可接收到
debugf("@@@@PP1 sent 5,3,2 to PP2@@@@\n");
}if(me == PP2){
debugf("wait PP1 start!\n");
while(finished < 1){
i++;
if((i%10000000) == 0){
debugf("%x now finished is %d\n", me,finished);
}
}
//pp2 完成初始化,给 pp1 发送开始信号。
kill(PP1, 5);
debugf("@@@@@send 5 from %x to %x\n", me, PP1);
debugf("PP2 for start\n");
while(finished <4){
i++;
if((i%10000000) == 0){
debugf("%x now finished is %d\n", me,finished);
}
}
}
return 0;
}

-------------------------------------------------------------------------------

init.c: mips_init() is called
Memory size: 65536 KiB, number of pages: 16384
to memory 80430000 for struct Pages.
pmap.c: mips vm init success
i am 1001
wait PP1 start!
i am 800
@@@@PP1 sent 5 to 1001@@@@
PP1 start wait finished sig
into sighandler to 5.
1001 received sig5.
adding finished!
@@@@@send 5 from 1001 to 800
PP2 for start
into sighandler to 5.
800 received sig5.
adding finished!
pp1received start sig and send!
@@@@PP1 sent 5,3,2 to PP2@@@@
[00000800] destroying 00000800
[00000800] free env 00000800
i am killed ...
into sighandler to 5.
1001 received sig5.
adding finished!
into sighandler to 5.
1001 received sig5.
adding finished!
into sighandler to 5.
1001 received sig5.
adding finished!
[00001001] destroying 00001001
[00001001] free env 00001001
i am killed ...


// 如果没有在处理信号函数中 set_asid(curenv->env_asid);
-------------------------------------------------------------------------------

init.c: mips_init() is called
Memory size: 65536 KiB, number of pages: 16384
to memory 80430000 for struct Pages.
pmap.c: mips vm init success
i am 1001
wait PP1 start!
i am 800
@@@@PP1 sent 5 to 1001@@@@
PP1 start wait finished sig
into sighandler to 5.
1001 received sig5.
adding finished!
@@@@@send 5 from 800 to 800
into sighandler to 5.
800 received sig5.
adding finished!
pp1received start sig and send!
into sighandler to 5.
1001 received sig5.
adding finished!
into sighandler to 5.
1001 received sig5.
adding finished!
@@@@@send 5 from 800 to 800
PP2 for start
into sighandler to 5.
800 received sig5.
adding finished!
800 now finished is 3
1001 now finished is 2
800 now finished is 3
1001 now finished is 2
800 now finished is 3
1001 now finished is 2
800 now finished is 3
...

实验反思

  • 本实验综合使用内核态和用户态跳转以及进程 tf 和