[关闭]
@SovietPower 2022-05-08T13:33:35.000000Z 字数 17272 阅读 1301

OS 实验6 参数传递 系统调用

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文件结构:

需先创建具有文件系统分区的磁盘:
userprogmake,然后进入build文件夹,执行:

  1. pintos-mkdisk filesys.dsk --filesys-size=2
  2. pintos -f -q
  3. pintos -p ../../examples/echo.c -a echo -- -q
  4. pintos -q run 'echo x'
  5. # 后三条可合并:pintos -p ../../examples/echo -a echo.c -- -f -q run 'echo x’

build出现filesys.dsk,且最后一条语句执行成功,说明磁盘文件创建成功。

参数传递

只需修改process.c
使用下面这种格式进行测试:

  1. pintos -v -k -T 60 --filesys-size=2 -p tests/userprog/args-none -a args-none -- -q -f run args-none
  2. pintos -v -k -T 60 --filesys-size=2 -p tests/userprog/args-single -a args-single -- -q -f run 'args-single onearg'
  3. pintos -v -k -T 60 --qemu --filesys-size=2 -p tests/userprog/open-empty -a open-empty -- -q -f run open-empty
  4. # 含义:
  5. pintos: perl 脚本; -v -k -T 60 :启动参数
  6. filesys-size=2 :定义文件系统大小
  7. -p:载入文件; -a:测试名; -q f run:系统运行命令

最后结果:

问题
使用命令pintos -q run 'echo x',会得到echo x被作为命令一共处理的错误:

  1. Executing 'echo x':
  2. Execution of 'echo x' complete. # 应为'echo'

命令echo和参数x被处理做了一个命令echo x,所以不能正确处理参数。

process_execute()

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,但第一次不可)。

  1. ...
  2. strlcpy (fn_copy, file_name, PGSIZE);
  3. char *thread_name, *tmp;
  4. thread_name = strtok_r(file_name, " ", &tmp);
  5. /* Create a new thread to execute FILE_NAME. */
  6. tid = thread_create (thread_name/* file_name */, PRI_DEFAULT, start_process, fn_copy); // 创建执行该程序的新线程
  7. if (tid == TID_ERROR)
  8. palloc_free_page (fn_copy);
  9. return tid;
  10. }

load()

thread_create()中传入的要执行的函数为start_process()start_process()中主要通过success = load (file_name, &if_.eip, &if_.esp),加载要执行的命令及参数到栈中(为该线程分配空间)。
根据要求,load()需将要执行命令/函数(主要是参数)分配一个新栈,然后令ESP指向该栈。
load()仅在setup_stack()新建了一个栈,并令esp指向新栈的地址,并未在栈中放入命令的参数。

  1. bool load (const char *file_name, void (**eip) (void), void **esp)
  2. {
  3. ...
  4. /* Set up stack. */
  5. if (!setup_stack (esp))
  6. goto done;
  7. ...
  8. }

因此,除在setup_stack()新建一个栈外,还需在其中将参数传进去。
所以修改setup_stack()的定义:

  1. static bool setup_stack (void **esp)
  2. 改为:
  3. static bool setup_stack (void **esp, char *file_name)

load()中,if (!setup_stack (esp))改为if (!setup_stack (esp, file_name))

此外,load()根据file_name打开可执行文件(命令)。

  1. /* Open executable file. */
  2. file = filesys_open (file_name);

此处的file_name也应为不包含参数的命令。所以同样要分开:

  1. /* Open executable file. */
  2. char *copy = malloc(strlen(file_name)+1);
  3. strlcpy(copy, file_name, strlen(file_name)+1);
  4. char *command, *tmp;
  5. command = strtok_r(file_name, " ", &tmp);
  6. file = filesys_open (command);
  7. free(copy);
  8. if (file == NULL)
  9. {
  10. printf ("load: %s: open failed\n", file_name);
  11. goto done;
  12. }

注意要拷贝file_name,因为strtok_r会对串本身进行改变,但之后要用到该串。之后可以释放。
process_execute()中也拷贝了fn_copy

setup_stack()

