第四课 shell 和 操作系统结构
作业
回顾作业shell
exec
为什么需要两个参数exec
进程结束后会发生什么?execv()
会返回么? 不会- shell在执行完一条命令后,怎么继续?
exec
是否如何实现重定向的?- 子进程的
fd tables
被修改会影响到主进程么? ls | wc -l
- 如果
ls
的输出超过wc
的接收,会怎么样? - 如果
ls
的输出慢于wc
的接收,会怎么样? - 每条子命令何时退出?
- 如果
wc
没有关闭写管道会如何? - 如果
ls
没有关闭读管道会如何?
- 如果
- 内核如何决定何时清理一个pipe buffer?
- shell怎么知道一个管道命令结束?
ls | sort | tail
wait? - 管道命令中可以尝试减少fork的次数么?
- 尝试执行
pcmd->left
不fork. - 尝试执行
runcmd
不fork. - 尝试执行
pcmd->right
不fork.这时执行sleep 10 | echo hi
,看看有什么变化.
- 尝试执行
- 管道命令中,为什么要在两个子进程都开始后再调用两次wait(). 如果将wait()移到两次fork之间会发什么?试试
ls | wc -l cat < big | wc -l
- 核心: 系统调用短小精悍,通过组合使用这些系统调用,从而达到各种各样的目的.
附加题shell
- 如何实现顺序执行
;
.为什么必须要在scmd->right
前wait()? - 如何实现后台执行
&
?当后台执行命令退出时,恰好sh正在等待一个前台进程退出,会怎么样? - 如何实现嵌套?
(echo a; echo b) | wc -l
. - 下面两个命令有什么区别?什么机制避免了内容覆盖?
- echo a > x ; echo b > x
- (echo a; echo b) > x 共享fd偏移
Unix系统调用
fork和exec
fork
和exec
分开,咋一看比较多余.fork
拷贝了内存,exec
又丢弃了这些内存,重新载入.为什么不用一个api
搞定呢?像这样pid = forkexec(path, argv, fd0, df1)
事实上,分离了fork
和exec
是有意义的.这样在fork
和exec
之间,我们可以插入自己的代码.比如重定向的功能就依赖于此.fork(); IO redirection; exec()
另外,fork()
的消耗很小.
文件描述符
文件描述符是一种间接抽象.进程的实际IO细节被隐藏在内核之中.在fork
和exec
时,会被保留.
文件描述符有助于程序抽象,不再需要关心fd代表的是文件,控制台或者是管道.
管道的必要性
为什么要设计管道,而不是使用临时文件替代?
ls > tempfile ; wc -l < tempfile
管道自管理自回收.
系统调用设计
- 系统调用往往设计的非常简单和抽象,比如文件描述符就是一个int值.如果在
open
文件后返回的不是一个文件描述符而是一个执行内核文件对象的指针,会怎么样? - 核心的Unix系统调用是非常古老的,从诞生以来几乎没有大的变动,可以说它们的设计是非常成功的.既能够很好的适应命令行的使用环境,同时在一般的程序开发中也能够非常好的使用.那么这些系统调用有没有缺点呢?如果有的话,大概可能是以下几点:
- 系统调用过于细节化,开发效率比较低.事实上,一般程序员很少直接接触系统调用,尤其是高级语言的开发者.
- 系统调用过于抽象,可能隐藏的一些有用的细节.比如socket网络时延等.
- 某些情况下,系统调动的效率不高.比如父进程非常大时,那么
fork
就会比较慢.如果只是为了执行新进程,是否有必要复制父进程就值得探讨了.
操作系统组织
- 如何实现一个系统调用接口?
- 为什么操作系统一定需要一个内核呢?假设没有内核,只有
app
和lib
库会如何?如果是单一目的的系统,这样做是可以的.但是在一个多进程的复杂环境中,如果没有内核的统一调度,那么app是可以绕过lib库,直接操纵硬件的.
- 内核的核心目的之一: 为每个进程提供一个看似隔离,实际复用硬件的环境.
- 通常而言,采用抽象的方法可以帮助我们达到隔离的目的.操作系统不会直接向用户空间提供硬件上的细节,而是在操作系统内部对细节进行抽象.比如文件读写不会暴露硬件细节,而是通过内核文件系统来达成.内存和网卡的使用同样如此.正是通过这样的抽象,操作系统向用户提供了抽象独立的工作环境.
- 各进程相互独立是操作系统的核心目标之一.
- 应用独立的目标包括:
- 应用不能够直接操作硬件
- 应用不能够突破操作系统的限制,也不能够破坏操作系统
- 应用不能直接和其他应用通信,而必须通过特定的系统调用接口来和其他应用通信.
- 微处理器提供了进程间相关隔离所必需的一些特性:
- 硬件提供了用户模式和内核模式.某些特殊的指令只能在内核模式下被调用,这些包括设备访问,处理器配置,隔离机制等.
- 在用户模式下,特权指令是不允许被执行的.
- 用户调用系统调用,必须陷入内核并切换到内核模式执行.
- 硬件提供了内核模式下配置用户模式的功能,比如在内核模式下可以在页表中配置进程的可用地址范围等.
- 基于硬件提供的功能,构筑了现代操作系统:
- 操作系统本身运行于内核模式.操作系统本身是一个大的程序,同时提供了很多服务包括进程,文件系统,网络,设备,虚拟内存等.所有这些服务都运行在内核模式下.
- 应用运行于用户模式.内核为每个应用设置了相互独立的内存地址空间.应用和内核交互需要通过系统调用来实现.通过这些系统调用,应用陷入内核,此时运行等级提升为内核特权模式,从而使用内核提供的若干服务.
常见操作系统设计
- xv6采用了传统的内核设计方法,即操作系统的所有部分均运行在内核态.
- 一个大的二进制文件,带有文件系统和驱动.
- 被称为宏内核
- 内核接口同时也是系统调用
- 优点: 便于内核各子系统之间协作,文件系统和虚拟内存可以访问同一块缓存.
- 缺点: 内核之内缺少隔离机制,内核各组件交互比较复杂.
- 微内核系统:
- 许多操作系统的功能不再运行在内核态,而是运行在用户态,作为一个普通的进程而存在.比如文件系统功能就可以运行在一个文件服务中.
- 内核仅仅实现少量必需的机制.各服务之间通过进程间通信协作运行.
- 此时内核接口不等同于系统调用
- 优点: 提供比宏内核更高的隔离性
- 缺点: 性能相较于宏内核有明显的下降.
- 外核:
- 应用可以半直接地操作硬件,系统提供了隔离功能.
- 例如: 应用可以修改自己的页表,但是操作系统会检查修改是否合法.
- 例如: 应用可以读写硬盘,但是操作系统会检查读写是否合法.
- 优点: 比较灵活?
- jos是微内核和外核的结合.
- 思考: 如果硬件没有提供内核态,用户态这种的功能,进程还可以相互独立么?
答案是肯定的,后面我们会看到这样的例子.但是大部分情况下,硬件都会提供内核态,用户态这种的功能.