project2是在src/userprog中进行代码修改,主要分为参数分离和系统调用者两大任务。

任务一 参数传递

用到的相干目录:pintos/src/userprog/process.c thread.h synch.h

在这个任务中,我们主要修改"process.c"和处理字符串。同时,为了测试我们的算法在此任务中的正确性,我们还必须在'syscall.c'中实现其他功能

预期pass:aggs-xxxx

具体修改如下:

  • 修改<process.c> :

    • 修改函数process_execute ()

    • 修改函数 start_process ()

    • 添加函数push_argument(void **esp,int argc, int argv[])

    • 修改函数process_wait ()

  • 修改<syscall.c>

    • 添加函数sys_write(int fd, const void *buffer, unsigned size, int* ret)

process_execute()

把传入的file_name用strtok_r()函数分离字符串,获得当前的线程名,为实现参数传递做准备

fn_copy2 = strtok_r (fn_copy2, " ", &save_ptr);

以此为线程名创建一个新线程,然后新线程转去执行start_process函数

tid = thread_create (fn_copy2, PRI_DEFAULT, start_process, fn_copy);

保证父进程与子进程的返回顺序,用同步操纵使得父进程在子进程之后返回。

sema_down(&thread_current()->sema);//降低父进程的信号量,等待子进程结束

通过线程的success变量值判断线程是否运行成功,如果子进程加载可执行文件的过程没有问题,就返回新建线程的tid;如果子进程加载可执行文件失败报错,则返回TID_ERROR。

  if (!thread_current()->success) return TID_ERROR; return tid;

start_process()

原本的函数中,如果线程加载不成功,则直接执行进程退出函数。

修改后:

①做与process_execute()相同的操作,先把传入的file_name用strtok_r()函数分离字符串,获得当前的线程名

file_name = strtok_r (file_name, " ", &save_ptr);

②如果调用load成功:将命令行输入的参数分离后得到的数组(调用push_argument()函数)

for (token = strtok_r (fn_copy, " ", &save_ptr); token != NULL; token = strtok_r (NULL, " ", &save_ptr)){if_.esp -= (strlen(token)+1);memcpy (if_.esp, token, strlen(token)+1);//栈指针退后来存放tokenargv[argc++] = (int) if_.esp;//argv数组的末尾存放栈顶地址}push_argument (&if_.esp, argc, argv);//新加函数thread_current ()->parent->success = true;sema_up (&thread_current ()->parent->sema);

③如果调用不成功,那么久保存父进程的执行状态(失败),提升信号量并退出

thread_current ()->parent->success = false;
sema_up (&thread_current ()->parent->sema);
thread_exit ();

process_argument()

这是新添加的函数,为了实现将start_process()中命令行的参数分离,将得到的数组压入栈中,具体代码如下

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;
}

process_wait()