使用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()

  1. {
  2. ...
  3. // 参数数统计,用于分配*argv的空间
  4. int i, argc=1, len=strlen(file_name);
  5. for (i=1; i<len; ++i)
  6. if (file_name[i]==' ' && file_name[i-1]!=' ')
  7. ++argc;
  8. char *argument, *tmp;
  9. unsigned int int_size = sizeof(int);
  10. // 保存各参数的地址*argv[i]
  11. int *argv = calloc(argc, int_size);
  12. // 将各字符串压入栈(顺序任意,为了方便就依次)
  13. argument = strtok_r(file_name, " ", &tmp);
  14. for(i=0; argument!=NULL; argument=strtok_r(NULL, " ", &tmp))
  15. {
  16. *esp -= strlen(strlen(argument)+1); // 栈向下增长
  17. memcpy(*esp, argument, strlen(argument)+1);
  18. argv[i++] = *((int *)esp);
  19. }
  20. // 对齐
  21. *esp -= (unsigned)*esp%4;
  22. // 将参数*argv[i]压入栈,每次都对齐(每次一个(unsigned) int即正好对齐)
  23. for (i=argc-1; ~i; --i)
  24. {
  25. *esp -= int_size;
  26. memcpy(*esp, &argv[i], int_size);
  27. }
  28. // 将参数**argv压入栈(对齐)。**argv即*argv[0]的起始地址,即此时的*esp值。
  29. *esp -= int_size;
  30. *(int *)*esp = (int)*esp + int_size; // 注意要转指针类型确保正确赋值!或是用memcpy。
  31. // memcpy(*esp, (int)*esp+4, int_size);
  32. // 将参数argc压入栈
  33. *esp -= int_size;
  34. *(int *)*esp = argc;
  35. // 将函数返回值0压入栈
  36. *esp -= int_size;
  37. *(int *)*esp = 0;
  38. return success;
  39. }

if_.esp原来指向esp的内核和用户区边界为避免内存访问越界(访问到内核区),需将setup_stack()函数中的*esp = PHYS_BASE;修改成*esp = PHYS_BASE - 12;

实现系统调用

目标是实现syscall-nr.h中的以下系统调用函数(每个调用也对应了一个编号):

  1. enum
  2. {
  3. /* Projects 2 and later. */
  4. SYS_HALT, /* Halt the operating system. */
  5. SYS_EXIT, /* Terminate this process. */
  6. SYS_EXEC, /* Start another process. */
  7. SYS_WAIT, /* Wait for a child process to die. */
  8. SYS_CREATE, /* Create a file. */
  9. SYS_REMOVE, /* Delete a file. */
  10. SYS_OPEN, /* Open a file. */
  11. SYS_FILESIZE, /* Obtain a file's size. */
  12. SYS_READ, /* Read from a file. */
  13. SYS_WRITE, /* Write to a file. */
  14. SYS_SEEK, /* Change position in a file. */
  15. SYS_TELL, /* Report current position in a file. */
  16. SYS_CLOSE, /* Close a file. */
  17. ...
  18. };

通过以下测试集:

  1. create
  2. exec
  3. wait
  4. halt
  5. open
  6. close
  7. read
  8. write

随便查看一个测试create-normal.c,可见它是调用了syscall.c中的

  1. bool create (const char *file, unsigned initial_size)
  2. {
  3. return syscall2 (SYS_CREATE, file, initial_size);
  4. }

该函数在成功时返回1。
该文件中定义了不同的syscall0, syscall1, syscall2, syscall3,分别是有0、1、2、3个参数的系统调用。
会分别使用pushl %[arg];将每个参数的地址依次压入栈,然后将当前系统调用的编号的地址压入栈,然后执行0x30号中断(int $0x30,AT&T语法的中断指令),然后将栈指针esp加是参数数+1,即压入栈的元素数),即(在执行完0x30号中断后?)将esp恢复到压栈前的位置。
syscall2

  1. /* Invokes syscall NUMBER, passing arguments ARG0 and ARG1, and
  2. returns the return value as an `int'. */
  3. #define syscall2(NUMBER, ARG0, ARG1) \
  4. ({ \
  5. int retval; \
  6. asm volatile \
  7. ("pushl %[arg1]; pushl %[arg0]; " \
  8. "pushl %[number]; int $0x30; addl $12, %%esp" \
  9. : "=a" (retval) \
  10. : [number] "i" (NUMBER), \
  11. [arg0] "r" (ARG0), \
  12. [arg1] "r" (ARG1) \
  13. : "memory"); \
  14. retval; \
  15. })

