@SovietPower
2022-05-08T13:33:35.000000Z
字数 17272
阅读 1301
OS
https://www.zybuluo.com/SovietPower/note/1885177
参考:
https://zhuanlan.zhihu.com/p/104497182
https://blog.csdn.net/weixin_44765402/article/details/111089137
要修改的文件在src/userprog
目录下:process.c
, process.h
, syscall.c
, syscall.h
,以及之前的thread.c, thread.h
。
src/userprog
文件结构:
需先创建具有文件系统分区的磁盘:
在userprog
下make
,然后进入build
文件夹,执行:
pintos-mkdisk filesys.dsk --filesys-size=2
pintos -f -q
pintos -p ../../examples/echo.c -a echo -- -q
pintos -q run 'echo x'
# 后三条可合并:pintos -p ../../examples/echo -a echo.c -- -f -q run 'echo x’
build
出现filesys.dsk
,且最后一条语句执行成功,说明磁盘文件创建成功。
只需修改process.c
。
使用下面这种格式进行测试:
pintos -v -k -T 60 --filesys-size=2 -p tests/userprog/args-none -a args-none -- -q -f run args-none
pintos -v -k -T 60 --filesys-size=2 -p tests/userprog/args-single -a args-single -- -q -f run 'args-single onearg'
pintos -v -k -T 60 --qemu --filesys-size=2 -p tests/userprog/open-empty -a open-empty -- -q -f run open-empty
# 含义:
pintos: perl 脚本; -v -k -T 60 :启动参数
–filesys-size=2 :定义文件系统大小
-p:载入文件; -a:测试名; -q –f run:系统运行命令
最后结果:
问题
使用命令pintos -q run 'echo x'
,会得到echo x
被作为命令一共处理的错误:
Executing 'echo x':
Execution of 'echo x' complete. # 应为'echo'
命令echo
和参数x
被处理做了一个命令echo x
,所以不能正确处理参数。
在process.c
中,process_execute()
创建处理命令的线程。
这里参数file_name
是完整的echo x
,创建的线程名称应为echo
而不是echo x
。
所以使用strtok_r()
将命令和参数分离,在线程名称处传入thread_name
。
strtok_r (char *s, const char *delimiters, char **save_ptr)
将s
从第一个分隔符字符串delimiters出现的位置分开,前一部分作为返回值,后一部分存入save_ptr
。
可连续调用以彻底分割字符串(在第二次及以后s
可为NULL,但第一次不可)。
...
strlcpy (fn_copy, file_name, PGSIZE);
char *thread_name, *tmp;
thread_name = strtok_r(file_name, " ", &tmp);
/* Create a new thread to execute FILE_NAME. */
tid = thread_create (thread_name/* file_name */, PRI_DEFAULT, start_process, fn_copy); // 创建执行该程序的新线程
if (tid == TID_ERROR)
palloc_free_page (fn_copy);
return tid;
}
thread_create()
中传入的要执行的函数为start_process()
,start_process()
中主要通过success = load (file_name, &if_.eip, &if_.esp)
,加载要执行的命令及参数到栈中(为该线程分配空间)。
根据要求,load()
需将要执行命令/函数(主要是参数)分配一个新栈,然后令ESP
指向该栈。
但load()
仅在setup_stack()
新建了一个栈,并令esp
指向新栈的地址,并未在栈中放入命令的参数。
bool load (const char *file_name, void (**eip) (void), void **esp)
{
...
/* Set up stack. */
if (!setup_stack (esp))
goto done;
...
}
因此,除在setup_stack()
新建一个栈外,还需在其中将参数传进去。
所以修改setup_stack()
的定义:
static bool setup_stack (void **esp)
改为:
static bool setup_stack (void **esp, char *file_name)
load()
中,if (!setup_stack (esp))
改为if (!setup_stack (esp, file_name))
。
此外,load()
根据file_name
打开可执行文件(命令)。
/* Open executable file. */
file = filesys_open (file_name);
此处的file_name
也应为不包含参数的命令。所以同样要分开:
/* Open executable file. */
char *copy = malloc(strlen(file_name)+1);
strlcpy(copy, file_name, strlen(file_name)+1);
char *command, *tmp;
command = strtok_r(file_name, " ", &tmp);
file = filesys_open (command);
free(copy);
if (file == NULL)
{
printf ("load: %s: open failed\n", file_name);
goto done;
}
注意要拷贝file_name
,因为strtok_r
会对串本身进行改变,但之后要用到该串。之后可以释放。
(process_execute()
中也拷贝了fn_copy
)
使用strtok_r()
将file_name
的参数分离开,依次放入栈。
就类似C的argc, **argv
。命令本身是第一个参数。
注意参数是字符串,类似**argv
,应将这些字符串先放入用户栈中(局部变量),再将这些字符串的地址作为实际参数**argv
的各位置的值依次(逆序,从*argv[argc-1]
到*argv[0]
)压入栈中。
同时要注意用户栈是向下增长的(栈底在高地址处)。
所以过程为:
1. 将各参数放入栈中(顺序任意),并记录每个参数的地址addr[i]
。这里不需要对齐,因为就是一长串的用户数据。
2. 将*esp
按4字节对齐。之后真正参数的压栈都需按4字节对齐。
3. 将每个参数的地址addr[i]
,即*argv[i]
,压入栈中。
4. 将argv
的地址,即**argv
,压入栈中。
5. 将argc
的数值,即参数个数,压入栈中。
6. 将函数返回值,压入栈中。因为这里类似main()
,返回值用不到,为0就可以了(但是必须要分配这个空间)。
修改后的setup_stack()
:
{
...
// 参数数统计,用于分配*argv的空间
int i, argc=1, len=strlen(file_name);
for (i=1; i<len; ++i)
if (file_name[i]==' ' && file_name[i-1]!=' ')
++argc;
char *argument, *tmp;
unsigned int int_size = sizeof(int);
// 保存各参数的地址*argv[i]
int *argv = calloc(argc, int_size);
// 将各字符串压入栈(顺序任意,为了方便就依次)
argument = strtok_r(file_name, " ", &tmp);
for(i=0; argument!=NULL; argument=strtok_r(NULL, " ", &tmp))
{
*esp -= strlen(strlen(argument)+1); // 栈向下增长
memcpy(*esp, argument, strlen(argument)+1);
argv[i++] = *((int *)esp);
}
// 对齐
*esp -= (unsigned)*esp%4;
// 将参数*argv[i]压入栈,每次都对齐(每次一个(unsigned) int即正好对齐)
for (i=argc-1; ~i; --i)
{
*esp -= int_size;
memcpy(*esp, &argv[i], int_size);
}
// 将参数**argv压入栈(对齐)。**argv即*argv[0]的起始地址,即此时的*esp值。
*esp -= int_size;
*(int *)*esp = (int)*esp + int_size; // 注意要转指针类型确保正确赋值!或是用memcpy。
// memcpy(*esp, (int)*esp+4, int_size);
// 将参数argc压入栈
*esp -= int_size;
*(int *)*esp = argc;
// 将函数返回值0压入栈
*esp -= int_size;
*(int *)*esp = 0;
return success;
}
if_.esp
原来指向esp的内核和用户区边界为避免内存访问越界(访问到内核区),需将setup_stack()
函数中的*esp = PHYS_BASE;
修改成*esp = PHYS_BASE - 12;
?
目标是实现syscall-nr.h
中的以下系统调用函数(每个调用也对应了一个编号):
enum
{
/* Projects 2 and later. */
SYS_HALT, /* Halt the operating system. */
SYS_EXIT, /* Terminate this process. */
SYS_EXEC, /* Start another process. */
SYS_WAIT, /* Wait for a child process to die. */
SYS_CREATE, /* Create a file. */
SYS_REMOVE, /* Delete a file. */
SYS_OPEN, /* Open a file. */
SYS_FILESIZE, /* Obtain a file's size. */
SYS_READ, /* Read from a file. */
SYS_WRITE, /* Write to a file. */
SYS_SEEK, /* Change position in a file. */
SYS_TELL, /* Report current position in a file. */
SYS_CLOSE, /* Close a file. */
...
};
通过以下测试集:
create
exec
wait
halt
open
close
read
write
随便查看一个测试create-normal.c
,可见它是调用了syscall.c
中的
bool create (const char *file, unsigned initial_size)
{
return syscall2 (SYS_CREATE, file, initial_size);
}
该函数在成功时返回1。
该文件中定义了不同的syscall0, syscall1, syscall2, syscall3
,分别是有0、1、2、3个参数的系统调用。
会分别使用pushl %[arg];
将每个参数的地址依次压入栈,然后将当前系统调用的编号的地址压入栈,然后执行0x30号中断(int $0x30
,AT&T语法的中断指令),然后将栈指针esp加(是参数数+1,即压入栈的元素数),即(在执行完0x30号中断后?)将esp恢复到压栈前的位置。
如syscall2
:
/* Invokes syscall NUMBER, passing arguments ARG0 and ARG1, and
returns the return value as an `int'. */
#define syscall2(NUMBER, ARG0, ARG1) \
({ \
int retval; \
asm volatile \
("pushl %[arg1]; pushl %[arg0]; " \
"pushl %[number]; int $0x30; addl $12, %%esp" \
: "=a" (retval) \
: [number] "i" (NUMBER), \
[arg0] "r" (ARG0), \
[arg1] "r" (ARG1) \
: "memory"); \
retval; \
})
userprog/syscall.c
中,syscall_init()
进行了初始化:
void syscall_init (void)
{
intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
}
它为0x30号中断定义了syscall_handler()
,即0x30号中断的中断处理程序,所以修改这个函数。
参数struct intr_frame *f
中保存了中断时的栈指针esp,此时esp指向中断号NUMBER的地址。
所以*esp
即中断号的地址,检查*esp
是一个合法地址后,**esp
即为中断号:
int *p = f->esp;
// 检查是否是合法地址
is_valid_addr(p);
int syscall_number = *p;
然后需要定义这些函数:
void syscall_halt(void);
void syscall_exit(struct intr_frame *f);
int syscall_exec(struct intr_frame *f);
int syscall_wait(struct intr_frame *f);
int syscall_create(struct intr_frame *f);
int syscall_remove(struct intr_frame *f);
int syscall_open(struct intr_frame *f);
int syscall_filesize(struct intr_frame *f);
int syscall_read(struct intr_frame *f);
int syscall_write(struct intr_frame *f);
void syscall_seek(struct intr_frame *f);
int syscall_tell(struct intr_frame *f);
void syscall_close(struct intr_frame *f);
然后调用这些函数:
switch (syscall_number)
{
case SYS_HALT: syscall_halt(); break;
case SYS_EXIT: syscall_exit(f); break;
case SYS_EXEC: f->eax = syscall_exec(f); break;
case SYS_WAIT: f->eax = syscall_wait(f); break;
case SYS_CREATE: f->eax = syscall_create(f); break;
case SYS_REMOVE: f->eax = syscall_remove(f); break;
case SYS_OPEN: f->eax = syscall_open(f); break;
case SYS_FILESIZE: f->eax = syscall_filesize(f); break;
case SYS_READ: f->eax = syscall_read(f); break;
case SYS_WRITE: f->eax = syscall_write(f); break;
case SYS_SEEK: syscall_seek(f); break;
case SYS_TELL: f->eax = syscall_tell(f); break;
case SYS_CLOSE: syscall_close(f); break;
default: printf("No such syscall %d.\n", syscall_number); break;
}
注意有返回值的函数,要将返回值赋值给f->eax
。
然后就是实现上述函数。
在thread.h
中:
在thread中添加一些值:
struct thread
{
...
/* userprog */
int exit_status; // 退出状态
int fd_count; // 打开文件数
struct list open_files; // 打开文件列表
struct file *self; // 指向自己的可执行文件的指针
struct thread *parent; // 父进程
struct list children_list; // 子进程列表
bool load_successful; // 子进程是否加载成功
struct semaphore load_sema; // 用于加载的信号量,会阻塞当前进程直到子进程加载完毕
struct child_process *waiting_child; // 等待加载完成的子进程
...
};
定义子进程:
struct child_process
{
int tid; // 进程号
int exit_status; // 退出状态
struct list_elem child_elem; // 子进程列表中的list元素
bool finished; // 是否待回收
struct semaphore wait_sema; // 用于回收的信号量,会阻塞当前进程直到子进程回收完毕
};
定一个全局锁sysfile_lock
,和退出状态的常量:
struct lock filesys_lock; // 用于文件操作
#define EXIT_STATUS 998244353
thread_init()
中,对filesys_lock
初始化:
lock_init (&filesys_lock);
thread_create()
中,初始化child_process
:
/* 初始化该子进程(是被thread_current()创建出来的) */
struct child_process *c = malloc(sizeof(*c));
c->tid = tid;
c->finished = false;
c->exit_status = t->exit_status;
sema_init(&c->wait_sema, 0);
list_push_back(&running_thread()->children_list, &c->child_elem);
// 注意这里不能用thread_current(),thread_current()会判断当前进程状态为RUNNING,其实不是?
init_thread()
中,对新定义的字段初始化:
// userprog
t->parent = running_thread();
// 注意这里不能用thread_current(),thread_current()会判断当前进程状态为RUNNING,其实不是?
t->self = NULL;
t->exit_status = EXIT_STATUS;
t->fd_count = 2;
list_init(&t->open_files);
t->load_successful = false;
sema_init(&t->load_sema, 0);
t->waiting_child = NULL;
list_init(&t->children_list);
在之后要实现的syscall_open
,也需对fd_count
和open_files
进行更新。
syscall.h
中定义文件结构process_file
:
#include "kernel/list.h"
struct process_file
{
int fd;
struct file *ptr; // 文件指针
struct list_elem elem; // 打开文件队列元素
};
process.c
的load()
中,在最后done前为file赋值:
...
thread_current()->self = file;
done:
...
process_exit()
中,释放文件和子进程文件:
// 释放当前进程(需检查退出状态)
if (cur->exit_status == EXIT_STATUS)
exit_process(-1);
printf("%s exits (%d)\n", cur->name, cur->exit_status);
if (lock_held_by_current_thread(&filesys_lock))
lock_release(&filesys_lock);
// 释放当前进程的文件和其打开文件列表中的文件
lock_acquire(&filesys_lock);
clean_all_files(&cur->open_files);
file_close(cur->self);
// 释放子进程
// 这里每次pop,而不是遍历队列删除,因为free后就没了
while(!list_empty(&cur->children_list))
{
struct list_elem *first = list_pop_front(&cur->children_list);
free(list_entry(first, struct child_process, child_elem));
}
lock_release(&filesys_lock);
这里需要定义释放进程的函数exit_process()
:
// 会将当前进程的退出状态设为status;将当前进程的父进程的子进程队列中,将该进程的child_process结构中的exit_status设为status,将finished设为true。最后调用thread_exit()终止进程。
void exit_process(int status)
{
struct child_process *cp;
struct thread *cur = thread_current();
struct list *children = &cur->parent->children_list;
enum intr_level old_level = intr_disable();
for (struct list_elem *e=list_begin(children); e!=list_end(children); e=list_next(e))
{
cp = list_entry(e, struct child_process, child_elem);
if (cp->tid == cur->tid)
{
cp->finished = true;
cp->exit_status = status;
break;
}
}
cur->exit_status = status;
intr_set_level(old_level);
thread_exit();
}
还要定义释放链表所有文件的函数clean_all_files()
:
void clean_all_files(struct list *files)
{
struct process_file *pf;
while(!list_empty(files))
{
pf = list_entry(list_pop_front(files), struct process_file, elem);
file_close(pf->ptr);
free(pf);
}
}
顺便定义释放指定文件的函数clean_single_file()
:
void clean_single_file(struct process_file *pf)
{
if (pf != NULL)
{
list_remove(&pf->elem);
file_close(pf->ptr);
free(pf);
}
}
以及查询链表中指定fd的链表元素的函数search_by_fd()
:
struct process_file *search_by_fd(struct list *files, int fd)
{
struct process_file *pf;
for (struct list_elem *e=list_begin(files); e!=list_end(files); e=list_next(e))
{
pf = list_entry(e, struct process_file, elem);
if (pf->fd == fd)
return pf;
}
return NULL;
}
函数基本通过调用filesys.h
和file.h
中的函数实现。
在syscall.c
中,实现判断地址是否合法的is_valid_addr()
。
如果地址为非法,或不是用户地址,直接结束进程。
void *is_valid_addr(const void *vaddr)
{
void *page_ptr = NULL;
if (!is_user_vaddr(vaddr) || !(page_ptr=pagedir_get_page(thread_current()->pagedir, vaddr)))
{
exit_process(-1);
return 0;
}
return page_ptr;
}
定义pop_stack()
,取出syscall时栈中指定的参数。
void pop_stack(int *esp, int *save, int offset)
{
int *tmp = esp;
*save = *((int *)is_valid_addr(tmp + offset));
}
syscall_halt()
:
// 关闭机器
void syscall_halt(void)
{
shutdown_power_off(); // 在"devices/shutdown.h"中
}
syscall_exit()
:
// 结束进程,并将进程的exit_status设为指定参数
void syscall_exit(struct intr_frame *f)
{
int status;
pop_stack(f->esp, &status, 1);
exit_process(status);
}
syscall_exec()
:
// 加载指定文件,文件名在参数中
int syscall_exec(struct intr_frame *f)
{
char *file_name = NULL;
pop_stack(f->esp, &file_name, 1);
if (!is_valid_addr(file_name))
return -1;
return exec_process(file_name);
}
需要定义exec_process()
:
// 加载指定文件名的进程
// 先获取文件系统锁,然后分离文件名,尝试是否能打开该文件。如果可以,则加载该进程,返回新tid。
int exec_process(char *file_name)
{
lock_acquire(&filesys_lock);
char *copy = malloc(strlen(file_name)+1);
strlcpy(copy, file_name, strlen(file_name)+1);
char *name, *tmp;
name= strtok_r(copy, " ", &tmp);
struct file *f = filesys_open(name);
int tid;
if (f == NULL)
tid = -1;
else
file_close(f), tid = process_execute(file_name);
lock_release(&filesys_lock);
return tid;
}
syscall_wait()
:
// 等待指定子进程结束,然后返回其退出状态
int syscall_wait(struct intr_frame *f)
{
int child_tid;
pop_stack(f->esp, &child_tid, 1);
return process_wait(child_tid);
}
syscall_create()
:
// 创建指定名称、指定初始大小的文件
int syscall_create(struct intr_frame *f)
{
off_t initial_size;
char *name;
pop_stack(f->esp, &name, 4);
pop_stack(f->esp, &initial_size, 5);
if (!is_valid_addr(name))
return -1;
lock_acquire(&filesys_lock);
int res = filesys_create(name, initial_size);
lock_release(&filesys_lock);
return res;
}
syscall_remove()
:
// 删除拥有指定名称的文件
int syscall_remove(struct intr_frame *f)
{
char *name;
pop_stack(f->esp, &name, 1);
if (!is_valid_addr(name))
return -1;
lock_acquire(&filesys_lock);
int res = (filesys_remove(name) != 0);
lock_release(&filesys_lock);
return res;
}
syscall_open()
:
注意要实现process_file
的初始化
// 打开拥有指定名称的文件
int syscall_open(struct intr_frame *f)
{
char *name;
pop_stack(f->esp, &name, 1);
if (!is_valid_addr(name))
return -1;
lock_acquire(&filesys_lock);
struct file *file = filesys_open(name);
lock_release(&filesys_lock);
if (file == NULL)
return -1;
// 初始化process_file,更新当前进程的fd_count和open_files
struct process_file *p = malloc(sizeof(*p));
p->ptr = file;
p->fd = thread_current()->fd_count++;
list_push_back(&thread_current()->open_files, &p->elem);
return p->fd;
}
syscall_filesize()
:
// 获取指定文件的大小
int syscall_filesize(struct intr_frame *f)
{
int fd;
pop_stack(f->esp, &fd, 1);
lock_acquire(&filesys_lock);
int res = file_length(search_by_fd(&thread_current()->open_files, fd)->ptr);
lock_release(&filesys_lock);
return res;
}
syscall_read()
:
注意input_getc()
在"devices/input.h"
中
// 从描述符为fd的文件中读入
int syscall_read(struct intr_frame *f)
{
int size, fd;
char *buffer;
pop_stack(f->esp, &fd, 5);
pop_stack(f->esp, &buffer, 6);
pop_stack(f->esp, &size, 7);
if (!is_valid_addr(buffer))
return -1;
if (fd == 0) // 从stdin输入
{
uint8_t *buffer = buffer;
for (int i=0; i<size; ++i)
buffer[i] = input_getc();
return size;
}
struct process_file *p = search_by_fd(&thread_current()->open_files, fd);
if (p == NULL)
return -1;
lock_acquire(&filesys_lock);
int res = file_read(p->ptr, buffer, size);
lock_release(&filesys_lock);
return res;
}
syscall_write()
:
注意putbuf
在"kernel/stdio.h"
中。
// 向描述符为fd的文件中输出
int syscall_write(struct intr_frame *f)
{
int size, fd;
char *buffer;
pop_stack(f->esp, &fd, 5);
pop_stack(f->esp, &buffer, 6);
pop_stack(f->esp, &size, 7);
if (!is_valid_addr(buffer))
return -1;
if (fd == 1) // 向stdout输出
{
putbuf(buffer, size);
return size;
}
struct process_file *p = search_by_fd(&thread_current()->open_files, fd);
if (p == NULL)
return -1;
lock_acquire(&filesys_lock);
int res = file_write(p->ptr, buffer, size);
lock_release(&filesys_lock);
return res;
}
syscall_seek()
:
// 更改文件指针的定位
void syscall_seek(struct intr_frame *f)
{
int fd, pos;
pop_stack(f->esp, &fd, 4);
pop_stack(f->esp, &pos, 5);
lock_acquire(&filesys_lock);
file_seek(search_by_fd(&thread_current()->open_files, fd)->ptr, pos);
lock_release(&filesys_lock);
}
syscall_tell()
:
// 获取当前文件指针的位置
int syscall_tell(struct intr_frame *f)
{
int fd;
pop_stack(f->esp, &fd, 1);
lock_acquire(&filesys_lock);
int res = file_tell(search_by_fd(&thread_current()->open_files, fd)->ptr);
lock_release(&filesys_lock);
return res;
}
syscall_close()
:
// 释放描述符为fd的文件
void syscall_close(struct intr_frame *f)
{
int fd;
pop_stack(f->esp, &fd, 1);
lock_acquire(&filesys_lock);
clean_single_file(search_by_fd(&thread_current()->open_files, fd));
lock_release(&filesys_lock);
}
pintos系统不保证多线程安全,为了防止多个线程同时对文件系统进行操作,需要在所有相关的地方对文件系统加锁。
除了上述定义的文件要注意加锁外,已有的函数中:
process.c
中的load()
要加锁,以及禁止文件写入。
{
...
// 获取文件系统锁
lock_acquire(&filesys_lock);
/* Allocate and activate page directory. */
t->pagedir = pagedir_create ();
if (t->pagedir == NULL)
goto done;
process_activate ();
...
file = filesys_open (command);
free(copy);
if (file == NULL)
{
printf ("load: %s: open failed\n", file_name);
goto done;
}
// 禁止当前文件被写入
file_deny_write(file);
...
done:
/* We arrive here whether the load is successful or not. */
file_close (file);
// 释放文件系统锁
lock_release(&filesys_lock);
return success;
}
之前定义了load_sema
,父进程在子进程加载完成前,需阻塞;当子进程加载完成后,解除阻塞并让父进程得知子进程的加载状态load_successful
。
process.c
中的process_execute()
里,thread_create()
创建了进程,若创建成功,此时应使用sema_down(&load_sema)
阻塞父进程并加载子进程,在子进程加载完毕即start_process()
的最后,使用sema_up(&load_sema)
唤醒父进程。
process_execute()
:
...
/* Create a new thread to execute FILE_NAME. */
tid = thread_create (thread_name/* file_name */, PRI_DEFAULT, start_process, fn_copy); // 创建执行该程序的新线程
if (tid == TID_ERROR)
palloc_free_page (fn_copy);
else
{
struct thread *cur = thread_current();
sema_down(&cur->load_sema);
if (!cur->load_successful)
return -1;
}
return tid;
}
start_process()
:
...
/* If load failed, quit. */
palloc_free_page (file_name);
// 子进程加载完成后,让父进程得知其加载状态
struct thread *cur = thread_current();
cur->parent->load_successful = success;
if (!success)
thread_exit ();
// 唤醒父进程
sema_up(&cur->parent->load_sema);
asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (&if_) : "memory");
NOT_REACHED ();
}
由于pintos设计要求,父进程每次只能回收一个进程,因此回收时也需用信号量限制。
在父进程调用process_wait()
等待一个指定子进程结束时,(如果该子进程未完成)需阻塞,直到子进程退出。
所以在process_wait()
中:
int
process_wait (tid_t child_tid)
{
enum intr_level old_level = intr_disable();
struct list_elem *e = find_child_process(child_tid);
struct child_process *c = list_entry(e, struct child_process, child_elem);
intr_set_level(old_level);
if (!e || !c)
return -1;
thread_current()->waiting_child = c;
// 子进程未完成,阻塞父进程
if (!c->finished)
sema_down(&c->wait_sema);
// 子进程结束
list_remove(e);
return c->exit_status;
}
这里要实现find_child_process()
:
// 查找指定的child_process
struct list_elem *find_child_process(int child_tid)
{
struct child_process *cp;
struct thread *cur = thread_current();
for (struct list_elem *e=list_begin(&cur->children_list); e!=list_end(&cur->children_list); e=list_next(e))
{
cp = list_entry(e, struct child_process, child_elem);
if (cp->tid == child_tid)
return e;
}
return NULL;
}
thread_exit()
中:
void thread_exit (void)
{
ASSERT (!intr_context ());
// 若父进程在等待当前进程结束,唤醒父进程
struct thread *cur = thread_current();
enum intr_level old_level = intr_disable();
if (cur->parent->waiting_child != NULL && cur->parent->waiting_child->tid == cur->tid)
sema_up(&cur->parent->waiting_child->wait_sema);
intr_set_level(old_level);
#ifdef USERPROG
process_exit ();
#endif
...
}
注意malloc()
是"threads/malloc.h"
里的!不是stdlib.h
,会出错。
头文件要引入正确!
应该参考文档的。