OS-lab6
OS-lab6
管道
在lab5的时候我们实现了文件类设备的读写操作,而在fd.c中,我们定义了3种设备:文件类设备、管道、终端,其中终端已经被完成了,剩下的就是管道了。

管道是一种父子进程间通信的设备,这样的通信是单向的。建立一个匿名管道的流程首先是建立一个缓冲文件,接着
user/pipe.c
首先我们来看看控制管道的结构体
Pipe。这个结构体很简单,由读取端位置、写入端位置、缓冲区组成,在这个实验中采用的是环形缓冲区。接着我们来创建一个管道。
pipe函数用于创建一个管道。在 pipe 中,首先分配两个文件描述符 fd0,fd1 并为其分配空间,然后给 fd0 对应的虚拟地址分配一页物理内存,再将 fd1 对应的虚拟地址映射到相同的物理内存。这一页上存的就是Pipe结构体,从而使得这两个文件描述符能够共享一个管道的数据缓冲区。
这个时候创建的管道结构是这样的

之后则需要根据具体情况关闭读端或写端,形成第一张图那样的单向流通的结构。
接下来我们开始完成管道的读写。
完成读写的功能思路很简单,也就是基本的生产者消费者模型。所以如果管道数据为空,即当
p_rpos >= p_wpos时,应该进程切换到写者运行;写者必须得在p_wpos - p_rpos < BY2PIPE时方可运行,也就是通过读写端的相对位置进行切换。
piperead函数先通过fd2data取得Pipe结构体,判断缓冲区是否为空,若为空则判断管道是否关闭,如果没关闭就进行调度;接着循环读取缓冲区,遇到缓冲区为空则需要判断管道是否关闭和进行调度,直到读取n字节。
pipewrite函数与piperead函数类似,不过这个函数每次必须写满缓冲区或写满n字节才会进行调度或退出。主体功能完成了,还有一些细节问题需要我们仔细思考,那就是读写结束应该如何判断,也就是判断管道另一端是否关闭。
假设这样的情景:管道写端已经全部关闭,读者读到缓冲区有效数据的末尾,此时有 p_rpos = p_wpos。按照上面的做法,我们这里应当切换到写者运行。但写者进程已经结束,进程切换就造成了死循环,这时候读者进程如何知道应当退出了呢?
在这里,我们利用页面的引用来完成这个功能。
第一张图上我们可以看到,管道文件所在的物理页被写端和读端同时引用,也就是说有这样的等式成立pageref(rfd) + pageref(wfd) = pageref(pipe)。对于每一个匿名管道而言,我们分配了三页空间:一页是读数据的文件描述符 rfd,一页是写数据的文件描述符 wfd,剩下一页是被两个文件描述符共享的管道数据缓冲区。
而如果写端关闭,则
pageref(wfd)=0,此时满足pageref(rfd)=pageref(pipe),因此可以使用这样的机制来判断某一端是否关闭。这个问题看上去确实解决了,不过还留有隐患。之前提到了进程需要关闭读端或写端才能进行通信,在这里调用了
close函数。int close(int fdnum)
{
int r;
struct Dev *dev;
struct Fd *fd;
if ((r = fd_lookup(fdnum, &fd)) < 0
|| (r = dev_lookup(fd->fd_dev_id, &dev)) < 0) {
return r;
}
r = (*dev->dev_close)(fd);
fd_close(fd);
return r;
}
可以看到这个函数分为两步,首先是移除设备到
fd的引用,然后是移除进程对fd的引用。就有可能出现这种情况:pipe(p);
if(fork() == 0){//子进程
close(p[1]);
//中断1发生在close和read之间
read(p[0], buf, sizeof buf);
}else{//父进程
close(p[0]);//中断2发生在dev_close和fd_close之间
write(p[1], "zzz", 3);
}
这个时候,父子进程都对
p[0]有引用,引用量为2,父进程对p[1]有引用,引用量为1,而pipe被父进程的p[1]和子进程的p[0]引用,引用量是2,这时子进程再执行read,比较pipe的引用和p[0]的引用发现一样,认为p[1]关闭,因此就退出了。不幸的是,fd.c中的
dup也出了类似的问题。在dup函数中发生了两次映射,首先是把oldfd映射到newfd中,然后再把oldfd的内容映射过去。比如dup(p[0], 10)这样的调用,会首先增加p[0]的引用,然后再增加pipe的引用,如果中断在这中间产生,那么pipe的引用是没有增加的,这时就出现了之前一样的问题,pipe的引用可能与p[0]或p[1]相等,造成误判。这两个函数最大的问题在于,建立映射和移除映射的顺序导致了
pipe的引用不能保证一直最大。在知道了这个问题的根源后,进行修复的操作也很容易了,就是调整顺序,保证pipe的引用最大即可。那现在就万无一失了吗?先别急,我们来看看判断管道关闭的函数
_pipeisclosed。根据我们的等式,当
p[0]或p[1]的引用与pipe相等时就判断为一端已经关闭,显然,我们需要先获得这两者的引用才行。pfd = pageref(fd);
pfp = pageref(pipe); if (pfd == pfp){
return 1;
}
return 0;
很显然,我们会想到这样写。
还是上面的例子,假如子进程执行完
close(p[1])后没有中断1,而是继续执行read,此时因为父进程未运行,缓冲区为空,就进入了_pipeisclosed函数判断是否一端已经关闭,在获得了pfd的引用量2后,中断1发生;父进程执行到wrtie中途中断2发生,这个时候已经关闭了p[0];子进程执行,获得的pipe的引用量为2,两者相等,发生误判。这个例子我们可以知道,我们获得的引用量也可能是错误的,原因在于我们没有得到最新的引用量,从而引起了误判。因此,我们需要一个循环来获得最新的引用,不过怎么知道循环可以退出呢?那就是得到的最新的引用,怎么知道这是最新的引用呢?那就是在执行过程中没有中断发生,怎么知道没有中断发生?在lab3中我们知道在时钟中断发生时,会进入调度函数
sched_yeild进行调度,在调度的最后会进入env_run切换运行,在这个函数中我们会增加env_runs代表运行的次数。也就是说,这个变量能够指示中断的发生。因此,我们在取得引用之前,先记录一下当时的env_runs,在取得引用之后再判断现在的env_runs是否一样,如果不一样则证明发生了中断,需要再次获得引用,一样则证明没有中断发生,可以退出循环。到这里,我们终于完成管道退出的判断函数
_pipeisclosed了。整个管道的读写我们就完成了。
shell
最后,我们还需要完成一个用户的交互界面,也就是shell。具体来说,我们需要能够读取用户输入的命令,并通过操作系统执行这些命令。那么命令在操作系统中是什么?在user文件夹下有ls.c、echo.c等几个文件,这几个文件就是命令的源码,最终会形成可执行文件,我们调用的命令,实际上就是运行这些可执行文件。而在此之前,我们还需要一个进程来解析这些命令,这就是sh.c。
user/sh.c
这就是shell进程。
_gettoken函数用于获得标识。
runcmd函数根据获得的标识运行指令。
readline函数用于读取一行指令。注意到在
runcmd中,在真正执行指令的时候实际是调用了spawn函数完成,这个函数就是具体的加载并运行一个命令文件的函数。user/spawn.c
这个文件完成了加载命令文件并运行的功能。
init_stack函数用于初始化栈。
usr_is_elf_format函数用于判断文件是否为可执行文件。
usr_load_elf函数用于将可执行文件载入到内存中。我们在lab3中完成了这个任务,即load_icode_mapper函数,基本上就是照抄一遍。那为什么不能直接调用那个函数呢?首先,那个函数处于内核态,没有系统调用可以访问,此外,我们在lab3中最终是在init.c中利用ENV_CREATE手动创建进程,而对于输入不确定的shell来说,这显然做不到。
spawn函数用于载入命令文件。首先通过open打开文件,得到文件描述符;接着判断这个文件是否是可执行文件;接着使用syscall_env_alloc获得一个空闲的进程;通过init_stack初始化栈,接着利用usr_load_elf载入可执行文件,这里需要知道elf文件的格式,找到并载入所有的程序段;设置pc值和栈帧等;复制获得父进程的共享页表;最后设置进程状态。这个函数中出现的子进程也就是当前的shell进程的子进程,就是命令文件执行的进程。
到这里,我们就能够通过命令行与操作系统进行交互了。
OS大厦,完成!
OS-lab6的更多相关文章
- ucore操作系统学习(六) ucore lab6线程调度器
1. ucore lab6介绍 ucore在lab5中实现了较为完整的进程/线程机制,能够创建和管理位于内核态或用户态的多个线程,让不同的线程通过上下文切换并发的执行,最大化利用CPU硬件资源.uco ...
- [OS] 操作系统课程(三)
工具 源码阅读:understand 源码文档自动生成工具:Doxygen 编译环境:gcc 运行环境:x86机器或QEMU 调试工具:QEMU+(GDB or IDE) IDE:Eclipse-CD ...
- NodeJs之OS
OS Node.js提供了一些基本的底层操作系统的模块OS. API var os = require('os'); console.log('[arch] 操作系统CPU架构'+os.arch()) ...
- Node.js:OS模块
os模块,可以用来获取操作系统相关的信息和机器物理信息,例如操作系统平台,内核,cpu架构,内存,cpu,网卡等信息. 使用如下所示: const os = require('os'); var de ...
- Xamarin+Prism开发详解四:简单Mac OS 虚拟机安装方法与Visual Studio for Mac 初体验
Mac OS 虚拟机安装方法 最近把自己的电脑升级了一下SSD固态硬盘,总算是有容量安装Mac 虚拟机了!经过心碎的安装探索,尝试了国内外的各种安装方法,最后在youtube上找到了一个好方法. 简单 ...
- Mac OS 使用 Vagrant 管理虚拟机(VirtualBox)
Vagrant(官网.github)是一款构建虚拟开发环境的工具,支持 Window,Linux,Mac OS,Vagrant 中的 Boxes 概念类似于 Docker(实质是不同的),你可以把它看 ...
- Mac OS、Ubuntu 安装及使用 Consul
Consul 概念(摘录): Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其他分布式服务注册与发现的方案,比如 Airbnb 的 SmartStac ...
- java 利用ManagementFactory获取jvm,os的一些信息--转
原文地址:http://blog.csdn.net/dream_broken/article/details/49759043 想了解下某个Java项目的运行时jvm的情况,可以使用一些监控工具,比如 ...
- 让 ASP.NET vNext 在 Mac OS 中飞呀飞。。。
写在前面 阅读目录: 娓娓道来 Install ASP.NET vNext Command Line Tools 安装 Homebrew 使用 Homebrew,安装 KVM Install Subl ...
- Mac OS X 上编写 ASP.NET vNext (二) IDE配置
上一篇中介绍了如何在OS X上搭建.Net运行时.不过光有运行时还不够,还需要有一个好用的IDE,有了IDE的支持,OS X上的开发才称为可能. 和上篇类似,这里先列举出具体步骤,个人可以根据自己的情 ...
随机推荐
- P32_全局配置 - tabBar
什么是 tabBar tabBar 是移动端应用常见的页面效果,用于实现多页面的快速切换.小程序中通常将其分为: 底部 tabBar 顶部 tabBar 注意:tabBar中只能配置最少 2 个.最多 ...
- P18_Day2.学习目标
能够使用 WXML 模板语法渲染页面结构 能够使用 WXSS 样式美化页面结构 能够使用 app.json 对小程序进行全局性配置 能够使用 page.json 对小程序页面进行个性化配置 能够知道如 ...
- 微信小程序分包
当我们程序太大的时候,打开小程序就会比较慢,此处就需要用到分包加载,按照模块划分不同的包,让用户在需要的时候才加载对用的模块,也就是用户在进入某些页面的时候才下载该页面的资源,提高小程序的打开速度,以 ...
- clicknium-划时代的自动化
说起UI自动化,selenium一直是不可替代的存在,它的安装量.百度数量已经奠定了它在自动化.爬虫等相关领域的霸主低位,可以说,只要涉及到UI自动化,选selenium就对了. 细数过往,相关的UI ...
- Json.stringfiy json未定义
在文档的顶部添加 <!DOCTYPE html> 没有它,IE10是一个奇怪的兼容模式,许多"新"功能不可用,例如JSON.如果你任然需要使用早期版本的IE,你可以使用 ...
- Chai 3D之创建项目
推荐:将 NSDT场景编辑器 加入你的3D开发工具链 1.构建应用目录 我们将设置使用 CHAI3D 构建应用程序所需的基本目录结构和文件.根据您可能要使用的显示器或 GUI 库,为 FreeGL ...
- XShell 连接 VMware Linux
如果你的系统是 Ubuntu,并且是精简安装的系统,极大可能使用ifconfig时会提示你安装相关依赖,所以你安装的时候要记得顺带安装 ifconfig: sudo apt install ifcon ...
- PACS系统各个功能介绍--九五小庞
PACS系统是HIS系统的重要组成部分,医生可在电脑上查看患者的医学影像报告. ■ 支持心电DICOM,心电图浏览和处理(显示格式.纸速.增益.滤波.测量等). ■ 支持MPR.CPR.MIP.SSD ...
- AttributeError: module 'openai' has no attribute 'ChatCompletion'的解决办法
原因 openai库版本过旧 解决办法(二选一) pip install -U openai 下载安装包放入你的项目根目录下, 改名格式zip为whl(即:openai-0.27.0-py3-none ...
- $forceUpdate和this.$set('userInfo',name,'小红');
在Vue官方文档中指出,$forceUpdate具有强制刷新的作用. 那在vue框架中,如果data中有一个变量:age,修改他,页面会自动更新. 但如果data中的变量为数组或对象,我们直接去给某个 ...