操作系统接口
用户接口
总共有三种用户接口:字符显示式联机用户接口、图形化联机用户接口、脱机用户接口
-
字符显示式联机用户接口
通过命令语言(以命令为基本单位指示操作系统完成特定功能)对作业进行控制并取得操作系统的服务。
-
命令行方式
以Command arg1…argn的格式。一般是串行处理,但是如果需要并行处理,可以在命令结尾加入特定标记让其在后台运行来进行并行。
-
批命令方式(Shell或Bat脚本)
允许用户现将一系列命令组织在批命令文件中,方便一次执行,避免出错,还提供了控制流语句。
-
-
图形化联机用户接口
为了避免记忆庞大的命令语法,推出了图形化界面。
使用WIMP(Window,Icon,Menu,Pointing device)技术,让GUI变得直观,易用。
Windows就是以explorer作为一开始的进程,当点击程序,就是产生一个新的进程。而点击键盘都是一个事件,实际上就是给应用发送了一个消息,达到控制的效果。
-
联机命令类型
一般分为系统访问类(登录),磁盘操作类,文件操作类(拷贝,输出,重命名等),目录操作类(创建文件夹,显示目录,删除等),通信类,及其他命令(输入输出重定向命令,管道连接,过滤命令,批命令)。
输入输出重定向:标准情况下是从键盘输入,终端输出,通过重定向就可以改变来源和去向
管道连接:将上一个命令的结果作为下一个命令的输入
过滤命令:grep
Shell命令语言
Shell:命令语言,命令解释器和程序设计语言的统称。
有着自己的命令集,并且支持大多数高级语言中支持的语言特性,且可对输入的命令解释执行。
-
简单命令
格式:Command -option argmentlist
分类:
- 可以分为系统提供的标准命令和用户自定义的命令。
- 也可以分为内部命令(cd等)与外部命令(cp等)。内部命令的程序是常住内存的,而外部命令则是常住外存区,当他被使用的时候才会暂时进入内存。这样能极大节省内存。但这个分类由于shell的处理对用户是透明的。
种类:
- Borune Shell(B sh),用$作为提示符,交互较差,但是性能高。其常见版本为Bourne Again Shell(Bash)
- C shell,是标准BSD命令解释,用%作为提示符,大多数Unix都同时支持B和C,提供比Bsh更多的功能,如!和!!。
- Korn shell(K sh),支持对任务控制。
-
简单命令的类型
-
进入和退出系统
进入系统:输入用户名和口令(密码),通过调用Login(sudo login)程序完成
退出系统:ctrl+D,通过调用Logout程序完成
-
文件操作命令
cat mv cp rm file(用来查看一个文件是什么类型,不是通过后缀来判断的)
-
目录操作命令
mkdir rmdir cd
-
系统询问命令
data(询问时间) who(询问系统当前用户) pwd
-
-
重定向与管道
可以在一个命令后通过>和<来重定向输入和输出。
使用>>可以不覆盖,而是追加
在使用管道运算符的时候Linux是真的建立了一个pipe文件来实现管道。当管道满的时候,前命令停止运行,管道空时,后命令停止运行。
-
通信命令
-
信箱通信命令mail
可以向自己或者向另一个用户发送邮件。
格式:mail 接收者,如果不填接收者,就是查看自己的信箱。
-
对话通信命令write
格式:write 用户名[终端名]
在发送后,对方的终端上会直接显示消息。但是得要别人的允许才行。
-
允许或拒绝接受消息mesg
格式:mesg -n/-y
禁止别人写或允许别人写。
-
-
后台命令
通过在命令末尾加上&实现让这条命令在后台运行。后台运行实际上仍然是用标准输入或输出的,但是一般系统会帮忙自动将输入自动重定向到一个/dev/null的空文件上,避免影响。
联机命令接口的实现
-
键盘终端处理程序
-
字符接收
一种是面向字符的方式,即不加处理直接送进用户程序。
另一种是面向行方式,以一行作为缓冲(可以随意更改这一行的内容),当接收到行结束符后才送至用户程序。有的计算机不是直接存储ascii码,而是键码,因此终端处理程序还要进行转换。
-
字符缓冲
通过缓冲来降低中断处理器的频率。
- 专用缓冲:给每个终端都专门配备一个很小的缓冲区,实现简单,但是利用率低且空间占用大
- 公用缓冲:设定一个由很多极小缓冲区组成的公用缓冲池,以链表的形式存储,当一个终端需要的时候,就申请一个,并且加入到自己的缓冲链中,当满了,就再申请,当使用完后就全部归还。

