Pintos project2 实验报告
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 实验报告相关推荐
- Stanford cs140 Pintos Project2实验目标
0. 前言 本实验出自斯坦福大学cs140课程,实验环境为Pintos+Bochs,鉴于其文档为全英文,故为了方便读者及自己尝试,整理翻译了其中实验2的主要任务.也加入了自己的理解,方便读者理解实验内 ...
- 操作系统课程设计pintos project1实验摘记
前言: 本篇意在记录本学期结束的操作系统课程设计pintos project1实验报告和实现过程.整个实验参考了多篇文章也查阅了一些代码,其中部分内容或与其他文章相同,还请见谅.同时,也为了测试CSD ...
- Pintos Project1:Thread 实验报告
Pintos Project1:Thread 实验报告 Pintos Project1:Thread 实验报告 一.Pintos简介 二.Ubuntu下安装Pintos 1.安装bochs 2.安装P ...
- 2019春第二次课程设计实验报告
2019春第二次课程设计实验报告 一.实验项目名称: 贪吃蛇游戏编写: 二.实验项目功能描述: 这个实验主要是实现游戏的正常运行,实现的目标是对小蛇移动的控制, 同时对小蛇数据的保存,如何实现转弯的效 ...
- JAVA第二次验证设计性实验报告
[实验任务一]:素数输出 (3)实验报告中要求包括程序设计思想.程序流程图.源代码.运行结果截图.编译错误分析等内容. 1. 实验内容 (1)计算并输出3~100之间的素数. (2)编程满足下列要 ...
- 计算机网络实验报告建立校园网,计算机网络实验报告
设计性实验报告 一.实验目的 通过对网络设备的连通和对拓扑的分析,加深对常见典型局域网拓扑的理解:通过路由建立起网络之间的连接,熟悉交换机.路由器的基本操作命令,了解网络路由的设计与配置. 二.背景描 ...
- c语言链表最高响应比优先,操作系统--最高响应比优先调度算法实验报告..doc
操作系统--最高响应比优先调度算法实验报告. 进程调度一.实验题目与要求 编写程序完成批处理系统中的作业调度,要求采用响应比高者优先的作业调度算法.实现具体包括:首先确定作业控制块的内容和组成方式:然 ...
- 计算机网络实验可变长子网掩码,计算机网络实验3-子网掩码与划分子网实验报告.docx...
PAGE PAGE # / 5 上机实验报告三 -.实验目的 (1 )掌握子网掩码的算法. 了解网关的作用. 熟悉模拟软件 packet tracer5.3的使用. 二.实验内容 1.( 1) 172 ...
- 实验报告Linux操作系统基本命令,linux操作系统实验报告全部.doc
linux操作系统实验报告全部 计算机操作系统 实验报告 学 号:姓 名:提交日期:2014.12.15成 绩: 东北大学秦皇岛分校 [实验题目]熟悉Linux/UNIX操作系统[实验目的]1.熟悉L ...
最新文章
- python面向对象重新梳理
- 如何在柱状图中点连线_练瑜伽,如何放松僵硬紧张的髂腰肌?
- string转date
- 3种关闭linux系统端口方法
- AGC001 补题小结
- C++ vector类详解
- 转: Linux Grep 命令说明
- 苹果CMSV10绿色毛毛虫主题模板
- VScode配置java开发环境
- maven服务器项目,Maven项目搭建
- 华北计算机系统工程研究所录取名单,2018年华北计算机系统工程研究所接收推免研究生复试录取通知...
- 【小知识】二分类问题,应该选择sigmoid还是softmax?
- axios 取消请求_封装 axios 取消重复请求
- lightoj1213推公式
- 光缆成端接头的含义是指
- 使用ORL人脸库,通过GRNN网络和HOG特征提取的人脸识别算法matlab仿真
- 一文搞懂DTFT,DFT,FFT
- 打游戏用什么蓝牙耳机好?英雄联盟手游推荐蓝牙耳机
- 文件过大 不能导入U盘怎么办
- 设计模式六大原则(SOLID)