获得当前父进程的子进程,遍历查询每个子进程,记录并判断退出状态。如果子进程已经结束,则减少子进程信号量以唤醒父进程,从子进程列表中删除该子进程并返回退出状态。下面是该函数内的遍历子进程循环。

  while (temp != list_end (l)){temp2 = list_entry (temp, struct child, child_elem);if (temp2->tid == child_tid){if (!temp2->isrun)//{temp2->isrun = true;sema_down (&temp2->sema);break;} else {return -1;//子进程还在运行,没有退出,则返回-1}}temp = list_next (temp);}

sys_write()

写入情况两种:

①往缓冲区写入,调用putbuf函数完成写入。

②往文件中写入调用find_file_id获取要写入的文件的标识符,调用file_write往文件写入数据。

任务二: 参数传递进程控制系统调用

涉及到的文件目录 :pintos/src/userprog/syscall.h syscall.c

pintos/src/threads/thread.h

预期pass:halt exec-xxxx multi-xxx rox-xxxx

具体修改:

数据结构修改

  • <thread.h>

    • 创建新的一个结构 child

      struct child{tid_t tid; /* 标识*/bool isrun;/* 运行是否成功 */struct list_elem child_elem; /* 子进程 */struct semaphore sema;  /* 控制等待的信号量 */int store_exit;/* 子线程的退出状态 */};
    • thread添加新的属性

      struct list childs;  /* 子进程 */
      struct child * thread_child;
      int st_exit;         /* 退出状态 */
      struct semaphore sema; /* 实现父进程等待子进程 */
      bool success;  /* 子进程是否运行成功 */
      struct thread* parent;  /* 父线程*/
  • <syscall.c>  添加syscalls 结构,有如下属性

struct list files;                  /* 打开的文件列表 */
int file_fd;                        /* 文件描述符 */
struct file * file_owned;           /* 打开的文件*/

函数修改

  • <syscall.c>

    • 添加函数get_user (const uint8_t *uaddr)

    • 修改函数syscall_handler ()

    • 修改函数syscall_init ()

    • 添加函数sys_halt (intr_frame* f), sys_exit (intr_frame* f), sys_exec (intr_frame* f), sys_write(intr_frame* f), int sys_wait(intr_frame* f);

syscall_handler()

功能:对于任务二,只需将1添加到其第一个参数,并打印其结果。弹出用户栈参数,将这一类去除,在按照这个类型去查找syscall_init中定义的syscalls数组,找到对应的系统调用,在执行系统调用之前检查地址是否指向有效地址,之后并执行它。如果地址无效,那么我们需要释放内存页,并在退出之前释放该进程中的所有锁或信号量。

static void
syscall_handler (struct intr_frame *f UNUSED)
{int * p = f->esp;check_ptr2 (p + 1);//检查有效性int type = * (int *)f->esp;//记录在栈顶的系统调用类型typeif(type <= 0 || type >= max_syscall){exit_special ();//类型错误,退出}syscalls[type](f);//类型正确,查找数组调用对应系统调用并调用执行
}

syscall_init ()

功能:初始化系统调用,通过syscall数组来存储13个系统调用,syscall_handler里通过识别数组的序号决定调用哪一个系统调用。

  syscalls[SYS_HALT] = &sys_halt;syscalls[SYS_EXIT] = &sys_exit;syscalls[SYS_EXEC] = &sys_exec;syscalls[SYS_WAIT] = &sys_wait;syscalls[SYS_CREATE] = &sys_create;syscalls[SYS_REMOVE] = &sys_remove;syscalls[SYS_OPEN] = &sys_open;syscalls[SYS_WRITE] = &sys_write;syscalls[SYS_SEEK] = &sys_seek;syscalls[SYS_TELL] = &sys_tell;syscalls[SYS_CLOSE] =&sys_close;syscalls[SYS_READ] = &sys_read;syscalls[SYS_FILESIZE] = &sys_filesize;

sys_halt()

功能:调用shutdown_power_off让pintos关机。

void
sys_halt (struct intr_frame* f)
{shutdown_power_off();
}

sys_exit()

功能:结束当前的用户程序,并返回状态给内核kernel.

sys_exec ()

功能:检查由file_name指向的文件是否有效(调用check_ptr2)。若有效,则调用process_execute来去执行它。

void
sys_exec (struct intr_frame* f)
{uint32_t *user_ptr = f->esp;check_ptr2 (user_ptr + 1);check_ptr2 (*(user_ptr + 1));*user_ptr++;f->eax = process_execute((char*)* user_ptr);
}

sys_wait()

功能:检查传入参数f是否有效。若有效则调用process_wait来完成系统调用,等待一个子进程的结束。

同步说明

在进程的执行过程中,execute()函数将返回-1,如果流程失败,则无法返回。为了解决这个问题,将'success'添加到结构'thread',以记录线程是否成功执行。此外,使用'parent'获取其父线程,并根据加载结果设置其状态。创建子进程时,它将关闭'sema'以阻止父进程。当子进程结束时,就会唤醒父进程。

在等待进程的过程中通过信号量实现等待进程),当父进程需要等待子进程时,调用'sema_down'来阻止父进程。

任务三 文件操作系统调用

涉及到的文件:process.c thread.h synch.h 预期pass:sc-xxx create-xxx open-xxx close-xxx read-xxx write-xxx bad-xxx lg-xxx sm-xxx syn-xxx multi-xxx

这里详细介绍如何实现剩余10个系统调用。实际上,系统调用操作的所有关键部分都由filesys/filesys.c提供。但是project2并不需要修改filesys目录。所以任务三中重要的方面是正确弹出和获取栈中的数据,并注意不要同时进行写操作。

数据结构修改

<thread.h>

  • thread添加新属性 与上述syscall.c相同

struct list files; /* 打开的文件列表  */
int file_fd; /*文件描述符 */
struct file * file_owned; /* 打开的文件 */

<syscall.h>

  • 创建新数据结构thread_file

struct thread_file{int fd;               /*  文件描述数字   */struct file* file;    /* 文件指针  */struct list_elem file_elem;
};

具体函数修改

  • <syscall.c>

    • 修改函数syscall_handler (struct intr_frame *)

    • 修改函数syscall_init (void)

      将初始化'syscalls',以存储此任务中的syscalls函数。

    • 增加如下函数

      void sys_create(struct intr_frame* f);   //创建文件
      void sys_remove(struct intr_frame* f);  //删除文件
      void sys_open(struct intr_frame* f);    //打开文件
      void sys_wait(struct intr_frame* f);     //等待打开
      void sys_filesize(struct intr_frame* f); //文件大小
      void sys_read(struct intr_frame* f);  //读文件`
      void sys_write(struct intr_frame* f);//printf和写文件
      void sys_seek(struct intr_frame* f);  //移动文件指针`
      void sys_tell(struct intr_frame* f); //文件指针位置
      void sys_close(struct intr_frame* f); //关闭文件
    • 增加函数is_valid_pointer(void* esp,uint8_t argc)

      验证esp是否是一个虚拟地址并且是否已经加载到当前页表

    • 增加函数 find_file_id(int file_id)

      得到文件的标识符

    • 增加函数 check_ptr2(const void *vaddr)

      检查每个系统调用是否存在内存无效、页面无效和页面内容错误等错误。

  • <thread.c>

    • 添加全局文件锁,保证线程安全

    static struct lock c;//执行文件操作时用锁来锁定线程
    • 添加文件锁定功能

    void acquire_lock_f(){lock_acquire(&lock_f);
    }
    void release_lock_f(){lock_release(&lock_f);
    }

在这个任务中,我们需要实现另外9个与文件操作系统调用相关的系统调用:创建create、删除remove、打开open、文件大小filesize、读取read、写入write、查找seek、通知tell 和关闭close。当用户程序运行时,我们必须确保没有人可以修改磁盘上的可执行文件。对于这个任务,我们使用一个全局锁来确保文件syscalls是线程安全的。当调用每个系统调用时,它必须获取锁,然后释放锁。

此外,Pintos的文件系统不是线程安全的,文件操作syscalls不能同时调用多个文件系统函数。为了确保这一点,我们在'thread'中添加了一个新变量'int file_fd',以使文件描述符大于'STDIN_FILENO'和'STDOUT_FILENO','file_u owned'用于保持线程打开的文件。

当系统调用出现时,由syscall_handler()函数来判断进程如何继续执行。当用户程序使用 lib/user/syscall.c执行系统操作时,所有参数都被压入栈中。所以,程序只需要从栈中取出参数。从栈中弹出系统调用编号由栈指针esp来实现。

10个文件操作系统调用解释

sys_create()

调用check_ptr2()检查文件地址是否有效,通过acquire_lock_f()获得锁,调用filesys_create()创建文件,完成后通过release_lock_f()释放锁。锁定过程将在其他文件系统操作中使用。

sys_remove()

检查当前指针是否有效(调用check_ptr2),通过acquire_lock_f()获得锁,调用filesys_remove() 删除当前文件,释放锁

sys_open()

检查当前指针是否有效(调用check_ptr2),调用filesys_open()打开当前文件,将数据结构为thread_file的文件push到线程打开的文件列表中。关键代码如下:

  acquire_lock_f ();//获得锁struct file * file_opened = filesys_open((const char *)*user_ptr);//打开文件release_lock_f ();//释放锁struct thread * t = thread_current();if (file_opened){struct thread_file *thread_file_temp = malloc(sizeof(struct thread_file));thread_file_temp->fd = t->file_fd++;thread_file_temp->file = file_opened;list_push_back (&t->files, &thread_file_temp->file_elem);//push到线程打开的文件列表中f->eax = thread_file_temp->fd;} else{f->eax = -1;}

sys_wait()

等待,调用process_wait()函数

sys_filesize()

调用file_length()来得到文件长度

sys_read()

调用is_valid_pointer()来验证地址的正确性。

fd=0,则使用input_getc()从键盘中获取数据,送入缓冲区

fd=1,则表示读文件,调用file_read()来实现。

sys_write()

设置一个变量 temp2 ,判断是往缓冲区写入还是文件中写入。

temp2=1,则表示写入到缓冲区中,调用putbuf()函数实现。

temp2不为1,则表示写入到文件中去。具体实现步骤,通过当前线程id,调用find_file_id()找到文件,然后通过调用函数file_write()来实现

sys_seek()

通过线程id,调用find_file_id()找到文件,在调用 file_seek()来移动文件指针。

sys_tell()

调用file_tell ()哈数实现

sys_close()

调用file_close()来实现,最后还要删除线程列表中的文件并释放文件

同步说明

①所有文件操作都要先通过acquire_lock_f()获得锁,执行文件操作之后,通过release_lock_f()释放锁。

②文件操作都受到全局文件系统锁的保护,全局文件系统锁可以同时防止同一个fd上的I/O。

③当使用thread_current()->parent->children_list或者thread_current()->opened_files禁止中断,以防止不必要的错误产生

实验收获与心得

1)对专业知识基本概念、基本理论和典型方法的理解

