Shell & OS organization
主题:
内核系统调用API
参考:https://pdos.csail.mit.edu/6.828/2017/lec/l-shell.txt
对内核进行讲解并将homework shell进行说明
概述图
用户态如何连接内核态
应用程序 - > printf() - > write ) - > SYSTEM CALL - > sys_write() - > ... 用户级库是应用程序的私有业务 内核内部函数不能由用户调用
进程的状态,进程和线程区别是什么??
process=address space + thread(s)??
计算机系统进程由以下资源组成(或被称为拥有):
与程序关联的可执行机器码的图像。
内存(通常是虚拟内存的某个区域); 其中包括可执行代码,特定于
进程的数据(输入和输出),调用堆栈(用于跟踪活动子例程和/或其
他事件)以及用于保存在运行时产生的中间计算数据的堆。分配给进程的资源的操作系统描述符,例如文件描述符(Unix术语)或句柄(Windows)以及数据源和接收器。
安全属性,例如进程所有者和进程的权限集(允许的操作)。
处理器状态(上下文),如寄存器内容和物理内存寻址。
系统调用
xv6有几十个系统调用,而今天的Linux系统有几百个系统调用。
让我们回顾家庭作业2(sh.c)
为什么两个execv()参数?
execv 函数原型:execv(char cmd,char argv[]);
execv()用来执行参数path 字符串所代表的文件路径,与execl()不同的地方在于execve()只需两个参数,第二个参数利用数组指针来传递给执行文件.
/* 执行/bin/ls -al /etc/passwd */
#include <unistd.h>
main()
{
char * argv[] = {"ls","-al","/etc/passwd",(char*)};
execv("/bin/ls",argv);
}
返回值:如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中.
重定向
以下是可以用来重定向的命令的完整列表:
pgm > file pgm 的输出被重定向到文件
pgm pgm 程序从文件度它的输入
pgm >> file pgm 的输出被添加到文件
n > file 带有描述符 n 的输出流重定向到文件
n >> file 带有描述符 n 的输出流添加到文件
n >& m 合并流 n 和 流 m 的输出
标准输入从开始行的下一个标记开始。
| 从一个程序或进程获取输入,并将其发送到另一个程序或进程。
需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。
管道
ls | wc -l 如果ls产生的输出比wc消耗的更快呢? 如果ls比wc慢,该怎么办? 每个命令如何决定何时退出? 如果读者没有关闭写入结束呢?[尝试一下] 如果作者没有关闭阅读结束呢? 内核如何知道何时释放管道缓冲区?
shell如何知道管道何时完成?
例如: ls | sort | tail -l 通过递归调用来解决先后完成的层次逻辑。
parsepipe(char **ps,char *es)
{
struct cmd *cmd;
cmd = parseexec(ps,es);
if(peek(ps,es,"|")){
gettoken(ps,0,0);
cmd = pipecmd(cmd,parsepipe(ps,es));
}
return cmd;
}
UNIX系统调用观察
fork / exec拆分看起来很浪费 - fork()拷贝memory而exec()丢弃。
为什么不使用 pid = forkexec(path,argv,fd0,fd1) ?
fork / exec 分割是有用的:
fork(); I/O redirection; exec()
or fork(); complex nested command; exit.
如( cmd1 ; cmd2 ) | cmd3
fork() alone: parallel processing
exec() alone: /bin/login … exec(“/bin/sh”)
fork对于小程序来说很便宜 - 在我的机器上:
fork + exec需要400微秒(2500 /秒)
fork只需要80微秒12000 /秒)
文件描述符设计:
FDs是一个间接的等级
- a process’s real I/O environment is hidden in the kernel
- preserved over fork and exec
- separates I/O setup from use
imagine writefile(filename,offset,buf size)
FDs help make programs more general purpose: don’t need special cases for files vs console vs pipe
Philosophy:小概念上简单的调用结合起来,
为什么必须内核支持管道 - 为什么不用shell来模拟它们,
例如:
ls> tempfile; wc -l <tempfile
猜测:如果用sh代替管道 执行的指令数,空间,时间都会变多。
系统调用接口简单,只是中断和字符缓冲区。为什么不用open()函数返回一个指向内核文件对象的指针?
核心的UNIX系统调用是古老的; 他们保持得好吗?
一方面来讲是非常成功的,历经多年的发展 :设计迎合了命令行和命令行开发 命令行用户,如命名文件,管道, 对于开发,调试,服务器维护都很重要
但另一方面UNIX思路并不完美:
系统调用API,程序员使用库通常不是很有价值,例如,隐藏sys调用细节的Python,
或者在智能手机上 应用程序可能与文件&c一些UNIX抽象是不是非常有效
而且fork()对于多GB进程是非常慢的
FDs隐藏的细节可能是重要的,
例如块大小的磁盘上的文件,
例如时间和网络消息的大小,
所以有很多将来的计划工作: 新的抽象的系统调用对于已有的系统调用
OS组织
如何实现一个系统调用接口?
为什么不是使用一个库?
即没有内核,只需在硬件上直接运行app +库即可。
灵活:应用程序可以绕过库,如果不正确的
应用程序可以直接与硬件交互
图书馆可以为一个单一目的的设备,
但如果计算机用于多个活动?
内核的关键要求:
隔离
多路复用
交互
helpful approach: abstract resources rather than raw hardware
File system,not raw disk
Processes,not raw cpu/memory
TCP,not ethernet packets
abstractions often ease isolation,multiplexing and interaction
also more convenient and portable
从隔离开始,因为这往往是最严格的要求。
隔离目标:
应用程序不能直接与硬件交互
应用程序不能损害操作系统
应用程序不能直接影响对方
应用程序只能通过操作系统界面与世界进行交互
处理器提供帮助隔离的机制
硬件提供用户模式和内核模式
有些功能只能在内核模式下执行
设备访问,处理器配置,隔离机制
*硬件禁止应用程序执行特权指令
- 而不是陷入内核模式
- 内核可以清理(例如,终止进程)
*硬件允许内核模式配置用户模式的各种约束
最关键的是:页表将用户的权限限制在自己的地址空间
内核构建在硬件隔离机制上
操作系统以内核模式运行
内核是一个大型的程序
服务:进程,文件,系统网络
低层设备,虚拟内存
所有的内核运行全硬件特权(方便)
应用程序运行在用户模式
- 内核设置每个进程隔离的地址空间
- 系统调用在用户和内核模式之间切换
应用程序执行一个特殊的指令进入内核,
硬件切换到内核模式
但只在内核指定的入口点
内核里要放什么?
xv6遵循传统的设计:操作系统的所有内容都以内核模式运行
微内核设计
- 许多操作系统服务作为普通用户程序 在文件服务器 - 内核实现在用户空间运行服务的最小机制 使用内存进程间通信(IPC) - 内核接口!=系统调用接口 -优点:多个隔离 -缺点:可能很难获得好的性能
外核:没有抽象
apps can use hardware semi-directly,but O/S isolates 应用程序可以读/写自己的页表,但是需要O / S审核 应用程序可以读/写磁盘块,但是O / S是块的所有者是 优点:对要求苛刻的应用程序有更多的灵活性 jos将是微内核和exokernel的混合
Next Lecture:x86硬件的隔离机制和xv6如何使用他们