userprog/syscall.c中,syscall_init()进行了初始化:

  1. void syscall_init (void)
  2. {
  3. intr_register_int (0x30, 3, INTR_ON, syscall_handler, "syscall");
  4. }

它为0x30号中断定义了syscall_handler(),即0x30号中断的中断处理程序,所以修改这个函数。

参数struct intr_frame *f中保存了中断时的栈指针esp,此时esp指向中断号NUMBER的地址。
所以*esp即中断号的地址,检查*esp是一个合法地址后,**esp即为中断号:

  1. int *p = f->esp;
  2. // 检查是否是合法地址
  3. is_valid_addr(p);
  4. int syscall_number = *p;

然后需要定义这些函数:

  1. void syscall_halt(void);
  2. void syscall_exit(struct intr_frame *f);
  3. int syscall_exec(struct intr_frame *f);
  4. int syscall_wait(struct intr_frame *f);
  5. int syscall_create(struct intr_frame *f);
  6. int syscall_remove(struct intr_frame *f);
  7. int syscall_open(struct intr_frame *f);
  8. int syscall_filesize(struct intr_frame *f);
  9. int syscall_read(struct intr_frame *f);
  10. int syscall_write(struct intr_frame *f);
  11. void syscall_seek(struct intr_frame *f);
  12. int syscall_tell(struct intr_frame *f);
  13. void syscall_close(struct intr_frame *f);

然后调用这些函数:

  1. switch (syscall_number)
  2. {
  3. case SYS_HALT: syscall_halt(); break;
  4. case SYS_EXIT: syscall_exit(f); break;
  5. case SYS_EXEC: f->eax = syscall_exec(f); break;
  6. case SYS_WAIT: f->eax = syscall_wait(f); break;
  7. case SYS_CREATE: f->eax = syscall_create(f); break;
  8. case SYS_REMOVE: f->eax = syscall_remove(f); break;
  9. case SYS_OPEN: f->eax = syscall_open(f); break;
  10. case SYS_FILESIZE: f->eax = syscall_filesize(f); break;
  11. case SYS_READ: f->eax = syscall_read(f); break;
  12. case SYS_WRITE: f->eax = syscall_write(f); break;
  13. case SYS_SEEK: syscall_seek(f); break;
  14. case SYS_TELL: f->eax = syscall_tell(f); break;
  15. case SYS_CLOSE: syscall_close(f); break;
  16. default: printf("No such syscall %d.\n", syscall_number); break;
  17. }

注意有返回值的函数,要将返回值赋值给f->eax
然后就是实现上述函数。

修改thread定义

thread.h中:
在thread中添加一些值:

  1. struct thread
  2. {
  3. ...
  4. /* userprog */
  5. int exit_status; // 退出状态
  6. int fd_count; // 打开文件数
  7. struct list open_files; // 打开文件列表
  8. struct file *self; // 指向自己的可执行文件的指针
  9. struct thread *parent; // 父进程
  10. struct list children_list; // 子进程列表
  11. bool load_successful; // 子进程是否加载成功
  12. struct semaphore load_sema; // 用于加载的信号量,会阻塞当前进程直到子进程加载完毕
  13. struct child_process *waiting_child; // 等待加载完成的子进程
  14. ...
  15. };

定义子进程:

  1. struct child_process
  2. {
  3. int tid; // 进程号
  4. int exit_status; // 退出状态
  5. struct list_elem child_elem; // 子进程列表中的list元素
  6. bool finished; // 是否待回收
  7. struct semaphore wait_sema; // 用于回收的信号量,会阻塞当前进程直到子进程回收完毕
  8. };

定一个全局锁sysfile_lock,和退出状态的常量:

  1. struct lock filesys_lock; // 用于文件操作
  2. #define EXIT_STATUS 998244353

增加thread的初始化

thread_init()中,对filesys_lock初始化:

  1. lock_init (&filesys_lock);