本次pintos实验,我完成了project1和project2。

project1实验难度较大,尤其是优先级捐赠这一部分,一开始的虚拟机配置环境就走了许多弯路,但是一整个实验做下来也让我对操作系统有了更深入的理解。实验一的线程(一开始以为看上去pintos并不支持多线程,这里的进程直接充当调度单位了,可能是因为核心态线程由内核直接管理)中虽然没有实用的进程在运行,但是让我看到了线程在操作系统中的调度方式,让我看到了同步的重要用途,让我看到了操作系统为了提高运行效率的一些手段。

project2主要让我们实现参数分离和系统调用这两大任务。参数分离需要对pintos的栈机制有一定的了解:用户空间是从高向低生长的,因此在参数压栈时要遵循“反压”的规则。

2)怎么建立模型

pintos这个项目让我们脱离了书本上刻板的知识,通过实践更好地理解OS这门课中的许多概念。比如项目中涉及到“优先级调度”、“信号量”、“锁”、“用户栈”、“中断”等等知识点,它们在pintos中串成了一个整体出现,这些概念都以看得见、摸得着的c代码的形式展现于眼前,是一个理解OS非常好的途径。

3)如何利用基本原理解决复杂工程问题

进程的基本状态反映了进程执行过程的变化。包括就绪状态、执行状态、阻塞状态、终止状态,分别对应了thread状态中的thread_ready、thread_running、thread_block、thread_dying。贯穿进程调度的整个过程,是进程调度的基础。

