sigaction简介

当一个信号被发送给一个进程时, 内核会中断进程的正常控制流,转而执行与该信号相关的用户态处理函数进行处理

执行该处理函数前,会将该信号所设置的信号屏蔽集加入到进程的信号屏蔽集中,在执行完该用户态处理函数后,又会将恢复原来的信号屏蔽集

任务描述

sigaction结构体用于设置所需要处理的信号集及其对应的处理函数

sigset_t使用32位表示MOS所需要处理的[1,32]信号掩码,对应位为1表示阻塞,为0表示未被阻塞

sigset_tsigaction结构体定义如下:

1
2
3
4
5
6
7
8
typedef struct sigset_t {
uint32_t sig;
} sigset_t;

struct sigaction {
void (*sa_handler)(int); //信号的处理函数
sigset_t sa_mask;
};

数据结构、宏等设计

信号相关设置

此处挂起信号队列沿用此前MOS中设计的TAILQ结构并使用相关函数,实现参考 kern\env.cenv_sched_list 相关部分

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
// include/signal.h
// 信号掩码控制宏 __how的取值
#define SIG_BLOCK (0) // 添加__set到当前掩码
#define SIG_UNBLOCK (1) // 从当前掩码中移除__set
#define SIG_SETMASK (2) // 设置当前掩码为__set

// SIGNUM编号
#define SIGINT (2)
#define SIGILL (4)
#define SIGKILL (9)
#define SIGSEGV (11)
#define SIGCHLD (17)
#define SIGSYS (31)

// 信号数量
#define SIG_MAX (32)

// 掩码结构体
typedef struct sigset_t {
uint32_t sig;
} sigset_t;

// 信号操作结构体
typedef void (*sa_handler)(int);
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
};

// 信号描述符
struct signal {
TAILQ_ENTRY(signal) sig_link;
int signum;
int time;
};

// 挂起的信号队列
TAILQ_HEAD(Sig_pend_list, signal);

Env结构体添加成员

1
2
3
4
5
6
7
8
9
10
11
// env.h
struct Env {
// lab4-challenge
u_int env_user_sighand_entry; // 用户态信号处理函数入口
u_int now_sig_time; // 记录当前正在处理的信号的到达时间,重入处理的时候用
u_int is_handling_SIGKILL; // 判定现在正在被处理的是否是SIGKILL信号,若是则为1,反之为0
u_int sig_exist[32]; // 每种信号在当前进程的存在性判断,保证每种信号的唯一性
sigset_t sig_blocked; // 信号掩码
struct Sig_pend_list sig_pend_list; // 挂起信号队列
struct sigaction sighand[32]; // 信号注册数组
};

错误返回值设置

1
2
// include/error.h
#define E_SIG 1 // 实际运用一般都是 return -E_SIG

头文件中添加相关函数声明

由于没有太多技术含量,此处省略,在实现具体函数后到相应头文件中添加即可

初始化与全局变量设置相关前置操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// kern/env.c
// lab4-challenge
int sigsTime = 1; // 用于标记信号到达时间,越小则到达时间越早
struct signal signals[SIG_MAX] __attribute__((aligned(PAGE_SIZE))); // 进程信号数组

int env_alloc(struct Env **new, u_int parent_id) {
// lab4-challenge
// 最初都初始化为0
e->sig_blocked.sig = 0;
e->env_user_sighand_entry = 0;
e->sig_pend_cnt = 0;
e->now_sig_time = 0;
e->is_handling_SIGKILL = 0;
TAILQ_INIT(&e->sig_pend_list);
for(int i=0; i<32; i++){
e->sig_exist[i] = 0;
}
for(int i=0; i<32; i++){
e->sighand[i].sa_handler = NULL;
e->sighand[i].sa_mask.sig = 0;
}
}

新增相关系统调用

统一流程

添加系统调用 syscall_func(u_int envid, ......)

  1. user/include/lib.h 中添加 void syscall_func(u_int envid, ......);

  2. user/lib/syscall_lib.c 中添加

    1
    2
    3
    4
    void syscall_func(u_int envid, ......) { 
    ......
    msyscall(SYS_func, envid, ......);
    }
  3. include/syscall.h 中的 enumMAX_SYSNO 前添加 SYS_func,

  4. kern/syscall_all.cvoid *syscall_table[MAX_SYSNO] 中加上 [SYS_func] = sys_func, (注意与前一步的对应)

  5. kern/syscall_all.cvoid *syscall_table[MAX_SYSNO] 之间完成函数
    void sys_func(u_int envid, ......); 的具体实现