thread_create()中,初始化child_process

  1. /* 初始化该子进程(是被thread_current()创建出来的) */
  2. struct child_process *c = malloc(sizeof(*c));
  3. c->tid = tid;
  4. c->finished = false;
  5. c->exit_status = t->exit_status;
  6. sema_init(&c->wait_sema, 0);
  7. list_push_back(&running_thread()->children_list, &c->child_elem);
  8. // 注意这里不能用thread_current(),thread_current()会判断当前进程状态为RUNNING,其实不是?

init_thread()中,对新定义的字段初始化:

  1. // userprog
  2. t->parent = running_thread();
  3. // 注意这里不能用thread_current(),thread_current()会判断当前进程状态为RUNNING,其实不是?
  4. t->self = NULL;
  5. t->exit_status = EXIT_STATUS;
  6. t->fd_count = 2;
  7. list_init(&t->open_files);
  8. t->load_successful = false;
  9. sema_init(&t->load_sema, 0);
  10. t->waiting_child = NULL;
  11. list_init(&t->children_list);

在之后要实现的syscall_open,也需对fd_countopen_files进行更新。

process

syscall.h中定义文件结构process_file

  1. #include "kernel/list.h"
  2. struct process_file
  3. {
  4. int fd;
  5. struct file *ptr; // 文件指针
  6. struct list_elem elem; // 打开文件队列元素
  7. };

process.cload()中,在最后done前为file赋值:

  1. ...
  2. thread_current()->self = file;
  3. done:
  4. ...

process_exit()中,释放文件和子进程文件:

  1. // 释放当前进程(需检查退出状态)
  2. if (cur->exit_status == EXIT_STATUS)
  3. exit_process(-1);
  4. printf("%s exits (%d)\n", cur->name, cur->exit_status);
  5. if (lock_held_by_current_thread(&filesys_lock))
  6. lock_release(&filesys_lock);
  7. // 释放当前进程的文件和其打开文件列表中的文件
  8. lock_acquire(&filesys_lock);
  9. clean_all_files(&cur->open_files);
  10. file_close(cur->self);
  11. // 释放子进程
  12. // 这里每次pop,而不是遍历队列删除,因为free后就没了
  13. while(!list_empty(&cur->children_list))
  14. {
  15. struct list_elem *first = list_pop_front(&cur->children_list);
  16. free(list_entry(first, struct child_process, child_elem));
  17. }
  18. lock_release(&filesys_lock);

这里需要定义释放进程的函数exit_process()

  1. // 会将当前进程的退出状态设为status;将当前进程的父进程的子进程队列中,将该进程的child_process结构中的exit_status设为status,将finished设为true。最后调用thread_exit()终止进程。
  2. void exit_process(int status)
  3. {
  4. struct child_process *cp;
  5. struct thread *cur = thread_current();
  6. struct list *children = &cur->parent->children_list;
  7. enum intr_level old_level = intr_disable();
  8. for (struct list_elem *e=list_begin(children); e!=list_end(children); e=list_next(e))
  9. {
  10. cp = list_entry(e, struct child_process, child_elem);
  11. if (cp->tid == cur->tid)
  12. {
  13. cp->finished = true;
  14. cp->exit_status = status;
  15. break;
  16. }
  17. }
  18. cur->exit_status = status;
  19. intr_set_level(old_level);
  20. thread_exit();
  21. }

还要定义释放链表所有文件的函数clean_all_files()

  1. void clean_all_files(struct list *files)
  2. {
  3. struct process_file *pf;
  4. while(!list_empty(files))
  5. {
  6. pf = list_entry(list_pop_front(files), struct process_file, elem);
  7. file_close(pf->ptr);
  8. free(pf);
  9. }
  10. }

顺便定义释放指定文件的函数clean_single_file()

  1. void clean_single_file(struct process_file *pf)
  2. {
  3. if (pf != NULL)
  4. {
  5. list_remove(&pf->elem);
  6. file_close(pf->ptr);
  7. free(pf);
  8. }
  9. }

以及查询链表中指定fd的链表元素的函数search_by_fd()

  1. struct process_file *search_by_fd(struct list *files, int fd)
  2. {
  3. struct process_file *pf;
  4. for (struct list_elem *e=list_begin(files); e!=list_end(files); e=list_next(e))
  5. {
  6. pf = list_entry(e, struct process_file, elem);
  7. if (pf->fd == fd)
  8. return pf;
  9. }
  10. return NULL;
  11. }