CPU调度算法-BSD,较好平衡了现场的不同需求,其中priority的根据recent_cpu、nice解决。其中recent_cpu是线程最近使用的CPU时间的估计值。近期recent_cpu越大优先级越低。

4)具有实验方案设计的能力

首先要分析问题,方案设计过程中要考虑实际数据要求,需要有创新精神与实践精神。

5)如何对环境和社会的可持续发展

学习操作系统,可以提高CPU运行的效率,缩短完成任务的时间和成本,在一定程度上可以减少建造的材料费(处理机数量减少),节约成本,对社会和环境具有可持续发展。

结果: all pass 代码可以私我

Pintos project2 实验报告相关推荐

  1. Stanford cs140 Pintos Project2实验目标

    0. 前言 本实验出自斯坦福大学cs140课程,实验环境为Pintos+Bochs,鉴于其文档为全英文,故为了方便读者及自己尝试,整理翻译了其中实验2的主要任务.也加入了自己的理解,方便读者理解实验内 ...

  2. 操作系统课程设计pintos project1实验摘记

    前言: 本篇意在记录本学期结束的操作系统课程设计pintos project1实验报告和实现过程.整个实验参考了多篇文章也查阅了一些代码,其中部分内容或与其他文章相同,还请见谅.同时,也为了测试CSD ...

  3. Pintos Project1:Thread 实验报告

    Pintos Project1:Thread 实验报告 Pintos Project1:Thread 实验报告 一.Pintos简介 二.Ubuntu下安装Pintos 1.安装bochs 2.安装P ...

  4. 2019春第二次课程设计实验报告

    2019春第二次课程设计实验报告 一.实验项目名称: 贪吃蛇游戏编写: 二.实验项目功能描述: 这个实验主要是实现游戏的正常运行,实现的目标是对小蛇移动的控制, 同时对小蛇数据的保存,如何实现转弯的效 ...

  5. JAVA第二次验证设计性实验报告

    [实验任务一]:素数输出 (3)实验报告中要求包括程序设计思想.程序流程图.源代码.运行结果截图.编译错误分析等内容. 1.   实验内容 (1)计算并输出3~100之间的素数. (2)编程满足下列要 ...

  6. 计算机网络实验报告建立校园网,计算机网络实验报告

    设计性实验报告 一.实验目的 通过对网络设备的连通和对拓扑的分析,加深对常见典型局域网拓扑的理解:通过路由建立起网络之间的连接,熟悉交换机.路由器的基本操作命令,了解网络路由的设计与配置. 二.背景描 ...

  7. c语言链表最高响应比优先,操作系统--最高响应比优先调度算法实验报告..doc

    操作系统--最高响应比优先调度算法实验报告. 进程调度一.实验题目与要求 编写程序完成批处理系统中的作业调度,要求采用响应比高者优先的作业调度算法.实现具体包括:首先确定作业控制块的内容和组成方式:然 ...

  8. 计算机网络实验可变长子网掩码,计算机网络实验3-子网掩码与划分子网实验报告.docx...

    PAGE PAGE # / 5 上机实验报告三 -.实验目的 (1 )掌握子网掩码的算法. 了解网关的作用. 熟悉模拟软件 packet tracer5.3的使用. 二.实验内容 1.( 1) 172 ...

  9. 实验报告Linux操作系统基本命令,linux操作系统实验报告全部.doc

    linux操作系统实验报告全部 计算机操作系统 实验报告 学 号:姓 名:提交日期:2014.12.15成 绩: 东北大学秦皇岛分校 [实验题目]熟悉Linux/UNIX操作系统[实验目的]1.熟悉L ...