新增功能实现所需系统调用的具体实现

  • sys_ 具体实现

    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
    // kern/syscall_all.c
    // 信号注册
    int sys_sigaction(int signum, const struct sigaction *newact,
    \struct sigaction *oldact) {
    struct Env *e;
    struct sigaction *sig = NULL;
    // 只需考虑signum小于或等于32的情况,超出该范围返回-1
    if (signum < 1 || signum > SIG_MAX) {
    return -E_SIG;
    }
    e = curenv;
    sig = &e->sighand[signum - 1];
    if (oldact) {
    *oldact = *sig;
    }
    if (newact) {
    *sig = *newact;
    }
    return 0;
    }

    // 设置进程的信号处理函数入口地址
    int sys_set_sighand_entry(u_int envid, u_int func) {
    struct Env *env;
    try(envid2env(envid, &env, 1));
    env->env_user_sighand_entry = func;
    return 0;
    }

    // 向进程发送信号
    extern int sigsTime;
    extern struct signal signals[SIG_MAX] __attribute__((aligned(PAGE_SIZE)));
    int sys_sendsig(u_int envid, int sig) {
    struct Env *e;
    // 当envid对应进程不存在,或者sig不符合定义范围时,返回异常码-1
    if ( sig < 1 || sig > SIG_MAX || envid2env(envid, &e, 0) != 0 ) {
    return -E_SIG;
    };
    // 当进程中已经有同种信号,则不再接收
    if ( e->sig_exist[sig-1] == 1 ){
    return 0;
    }
    // 将信号添加进对应进程的信号处理队列
    signals[sig-1].signum = sig;
    signals[sig-1].time = sigsTime;
    struct signal *s = (struct signal *)(&signals[sig-1]);
    e->sig_exist[sig-1] = 1;
    TAILQ_INSERT_HEAD(&e->sig_pend_list, (s), sig_link);
    sigsTime++;
    e->sig_pend_cnt++;
    return 0;
    }

    // 根据__how的值更改当前进程的信号屏蔽字
    // __set是要应用的新掩码,__oset(如果非NULL)则保存旧的信号屏蔽字
    // __how可以是SIG_BLOCK(添加__set到当前掩码)、SIG_UNBLOCK(从当前掩码中移除__set)
    // 或SIG_SETMASK(设置当前掩码为__set)
    int sys_sigprocmask(int __how, const sigset_t * __set, sigset_t * __oset){
    struct Env *e;
    sigset_t *sigset;
    e = curenv;
    sigset = &e->sig_blocked;
    if (__oset!=NULL) {
    *__oset = *sigset;
    }
    if (__set!=NULL) {
    switch (__how) {
    case SIG_BLOCK:
    sigset->sig |= __set->sig;
    break;
    case SIG_UNBLOCK:
    sigset->sig &= ~__set->sig;
    break;
    case SIG_SETMASK:
    sigset->sig = __set->sig;
    break;
    default:
    return -E_SIG;
    }
    }
    return 0;
    }

    // 检查信号__signo是否是__set信号集的成员。如果是,返回1;如果不是,返回0。
    int _sigismember(const sigset_t *__set, int __signo){
    if (__set == NULL){
    return 0;
    }
    __signo -= 1;
    return 1 & (__set->sig >> (__signo));
    }

    // 获取当前被阻塞且未处理的信号集,并将其存储在__set中
    int sys_sigpending(sigset_t *__set){
    struct signal *s = NULL;
    int t;
    uint32_t newSig = __set->sig;
    TAILQ_FOREACH (s, &curenv->sig_pend_list, sig_link) {
    // 检查掩码
    if ( _sigismember(&curenv->sig_blocked, s->signum) ) {
    t = s->signum;
    newSig |= (1 << (t-1));
    }
    }
    __set->sig = newSig;
    return 0;
    }

    // 获取进程状态
    int sys_check_env_status(u_int envid){
    struct Env * e;
    try(envid2env(envid, &e, 0));
    return e->env_status;
    }
  • 部分除 msyscall 还有其他操作的 syscall_

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // user/lib/syscall_lib.c
    int syscall_sigaction(int signum, const struct sigaction *newact,
    \struct sigaction *oldact) {
    if (oldact) {
    memset(oldact, 0, sizeof(oldact));
    }
    return msyscall(SYS_sigaction, signum, newact, oldact);
    }

    int syscall_sigprocmask(int __how, const sigset_t * __set, sigset_t * __oset) {
    if (__oset) {
    memset(__oset, 0, sizeof(__oset));
    }
    return msyscall(SYS_sigprocmask, __how, __set, __oset);
    }