syscall

函数基本通过调用filesys.hfile.h中的函数实现。

syscall.c中,实现判断地址是否合法的is_valid_addr()
如果地址为非法,或不是用户地址,直接结束进程。

  1. void *is_valid_addr(const void *vaddr)
  2. {
  3. void *page_ptr = NULL;
  4. if (!is_user_vaddr(vaddr) || !(page_ptr=pagedir_get_page(thread_current()->pagedir, vaddr)))
  5. {
  6. exit_process(-1);
  7. return 0;
  8. }
  9. return page_ptr;
  10. }

定义pop_stack(),取出syscall时栈中指定的参数。

  1. void pop_stack(int *esp, int *save, int offset)
  2. {
  3. int *tmp = esp;
  4. *save = *((int *)is_valid_addr(tmp + offset));
  5. }

syscall_halt()

  1. // 关闭机器
  2. void syscall_halt(void)
  3. {
  4. shutdown_power_off(); // 在"devices/shutdown.h"中
  5. }

syscall_exit()

  1. // 结束进程,并将进程的exit_status设为指定参数
  2. void syscall_exit(struct intr_frame *f)
  3. {
  4. int status;
  5. pop_stack(f->esp, &status, 1);
  6. exit_process(status);
  7. }

syscall_exec()

  1. // 加载指定文件,文件名在参数中
  2. int syscall_exec(struct intr_frame *f)
  3. {
  4. char *file_name = NULL;
  5. pop_stack(f->esp, &file_name, 1);
  6. if (!is_valid_addr(file_name))
  7. return -1;
  8. return exec_process(file_name);
  9. }

需要定义exec_process()

  1. // 加载指定文件名的进程
  2. // 先获取文件系统锁,然后分离文件名,尝试是否能打开该文件。如果可以,则加载该进程,返回新tid。
  3. int exec_process(char *file_name)
  4. {
  5. lock_acquire(&filesys_lock);
  6. char *copy = malloc(strlen(file_name)+1);
  7. strlcpy(copy, file_name, strlen(file_name)+1);
  8. char *name, *tmp;
  9. name= strtok_r(copy, " ", &tmp);
  10. struct file *f = filesys_open(name);
  11. int tid;
  12. if (f == NULL)
  13. tid = -1;
  14. else
  15. file_close(f), tid = process_execute(file_name);
  16. lock_release(&filesys_lock);
  17. return tid;
  18. }

syscall_wait()

  1. // 等待指定子进程结束,然后返回其退出状态
  2. int syscall_wait(struct intr_frame *f)
  3. {
  4. int child_tid;
  5. pop_stack(f->esp, &child_tid, 1);
  6. return process_wait(child_tid);
  7. }

syscall_create()

  1. // 创建指定名称、指定初始大小的文件
  2. int syscall_create(struct intr_frame *f)
  3. {
  4. off_t initial_size;
  5. char *name;
  6. pop_stack(f->esp, &name, 4);
  7. pop_stack(f->esp, &initial_size, 5);
  8. if (!is_valid_addr(name))
  9. return -1;
  10. lock_acquire(&filesys_lock);
  11. int res = filesys_create(name, initial_size);
  12. lock_release(&filesys_lock);
  13. return res;
  14. }

syscall_remove()

  1. // 删除拥有指定名称的文件
  2. int syscall_remove(struct intr_frame *f)
  3. {
  4. char *name;
  5. pop_stack(f->esp, &name, 1);
  6. if (!is_valid_addr(name))
  7. return -1;
  8. lock_acquire(&filesys_lock);
  9. int res = (filesys_remove(name) != 0);
  10. lock_release(&filesys_lock);
  11. return res;
  12. }

syscall_open()
注意要实现process_file的初始化

  1. // 打开拥有指定名称的文件
  2. int syscall_open(struct intr_frame *f)
  3. {
  4. char *name;
  5. pop_stack(f->esp, &name, 1);
  6. if (!is_valid_addr(name))
  7. return -1;
  8. lock_acquire(&filesys_lock);
  9. struct file *file = filesys_open(name);
  10. lock_release(&filesys_lock);
  11. if (file == NULL)
  12. return -1;
  13. // 初始化process_file,更新当前进程的fd_count和open_files
  14. struct process_file *p = malloc(sizeof(*p));
  15. p->ptr = file;
  16. p->fd = thread_current()->fd_count++;
  17. list_push_back(&thread_current()->open_files, &p->elem);
  18. return p->fd;
  19. }

