光是Fork还不够,我们的子进程将来完成了对应的工作后是要被抹杀掉的,我们比较仁慈,让它自己了结自己,这就是Exit功能,应该叫自杀比较合适,但linux中用的是exit,还是别那么有个性吧。。
父进程有时需要等待一个子进程执行完毕后,才能开始继续执行,这就是Wait。
下面来实现这两个功能吧。
首先修改PCB,添加两个字段:
include/proc.h
- u32 status; /* Wait,Exit函数相关的状态 */
- int exit_status; /* 一个子进程Exit后返回给父进程的退出状态码 */
在Init_PCB函数中初始化这两个字段:
kernel/proc.c
- p_Cur_PCB->status = 0; /* 即不WAITING也不HANGING */
- p_Cur_PCB->exit_status = -1; /* 默认-1 */
添加status字段描述的两个状态值:
include/proc.h
- #define WAITING 0x1 /* 等待子进程Exit */
- #define HANGING 0x2 /* 等待父进程Wait */
- #define NO_CHILD_PROC -1 /* 如果父进程调用Wait时没有子进程,则返回-1 */
接着把框架搭好,添加Wait和Exit的用户接口:
kernel/mm.c
- /*--------------------------------------------------------------------------Wait
- Wait函数的用户接口
- 成功返回则返回结束的子进程的进程号
- 在container中填充子进程的退出码
- */
- u32 Wait(int *container)
- {
- Message wait_msg;
- wait_msg.msg_type = MM_WAIT;
- Send_Receive_Shell(BOTH,PROC_MM_PID,&wait_msg);
- *container = wait_msg.r1; /* container内存放退出码 */
- return wait_msg.r2; /* 返回Exit的子进程的进程号 */
- }
- /*--------------------------------------------------------------------------Exit
- Exit函数的用户接口
- 形参为该进程的退出码
- */
- void Exit(int exit_status)
- {
- Message exit_msg;
- exit_msg.msg_type = MM_EXIT;
- exit_msg.i1 = exit_status; /* i1字段存放退出码 */
- Send_Receive_Shell(SEND,PROC_MM_PID,&exit_msg); /* 只发不收 */
- while(1); /* 死循环,等待父进程或自己抹杀 */
- }
再在MM进程中处理这两种消息:
kernel/mm.c
- /*-----------------------------------------------------------------------Proc_MM
- 内存管理进程执行体
- */
- void Proc_MM()
- {
- Init_MM();
- Message mm_msg;
- int reply; /* 是否发回消息 */
- while(1)
- {
- Send_Receive_Shell(RECEIVE,ANY,&mm_msg);
- switch(mm_msg.msg_type)
- {
- /* 处理MM_FORK */
- case MM_FORK:
- reply = 1;
- mm_msg.r1 = Do_Fork(&mm_msg);
- break;
- /* 处理MM_WAIT */
- case MM_WAIT:
- reply = 0;
- Do_Wait(&mm_msg);
- break;
- /* 处理MM_EXIT */
- case MM_EXIT:
- reply = 0;
- Do_Exit(&mm_msg);
- break;
- default:
- Panic("UNKNOWN MSG TYPE!/n");
- break;
- }
- if(reply)
- {
- Send_Receive_Shell(SEND,mm_msg.src_proc_pid,&mm_msg);
- }
- }
- }
相关宏如下:
include/const.h
- #define MM_WAIT 16 /* WAIT消息 */
- #define MM_EXIT 17 /* EXIT消息 */
OK,接下来是具体功能的实现了,先看Wait的实现:
kernel/mm.c
- /*-----------------------------------------------------------------------Do_Wait
- 处理MM_WAIT消息的功能函数
- */
- static void Do_Wait(Message *wait_msg)
- {
- u32 i,child_num = 0;
- int parent_pid = wait_msg->src_proc_pid; /* 获得要WAIT的进程的PID */
- /* 遍历所有进程 */
- for(i = 0;i < MAX_PROC;i++)
- {
- /* 如果发现子进程 */
- if(PCB_Table[i].parent_pid == parent_pid)
- {
- child_num++; /* 子进程数加1 */
- /* 如果有某个子进程已经在等待父进程WAIT了 */
- if(PCB_Table[i].status & HANGING)
- {
- /* 抹杀子进程,并返回父进程调用Wait函数后继续执行 */
- Clean_Child_Proc(i);
- return;
- }
- }
- }
- /* 该父进程有至少一个子进程,但目前为止没有子进程Exit,自己先WAITING,等待子进程Exit */
- if(child_num > 0)
- {
- PCB_Table[parent_pid].status |= WAITING;
- }
- /* 该父进程没有子进程,给父进程发送no_child_msg消息 */
- else
- {
- Message no_child_msg;
- no_child_msg.r1 = -1; /* 错误码 */
- no_child_msg.r2 = NO_CHILD_PROC; /* 子进程号 */
- Send_Receive_Shell(SEND,parent_pid,&no_child_msg);
- }
- }
流程很简单,看注释即可。
抹杀子进程的函数如下:
kernel/mm.c
- /*--------------------------------------------------------------Clean_Child_Proc
- 取走子进程的返回码,连同子进程号发给父进程,并设置子进程PCB可用
- */
- static void Clean_Child_Proc(u32 child_pid)
- {
- Message wait_msg;
- wait_msg.r1 = PCB_Table[child_pid].exit_status; /* 退出码 */
- wait_msg.r2 = child_pid; /* 子进程PID */
- Send_Receive_Shell(SEND,PCB_Table[child_pid].parent_pid,&wait_msg);
- PCB_Table[child_pid].parent_pid = -1; /* 父进程号置-1 */
- PCB_Table[child_pid].available = 1; /* 设置子进程PCB可用,并不参与调度了 */
- }
还得修改调度函数Schedule,当PCB可用时不允许被调度:
kernel/proc.c
接着是Exit的实现了:
kernel/mm.c
- /*-----------------------------------------------------------------------Do_Exit
- 处理MM_EXIT消息的功能函数
- */
- static void Do_Exit(Message *exit_msg)
- {
- u32 exit_proc_pid = exit_msg->src_proc_pid; /* 取得调用Exit进程的进程号 */
- PCB *exit_pcb = &PCB_Table[exit_proc_pid];
- u32 exit_parent_pid = exit_pcb->parent_pid; /* 得到子进程的父进程号 */
- /* 让FS进程处理退出进程共享文件的清理工作 */
- Message fs_exit_msg;
- fs_exit_msg.msg_type = MM_EXIT;
- fs_exit_msg.i1 = exit_proc_pid;
- Send_Receive_Shell(BOTH,PROC_FS_PID,&fs_exit_msg);
- exit_pcb->exit_status = exit_msg->i1; /* 把退出码放到子进程PCB中的相应字段 */
- free_memory_size += block_memory_size; /* 更新剩余内存 */
- Printf("FREE SIZE:%dMB/n",free_memory_size / (1024 * 1024));
- /* 如果父进程处于WAITING状态 */
- if(PCB_Table[exit_parent_pid].status & WAITING)
- {
- PCB_Table[exit_parent_pid].status &= ~WAITING; /* 状态更新 */
- Clean_Child_Proc(exit_proc_pid); /* 抹杀子进程 */
- }
- /* 反之 */
- else
- {
- exit_pcb->status |= HANGING; /* 则子进程等待 */
- }
- /*
- 过继功能,即当子进程有子子进程时,子进程理应先Wait等待子子进程运行结束
- 子进程再Exit。但允许子进程在子子进程还没Exit的情况下Exit掉,这样要把子子
- 进程过继给A进程,也就是LINUX中的INIT进程,让A进程来Wait这些子子进程的Exit
- 这样就要求A进程不断的执行Wait。
- */
- /* 遍历所有进程表 */
- int i;
- for(i = 0;i < MAX_PROC;i++)
- {
- /* 如果发现存在子子进程 */
- if(PCB_Table[i].parent_pid == exit_proc_pid)
- {
- /* 则把子子进程的父进程号设置A进程的进程号 */
- PCB_Table[i].parent_pid = PROC_A_PID;
- /* 如果进程A正好在WAITING并且当前子子进程在HANGING,则抹杀掉子子进程 */
- if((PCB_Table[PROC_A_PID].status & WAITING) && (PCB_Table[i].status & HANGING))
- {
- PCB_Table[PROC_A_PID].status &= ~WAITING;
- Clean_Child_Proc(i);
- }
- }
- }
- }
流程也很清楚,不赘述。有一点要说明,在函数的开头发了个消息给FS进程,这是清理共享文件的问题。下面来看代码:
首先在FS进程中接收这个消息:
kernel/fs.c
- /* 接收MM_EXIT消息,处理共享FD的问题 */
- case MM_EXIT:
- Do_MM_Exit(&m);
- break;
接着是功能函数:
- /*--------------------------------------------------------------------Do_MM_Exit
- 处理MM_EXIT的功能函数
- */
- static void Do_MM_Exit(Message *m)
- {
- PCB *p = &PCB_Table[m->i1]; /* 取得子进程的PCB指针 */
- /* 遍历FD指针数组 */
- int i;
- for(i = 0;i < MAX_FILE_PER_PROC;i++)
- {
- /* FD指针不为0 */
- if(p->fd_ptr_table[i] != 0)
- {
- p->fd_ptr_table[i]->fd_inode->shared_count--; /* i结点共享次数减1 */
- p->fd_ptr_table[i]->fd_count--; /* FD共享此数减1 */
- /* 如果FD共享次数<=0,则把FD的fd_inode清0,表示可用 */
- if(p->fd_ptr_table[i]->fd_count <= 0)
- {
- p->fd_ptr_table[i]->fd_inode = 0;
- }
- p->fd_ptr_table[i] = 0; /* 相应数组项可用 */
- }
- }
- }
嗯,可以测试一下了吧:
进程A在生成子进程B后就不断的Wait,接收到就打印子进程的进程号和退出码;子进程B生成子子进程C后不等进程C自杀,就先自己自杀了;而进程C先Delay一小会再自杀,目的是让进程B比自己先自杀,这样可以测试一下过继功能。
kernel/proc.c
- /*------------------------------------------------------------------------Proc_A
- 进程A的执行体
- */
- void Proc_A()
- {
- int pid = Fork();
- int pid1;
- int exit_status;
- int child_pid;
- if(pid > 0)
- {
- Printf("parent is running!/n");
- child_pid = Wait(&exit_status);
- Printf("child (%d) exited,exit status is %d/n",child_pid,exit_status);
- while(1)
- {
- child_pid = Wait(&exit_status);
- if(child_pid == -1)
- {
- //no child!
- }
- else
- {
- Printf("child (%d) exited,exit status is %d/n",child_pid,exit_status);
- }
- }
- }
- else if(pid == 0)
- {
- Printf("child is running!/n");
- pid1 = Fork();
- if(pid1 > 0)
- {
- Exit(1234);
- }
- else if(pid1 == 0)
- {
- Printf("grand is running!/n");
- Milli_Delay(200);
- Exit(5678);
- }
- }
- }
运行结果如下:
嗯,感觉不错,不过测试还是不够充分,还是那句话,碰到BUG再说吧。。