最新文章

  1. python面向对象重新梳理
  2. 如何在柱状图中点连线_练瑜伽,如何放松僵硬紧张的髂腰肌?
  3. string转date
  4. 3种关闭linux系统端口方法
  5. AGC001 补题小结
  6. C++ vector类详解
  7. 转: Linux Grep 命令说明
  8. 苹果CMSV10绿色毛毛虫主题模板
  9. VScode配置java开发环境
  10. maven服务器项目,Maven项目搭建
  11. 华北计算机系统工程研究所录取名单,2018年华北计算机系统工程研究所接收推免研究生复试录取通知...
  12. 【小知识】二分类问题,应该选择sigmoid还是softmax?
  13. axios 取消请求_封装 axios 取消重复请求
  14. lightoj1213推公式
  15. 光缆成端接头的含义是指
  16. 使用ORL人脸库,通过GRNN网络和HOG特征提取的人脸识别算法matlab仿真
  17. 一文搞懂DTFT,DFT,FFT
  18. 打游戏用什么蓝牙耳机好?英雄联盟手游推荐蓝牙耳机
  19. 文件过大 不能导入U盘怎么办
  20. 设计模式六大原则(SOLID)

热门文章

  1. 实验吧 - 天下武功唯快不破
  2. Unreal Property System (Reflection) 虚幻属性系统(反射)
  3. LeetCode7.10 股票问题汇总 贪心,动态规划,排序
  4. leetcode1229. 安排会议日程
  5. HTML Tab选项卡
  6. 【TwinCAT3】安装注意事项记录
  7. 软件设计师每日一练真题笔记
  8. shutdown、halt、poweroff、reboot的区别
  9. BZOJ4892: [Tjoi2017]dna
  10. html网页标签用法