syscall_filesize()

  1. // 获取指定文件的大小
  2. int syscall_filesize(struct intr_frame *f)
  3. {
  4. int fd;
  5. pop_stack(f->esp, &fd, 1);
  6. lock_acquire(&filesys_lock);
  7. int res = file_length(search_by_fd(&thread_current()->open_files, fd)->ptr);
  8. lock_release(&filesys_lock);
  9. return res;
  10. }

syscall_read()
注意input_getc()"devices/input.h"

  1. // 从描述符为fd的文件中读入
  2. int syscall_read(struct intr_frame *f)
  3. {
  4. int size, fd;
  5. char *buffer;
  6. pop_stack(f->esp, &fd, 5);
  7. pop_stack(f->esp, &buffer, 6);
  8. pop_stack(f->esp, &size, 7);
  9. if (!is_valid_addr(buffer))
  10. return -1;
  11. if (fd == 0) // 从stdin输入
  12. {
  13. uint8_t *buffer = buffer;
  14. for (int i=0; i<size; ++i)
  15. buffer[i] = input_getc();
  16. return size;
  17. }
  18. struct process_file *p = search_by_fd(&thread_current()->open_files, fd);
  19. if (p == NULL)
  20. return -1;
  21. lock_acquire(&filesys_lock);
  22. int res = file_read(p->ptr, buffer, size);
  23. lock_release(&filesys_lock);
  24. return res;
  25. }

syscall_write()
注意putbuf"kernel/stdio.h"中。

  1. // 向描述符为fd的文件中输出
  2. int syscall_write(struct intr_frame *f)
  3. {
  4. int size, fd;
  5. char *buffer;
  6. pop_stack(f->esp, &fd, 5);
  7. pop_stack(f->esp, &buffer, 6);
  8. pop_stack(f->esp, &size, 7);
  9. if (!is_valid_addr(buffer))
  10. return -1;
  11. if (fd == 1) // 向stdout输出
  12. {
  13. putbuf(buffer, size);
  14. return size;
  15. }
  16. struct process_file *p = search_by_fd(&thread_current()->open_files, fd);
  17. if (p == NULL)
  18. return -1;
  19. lock_acquire(&filesys_lock);
  20. int res = file_write(p->ptr, buffer, size);
  21. lock_release(&filesys_lock);
  22. return res;
  23. }

syscall_seek()

  1. // 更改文件指针的定位
  2. void syscall_seek(struct intr_frame *f)
  3. {
  4. int fd, pos;
  5. pop_stack(f->esp, &fd, 4);
  6. pop_stack(f->esp, &pos, 5);
  7. lock_acquire(&filesys_lock);
  8. file_seek(search_by_fd(&thread_current()->open_files, fd)->ptr, pos);
  9. lock_release(&filesys_lock);
  10. }

syscall_tell()

  1. // 获取当前文件指针的位置
  2. int syscall_tell(struct intr_frame *f)
  3. {
  4. int fd;
  5. pop_stack(f->esp, &fd, 1);
  6. lock_acquire(&filesys_lock);
  7. int res = file_tell(search_by_fd(&thread_current()->open_files, fd)->ptr);
  8. lock_release(&filesys_lock);
  9. return res;
  10. }

syscall_close()

  1. // 释放描述符为fd的文件
  2. void syscall_close(struct intr_frame *f)
  3. {
  4. int fd;
  5. pop_stack(f->esp, &fd, 1);
  6. lock_acquire(&filesys_lock);
  7. clean_single_file(search_by_fd(&thread_current()->open_files, fd));
  8. lock_release(&filesys_lock);
  9. }

同步

pintos系统不保证多线程安全,为了防止多个线程同时对文件系统进行操作,需要在所有相关的地方对文件系统加锁。

加文件系统锁