-
回送显示
当终端输入一个字后应该回送到屏幕上进行显示。可以使用硬件实现,速度较快,但不灵活。因此一般用软件实现,也可以处理诸如换行之类的事情。
-
屏幕编辑
需要提供编辑键对缓冲区内的字符进行修改。删除字符键backspace,删除一行键,插入键,移动光标键,屏幕上下移动键。
-
特殊字符处理
处理中断字符ctrl+c,停止/恢复上卷字符等。
-
-
为了方便与用户交互,一般把命令解释程序放在用户层以用户态运行。
根据输入的命令输出提示符,请用户输入命令并执行对应命令。
-
组成
常驻部分:包含一些中断服务子程序,及在用户程序终止后检查暂存部分时候被用户程序覆盖并将对应调入内存的程序。
初始化部分:在常驻内存之后,在启动时获得控制权,包含对autoexec.bat的处理程序并决定命令解释程序的基地址。
暂存部分:主要的命令解释程序,包含了所有命令,但是可以被用户程序覆盖,在完成后再调回来。
-
工作流程
初始化部分获得控制权,进行初始化,然后自动执行autoexec.bat文件,最后将控制权交给暂存部分。然后的过程如下图。

识别命令一般是有一张命令表进行逐行判断的,而程序的地址也直接记录在表格中。如果是外部命令就要通过exec去调入并获取地址。
-
-
Shell解释程序
相较于MS-DOS,Shell的命令更复杂,一行可以有多个命令,而分隔符的不同也会导致执行顺序的不同。
因此Shell的处理方式是将整一个命令解释成一颗二叉树。
-
命令表型节点
当遇到;或者&就建立一个命令表型节点,左部作为左子树,右部作为右子树。
对于;来说,左子树执行完才能执行右子树。
对于&来说则可以同时执行。
-
管道文件型节点
当遇到|就建立一个管道文件型节点,左部作为左子树,右部作为右子树。
-
简单命令型节点
如果是内部命令,则直接执行(无须创建进程,因为shell解释程序内部就内嵌了其可执行程序),但如果不是内部命令,则创建一个子进程执行,执行完再恢复shell。
整体工作流程:
在系统打开之后就运行shell可解释程序。
首先读取命令。然后对命令分析,建立命令行树。如果是非内部命令,就分离命令名和其参数按照execve的格式放置参数。fork一个子进程并调用exec更换执行的程序,执行子进程,结束后主进程wait结束便继续循环,如果是&就不用wait。

-
系统调用的概念和类型(程序接口)
系统调用提供了用户程序和操作系统内核之间的接口。
-
系统调用基本概念
-
计算机中有两种状态:系统态和用户态。因此CPU指令集也被划分为特权指令和非特权指令。
特权指令:能访问全部内存空间,完全不受限,答案只允许OS使用,保护系统
非特权指令:在用户态运行的指令,应用程序运行的指令,只能完成一般性的操作和任务,不能对系统中的硬件和软件直接进行访问,内存空间也仅允许访问用户空间。
这一区分是在硬件上实现的,如果应用程序使用了特权指令就会直接发出错误信号。
-
系统调用也是一种过程调用,但是,与过程调用有一些区别:
调用程序和被调用程序处在不同的系统状态,这种调用需要进行状态的转换,通过软中断机制实现。而在返回的时候也不一定会回到调用者程序(如果使用了抢占式)。
也可以进行嵌套,但对嵌套深度有一定要求。
-
中断机制:系统调用是通过终端实现的,所有的系统调用都是通过同一个中断入口来是实现的,如MS-DOS提供的是INT21H。有些系统虽然会提供很多中断号,但仅能通过其中被授权给应用程序保护等级的终端号才能被使用,如Linux的3,4,5是调试用的中断号,只有80是真正的系统调用中断号。如果使用不合规的中断号也会崩溃。
-
-
系统调用的类型
对于一般通用的OS来说可分为三大类。
- 进程控制类:创建和终止进程;获得和设置进程属性;等待某事件
- 文件操纵类:创建和删除文件;打开和关闭文件;读和写文件
- 进程通信类:打开关闭同意拒绝连接;发送接收消息;建立销毁读写共享存储区
还有设备管理类和信息维护类,前者操作设备,后者获取信息。
-
POSIX标准
各种操作系统实现的系统调用的方式都是不一样的。因此ISO定义了POSIX(基于UNIX的可移植操作系统接口),定义了系统调用的API。一般实现上都是如下图的。

