117 lines
3.5 KiB
C
117 lines
3.5 KiB
C
#include <assert.h>
|
||
#include <x86.h>
|
||
|
||
#include <kern/exit.h>
|
||
#include <kern/kmalloc.h>
|
||
#include <kern/pmap.h>
|
||
#include <kern/sche.h>
|
||
#include <kern/syscall.h>
|
||
#include <kern/trap.h>
|
||
|
||
static void
|
||
awake_father_and_become_zombie(PROCESS_0 *p_proc)
|
||
{
|
||
PROCESS_0 *p_fa;
|
||
// 由于我们已经协商了上锁的顺序,上锁顺序是从父亲到儿子
|
||
// 但是这里我们必须锁了自己才能知道父进程是谁
|
||
// 所以这里我们换了一种做法,不再一个劲的等锁
|
||
// 如果父进程上锁失败,就直接将自己的锁释放拱手让人
|
||
// 这样做有个好处,要么两个锁同时被上掉,要么两个锁同时被释放
|
||
// 这也是一个非常有趣的实现方法
|
||
// 而真实情况是将大锁拆小锁,不可能一个pcb就一个大锁保着,这样又浪费效率又难写
|
||
while (1) {
|
||
if (xchg(&p_proc->lock, 1) == 1)
|
||
goto loop;
|
||
p_fa = p_proc->fork_tree.p_fa;
|
||
if (xchg(&p_fa->lock, 1) == 1)
|
||
goto free;
|
||
break;
|
||
free:
|
||
xchg(&p_proc->lock, 0);
|
||
loop:
|
||
schedule();
|
||
}
|
||
// 这两句assert防止其他奇奇怪怪的状态出现
|
||
assert(p_proc->statu == READY);
|
||
assert(p_fa->statu == READY || p_fa->statu == SLEEP);
|
||
p_proc->statu = ZOMBIE;
|
||
p_fa->statu = READY;
|
||
|
||
xchg(&p_fa->lock, 0);
|
||
xchg(&p_proc->lock, 0);
|
||
}
|
||
|
||
static void
|
||
transfer_orphans(PROCESS_0 *p_proc)
|
||
{
|
||
PROCESS_0 *p_init = &proc_table[0].pcb;
|
||
// 上锁顺序为:初始进程->当前进程->子进程
|
||
while (xchg(&p_init->lock, 1) == 1)
|
||
schedule();
|
||
while (xchg(&p_proc->lock, 1) == 1)
|
||
schedule();
|
||
|
||
for (struct son_node *p = p_proc->fork_tree.sons ; p ;) {
|
||
PROCESS_0 *p_son = p->p_son;
|
||
struct son_node *p_nxt = p->nxt;
|
||
// 上子进程的锁,因为需要修改子进程的父进程信息(移到初始进程下)
|
||
while (xchg(&p_son->lock, 1) == 1)
|
||
schedule();
|
||
// 将子进程的进程树信息做修改
|
||
// 将节点node移到初始进程的链表头处
|
||
p_son->fork_tree.p_fa = p_init;
|
||
// 确保这个是双向链表头
|
||
assert(p->pre == NULL);
|
||
// 接下来就是一坨又臭又长的链表操作部分
|
||
if (p->nxt != NULL)
|
||
p->nxt->pre = p->pre;
|
||
p->nxt = p_init->fork_tree.sons;
|
||
if (p->nxt != NULL)
|
||
p->nxt->pre = p;
|
||
p_init->fork_tree.sons = p;
|
||
// 最后释放子进程的锁
|
||
xchg(&p_son->lock, 0);
|
||
p = p_nxt;
|
||
}
|
||
// 在移交完后当前进程的子进程信息会被清空
|
||
p_proc->fork_tree.sons = NULL;
|
||
// 初始进程可能在休眠,而且子进程可能是僵尸进程,需要将初始进程唤醒
|
||
// 初始进程会一直调用wait系统调用回收僵尸子进程
|
||
assert(p_init->statu == READY || p_init->statu == SLEEP);
|
||
p_init->statu = READY;
|
||
free:
|
||
xchg(&p_proc->lock, 0);
|
||
xchg(&p_init->lock, 0);
|
||
}
|
||
|
||
ssize_t
|
||
kern_exit(PROCESS_0 *p_proc, int exit_code)
|
||
{
|
||
// 托孤,将所有子进程转移到初始进程下
|
||
transfer_orphans(p_proc);
|
||
|
||
// 上锁修改exit code
|
||
while (xchg(&p_proc->lock, 1) == 1)
|
||
schedule();
|
||
p_proc->exit_code = exit_code;
|
||
xchg(&p_proc->lock, 0);
|
||
|
||
// 下面两个操作会修改进程的状态,
|
||
// 这是非常危险的,最好用开关中断保护上
|
||
DISABLE_INT();
|
||
// 这个函数干了两件事,唤醒父进程,将自己状态置为僵尸进程
|
||
// 关中断就相当于两件事同时干了
|
||
awake_father_and_become_zombie(p_proc);
|
||
// 在触发了调度之后这个进程在被回收之前永远无法被调度到
|
||
schedule();
|
||
|
||
ENABLE_INT();
|
||
panic("exit failed!");
|
||
}
|
||
|
||
ssize_t
|
||
do_exit(int status)
|
||
{
|
||
// 为什么这个参数这么奇怪?你可能需要读读手册
|
||
return kern_exit(&p_proc_ready->pcb, (status & 0xFF) << 8);
|
||
} |