信号处理流程实现

信号处理的触发

ret_from_exception 中加入跳转到 do_signal 的代码,使得每次从用户态到内核态都调用一次信号检查函数

1
2
3
4
5
6
7
// kern/genex.S
FEXPORT(ret_from_exception)
// 加入跳转到do_signal的代码(每次从用户态到内核态都调用一次)
move a0, sp
addiu sp, sp, -8
jal do_signal
addiu sp, sp, 8

各类信号的发送触发

  • SIGINTSIGKILL 一般在程序中手动触发,不会直接在MOS代码中发送

  • SIGILL

    发送契机在进行异常处理前,发现当前异常由非法指令引发,向自身发送 SIGILL

    1
    2
    3
    4
    5
    6
    7
    // kern/traps.c
    void do_reserved(struct Trapframe *tf) {
    if(((tf->cp0_cause >> 2) & 0x1f) == 10){
    sys_sendsig(0, SIGILL);
    return;
    }
    }
  • SIGSEGV

    发送契机当前地址过低不允许访问,则向自身发送 SIGSEGV

    1
    2
    3
    4
    5
    6
    7
    // kern/tlbex.c
    static void passive_alloc(u_int va, Pde *pgdir, u_int asid) {
    if (va < UTEMP) {
    // panic("address too low");
    sys_sendsig(curenv->env_id, SIGSEGV);
    }
    }
  • SIGCHLD

    发送契机子进程退出时,若父进程仍在运行,则子进程向父进程发送 SIGCHLD

    1
    2
    3
    4
    5
    6
    7
    // kern/env.c
    void env_destroy(struct Env *e) {
    // lab4-challenge
    if( e->env_parent_id != 0){
    sys_sendsig( e->env_parent_id, SIGCHLD);
    }
    }
  • SIGSYS

    发送契机当前系统调用号不存在时,需要忽视(跳过)此条语句,再向自身发送 SIGSYS

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // kern/syscall_all.c
    void do_syscall(struct Trapframe *tf) {
    int sysno = tf->regs[4];
    if (sysno < 0 || sysno >= MAX_SYSNO) {
    tf->regs[2] = -E_NO_SYS;
    tf->cp0_epc += 4;
    sys_sendsig(curenv->env_id, SIGSYS);
    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
// 信号注册函数
int sigaction(int signum, const struct sigaction *newact, struct sigaction *oldact) {
// 确保设置好信号处理函数入口地址
if (env->env_user_sighand_entry != (u_int)sighand_entry) {
try(syscall_set_sighand_entry(0, (u_int)sighand_entry));
try(syscall_set_tlb_mod_entry(0, (u_int)entry_wrapper));
}
int r = syscall_sigaction(signum, newact, oldact);
return r;
}

// 信号发送函数
int kill(u_int envid, int sig) {
// 如果sig为SIGCHLD,SIGILL,SIGSYS,SIGSEGV信号,返回异常码-1
if (sig == SIGILL || sig == SIGSYS || sig == SIGCHLD || sig == SIGSEGV) {
// debugf("Only MOS can send SIGCHLD & SIGILL & SIGSYS & SIGSEGV signal.\n");
return -E_SIG;
}
// 确保设置好信号处理函数入口地址
if (env->env_user_sighand_entry != (u_int)sighand_entry) {
try(syscall_set_sighand_entry(0, (u_int)sighand_entry));
}
int r = syscall_sendsig(envid, sig);
return r;
}

信号检查

  • do_signal 实现对当前挂起信号队列的检查,选择合适的信号后调用 sig_setuptf 跳转到信号处理函数入口并为其传递参数
  • 保存寄存器上下文函数 sig_setuptf 参照 kern\tlbex.cdo_tlb_mod 实现,具体参数设计见后续信号处理函数入口
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
// kern/env.c
// lab4-challenge
void do_signal(struct Trapframe *tf) {
// 在为进程设置sigpri之前也可能会发生一些异常产生异常重入的现象
// 而异常重入的时候不可能产生任何新的信号,没必要处理信号,直接return
if (((int)tf->cp0_epc)<0){
return;
}
// 如果没有待处理信号,直接返回
if (TAILQ_EMPTY(&curenv->sig_pend_list)){
return;
}

struct signal *s = NULL, *s_min = NULL;
int signo = 10000; // 初始化为10000,后续用于判断当前信号优先级是否高于之前选中的信号
int time = 0;

// 从进程的队列中取出未被阻塞的signal
// SIGKILL优先级最高
if (curenv->sig_exist[SIGKILL-1] == 1){
, TAILQ_FOREACH (s_min, &curenv->sig_pend_list, sig_link) {
if (s_min->signum == SIGKILL) {
signo = s_min->signum;
time = s_min->time;
curenv->is_handling_SIGKILL = 1;
break;
}
}
}else if(curenv->is_handling_SIGKILL == 0){
TAILQ_FOREACH (s, &curenv->sig_pend_list, sig_link) {
// 如果当前有信号正在处理且此信号比当前信号到达时间早,则break
if ( s->time <= curenv->now_sig_time ){
break;
}
// 选取优先级最高的
if ( s->signum < signo
&& ( (curenv->sig_blocked.sig & (1<<(s->signum-1))) == 0 )){
signo = s->signum;
time = s->time;
s_min = s;
}
}
}
// 若存在未被阻塞的signal, 则修改进程上下文, 转到用户态的信号处理函数
if (s_min) {
curenv->now_sig_time = time;
curenv->sig_exist[signo-1] = 0;
TAILQ_REMOVE(&curenv->sig_pend_list, s_min, sig_link);
// 保存寄存器上下文,给信号处理函数传递参数
sig_setuptf(tf, signo);
} else {
return;
}
}

// 保存寄存器上下文(仿写do_tlb_mod)
void sig_setuptf(struct Trapframe *tf, int signum) {
struct Trapframe tmp_tf = *tf;
if (tf->regs[29] < USTACKTOP || tf->regs[29] >= UXSTACKTOP) {
tf->regs[29] = UXSTACKTOP;
}
tf->regs[29] -= sizeof(struct Trapframe);
*(struct Trapframe *)tf->regs[29] = tmp_tf;
// 将信号处理需要传递的相关参数保存至异常现场栈中
tf->regs[4] = tf->regs[29];
tf->regs[5] = signum;
tf->regs[6] = (unsigned int)(curenv->sighand[signum-1].sa_handler);
uint32_t newMask = 0;
newMask = curenv->sighand[signum-1].sa_mask.sig | curenv->sig_blocked.sig
\ | (1 << (signum - 1)) ;
tf->regs[7] = (uint32_t)newMask;
tf->regs[29] -= sizeof(tf->regs[4]);
tf->regs[29] -= sizeof(tf->regs[5]);
tf->regs[29] -= sizeof(tf->regs[6]);
tf->regs[29] -= sizeof(tf->regs[7]);
tf->cp0_epc = curenv->env_user_sighand_entry;
}

信号的实际处理

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
// 执行信号
void __attribute__((noreturn)) sighand_entry(struct Trapframe *tf, int signum,
\ void (*sa_handler)(int), uint32_t newMask) {
int r;
// 若设置了处理函数
if (sa_handler && signum != SIGKILL ) {
// 处理前修改进程掩码
sigset_t oldSigset={0}, newSigset={0};
newSigset.sig = newMask;
r= syscall_sigprocmask(2, (sigset_t *)&newSigset, (sigset_t *)&oldSigset);
sa_handler(signum);
// 恢复进程原掩码
syscall_sigprocmask(2, (sigset_t *)&oldSigset, NULL);
// 恢复上下文
r = syscall_set_sig_trapframe(0, tf);
user_panic("sig_entry syscall_set_sig_trapframe returned %d", r);
}
switch (signum) {
case SIGINT: case SIGILL: case SIGKILL: case SIGSEGV:
// 退出用exit
exit();
user_panic("sig_entry syscall_env_destroy returned");
default:
r = syscall_set_sig_trapframe(0, tf);
user_panic("sig_entry syscall_set_sig_trapframe returned %d", r);
}
}

信号处理后上下文的信号

参考同文件中的 sys_set_trapframe 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// kern/syscall_all.c
int sys_set_sig_trapframe(u_int envid, struct Trapframe *tf) {
if (is_illegal_va_range((u_long)tf, sizeof *tf)) {
return -E_INVAL;
}
struct Env *env;
try(envid2env(envid, &env, 1));
// 当前信号处理完毕,故重置 env->now_sig_time 为 0
env->now_sig_time = 0;
if (env == curenv) {
*((struct Trapframe *)KSTACKTOP - 1) = *tf;
// return `tf->regs[2]` instead of 0, because return value overrides regs[2] on
// current trapframe.
return tf->regs[2];
} else {
env->env_tf = *tf;
return 0;
}
}

信号集处理函数实现

注意如果传入参数不合法,返回值为 -E_SIG

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
// user/lib/syscall_lib.c
// 计算两个信号集__left和__right的并集,并将结果存储在__set中
int sigorset(sigset_t *__set, const sigset_t *__left, const sigset_t *__right){
if ( __left == NULL || __right == NULL ){
return -E_SIG;
}
__set->sig = __left->sig | __right->sig;
return 0;
}

// 根据__how的值更改当前进程的信号屏蔽字。__set是要应用的新掩码
// __oset(如果非NULL)则保存旧的信号屏蔽字
// __how可以是SIG_BLOCK(添加__set到当前掩码)、SIG_UNBLOCK(从当前掩码中移除__set)
// 或SIG_SETMASK(设置当前掩码为__set)
int sigprocmask(int __how, const sigset_t * __set, sigset_t * __oset){
syscall_sigprocmask(__how, __set, __oset);
return 0;
}

// 清空参数中的__set掩码,初始化信号集以排除所有信号。这意味着__set将不包含任何信号。(清0)
int sigemptyset(sigset_t *__set){
if ( __set == NULL){
return 0;
}
__set->sig = 0;
return 0;
}

// 将参数中的__set掩码填满,使其包含所有已定义的信号。这意味着__set将包括所有信号。(全为1)
int sigfillset(sigset_t *__set){
if ( __set == NULL){
return -E_SIG;
}
sigemptyset(__set);
__set->sig = ~(__set->sig);
return 0;
}

// 向__set信号集中添加一个信号__signo。如果操作成功,__set将包含该信号。(置位为1)
int sigaddset(sigset_t *__set, int __signo){
if ( __set == NULL){
return -E_SIG;
}
if ( __signo < 1 || __signo > SIG_MAX ) {
return -E_SIG;
};
__set->sig |= (1<<(__signo-1));
return 0;
}

// 从__set信号集中删除一个信号__signo。如果操作成功,__set将不再包含该信号。(置位为0)
int sigdelset(sigset_t *__set, int __signo){
if ( __set == NULL){
return 0;
}
if ( __signo < 1 || __signo > SIG_MAX ) {
return -E_SIG;
};
__set->sig &= ~(1<<(__signo-1));
return 0;
}

// 检查信号__signo是否是__set信号集的成员。如果是,返回1;如果不是,返回0
int sigismember(const sigset_t *__set, int __signo){
if ( __set == NULL){
return 0;
}
return 1 & (__set->sig >> (__signo-1));
}

// 检查信号集__set是否为空。如果为空,返回1;如果不为空,返回0
int sigisemptyset(const sigset_t *__set){
if ( __set == NULL){
return 1;
}
return __set->sig == 0;
}

// 计算两个信号集__left和__right的交集,并将结果存储在__set中。
int sigandset(sigset_t *__set, const sigset_t *__left, const sigset_t *__right){
if ( __left == NULL || __right == NULL ){
return -E_SIG;
}
__set->sig = __left->sig & __right->sig;
return 0;
}

// 获取当前被阻塞且未处理的信号集,并将其存储在__set中
int sigpending(sigset_t *__set){
if ( __set == NULL){
return -E_SIG;
}
syscall_sigpending( __set);
return 0;
}

其他修改(踩过的坑)

寄存器Trapframe保存与传递设置

env_pop_tf 函数被调用时,它会将 curenv->env_tf(即当前进程的上下文)加载到处理器寄存器中,并且将 curenv->env_tf 的地址赋值给堆栈指针 sp。进入 do_signal 函数时,堆栈指针 sp 指向 curenv->env_tf 的位置。

假设 do_signal 函数会在其执行过程中使用堆栈保存临时数据和函数调用信息,则这些数据会覆盖 curenv->env_tf 的内容,导致进程控制块中的数据被意外修改

所以需要通过curenv->env_tf 复制到当前栈上一个临时变量 tmp_tf,然后传入 env_pop_tf 函数,从而避免直接使用进程控制块中的地址

将 kern/env.c 文件中 env_run 函数的末尾修改为:

1
2
3
- env_pop_tf(&curenv->env_tf, curenv->env_asid);
+ struct Trapframe tmp_tf = curenv->env_tf;
+ env_pop_tf(&tmp_tf, curenv->env_asid);

Q:为什么不能将 curenv->env_tf 复制到 (struct Trapframe *)KSTACKTOP - 1?

因为KSTACKTOP 是内核栈的顶部,直接在此位置存储 Trapframe 结构体稍有不慎可能导致堆栈溢出,从而覆盖其他关键的内核数据,干扰内核栈的正常使用

fork时相关设置的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// kern/syscall_all.c
int sys_exofork(void) {
// lab4-challenge
// 全部和sigaction有关的都要复制
e->sig_blocked = curenv->sig_blocked;
e->env_user_sighand_entry = curenv->env_user_sighand_entry;
e->sig_pend_cnt = curenv->sig_pend_cnt;
e->now_sig_time = curenv->now_sig_time;
e->is_handling_SIGKILL = 0;
e->sig_pend_list = curenv->sig_pend_list;
for(int i=0; i<32; i++){
e->sig_exist[i] = curenv->sig_exist[i];
}
for(int i=0; i<32; i++){
e->sighand[i].sa_handler = curenv->sighand[i].sa_handler;
e->sighand[i].sa_mask.sig = curenv->sighand[i].sa_mask.sig;
}
}

同时,为了防止父进程没有注册信号处理函数,在fork时可以“再加一层保险”

1
2
3
4
5
6
7
// user/lib/fork.c
int fork(void) {
// lab4-challenge
if (env->env_user_sighand_entry != (u_int)sighand_entry) {
try(syscall_set_sighand_entry(0, (u_int)sighand_entry));
}
}

页错误处理函数入口的同步设置

在实际编码中,发现可能会出现页错误情况,报错未注册页错误处理函数。因此,在注册信号时也实现页错误处理函数入口的同步设置

由于 cow_entry 是静态函数,因此需要用一个函数将其包裹以便在其他文件中调用

1
2
3
4
// user/lib/fork.c
void entry_wrapper(struct Trapframe *tf) {
cow_entry(tf);
}

其他踩过的坑/关键设计

  • 需要区分“打断”与“早已到”并妥善设计
    • 问题:在一个信号处理完成之前,也可能会进行一些系统调用,这些系统调用在返回之前也会扫描信号队列,怎么防止系统转而去执行更早到达的信号(早已到)?但同时,我们也要允许当前处理完成之前,晚于当前信号到达的信号(打断)能够被先处理
    • 解决思路
      1. 先在 env.c 里设置一个全局变量 sigsTime
      2. 每次发送信号的时候让 s->sig_time=sigsTime ,然后 sigsTime++
      3. Env 结构体增加成员变量 handlingSig_time ,进程每开始处理一个信号就让 curenv->handlingSig_time=s->sig_time
      4. dosignal 中根据比较 s->sig_timecurenv->handlingSig_time=s->sig_time 的大小来判断信号发出的先后
    • 不严谨的地方:发送信号的时间不严格等于信号开始被处理的时间,后续可以优化
  • 处理前修改进程掩码,处理后恢复进程原掩码的实现需要小心谨慎,要想清楚到底要恢复成什么样