0. 引言
本文是对以下仓库中Pintos Project2源代码的思路分析,截取了重要的核心函数,以方便读者理解整个的实现思路。仅供参考,完成作业记得自己深入理解噢~
https://github.com/NicoleMayer/pintos_project2
1. 打印进程终结信息
1.1 process_exit
void
process_exit (void)
{
struct thread *cur = thread_current ();
uint32_t *pd;
int exit_status = current_thread->exit_status;//exit_status变量为退出状态
if (exit_status == INIT_EXIT_STAT)
exit_process(-1);
printf("%s: exit(%d)\n",current_thread->name,exit_status);//打印当前线程名和退出状态
/* 销毁当前线程的页目录,切换回内核目录 */
pd = cur->pagedir;
if (pd != NULL)
{
cur->pagedir = NULL;//当前线程页面设置为空,保证timer的中断就不会切换回进程页面目录
pagedir_activate (NULL);//激活线程页表
pagedir_destroy (pd);//销毁线程页面之前的目录
}
}
每当一个用户进程因为该进程调用exit
或其它原因而结束时,需要打印该进程的进程名和退出码(exit code
)。
同时,在结束一个进程时,我们要释放该进程占用的所有资源。
2. 参数传递
2.1 process_execute()
tid_t
process_execute (const char *file_name)
{
tid_t tid;
char *fn_copy = malloc(strlen(file_name)+1);
char *fn_copy2 = malloc(strlen(file_name)+1);
strlcpy (fn_copy, file_name, strlen(file_name)+1);
strlcpy (fn_copy2, file_name, strlen(file_name)+1);//file_name的两份拷贝,避免caller和load的冲突
char *save_ptr;
fn_copy2 = strtok_r (fn_copy2, " ", &save_ptr);//用strtok_r函数分离字符串,获得thread_name(存放在fn_copy2),为实现参数传递做准备
/* 创建以file_name为名字的新线程,新的子进程执行start_process函数. */
tid = thread_create (fn_copy2, PRI_DEFAULT, start_process, fn_copy);
free(fn_copy2); //手动释放fn_copy2
if (tid == TID_ERROR){
free (fn_copy);
return tid;
}
sema_down(&thread_current()->sema);//降低父进程的信号量,等待子进程结束
if (!thread_current()->success) return TID_ERROR;//子进程加载可执行文件失败报错
return tid;
}
把传入的参数file_name用strtok_r
函数分隔开,获得线程名,为接下来的参数传递(之后会传递给start_process
,load
, setup_stack
)做准备。并以此为线程名创建一个新线程,然后新线程转去执行start_process
函数。若子进程加载可执行文件的过程没有问题,则返回新建线程的tid.在这之前,父进程无法返回。这样的同步操作是依靠struct thread里的success变量以及信号量的增减实现的。success记录了线程是否成功执行,而通过创建子进程时父进程信号量减少、子进程结束时父进程信号量增加来实现父进程等待子进程的效果,并保证子进程结束唤醒父进程。
2.2 push_argument()
void
push_argument (void **esp, int argc, int argv[]){
*esp = (int)*esp & 0xfffffffc;
*esp -= 4;
*(int *) *esp = 0;
/*下面这个for循环的意义是:按照argc的大小,循环压入argv数组,这也符合argc和argv之间的关系*/
for (int i = argc - 1; i >= 0; i--)
{
*esp -= 4;//每次入栈后栈指针减4
*(int *) *esp = argv[i];
}
*esp -= 4;
*(int *) *esp = (int) *esp + 4;
*esp -= 4;
*(int *) *esp = argc;
*esp -= 4;
*(int *) *esp = 0;
}
简单来说,这个函数完成了根据argc的大小将argv数组压入栈的操作。在2.3start_process
函数中被调用。压入栈顶的过程中,依次存放入参数argv数组(靠参数分离得到)、argv的地址和argc的地址。
2.3 start_process()
static void
start_process (void *file_name_)
{
char *file_name = file_name_;
struct intr_frame if_;
bool success;
char *fn_copy=malloc(strlen(file_name)+1);
strlcpy(fn_copy,file_name,strlen(file_name)+1);//file_name的一份拷贝
memset (&if_, 0, sizeof if_);
if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG;
if_.cs = SEL_UCSEG;
if_.eflags = FLAG_IF | FLAG_MBS;
char *token, *save_ptr;
file_name = strtok_r (file_name, " ", &save_ptr);//字符串分离,得到线程名,为了传入接下来load函数的参数
success = load (file_name, &if_.eip, &if_.esp);
//调用load函数,判断其是否成功load
if (success){
int argc = 0;
//限制命令行长度不得超过50
int