UNIX系统调用
-
进程控制
fork:创建一个与父进程完全一致的子进程。
exit:终止自己这个进程留下一条记账信息status包含运行时记录的统计信息。
exec:执行一个文件,将自身的进程映像更改为这个文件。
wait:等待子进程结束,用于同步
获取进程ID:getp-id获取自身id/getpgrp获取进程组id/getppid获取父进程id
获取用户ID:getuid获得真正用户id/geteuid获取有效用户id/getgid获取用户组id
进程暂停
-
文件操纵
creat:创建一个新文件或重写已有文件
删除文件:没有专门的系统调用
open:打开文件,实际上是将其从外存搬到内存并建立一条通路,返回一个fd,通过fd就可访问这个文件
close:关闭文件,实际上是将计数-1,当且仅当没有任何访问的时候才真正关闭文件
read/write:有三个参数fd,buf和nbytes,读就是将nbytes读到buf中,写就是将buf中的nbytes写到文件中
link:为了实现共享,需要记住共享该文件的用户数目,用link就代表提醒他+1.
unlink:让计数-1,当计数为0的时候就代表没有人需要这个文件,也就删除了,因此没有一个专门的删除功能。
-
进程通信和信息保护
-
msgget:建立消息队列,并获取其id:msgid
msgsend:向消息队列发送消息
msgrcv:从消息队列接收消息
-
shmget:创建共享存储区,并获取其id:shmid
shmat:将共享存储区连接到本进程的虚地址空间上
shmdt:拆除连接
-
信号量相关的系统调用
-
stime:以超级用户设置时间
time:获取时间
times:获取进程和子进程时间
utime:设置文件访问和修改时间
-
uname:获取UNIX系统的名称
-
系统调用的实现
-
首先要实现系统调用,就要将系统调用号和其调用参数传给中断和陷入。
系统调用号传入:直接放在系统调用命令中或者放在寄存器中。
调用参数传入:系统调用命令可以顺便携带少量参数;送至有限的寄存器;或者主流的方法是放在一张表中,而寄存器中只存储指向这张表的指针。这张表也有两种方式,一种是直接,将个数和参数放在一起,另一种是间接,第一张表只放个数和指向真正的表的指针。UNIX和LINUX都使用这种方法。

-
系统调用的步骤
从用户态转为系统态:将要中断的进程的信息保存,然后将用户定义的参数进行保存。
分析调用类型:根据调用号和系统调用表找到子程序入口。
完成后恢复并执行。
-
UNIX系统调用实现
-
CPU环境保护:将参数表地址送入寄存器R0,并转到核心态(自动将进程信息储存,如PSL,PC,code),并取出中断和陷入总控程序trap.S程序的入口地址运行。将陷入类型type和用户栈指针usp,和要被中断进程的CPU环境的寄存器根据屏蔽码的情况选择性的压入用户核心栈中。
-
参数表指针:AP存储当前执行的系统调用的参数表的地址
调用栈帧指针:FP存储被压入用户核心栈的内容的地址
这两个指针实现了嵌套调用。
接着调用公共处理程序trap.C。
-
trap.C确定系统调用号,调用子程序,格式:trap(usp,type,code,PC,PSL)。可以通过code&0377来确定系统调用号,如果0< <64就是系统调用号,否则就通过间接参数,间接参数指针来找到系统调用号。
-
参数传递:将参数表的指针赋予U.U-arg中及完成了参数传递。
-
转入相应的处理程序:根据表进行查找,实际上就是一个三元表,调用所需参数,经寄存器传递的参数个数,子程序的入口地址。在子程序完成后回到trap.C,完成公共处理部分。
-
公共处理:重新计算所有优先级,然后进行调度。如果在系统调用中出错,就设置再调度标志,强行重新调度。
如果进程在系统态运行,则不管其他进程的信号,直到返回用户态,再进行处理,并且在返回的时候执行RET命令将压入用户核心栈中的所有数据退回寄存器中,继续正常执行。
-
-
Linux系统调用:
内核态对应特权级0,用户态对应特权级3。
系统调用由两个方面组成:内核函数(真正的系统调用),接口函数(供程序调用,调用内核函数,并将参数传递,并让用户态运行的应用程序陷入核心态)。
其他基本一致。
-
Win32系统调用:定义了一系列程序(Win32 API),提供系统调用的服务。对Win32的调用可以创建各种核心对象,并将其句柄给其他进程使用。当调用的时候,执行2E中断指令,进行类似的操作。通过Kernel,User和GUI三个组件来支持API,都存放在DLL中。