除了上述定义的文件要注意加锁外,已有的函数中:
process.c中的load()要加锁,以及禁止文件写入。

  1. {
  2. ...
  3. // 获取文件系统锁
  4. lock_acquire(&filesys_lock);
  5. /* Allocate and activate page directory. */
  6. t->pagedir = pagedir_create ();
  7. if (t->pagedir == NULL)
  8. goto done;
  9. process_activate ();
  10. ...
  11. file = filesys_open (command);
  12. free(copy);
  13. if (file == NULL)
  14. {
  15. printf ("load: %s: open failed\n", file_name);
  16. goto done;
  17. }
  18. // 禁止当前文件被写入
  19. file_deny_write(file);
  20. ...
  21. done:
  22. /* We arrive here whether the load is successful or not. */
  23. file_close (file);
  24. // 释放文件系统锁
  25. lock_release(&filesys_lock);
  26. return success;
  27. }

load_sema

之前定义了load_sema,父进程在子进程加载完成前,需阻塞;当子进程加载完成后,解除阻塞并让父进程得知子进程的加载状态load_successful
process.c中的process_execute()里,thread_create()创建了进程,若创建成功,此时应使用sema_down(&load_sema)阻塞父进程并加载子进程,在子进程加载完毕即start_process()的最后,使用sema_up(&load_sema)唤醒父进程。

process_execute()

  1. ...
  2. /* Create a new thread to execute FILE_NAME. */
  3. tid = thread_create (thread_name/* file_name */, PRI_DEFAULT, start_process, fn_copy); // 创建执行该程序的新线程
  4. if (tid == TID_ERROR)
  5. palloc_free_page (fn_copy);
  6. else
  7. {
  8. struct thread *cur = thread_current();
  9. sema_down(&cur->load_sema);
  10. if (!cur->load_successful)
  11. return -1;
  12. }
  13. return tid;
  14. }

start_process()

  1. ...
  2. /* If load failed, quit. */
  3. palloc_free_page (file_name);
  4. // 子进程加载完成后,让父进程得知其加载状态
  5. struct thread *cur = thread_current();
  6. cur->parent->load_successful = success;
  7. if (!success)
  8. thread_exit ();
  9. // 唤醒父进程
  10. sema_up(&cur->parent->load_sema);
  11. asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (&if_) : "memory");
  12. NOT_REACHED ();
  13. }

wait_sema

由于pintos设计要求,父进程每次只能回收一个进程,因此回收时也需用信号量限制。

在父进程调用process_wait()等待一个指定子进程结束时,(如果该子进程未完成)需阻塞,直到子进程退出。
所以在process_wait()中:

  1. int
  2. process_wait (tid_t child_tid)
  3. {
  4. enum intr_level old_level = intr_disable();
  5. struct list_elem *e = find_child_process(child_tid);
  6. struct child_process *c = list_entry(e, struct child_process, child_elem);
  7. intr_set_level(old_level);
  8. if (!e || !c)
  9. return -1;
  10. thread_current()->waiting_child = c;
  11. // 子进程未完成,阻塞父进程
  12. if (!c->finished)
  13. sema_down(&c->wait_sema);
  14. // 子进程结束
  15. list_remove(e);
  16. return c->exit_status;
  17. }

这里要实现find_child_process()

  1. // 查找指定的child_process
  2. struct list_elem *find_child_process(int child_tid)
  3. {
  4. struct child_process *cp;
  5. struct thread *cur = thread_current();
  6. for (struct list_elem *e=list_begin(&cur->children_list); e!=list_end(&cur->children_list); e=list_next(e))
  7. {
  8. cp = list_entry(e, struct child_process, child_elem);
  9. if (cp->tid == child_tid)
  10. return e;
  11. }
  12. return NULL;
  13. }

thread_exit()中:

  1. void thread_exit (void)
  2. {
  3. ASSERT (!intr_context ());
  4. // 若父进程在等待当前进程结束,唤醒父进程
  5. struct thread *cur = thread_current();
  6. enum intr_level old_level = intr_disable();
  7. if (cur->parent->waiting_child != NULL && cur->parent->waiting_child->tid == cur->tid)
  8. sema_up(&cur->parent->waiting_child->wait_sema);
  9. intr_set_level(old_level);
  10. #ifdef USERPROG
  11. process_exit ();
  12. #endif
  13. ...
  14. }

结果



注意

注意malloc()"threads/malloc.h"里的!不是stdlib.h,会出错。
头文件要引入正确!
应该参考文档的。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注