pintos Lab2 实验报告
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
华东师范大学软件学院实验报告
实验课程:操作系统实践年级:大二实验成绩:
实验名称:Pintos-User Programs 姓名:
实验编号:学号:实验日期:2018/12/27
指导教师:组号:实验时间:4学时
一、实验目的
当前, 我们已经完成了pintos 的第一部分(熟悉了其基础结构和线程包), 现在是开始处理系统中允许运行用户程序的部分的时候了。
基本代码已经支持加载和运行用户程序, 但不能加载和运行或交互性。
在此项目中, 我们将使程序能够通过系统调用与操作系统进行交互。
我们将在"userprog" 目录中进行工作, 但我们也将与pintos 的几乎所有其他部分进行交互。
具体目的如下:
(1)了解Pintos操作系统的功能流程及内核的软件工程结构。
(2)通过Pintos操作系统内核的剖析,了解现有Pintos操作系统在处理用户程序方面中存在的参数传递问题,有效解决其参数传递的问题。
(3)通过Pintos内核剖析,了解其中断处理的机制,学会操作系统中断功能的编写方法。
(4)了解现有Pintos操作系统的系统调用功能,根据其中断机制,完善系统调用功能,使Pintos系统具有处理用户中断请求的功能。
(5)通过Pintos内核剖析,解决现有Pintos操作系统中存在的进程终止时缺少终端提示的问题。
(6)通过Pintos内核剖析,解决现有Pintos操作系统中存在的运行文件禁止写操作的问题。
二、实验内容与实验步骤
实验内容如下:
(1)在分析内核的基础上,对Pintos操作系统的参数传递问题提出有效的策略,设计算法,分步跟踪和调试,通过实践,有效解决参数传递问题,并对实验结果进行分析。
(2)通过Pintos操作系统内核的剖析,了解其中断处理的机制,在此基础上,完善Pintos的系统调用功能,设计算法,分步跟踪和调试,通过测试分析完善的系统调用功能。
(3)在分析内核的基础上,对现有Pintos操作系统进行完善,增加进程终止的终端提示功能,设计算法,分步跟踪和调试,通过实践,验证终端提示功的有效性。
(4)在分析内核的基础上,对现有Pintos操作系统进行完善,增加运行文件禁止写操作的功能,设计算法,分步跟踪和调试,通过实践,验证禁止写操作功能的有效性。
具体步骤:
(1)进程终止的终端提示
每当用户进程终止时,不管是自愿退出或出于任何其他原因, 请打印进程的名称和退出代码, 格式为printf ("%s: exit(%d)\n", ...)。
打印的名称应该是传递给
process_execute() 的全名, 省略了命令行参数。
当不是用户进程的内核线程终止
或调用停止系统调用时, 不要打印这些消息。
当进程加载失败时, 该消息是可选
的。
除此之外, 不要打印任何其他消息。
(2)参数传递
目前, process_execute() 不支持将参数传递给新进程。
实现此功能, 通过扩展process_execute(), 使其不是简单地将程序文件名作为其参数, 而是将其划分为空
格中的单词。
第一个单词是程序名称, 第二个单词是第一个参数, 依此类推。
也
就是说, process_execute ("grep foo bar") 应该运行grep 传递两个参数foo 和
bar。
在命令行中, 多个空格等效于单个空格, 因此process_execute("grep foo bar")
相当于我们最初的示例。
我们可以对命令行参数的长度施加合理的限制。
(3)系统调用
在"userprogsecalls. c" 中实现系统调用处理程序。
我们通过终止进程提供"
句柄" 系统调用的骨架实现。
它将需要检索系统调用号码, 然后检索任何系统调
用参数, 并执行适当的操作。
实现以下系统调用。
列出的原型是由一个用户程序
看到的, 其中包括"lib/usersicl. h"(此h文件以及“lib/user“中的所有其他h文
件仅供用户程序使用)。
每个系统调用的系统调用编号在"libsicall-nr. h" 中定义:
1.void halt (void) ;
2.void exit (int status) ;
3.tid_t exec (const char *cmd_line) ;
4.int wait (tid_t pid) ;
5.bool create (const char *file, unsigned initial_size) ;
6.bool remove (const char *file) ;
7.int open (const char *file) ;
8.int filesize (int fd) ;
9.int read (int fd, void *buffer, unsigned size) ;
10.int write (int fd, const void *buffer, unsigned size) ;
11.void seek (int fd, unsigned position) ;
12.unsigned tell (int fd) ;
13.void close (int fd) ;
若要实现syscalls, 我们需要提供在用户虚拟地址空间中读取和写入数据的方法。
我们需要此能力, 然后才能获得系统呼叫号码, 因为该系统的数量是用户
的虚拟地址空间。
我们必须同步系统调用, 以便任何数量的用户进程都可以同时进行调用。
特别是, 一次从多个线程调用"filesys" 目录中提供的文件系统代码是不安全的。
我们的系统调用实现必须将文件系统代码视为关键部分,不要忘记进process_execute() 也会访问文件。
现在, 我们建议不要修改"file" 目录中的代
码。
我们已经拥有了一个用户级函数, 用于"lib/usideslixl. c" 中的每个系统调用。
它们为用户进程提供了一种从c 程序调用每个系统调用的方法。
每个都使
用少量内联程序集代码来调用系统调用, 并且(如果适当) 返回系统调用的返回
值。
我们需要记住的是,用户程序所能做的任何事情都不会导致操作系统崩溃、崩溃、断言失败或以其他方式出现故障。
实验需要强调的是: pintos的测试将尝试以许多、许多方式中断我们的系统调用。
我们需要考虑所有的角落案件, 并处理它们。
用户程序应该能够导致操作系统停止的唯一方式是调用停止系统调用。
如果系统调用传递的是无效参数, 则可接受的选项包括返回错误值(对于返回值的
调用)、返回未定义的值或终止进程。
(4)运行文件禁止写操作
添加代码以拒绝对用作可执行文件的文件写入。
许多操作系统之所以这样做, 是因为如果进程试图运行磁盘上正在更改的代码, 则会产生不可预知的结果。
一旦在项目3中实现了虚拟内存, 这一点尤其重要, 但即使是现在也不会造成伤
害。
可以使用file_deny_write()来防止写入打开的文件。
在文件上调用file_allow_ write()将重新启用它们(除非该文件被另一个打开程序拒绝写入)。
关闭文件也将重新启用写入。
因此, 要拒绝对进程的可执行文件的写入, 只要进程仍在运行, 就必须使其保持打开状态。
三、实验环境
操作系统:Ubuntu 18.04
软件要求:VScode、GCC、GNU、Perl、QEMU、X、Bochs
Pintos为/下载的最新版本(Pintos-anon),与老版本pintos可能会略有区别。
四、实验过程与分析
首先我们注意到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. };
因此我们可以在syscall.c中完善如下框架:
(注意系统调用的返回值保存在f->eax中)
1.static void
2.syscall_handler (struct intr_frame *f)
3.{
4. esp=f->esp;
5. check_uadd_r(esp);
6.switch (*esp){
7.case SYS_HALT:
8. halt();
9.break;
10.case SYS_EXIT:
11. check_uadd_r(esp+1);
12. exit(*(esp+1));
13.break;
14.case SYS_EXEC:
15. check_uadd_r(esp+1);
16. check_uadd_r(*(esp+1));
17. lock_acquire(&filesys_lock);
18. f->eax=exec(*(esp+1));
19. lock_release(&filesys_lock);
20.break;
21.case SYS_WAIT:
22. check_uadd_r(esp+1);
23. f->eax=wait(*(esp+1));
24.break;
25.case SYS_CREATE:
26. check_uadd_r(esp+1);
27. check_uadd_r(esp+2);
28. check_uadd_r(*(esp+1));
29. f->eax=create(*(esp+1),*(esp+2));
30.break;
31.case SYS_REMOVE:
32. check_uadd_r(esp+1);
33. check_uadd_r(*(esp+1));
34. f->eax=remove(*(esp+1));
35.break;
36.case SYS_OPEN:
37. check_uadd_r(esp+1);
38. check_uadd_r(*(esp+1));
39. f->eax=open(*(esp+1));
40.break;
41.case SYS_FILESIZE:
42. f->eax=filesize(*(esp+1));
43.break;
44.case SYS_READ:
45. check_uadd_r(esp+1);
46. check_uadd_r(esp+2);
47. check_uadd_r(esp+3);
48. check_uadd_r(*(esp+2));
49. check_uadd_r(*(esp+2)+*(esp+3)-1);
50. f->eax=read(*(esp+1),*(esp+2),*(esp+3));
51.break;
52.case SYS_WRITE:
53. check_uadd_r(esp+1);
54. check_uadd_r(esp+2);
55. check_uadd_r(esp+2);
56. check_uadd_r(*(esp+2));
57. f->eax=write(*(esp+1),*(esp+2),*(esp+3));
58.break;
59.case SYS_SEEK:
60. check_uadd_r(esp+1);
61. check_uadd_r(esp+2);
62. seek(*(esp+1),*(esp+2));
63.break;
64.case SYS_TELL:
65. check_uadd_r(esp+1);
66. f->eax=tell(*(esp+1));
67.break;
68.case SYS_CLOSE:
69. check_uadd_r(esp+1);
70. close(*(esp+1));
71.break;
72.default:
73. exit(-1);
74.break;
75. }
76.}
当然我们要注意以下虚拟内存保护,当进程访问非法的内存区是要及时把它kill掉(exit(-1)):
1./* Reads a byte at user virtual address UADDR.
2.UADDR must be below PHYS_BASE.
3.Returns the byte value if successful,
4. -1 if a segfault occurred. */
5.static int get_user (const uint8_t *uaddr) {
6.int result;
7. asm ("movl $1f, %0; movzbl %1, %0; 1:" : "=&a" (result) : "m" (*uaddr));
8.return result;
9.}
10.
11./* Writes BYTE to user address UDST.
12.UDST must be below PHYS_BASE.
13.Returns true if successful,
14.false if a segfault occurred. */
15.static bool put_user (uint8_t *udst, uint8_t byte) {
16.int error_code;
17. asm ("movl $1f, %0; movb %b2, %1; 1:" : "=&a" (error_code), "=m" (*udst) : "q" (byte));
18.return error_code != -1;
19.}
(1)进程终止的终端提示
在exit()中差入如下代码即可:
1.struct thread *cur=thread_current();
2.printf ("%s: exit(%d)\n",cur->name,status);
(2)参数传递
根据实验手册提供的参数传递例子与所学知识我们可以写出如下代码。
因此我们只需要在process.c中load (const char *file_name, void (**eip) (void), void **esp)函数调用setup_stack (esp)完成后插入如下代码即可:
1.char *ch=s+len-1;
2.int cnt=0;
3.int tmplen;
4.while (ch!=s){
5.if (*ch!='\0'&&*(ch-1)=='\0'){
6. tmplen=strlen(ch);
7. tmplen++;
8. *esp-=tmplen;
9. strlcpy(*esp,ch,tmplen);
10. cnt++;
11. }
12. ch--;
13. }
14. tmplen=strlen(ch);
15. tmplen++;
16. *esp-=tmplen;
17. strlcpy(*esp,ch,tmplen);
18. cnt++;
19.
20. ch=*esp;
21. *esp=((int)(*esp)&0xfffffffc);
22. *esp-=4;
23. *(int *)(*esp)=0;
24. *esp-=cnt<<2;
25.for (int i=0;i<cnt;i++){
26. *(int *)(*esp+(i<<2))=ch;
27.while (*ch!='\0') ch++;
28. ch++;
29. }
30. *esp-=4;
31. *(int *)(*esp)=(unsigned)*esp+4;
32. *esp-=4;
33. *(int *)(*esp)=cnt;
34. *esp-=4;
35. *(int *)(*esp)=0;
(3)系统调用
①void halt (void) ;
通过调用shutdown_power_off() (在"devices/stutdown. h" 中声明) 来终止
pintos。
1.void halt(void){
2. shutdown_power_off();
3.}
②void exit (int status) ;
终止当前用户程序, 将状态返回到内核。
如果这个过程的父母等待(见下面),
返回其状态。
通常, 状态为0表示成功, 非零值表示错误。
1.void exit(int status){
2.struct thread *cur=thread_current();
3. printf ("%s: exit(%d)\n",cur->name,status);
4.for (struct list_elem *e=list_begin(&fd_list);e!=list_end(&fd_list);){
5.struct file_d *tmp=list_entry(e,struct file_d,elem);
6. e=list_next(e);
7.if (tmp->onwer==cur->tid){
8. list_remove(&tmp->elem);
9. file_close(tmp->file);
10. free(tmp);
11. }
12. }
13.if (cur->release_lock){
14. cur->me->status=status;
15. lock_release(&cur->me->lk);
16. }
17.for (struct list_elem *e=list_begin(&cur->son);e!=list_end(&cur->son);){
18.struct thread_son *a=list_entry(e,struct thread_son,elem);
19.if (a->status==-2) a->add->release_lock=false;
20. e=list_next(e);
21. free(a);
22. }
23. thread_exit();
24.}
③tid_t exec (const char *cmd_line) ;
运行在cmd 行中给出其名称的可执行文件, 传递任何给定的参数, 并返回新进程的程序id (pid)。
如果程序因任何原因无法加载或运行, 则必须返回pid-
1, 否则它不应该是有效的pid。
因此, 父进程不能从exec 返回, 直到它知道子
进程是否成功加载了其可执行文件。
必须使用适当的同步来确保这一点。
1.tid_t exec(const char *cmd_line){
2.return process_execute(cmd_line);
3.}
该部分主要在process.c中实现。
1.tid_t
2.process_execute (const char *file_name)
3.{
4.char *fn_copy;
5. tid_t tid;
6.
7./* Make a copy of FILE_NAME.
8. Otherwise there's a race between the caller and load(). */
9.if (strlen(file_name)>PGSIZE-sizeof(struct lock *))
10. printf("Warning:some of file_name has been thrown away! :(");
11.
12. fn_copy = palloc_get_page (0);
13.if (fn_copy == NULL)
14.return TID_ERROR;
15. strlcpy (fn_copy, file_name, PGSIZE);
16.
17./* Create a new thread to execute FILE_NAME. */
18. tid=create_son(file_name,fn_copy);
19.
20.return tid;
21.}
22.
23.tid_t create_son(const char *file_name,void *args){
24. tid_t tid;
25.struct thread *t=thread_current();
26.struct thread_son *n_son=calloc(1,sizeof(struct thread_son));
27. list_push_back(&t->son,&n_son->elem);
28. lock_init(&n_son->lk);
29. lock_acquire(&n_son->lk);
30. n_son->tid=-2;
31. *(struct thread_son **)(args+PGSIZE-4)=n_son;
32. tid = thread_create (file_name, PRI_DEFAULT, start_process, args);
33.while (tid!=TID_ERROR && n_son->tid==-2){
34. thread_yield();
35. }
36. lock_release(&n_son->lk);
37.if (tid == TID_ERROR)
38. palloc_free_page (args);
39.if (n_son->tid==-1) {
40. tid=-1;
41. wait(tid);
42. }
43./*if (tid==TID_ERROR) {
44. list_remove(&n_son->elem);
45. palloc_free_page(n_son);
46. }*/
47.return tid;
48.}
涉及一些进程间同步的操作要多加注意。
此外补充一下thread_son类以及在thread类中的位置:
1.struct thread_son{
2. tid_t tid;
3.int status;
4.struct thread *add;
5.struct lock lk;
6.struct list_elem elem;
7.};
8.
9.struct thread
10. {
11./* Owned by thread.c. */
12. tid_t tid; /* Thread identifier. */
13.enum thread_status status; /* Thread state. */
14.char name[16]; /* Name (for debugging purposes). */
15. uint8_t *stack; /* Saved stack pointer. */
16.int priority; /* Priority. */
17.struct list_elem allelem; /* List element for all threads list. */
18.
19./* Shared between thread.c and synch.c. */
20.struct list_elem elem; /* List element. */
21.
22.#ifdef USERPROG
23./* Owned by userprog/process.c. */
24. uint32_t *pagedir; /* Page directory. */
25.struct thread_son *me;
26.struct list son;
27.bool release_lock;
28.struct file *exe_file;
29.#endif
30.
31./* Owned by thread.c. */
32. unsigned magic; /* Detects stack overflow. */
33. };
④int wait (tid_t pid) ;
等待子进程pid 并检索孩子的退出状态。
如果pid 仍然活动, 请等待, 直到它终止。
然后, 返回pid 传递退出的状态。
如果pid 没有调用exit (), 但被
内核终止(例如, 由于异常而被终止), 则等待(pid) 必须返回-1。
父进程等待在
父调用等待时已终止的子进程是完全合法的, 但内核仍然必须允许父进程检索
其子进程的退出状态, 或者知道子进程已被内核终止。
等待必须失败, 如果满足
以下任一条件, 则立即返回-1:
pid 是调用进程的直接子级, 如果并且只有当调用进程收到pid 作为成功调用exec 的返回值。
请注意, 子级不是遗传的: 如果生成子b 和b 生成子进
程c, 则a 不能等待c, 即使b 已死亡。
按进程a 等待(c) 的调用必须失
败。
同样, 如果孤立进程的父进程在退出之前退出, 则不会将其分配给新父进
程。
调用等待的过程已经称为"等待"。
也就是说, 一个过程最多可以等待任何给定的孩子一次。
进程可能会生成任意数量的孩子, 按任意顺序等待他们, 甚至可以在没有等待部分或全部孩子的情况下退出。
我们的设计应考虑等待可能发生的所有方
式。
进程的所有资源(包括其结构线程) 都必须释放, 无论其父进程是否等待它,
也无论子进程是在其父进程之前还是之后退出。
我们必须确保在初始进程退出之前, pintos 不会终止。
提供的pintos 代码试图通过从main () 调用process_wait() (在"‘userprog/process.c’" 中) 来实现
此目的。
我们建议您根据函数顶部的注释实现process_wait(), 然后按照
process_wait()的形式实现等待系统调用。
实现此系统调用所需的工作比其他任
何调用都要多。
1.int wait(tid_t pid){
2.struct thread *t=thread_current();
3.for (struct list_elem *e=list_begin(&t->son);e!=list_end(&t->son);e=list_next(e)){
4.struct thread_son *a=list_entry(e,struct thread_son,elem);
5.if (a->tid==pid){
6.if (a->status!=-2){
7.int tmp=a->status;
8. list_remove(&a->elem);
9. free(a);
10.return tmp;
11. }
12.while (a->status==-2){
13. thread_yield();
14. lock_acquire(&a->lk);
15. }
16.int tmp=a->status;
17. list_remove(&a->elem);
18. free(a);
19.return tmp;
20. }
21. }
22.return -1;
23.}
⑤bool create (const char *file, unsigned initial_size) ;
创建一个名为file的新文件, 最初的初始大小字节的大小。
如果成功, 则返回true, 否则返回false。
创建新文件不会打开它: 打开新文件是一个单独的操
作, 需要打开系统调用。
1.bool create (const char *file, unsigned initial_size){
2. lock_acquire(&filesys_lock);
3.bool ret=filesys_create(file,initial_size);
4. lock_release(&filesys_lock);
5.return ret;
6.}
⑥bool remove (const char *file) ;
删除名为file的文件。
如果成功, 则返回true, 否则返回false。
无论文件是打开还是关闭, 都可能被删除, 并且删除打开的文件不会将其关闭。
1.bool remove (const char *file){
2. lock_acquire(&filesys_lock);
3.bool ret=filesys_remove(file);
4. lock_release(&filesys_lock);
5.return ret;
6.}
⑦int open (const char *file) ;
打开名为file的文件。
返回一个非负整数句柄, 称为"文件描述符" (fd), 如果无法打开该文件, 则返回-1。
编号为0和1的文件描述符保留给控制台: fd 0
(STDIN_FILENO) 是标准输入, fd 1 (STDOUT_FILENO) 是标准输出。
开放系
统调用将永远不会返回这两个文件描述符中的任何一个, 这些描述符作为系统
调用参数有效, 仅如下所述。
每个进程都有一组独立的文件描述符。
子进程不
会继承文件描述符。
当一个文件多次打开时, 无论是通过单个进程还是通过不
同的进程, 每次打开都会返回一个新的文件描述符。
单个文件的不同文件描述
符在单独的调用中独立关闭以关闭, 并且它们不共享文件位置。
首先我们需要补充一下fd类以及他的相关操作:
1.static int fd_cnt=2;
2.struct file_d{
3.int fd;
4. tid_t onwer;
5.struct file *file;
6.struct list_elem elem;
7.};
8.static struct list fd_list;
9.
10.int alloc_fd(){
11. fd_cnt++;
12.return fd_cnt;
13.}
14.
15.struct file *fd_2_file(int fd){
16. tid_t tid=thread_current()->tid;
17.for (struct list_elem *e=list_begin(&fd_list);e!=list_end(&fd_list);e=list_next(e)){
18.struct file_d *cur=list_entry(e,struct file_d,elem);
19.if (cur->fd==fd && cur->onwer==tid) return cur->file;
20. }
21.return NULL;
22.}
23.
24.struct file_d *fd_2_file_d(int fd){
25. tid_t tid=thread_current()->tid;
26.for (struct list_elem *e=list_begin(&fd_list);e!=list_end(&fd_list);e=list_next(e)){
27.struct file_d *cur=list_entry(e,struct file_d,elem);
28.if (cur->fd==fd && cur->onwer==tid) return cur;
29. }
30.return NULL;
31.}
然后我们可以放心得实现open操作。
1.int open (const char *file){
2. lock_acquire(&filesys_lock);
3.struct file *n_f=filesys_open(file);
4. lock_release(&filesys_lock);
5.if (n_f==NULL) return -1;
6.struct file_d *n_file_d=calloc(1,sizeof(struct file_d));
7. n_file_d->fd=alloc_fd();
8. n_file_d->file=n_f;
9. n_file_d->onwer=thread_current()->tid;
10. list_push_back(&fd_list,&n_file_d->elem);
11.return n_file_d->fd;
12.}
⑧int filesize (int fd) ;
返回以fd 形式打开的文件的大小(以字节为单位)。
1.int filesize(int fd){
2.struct file *f=fd_2_file(fd);
3.if (f==NULL) {
4. exit(-1);
5. }
6.return (file_length(f));
7.}
⑨int read (int fd, void *buffer, unsigned size) ;
读取文件中打开的文件中的字节大小, 并将其转换为缓冲区。
返回实际读取的字节数(文件末尾为0), 如果无法读取文件, 则返回-1 (由于文件末尾以外
的条件)。
fd 0 使用input_getc()从键盘读取。
1.int read (int fd, void *buffer, unsigned size){
2.if (fd==0){
3.for (int i=0;i<size;i++) ((char *)buffer)[i]=input_getc();
4.return size;
5. }
6.struct file *f=fd_2_file(fd);
7.if (f==NULL) {
8. exit(-1);
9. }
10.return file_read(f,buffer,size);
11.}
⑩int write (int fd, const void *buffer, unsigned size) ;
将size字节从缓冲区写入打开的文件fd。
返回实际写入的字节数, 如果无法写入某些字节, 则该字节数可能小于大小。
编写过去的文件结尾通常会扩展
文件, 但文件的增长不是由基本文件系统实现的。
预期的行为是将尽可能多的
字节写入文件结束, 并返回写入的实际数字, 如果根本不能写入字节, 则为0。
fd 1 写入控制台。
要写入控制台的代码应在一次调用putbuf () 时写入所有缓
冲区, 至少只要大小不大于几百个字节。
(拆分较大的缓冲区是合理的。
否则,
不同进程的文本输出行最终可能会在控制台上交错, 从而混淆人类读者和我们
的分级脚本。
)
1.int write (int fd, const void *buffer, unsigned size){
2.if (fd==1){
3. putbuf(buffer,size);
4.return size;
5. }
6.struct file *f=fd_2_file(fd);
7.if (f==NULL) {
8. exit(-1);
9. }
10.return file_write(f,buffer,size);
11.}
⑪void seek (int fd, unsigned position) ;
将要在打开的文件fd 中读取或写入的下一个字节更改为位置, 以文件开头的字节表示。
(因此, 位置为0是文件的开始。
超过文件当前端的查找不是错
误。
更高版本读取获取0个字节, 指示文件的结尾。
以后的写入扩展了文件, 用
零填充任何未写入的空白。
)但是, 在pintos 文件中, 在项目4完成之前, 文件
的长度是固定的, 因此写入文件的过去将返回错误。
这些语义在文件系统中实
现, 不需要在系统调用实现中进行任何特殊的工作。
1.void seek (int fd, unsigned position){
2.struct file *f=fd_2_file(fd);
3.if (f==NULL) {
4. exit(-1);
5. }
6. file_seek(f,position);
7.}
⑫unsigned tell (int fd) ;
返回要在打开的文件fd 中读取或写入的下一个字节的位置, 以文件开头的字节表示。
1.unsigned tell (int fd){
2.struct file *f=fd_2_file(fd);
3.if (f==NULL) {
4. exit(-1);
5. }
6.return file_tell(f);
7.}
⑬void close (int fd) ;
关闭文件描述符fd。
退出或终止进程会隐式地关闭其所有打开的文件描述符, 就像通过为每个描述符调用此函数一样。
1.void close (int fd){
2.struct file_d *f_d=fd_2_file_d(fd);
3.if (f_d==NULL) {
4. exit(-1);
5. }
6. file_close(f_d->file);
7. list_remove(&f_d->elem);
8. free(f_d);
9.}
(4)运行文件禁止写操作
这一步格外简单,只需要在load()中插入一行代码即可:
1.file_deny_write(file);
当然,也要记得在process_exit()中关闭文件:
1.file_close(cur->exe_file);
至此我们pintos第二个project:User Program 已全部完成,make check返回的结果:
(这里说明一下,因为我使用的是斯坦福最新的pintos-anon,USERPROG的测试点为80个,与老师提供的版本略有差异(还更加严格一些…))
六、附录
Pintos下载地址:/。