操作系统设计与实现(第一章 引论-操作系统概念)

《Operating Systems: Design andImplementation Second Edition操作系统设计与实现 (第二版)安德鲁.坦尼鲍姆(Andrew S. Tanenbaum)阿尔伯特.伍德豪尔(Albert S. Woodhull)》


典型的操作系统由四部分构成:进程管理,I/O设备管理,存储器管理和文件管理。

第一章 引论

计算机软件大致分为两类:系统软件和应用软件。系统软件管理计算机本身及应用程序;应用软件执行用户最终所需要的功能。最基本的系统软件是操作系统(operating system),它控制计算机的所有资源并提供开发应用程序的基础。

计算机系统包含一个或多个处理器、若干内存(常称为RAM-随机存取存储器)、磁盘、打印机、网络接口及其他输入/输出设备。

许多年以前人们就认识到必须找到某种方法将硬件的复杂性同程序员分离开来。目前采用的方法是在裸机上加载一层软件来管理整个系统,同时给用户提供一个更容易理解和进行程序设计的接口,这被称为虚拟机(virtual machine)。这样一层软件就是操作系统。

图 1-1 计算机系统由硬件、系统程序和应用程序组成。

底层是硬件,它本身可能包括两层或多层。最低一层是物理器件,包括集成电路芯片、连线、电源、监视器等,它们的构造和工作方式属于电气工程师的范围。

微程序(microprogram),通常存放在只读存储器中,它是一层很原始的软件,用来控制设备并向上一层提供一个更清晰的接口。微程序实际上是一个解释器,它先取得机器语言指令,如ADD, MOVE和JUMP等,然后通过一个动作序列来执行这些指令。由微程序解释执行的这一套指令称为机器语言。机器语言并不是硬件的组成部分,但硬件制造商通常在手册中给出机器语言的完整描述,所以许多人将它认作真正的“计算机”。采用精简指令集计算机(RISC)技术的计算机没有微程序层,其机器指令通过硬件逻辑直接执行。例如Motorola 680X0有微程序,而IBM PowerPC 则没有。

机器语言典型地有50到100条指令,大多数用来完成数据传送、算术运算和数值比较等操作。在这个层次上,通过向特殊的设备寄存器写特定的数值来控制输入/输出设备。例如将磁盘地址、内存地址、读字节数和操作类型(读/写)等值写入特定的寄存器便可完成硬盘读操作。实际操作往往需要更多的参数,而操作完成后的返回状态也非常复杂。

操作系统的主要功能之一就是将所有这些复杂性隐藏起来,同时为程序员提供一套更加方便的指令,比如,“从文件中读一个数据块”在概念上比低层的“移动磁头臂,等待旋转延迟”之类的细节来得简单、方便。

在操作系统之上是其他系统软件,包括命令解释器(shell)、窗口系统、编译器、编辑器及类似的独立于应用的程序。要注意它们本身并不是操作系统的组成部分。操作系统专指在核心态(kernel mode),或称管态(supervisor mode)下运行的软件,它受硬件保护而免遭用户的篡改。编译器和编辑器运行在用户态(user mode)。

系统软件之上是应用软件,这些软件可以是购买的或者是用户自行开发的,它们用来解决特定的问题,如字处理、表格处理、工程计算或者电子游戏等。

1.1、什么是操作系统

操作系统完成两项相对独立的任务:

1.1.1 操作系统作为虚拟机(自顶向下)

将硬件细节与程序员隔离开来、同时提供一个简洁的命名文件方式的程序,就是操作系统。与磁盘抽象类似,它还隐藏了其他许多低层硬件的特性,包括中断、时钟、存储器等。总之,操作系统提供的每一种抽象都较低层硬件本身更简单、更易用。

从这个角度看,操作系统的作用是为用户提供一台等价的扩展计算机,或称虚拟机,它比低层硬件更容易编程。

1.1.2 操作系统作为资源管理器(自底向上)

操作系统则用来管理一个复杂系统的各个部分。现代计算机都包含处理器、存储器、时钟、磁盘、鼠标、网络接口、激光打印机以及其他许多设备,从这个角度看,操作系统的任务是在相互竞争的程序间有序地控制这些设备的分配。

1.2、操作系统发展历史

第一台真正的数字计算机是英国数学家Charles Babbage(1792-1871)设计的。Babbage投入毕生精力去建造他的“分析机”,但却没能让它成功地运行起来。因为它是纯机械式的,而当时的技术不可能使分析机的零部件达到他所需要的精度。很显然,分析机没有操作系统。Ada由此成了世界上第一位程序员。

第一代计算机(1945-1955):真空管和插板

第二代计算机(1955-1965):晶体管和批处理系统

第三代计算机(1965-1980):集成电路芯片和多道程序

在CTSS研制成功之后,M.I.T、贝尔实验室和通用电气公司(GE,当时一个主要的计算机制造厂商)决定开发一种“公用计算服务系统” - 一种能够同时支持数百名分时用户的机器。它借鉴于供电系统 - 当你需要电能时,只需将电气设备接到墙上的插座即可。该系统称作MULTICS(MULTiplexed Information and Computing Service),其设计者着眼于建造一台机器来满足整个波士顿所有用户的计算需求。MULTICS引入了计算机领域许多概念的雏形,但其研制难度却超出了所有人的预料。在开发过程中贝尔实验室退出了该项目,通用电气公司也退出了计算机领域。最终MULTICS被成功地应用在M.I.T的实际生产环境以及其他几十个系统中。但“公用计算服务系统”的概念却随着计算机价格的暴跌而被人们遗弃,不过MULTICS对随后的系统却有着巨大的影响。

POSIX定义了相互兼容的UNIX系统必须支持的一个最小的系统调用接口,实际上,一些其他操作系统现在也支持POSIX接口。

第四代计算机(1980-现在):个人计算机

个人计算机和工作站领域有两种主流操作系统:

(1)微软的MS-DOS 和 UNIX MS-DOS --> Windows95/Windows NT 广泛用于IBM PC及其他采用Intel 80X86芯片的计算机;

(2)UNIX,它在工作站和高档计算机领域(如网络服务器)占据了统治地位,尤其对于采用高性能RISC芯片的计算机。

运行网络操作系统(network operating systems)和分布式操作系统(distributed operating systems)(Tenenbaum, 1995)的个人计算机网络的崛起。在网络操作系统中,用户知道多台计算机的存在。他能够登录到一台远地机器上并将文件从一台机器拷贝到另一台机器,每台计算机都运行自己本地的操作系统,有自己的本地用户(或多个用户)。网络操作系统与单处理机的操作系统没有本质区别。它们需要一个网络接口控制器以及一些低层软件来驱动它 ,同时还需要一些程序来进行远程登录和远地文件访问,但这些附加物并未改变操作系统的本质结构。与之相反,一个分布式操作系统在用户看来就象一个普通的单处理机系统。尽管它实际上由多个处理机组成,但用户不会感知到他们的程序在哪个处理机上运行,或者他们的文件存放在哪里,所有这些均由操作系统自行高效地完成。真正的分布式操作系统不仅仅是在单机操作系统上增添一小段代码,其原因是分布式系统与集中式系统有本质的区别。例如,分布式系统通常允许一个应用在多台处理器上同时运行,因此需要更复杂的处理器调度算法来获得最大的并行度。网络中的通信延迟往往导致分布式算法必须能适应信息不完备、信息过时甚至信息不正确的环境。这与单机系统完全不同,对于后者,操作系统掌握整个系统的完备信息。

MINIX 的历史:

UNIX的早期(版本6),源代码可以免费获得并被人们加以广泛的研究。在AT&T发布版本7时,它开始认识到UNIX的商业价值,于是发布的版本7许可证禁止在课程中研究其源代码以免其商业利益受到损害。许多学校为了遵守该规定,就在课程中略去UNIX的内容而只讲操作系统理论。只讲理论使学生对实际的操作系统产生一种片面的认识。书本上作为重点讲述的内容,如进程调度算法,实际中并没有那么重要;而实际系统中很重要的内容,如I/O系统和文件系统又因为缺乏理论性而被忽略。为了扭转这种局面,本书的作者之一坦尼鲍姆决定编写一个在用户看来与UNIX完全兼容,然而内核全新的操作系统 - MINIX。MINIX没有借用AT&T一行代码,所以不受其许可证的限制,它可以被班级和个人用来学习。通过它读者可以剖析一个操作系统,研究其内部如何运作。其名称源于“小UNIX”。

除合法性以外,MINIX与UNIX相比还有另一个优点:它比UNIX晚出现十年,并且其代码采用了一种更加模块化的组织方法。例如,MINIX的文件系统不是操作系统的一部分而是作为一个用户程序运行。另一个不同之处在于UNIX着重效率而MINIX着重可读性(数百页的程序通常认为是可读的),其代码中有数千行注释。

MINIX最初设计成与UNIX版本7兼容。UNIX版本7由于其简洁和优美而被奉为典范,随着POSIX的出现,MINIX在保持既有的向后兼容性的同时开始向POSIX靠拢。

在MINIX发布后不久,便出现了一个面向它的USENET新闻组,在数周之内便有多达40000个用户订阅该新闻组。其中的大多数人都想向MINIX中加入一些新特性以使之更大、更有用。每天都有数百人提供自己的建议、思想甚至代码。而MINIX的作者在几年内一直坚持不采纳这些建议,目的是使MINIX保持足够的短小精悍,以便于学生理解。人们最终意识到不可能动摇作者的立场,于是一个芬兰学生Linus Torvalds决定编写一个类似MINIX的系统,但是它特征繁多、面向实用而非教学,这就是LINUX

1.3 操作系统基本概念

操作系统与用户程序的界面由操作系统提供的“扩展指令”集定义。尽管有多种不同的实现方法,这些扩展指令传统上称作“系统调用”。例如,一个确定的系统,讲述该系统的系统调用(如:MINIX有一条READ系统调用,它有三个参数:一个指定所操作的文件,一个指定将读到的数据存放在何处,最后一个指定读多少字节)。

MINIX系统调用大致分为两类:与进程有关的系统调用和与文件有关的系统调用。

1.3.1 进程

一个进程本质上是一个程序的执行。

一个(挂起的)进程包括两部分:进程的地址空间 - 称作核心映像(core image),以及对应的进程表项(包含寄存器值及其他信息)。将寄存器值保存到堆栈中

在与进程管理有关的系统调用中最关键的是完成进程创建和进程终止的系统调用。其他系统调用的功能包括:申请更多的内存(或释放不再需要的内存)、等待一个子进程结束、加载并执行另一个程序等。

MINIX中的每一个合法用户都有一个由系统管理员分配的用户标识(uid),MINIX中的每一个进程都记录有启动它的用户的uid。子进程的uid与其父亲相同。系统中有一个特殊的用户 - 超级用户,他拥有特殊的权力,许多保护规则对超级用户无效。在大型系统中,只有系统管理员知道超级用户的口令。

1.3.2 文件

操作系统的主要功能之一是屏蔽硬件设备的特殊细节以便为程序员提供一个简洁方便的与设备无关的文件模型。显然,一个操作系统需要有相应的系统调用来创建文件、删除文件、读文件和写文件。在读一个文件之前首先要打开它,在读完之后要关闭它,完成此类功能的系统调用都是存在的。

进程和文件都可以组织成树状结构,但有许多不同之处。进程树的层次一般都不会很深(很少超过三层),而文件层次常多达四层、五层或更多。进程树的层次结构是暂时性的,通常最多存在几分钟,而目录层次则可能长达数年之久。进程和文件在属主及保护方面也是有区别的。典型地,只有父进程可以控制和访问子进程,而对于文件和目录则通常存在一种机制使属主以外的其他用户也可以访问该文件。

目录层次结构中的每一个文件都可以用一个从根目录开始的路径名来确定,这种绝对路径名中包含了从根目录到该文件的所有中间目录,相互之间由正斜杠“/”隔开。

在文件读写之前,首先要将其打开,执行打开操作时将检查其访问权限,若访问权限许可,系统将返回一个小的整数,称作文件描述符,供后续操作使用;若访问权限不够则返回一个错误码。

MINIX允许将软盘上的文件系统链接到主文件树上:

图 1-7 (a)在安装前,驱动器0中的文件不可访问。 (b)安装后,驱动器0中的文件成为文件树的一部分。

在MINIX中,使用MOUNT系统调用便可以将软驱0中的文件系统链接到根文件系统的任一目录。在图1-7(b)中软驱上的文件系统被安装到目录b下,这样就可以访问文件/b/x和/b/y。如果目录b下原先存有文件,则在软驱0被安装期间这些文件无法访问,因为目录“/b”现在指向软驱0的根目录。(这种情况通常不会造成很多不便,其原因是文件系统总是被安装在空目录下)。

设备文件(special file)提供设备文件的目的是使I/O设备使用起来更类似于文件,在这种方式下,设备文件的读写可以使用与普通文件相同的系统调用。

设备文件分为两类:块设备文件(block special files)和字符设备文件(character special files)。块设备文件指那些由可以随机存取的数据块组成的设备,如磁盘。打开一个块设备文件,然后读第四个块,程序可以直接访问设备上的第四块而不管其文件系统的格式。类似的,字符设备文件指那些以字符流方式进行操作的设备,如打印机,调制解调器等。

图 1-8 一个管道连接两个进程。

管道(pipe):管道是一种用来连接两个进程的虚拟文件,如图1-8所示。当进程A欲向进程B发送数据时,它把管道文件视作输出文件,向其中写数据,进程B则可将管道文件视作输入文件,从中读数据。于是,MINIX中的进程间通信很象普通文件的读写。一个进程判断其输出是普通文件还是管道的唯一方法是调用一条特殊的系统调用。

1.3.3 外壳(shell)

命令解释器:shell。体现了操作系统的许多特性并很好地说明了系统调用的具体用法,同时它也是终端用户与操作系统之间的界面。

MINIX操作系统是指完成系统调用的代码,编辑器,编译器,汇编程序,链接程序以及命令解释器等均不是操作系统的组成部分。

若用户键入date则shell创建一个子进程并使其运行date程序。在该子进程运行期间,shell等待它结束。当子进程结束后,shell再次显示系统提示符并等待下一个输入。

通过管道可以将一个程序的输出作为另一个程序的输入,因此 cat file1 file2 file3 | sort > /dev/lp 将调用cat程序将这三个文件合并,结果送到sort程序按字典序排序,sort的输出又重定向到文件/dev/lp,这正是打印机的设备文件名(一般将所有设备文件都放在/dev/目录下)。如果在一条命令后加上一个“&”符号,则shell将不等待其结束而直接显示出系统提示符。所以 cat file1 file2 file3 | sort > /dev/lp& 将启动sort程序作为后台任务执行,这样就可以在本条命令尚未结束时允许用户继续下面的工作。

1.4 系统调用

定义:操作系统与应用程序之间的接口。

由于系统调用的实现往往与机器有关,而且总是用汇编语言表述,所以为使C程序能够使用系统调用,必须额外提供一个例程库。例如:READ系统调用为例子。它有三个参数:第一个指定所操作的文件,第二个指定使用的缓冲区,第三个指定要读的字节数。在C程序中调用该系统调用的方法如下:count = read(file, buffer, nbytes); 本系统调用将真正读到的字节数返回给count变量,正常情况下这个值与nbytes相等,但当读至文件结尾符时则可能比nbytes小。若由于参数非法或磁盘操作错导致该系统调用无法执行,则count被置为-1,同时错误码被放在一个全局变量errno中。程序应该经常检查系统调用的返回值以确定其是否正确地执行。

系统调用分为六大类:

图 1-9 MINIX的系统调用,返回值s在出错时为 -1, fd为文件描述符,n为字节计数。其他返回码由其字面意思确定

1.4.1 进程管理系统调用

fork:是创建进程的唯一途径。它实际上是做一个调用它的进程的精确拷贝,包括文件描述符,寄存器值等所有内容。调用fork后,原进程和所得的拷贝各自执行,互不相关。在执行fork时,二者所有的对应变量都有相同的值,但由于子进程在创建过程中对父进程的数据作了一个拷贝,所以在此之后,其中任一进程中变量值的改变不会对另一个进程产生任何影响(正文段不可修改,它由父、子进程共享)。正确情况下fork的返回值对子进程为0,对父进程为一个正整数,即子进程的标识号pid。通过该返回值可以将父子进程区分开来。

waitpid系统调用:该系统调用使调用进程阻塞直到子进程中的任一个结束(若将其第一个参数置为-1),也可以等待一指定的子进程结束。waitpid结束时,子进程的终止状态值(正常结束或异常结束,以及正常结束时的返回值)被放在第二个参数指向的地址单元。waitpid有若干选择项可用。waitpid 取代了先前的wait系统调用,wait系统调用虽然已经过时,但目前仍旧提供,其目的是为了保持兼容性。

shell如何使用fork:当键入一条命令时,shell首先创建一个新进程。该子进程需执行该用户命令,这通过调用exec系统调用实现,exec用其第一个参数指定的可执行文件替换其核心映像,一个高度简化的shell框架如图1-10所示。

图 1-10 一个简化的shell,在本书中,TRUE被定义为 1。

exec有三个参数:待执行的文件名,指向参数数组的指针和指向环境变量数组的指针。系统提供了若干库例程来简化这些参数的使用,包括execl, execv, execle和execve。

对于如下一条命令 “cp file1 file2” 其功能是为文件file1作一个拷贝file2,在shell创建一个子进程后,子进程执行程序cp,同时向该程序传递执行的参数:源文件名和目标文件名。cp程序的主函数格式如下:

main( argc, argv, envp)

这里argc是命令行中包括程序名在内的参数个数。对于以上例子,argc为3。

第二个参数argv 是一个指向数组的指针。该数组中第i个因素就是命令行中第i个字符串,此处argv[0]为:“cp”,argv[1]为“file1”,argv[2]为“file2”。

第三个参数envp是一个环境变量指针。环境是由一系列形如“name = value”的字符串组成的,用来将环境信息诸如终端类型,用户主目录等传递给程序。在图1-10中,没有向子进程传递任何环境变量,因此execve的第三个参数为空。

实际上EXEC是最复杂的系统调用,其他系统调用都比它简单得多。

EXIT系统调用的作用是结束一个进程,它只有一个参数指定其出口值(0~255)。这个值通过wait和waitpid系统调用中的变量status,返回给父进程。status的低字节存放结束状态,0为正常结束,其他值均表示出错。status的高字节包含子进程的出口值(0~255)。例如若父进程执行:

n = waitpid( -1, &status, options);

则父进程被阻塞直至有一个子进程结束。如果子进程结束时将4作为exit的参数值,则父进程唤醒时n被置为该结束子进程的pid,而status的值为0x0400。

MINIX中进程的存储空间分为三部分正文段(text segment,即程序代码),数据段(data segment,即变量)和堆栈段(stack segment)。数据段是向上增长而堆栈向下增长,如图1-11所示。在两者之间是空闲区。堆栈的增长随程序的执行自动进行,而数据段的扩展则通过BRK系统调用显式地完成,BRK有一个参数指定数据段的结束地址,它可以比当前值大(数据段扩展),或比当前值小(数据段缩小)。该参数必须小于堆栈指针,否则堆栈和数据段将重叠,这是不允许的。

图 1-11 进程有三个段:正文段、数据段和堆栈段。在本例中,所有三个段在同一个地址空间中,但是也支持分离的指令空间和数据空间。

库函数sbrk:出于为程序员的方便考虑,系统提供了一个库函数sbrk来改变数据段大小,它的参数是数据段的变化量(负数表示数据段缩小)。其原理是跟踪数据段的当前值,(该值由BRK返回)。计算其新的大小,然后申请所需的空间。BRK和sbrk均与实现密切相关,所以不是POSIX的组成部分。

getpid:返回调用进程的进程标识号,注意在调用fork时,只有父进程能够获得子进程的进程标识号如果子进程要得到自己的进程标识号,它必须使用getpid。同样地,getgrp返回进程的组标识号。SETSID系统调用启动一个新的会话(session),并将进程组的pid设为调用者的pid。

ptrace:它被调试器用来对被调试程序进行控制,通过ptrace,调试器可以读写被控进程的地址空间并实施其他控制。

1.4.2 信号管理系统调用

当一个信号被发送给一个事先并未声明愿意接收它的进程时,该进程只是简单地被撤销,即被杀死。进程可以用SIGACTION系统调用来声明它准备接收某些类型的信号,并同时提供两个地址:一个是信号处理过程的地址,另一个用于保存该信号的原先处理过程的地址。执行完SIGACTION系统调用后,此进程若接收到相关类型的信号,则先将该进程的当前状态压栈,然后调用指定的信号处理过程。信号处理过程可能很长,它本身也可以调用系统调用,但一般情况下,它往往很短。信号处理过程结束后,它调用SIGRETURN以继续执行被该信号中断的操作,类似于硬件中的中断返回。SIGACTION替换了原先的SIGNAL系统调用,出于兼容性的考虑,SIGNAL仍然提供。

在MINIX中信号可以被阻塞,被阻塞的信号一直被挂起,直到阻塞解除。这段时间内它不被传递,但也不会丢失。SIGPROCMASK系统调用允许一个进程定义其阻塞的信号集,其实现方法是向核心提交一张位图。进程也可以查询当前因阻塞而挂起的信号集,为此可使用系统调用SIGPENDING,它以位图方式返回该信息。最后,SIGSUSPEND系统调用使进程可以原子性地设定一张阻塞信号位图并将其挂起。

除了提供一个函数以捕获一个信号(也就是在接收到该信号时插入执行此函数)外,程序也可以使用常数SIG_IGN来忽略指定类型的信号,或者使用SIG_DFL来恢复缺省的信号处理过程。缺省的处理方式随信号而异,可以是撤销该进程,或者是忽略该信号。

SIG_IGN的用法例子:command &,希望DEL进程不要对后台进程产生影响,所以shell在执行fork之后,exec之前需要执行 sigaction (SIGINT, SIG_IGN, NULL);及 sigaction (SIGQUIT, SIG_IGN, NULL); 来忽略DEL和quit信号。(quit信号由CTRL-\产生,它与DEL信号的作用基本相同,不同之处在于如果它未被捕获或忽略,则它将产生被撤销进程的核心映像文件)对于前台进程(命令不带&),这些信号不能被忽略。

按DEL键并不是发送信号的唯一途径,使用KILL系统调用可以向另一个进程发送信号(前提条件是两进程有相同的用户标识号,也即无关的进程间不能发送信号)。例如:一个后台进程已被启动,但随后发现它应被终止,此时SIGINT和SIGQUIT都已被屏蔽,所以只能使用KILL来向它发送一个信号。向后台进程发送信号9(SIGKILL)将撤销该进程,SIGKILL不能被捕获或忽略

ALARM系统调用:作用,对于许多实时应用,需要在一段指定时间后,中断进程的原有操作,以进行某种其他处理。--(延时发送中断信号)。ALARM的参数指定一个以秒为单位的时间间隔,一旦该时间段到点就向该进程发送一个SIGALRM信号。在任意时刻一个进程只能设定一个时间闹钟。如果进程先设定一个10秒的时间闹钟,在3秒后又设定一个20秒的时间闹钟,则只有其中一个有效,即在第二个ALARM调用之后20秒会发送一个SIGALRM信号。如果ALARM的参数为0,则所有挂起的SIGALRM信号都被取消。若不捕获SIGALRM信号,则其缺省的处理方法是撤销该进程。

某些情况下进程在信号到达之前不要做任何操作,例如一个测验阅读和理解速度的CAI系统,它先在屏幕上显示一些课文,然后调用ALARM设定在30秒后向自己发送一个信号,以激活程序进行一些处理。当学生阅读课文时,程序无需执行任何操作。它可以采用的一种方法是执行空操作循环以等待时间到,但假如此时系统中还有其他进程运行,这将浪费CPU时间,好的方法是使用PAUSE系统调用,它将挂起调用进程直至信号到来,这段时间里别的进程便可以使用CPU。

1.4.3 文件管理系统调用

创建新文件要使用CREATE系统调用,其参数指定文件路径名和保护模式。例如,fd = creat ("abc", 0751); 将创建一个名为abc的文件,其保护模式为0751(在C语言中,开头的0表示一个常数为八进制),它的低9位指明文件主(7表示可读、写、执行),同组用户(5表示可读、执行)及其他用户(1表示只可执行)的操作权限。

CREAT在创建文件的同时还以写方式将其打开,而不管文件模式是什么。CREAT返回的文件描述符fd可用于对该文件执行写操作。如果对一个已经存在的文件进行CREAT操作,在操作权限许可的情况下该文件内容将被破坏,CREAT属于已经过时的系统调用,因为OPEN系统调用也可以创建新文件,但系统仍提供CREAT以保持兼容性。

MKNOD系统调用:创建设备文件。

fd = mknod("/dev/ttyc2", 020744, 0x0402);

这将创建一个名为“/dev/ttyc2”的文件(二号控制台通常用的文件名),并将其模式代码置为八进制的020744(意为该文件是字符设备文件,保护模式为rwxr--r--),第三个参数的高字节指定其主设备号为4,低字节指定其次设备号为2。主设备号可以取任何值,但名为/dev/ttyc2的文件次设备号应当为2。MKNOD只能被超级用户使用。

OPEN系统调用:读写一个文件之前首先必须用OPEN系统调用将其打开。OPEN的第一个参数指定文件路径名,可使用绝对路径名或相对于当前工作目录的相对路径名;第二个参数指定打开方式O_RDONLY,O_WRONLY或O_RDWR(分别表示只读,只写和可读可写),OPEN返回的文件描述符可用于文件读写。文件在操作完毕后要用CLOSE关闭,这样该文件描述符可供其后的CREAT和OPEN系统调用再次使用。

READ和WRITE系统调用:

LSEEK系统调用:(1)作用。多数程序对文件的读写操作都是顺序进行的,但有些却需要随机地访问文件的任意部分。每个文件都有一个指针指明其当前读写位置。在顺序读写时,该指针通常指向下次要读写的字节。使用LSEEK系统调用可以直接修改文件指针的值,这样随后的READ或WRITE就可在文件的任一位置进行操作,甚至可以超越文件尾。(2)参数。LSEEK有三个参数:第一个指定文件描述符,第二个指定文件的位置,第三个指明文件位置是相对于文件开头、当前位置、还是文件尾。LSEEK的返回值是文件指针被修改之后的绝对位置。

STAT和FSTAT系统调用:获取文件类型(普通文件、设备文件以及目录等),文件大小,最后修改时间等等。其不同之处仅在于STAT通过文件名来指定文件,而FSTAT则使用文件描述符,这样FSTAT很适用于已打开的文件,尤其是标准输入和标准输出这类文件名可能不可知的情况。STAT和FSTAT的第二个参数指定一个用来存放所获取信息的数据结构:


图 1-12 STAT和FSTAT系统调用所用的存放返回信息的数据结构。在实际代码中,其中的某些数据类型使用符号名。

DUP系统调用:常用于对文件描述符的操作,例如一个程序需要关闭标准输出(文件描述符为1),代之以使一个普通文件成为标准输出,随后向标准输出写一些信息,最后恢复原先的状态。为实现这些功能可以先关闭文件描述符1,再打开另一个文件,这时该文件就成为标准输出(设标准输入正被使用),但这样处理无法恢复原先的标准输出。解决办法是先调用 fd = dup(1); 该操作将为标准输出分配一个新的文件描述符fd,并使对fd的操作与直接对标准输出的操作完全一样。随后将标准输出关闭,再打开一个新文件,至此该文件将被作为标准输出。当需要恢复原先的标准输出时,先关闭文件描述符1,再执行 n = dup(fd); 将最小的文件描述符号,即1,定向到fd所指的文件,最后将fd关闭就恢复了最初状态。

DUP系统调用变种:允许将任一未使用的文件描述符定向到一个指定的打开文件,其调用方法为:dup2 (fd, fd2); 此处fd指向一打开的文件,fd2为一个未使用的文件描述符,执行完这条语句后fd2将指向fd所指向的文件。若fd指向标准输入(文件描述符0),fd2为4,则在此调用后描述符0和4都指向标准输入。

PIPE系统调用创建一个管道并返回两个文件描述符,一个用于写,另一个用于读,PIPE的调用格式为:pipe(&fd[0]); 这里fd是由两个整数组成的数组,fd[0]存放供读使用的文件描述符,fd[1]存放供写使用的文件描述符。通常典型的用法是在本条语句之后调用一个fork创建一个子进程,然后父进程关掉用于读的文件描述符,子进程关掉用于写的文件描述符(或者相反),这样便可以做到一个进程从管道读数据,另一个向管道写数据。

图 1-13 建立一个两进程的管道的程序框架。

图1-13提供了一个框架过程,其中创建了两个进程,通过管道将进程1的输出导向进程2(更实际的例子还要进行错误检查,并作参数处理)。其处理过程如下:首先创建一个管道,随后调用fork,将父进程作为管道中的进程1,子进程作为进程2,由于待运行的两个文件process1和process2并不知道它们是管道的一部分,所以必须对文件描述符进行控制以使进程1的标准输出和进程2的标准输入都指向管道。父进程首先关掉从管道读的文件描述符和标准输出,随后执行DUP,这样就使文件描述符1可被用于向管道写。注意DUP总是返回最小的可用文件描述符,在这里即为1。然后程序关闭另一个管道文件描述符。在EXEC系统调用之后,父进程将保留文件描述符0和2,而文件描述符1则用于向管道中写。子进程的代码与父进程类似。execl的参数被重复,因为其第一个参数是待执行的文件名,而第二个参数是执行文件的第一个参数,对多数程序来说,该参数都是文件名。

IOCTL系统调用:适用于所有设备文件,例如它可用于设备驱动程序,SCSI设备驱动程序用它来控制磁带机和CD-ROM。但其主要还是用于字符设备文件,尤其是终端。POSIX定义了许多函数,最终都转化为IOCTL调用。库函数tcgetattr和tcsetattr使用IOCTL来改变终端的模式和各种属性。IOCTL有三个参数,例如调用tcsetattr函数设置终端参数最终将转换成以下调用:ioctl (fd, TCSETS, &termios); 第一个参数指定一个文件,第二个参数指定操作类型,第三个参数指定一个POSIX数据结构的地址,其中包含了各种标志及控制字符的数组。除了TCSETS以外,还有一些其他的操作码,其功能包括:推迟对终端参数所作的修改直到全部输出被送出,将未读取的输入信息作废,返回参数的当前值等。

终端:(1)最常用的模式是Cooked模式,在该模式下删除键和终止键能正常地工作,CTRL_S和 CTRL_Q用来停止和恢复终端输出,CTRL_D为文件结束符,按DEL键产生一个中断信号,而CTRL-\产生一个退出信号并强制进行核心映像转储。(2)在raw模式下,所有这些功能都被取消,每个字符都被不加处理地送给程序,而且不等一行结束就将从终端读到每一个字符发送给程序。与此不同的是在Cooked模式下,终端输入的数据等到一行结束才送给程序。(3)Cbreak模式介于上述两者之间,用作编辑的删除键和终止键,以及CTRL_D被屏蔽,但CTRL_S,CTRL_Q,DEL和CTRL_\则仍然有效,与raw模式一样,单个字符不等一行结束就送给程序(如果禁止行内编辑功能,则没必要等待接收到完整的一行,因为用户不可能象在cooked模式下那样改变主意并删除它)。(4)POSIX并不采用以上所列的术语Cooked, raw 和 Cbreak,POSIX中的正规模式对应于Cooked模式。在正规模式中定义了11个特殊字符,输入也是以行为单位进行。在POSIX的非正规模式中,读数据操作由一个最小接受字符数和一个以0.1秒为单位的时间段决定。POSIX标准有具有很大的灵活性,其中有许多标志可以被设置,使得非正规模式使用起来很象Cooked模式和raw模式。原先的术语:Cooked模式、raw模式和Cbreak模式更具描述性,因此以后仍使用它们。

ACCESS系统调用:检查对一个文件是否具有某种访问权限,因为有些程序可以用另一用户的用户标识号运行,所以ACCESS非常有用。

RENAME系统调:用将文件更名,其参数分别指定老文件名和新文件名。

FCNTL系统调用:对文件进行控制,它有些类似IOCTL(这两条系统调用一般被熟练程序员使用)。FCNTL有若干选项,最常用的是文件加锁。使用FCNTL可以对一个文件的一部分加锁和解锁,也可以检测一个文件的某个部分是否被上锁。FCNTL本身不包含任何与锁操作有关的语义,这要由程序员自行定义。

1.4.4 目录管理系统调用

MKDIR和RMDIR系统调用:分别用来创建和删除空目录。

LINK系统调用:允许同一个文件按不同路径名出现,一种典型的应用是允许开发小组的几个成员共享一个文件,同时该文件出现在每个人自己的目录下。共享一个文件不同于给每个人一个私有的拷贝,对前者,任何一个人所作的修改都对其他人可见-只存在一个文件;而对后者,所作的修改只有自己可见,而不会更新其他人的拷贝。

LINK的工作原理:两个用户ast和jim,在他们各自的目录下都有一些文件,如果ast执行以下语句:link ("/usr/jim/memo", "/usr/ast/note" ); 则jim目录下的文件memo将以文件名note出现在ast的目录下,此后/usr/jim/memo和/usr/ast/note指的是同一个文件。

图 1-14 (a)将/usr/jim/memo链接到ast的目录之前的两个目录。(b)同样两个目录在链接后。

理解LINK的工作原理有助于掌握其功能,MINIX中的每个文件都有一个唯一的数字 -i-节点(i-node)号来标识它。i-节点号是i-节点表的索引值,每个文件有一个i-节点,里面存放有文件主以及该文件所占用的磁盘块等信息。目录实际上也是文件,只是其内容存放的是一些i-节点和文件名的对应信息。在图1-14中,文件mail的i-节点号为16,其他文件都与此类似。LINK所做的只是创建一个新的目录项,它有一个新文件名,但i-节点号则是被链接的文件的i-节点号。图1-14(b)中,两个目录项有相同的i-节点号70,所以它们指向同一个文件。如果两个目录项的任何一个以后用UNLINK系统调用删除,另外一项还存在,则相关文件也继续存在;如果两项都被删除,那么MINIX检测到没有目录项指向该文件(i-节点中包含一个字段,它记录指向该文件的目录项数),则该文件被从磁盘上删除。

MOUNT系统调用:可将两个文件系统合并成一个。通常情况是先有一个存在于RAM盘上的根文件系统,其中包含有常用命令的可执行文件及其他常用文件。然后用户可以在驱动器0中插入一张存有用户程序的软盘。使用MOUNT系统调用就可以将软盘上的文件系统安装到根文件系统下,如图1-15所示。执行安装操作的典型C语句为:mount ("/dev/fd0", "/mnt", 0);其中第一个参数是软驱的设备文件名,第二个参数指定在文件树中的安装点。

图 1-15 (a)安装前的文件系统。 (b)安装后的文件系统。

执行完MOUNT后,软驱上的文件即可通过路径名访问而与具体的物理设备无关,就是说软盘插入哪一个驱动器都是可以的。使用MOUNT命令,用户可以将可移动介质集成到单一的集成的文件层次结构中而不必关心文件在哪台具体设备上。上例中只涉及到软盘,实际上硬盘,CD-ROM和硬盘的分区(或称次设备)都可以这样安装,当一个文件系统不再需要时,可以用UMOUNT系统调用将其卸装。

SYNC系统调用:MINIX在内存中开辟了一个缓冲区以保存最近经常访问的磁盘数据,这样可以避免再重复地从磁盘上读数据。如果缓冲区中的某数据块被修改,而在其被写回磁盘之前系统发生崩溃,那么文件系统可能被损坏。为避免这种现象,必须周期性地将这些缓冲区中的数据写回磁盘系统调用SYNC用来将被修改的缓冲区数据写回磁盘。MINIX启动后,一个名为update的程序被启动作为后台进程运转,它每隔30秒执行一次SYNC操作,将更新了的数据写回磁盘。

CHDIR和CHROOT系统调用:CHDIR改变当前工作目录,CHROOT改变根目录。若先调用chdir ("/usr/ast/test");然后打开文件xyz,则这将开"/usr/ast/test/xyz"文件。CHROOT的功能与此类似,进程改变其根目录后所有绝对路径名都将从该新的根目录开始。只有超级用户可以执行CHROOT,而且即使超级用户也很少使用CHROOT。

1.4.5 权限管理系统调用

每个文件都有一个包含11个比特的保护方式码,其中的9比特标识文件主,同组用户和其他用户的操作权限。保护方式码的另外两个比特,02000和04000分别是SETGID位和SETUID位。用户在执行一个SETUID位设置的程序时,在进程运行期间它的有效用户标识被设为文件主的用户标识号uid。该特性常被用来使一般用户可以执行那些通常只有超级用户才能执行的功能。例如只有超级用户才能使用MKNOD来创建设备文件,将MKNOD程序的属主置为超级用户,同时将其保护码设为04755就可以使一般用户能够执行MKNOD,而同时又对一般用户施以严格的限制。当进程执行一个SETUID或SETGID位设置的文件时,它便获得了与其真实uid或gid不同的有效uid或gid。

GETUID和GETGID系统调:有时进程需要获得其真实和有效的uid和gid, MINIX为此提供了GETUID和GETGID系统调用以获取这些信息。GETUID同时返回真实uid和有效uid,GETGID同时返回真实gid和有效gid。为此,系统提供了四个库例程来获取相应的信息,分别是:getuid, getgid, geteuid, getegid。一般用户不能改变他们的uid,除非执行一个SETUID的程序。但超级用户则拥有特别的权力:他可以使用SETUID系统调用设置自己的有效和真实uid,也可以使用SETGID设置有效和真实gid,他还可以使用CHOWN改变文件的属主。总之超级用户不受系统保护规则的约束。

CHMOD系统调用:可以改变文件的保护方式,例如,要使一个文件对文件主之外的所有用户只读,可以调用:chmod("file", 0644);

UMASK:UMASK设置一个内部掩码,它在创建文件时使用。例如执行了umask(022);后,CREAT和MKNOD指定的文件权限码都要与022的反码(755)相与,所得的结果才是最终的保护码,所以如下调用:creat ("file", 0777); 将把文件的保护码设置为0755而非0777。由于该掩码被子进程继承,所以如果在登录后shell执行了一次UMASK操作,则此次登录期间所有的用户进程都将受到该掩码的约束,这样就保证不会因为用户的疏忽而导致文件被非法访问。

ACCESS系统调用:对于一个文件主为ROOT的SETUID程序,因为其有效用户标识为超级用户,所以它可以访问任何文件。在许多情况下,使程序了解调用此程序的用户对某个文件是否拥有访问权限是很有意义的。实际上有意义的是检查真正uid是否对文件有访问权限,ACCESS系统调用提供了这样的检查方法,ACCESS的mode参数若为4则检查读权限,为2则检查写权限,为1检查执行权限,而且允许使用这几者的组合。例如若mode为6,则只有当进程的真正uid用户对文件可读可写时再返回成功(0),否则返回-1。当mode为0时仅检查文件是否存在,同时检查从根目录直到该文件的所有目录是否允许搜索。

1.4.6 时间管理系统调用

TIME系统调用:返回当前距1970年1月1日零时的时间,以秒为单位。

STIME:用来设置系统时间(仅由超级用户执行)。

UTIME:允许文件主(或超级用户)修改存储在文件i-节点中的时间,例如touch命令就使用UTIME将文件时间设为当前时间。

TIMES系统调用:它返回进程的计账信息,通过它可以知道进程已经占用了多少CPU时间,以及操作系统本身的操作所用的时间(即执行系统调用),同时也返回其所有子进程所用的用户时间与系统时间的总和。

1.5 操作系统结构

前面了解了操作系统的外部特性(即程序员接口),现在开始观察其内部的组成结构。介绍四种组织结构,即整体式系统,层次式系统,虚拟机系统和客户-服务器系统。

1.5.1 整体式系统

整体式系统是最常用的组织方式,但常被人们形容为“一锅粥”,其结构实际就是“无结构”,整个操作系统是一堆过程的集合,每个过程都可以调用任意其他过程。使用这种技术时,系统中的每一过程都有一个定义完好的接口,即它的人口参数和返回值,而且相互间的调用不受约束。

在整体式系统中,为了构造最终的目标操作系统程序,开发人员首先将一些独立的过程进行编译,然后用链接程序将其链接在一起成为一个单独的目标程序。从信息隐藏的观点看,它没有任何程度的隐藏-每个过程都对其他过程可见。(与此相对的是将系统分成若干个模块,信息被隐藏在这些模块内部,在外部只允许从预先定好的调用点访问这些模块)。但即使在整体式系统中,也存在一些程度很低的结构化。

图 1-16 系统调用的操作过程:(1)用户程序陷入核心 (2)操作系统确定所请求的服务编号(3)操作系统调用服务过程 (4)控制返回到用户程序

操作系统提供的服务(系统调用)的调用过程:先将参数放入预先确定的寄存器或堆栈中,然后执行一条特殊的陷入指令,即访管指令或核心调用(kernel call)指令。这条指令将机器由用户态切换到核心态,并将控制转到操作系统。

(多数CPU有两种状态:核心态:供操作系统使用,该状态下可以执行机器的所有指令;用户态:供用户程序用,该状态下I/O操作和某些其他操作不能执行。)

操作系统随后检查该调用的参数以确定应执行哪条系统调用,这如图1-16(2)所示。然后,操作系统查一张系统调用表,其中记录了每条系统调用的执行过程,这一步操作如图1-16(3)所示,它确定了将调用的服务过程。当系统调用结束后,控制又返回给用户程序(第4步),于是继续执行系统调用后面的语句。

图 1-17 整体式操作系统的简单结构模型。

这种组织方式提出了操作系统的一种基本结构1 一个用来调用被请求服务例程的主程序。2 一套执行系统调用的服务例程。3 一套支持服务例程的实用过程。在这种模型中,每一条系统调用都由一个服务例程完成;一组实用过程用来完成若干服务例程都需要用到的功能,如从用户程序获取数据等,这种将各种过程分为三层的模型如图1-17所示。

1.5.2 层次式系统

图1-17所示的系统进一步通用化就成为层次式系统,即上层软件基于下层软件之上。按此模型构造的第一个操作系统是E.W.Dijkstra和他的学生在荷兰的 Eindhoven 技术学院开发的THE系统(1968年)。THE系统是为荷兰制造的Electrologica X8计算机(内存为32K个27比特的字)配备的一个简单的批处理系统。该系统分为六层,如图1-18所示。第零层进行处理器分配,当发生中断或时钟到达期限时由该层软件进行进程切换。在第零层之上有若干个顺序进程运行,编写这些进程时就不用再考虑多个进程在单一处理器上运行的细节,总之,第零层提供了CPU基本的多道程序功能。

图 1-18 THE操作系统的结构。

第一层进行内存管理,它为进程分配内存空间,当内存用完时则会在用作对换的512K字的磁鼓上分配空间。在第一层之上,进程不用再考虑它是在内存还是在磁鼓上,因为第一层软件保证在需要访问某一页面时,它必定在内存中。

第二层软件处理进程与操作员控制台之间的通信,在第二层之上则可认为每个进程都有它自己的操作员控制台。

第三层软件管理I/O设备和相关的信息流缓冲。在第三层之上,每个进程都与适当抽象了的设备打交道而不必考虑物理设备的细节。

第四层是用户程序层,用户程序在此不必考虑进程、内存、控制台和I/O设备等环节。

系统操作员进程位于第五层

MULTICS对层次化概念进行了更进一步的通用化,它不采用层而是由许多同心的环构成,内层的环比外层的环有更高的特权级。当外层环的过程调用内层环的过程时,它必须执行一条类似系统调用的TRAP指令,TRAP指令执行前要进行严格的参数合法性检查。尽管在MULTICS中操作系统是各个用户进程地址空间的一部分,硬件仍然能够对单个进程(实际上是内存中的一个段)的读,写和执行权限进行保护。实际上THE分层方案只是在设计上提供了一些方便,因为系统的各个部分最终仍然被链接成一个完整的单个目标程序,而在MULTICS中,上述环形方案在运行中是实际存在的且由硬件实现。环形方案的一个优点是它很容易被扩展,以构造用户子系统。例如在一个系统中,教授可以写一个程序来检查学生编写的程序并打分,将教授的程序放在第n个环中运行,而将学生的程序放在第n+1个环中运行,则学生无法篡改教授给出的成绩。

1.5.3 虚拟机系统

思想:一个分时系统应该提供以下特性:(1)多道程序, (2) 一个具有比裸机更方便、界面扩展的计算机。

该系统的核心称作虚拟机监控程序,它在裸机上运行并具备多道程序功能。它向上层提供了若干台虚拟机,这如图1-19所示。但与其他操作系统不同的是:这些虚拟机不是那种具有文件等良好特征的扩展计算机,而仅仅是裸机硬件的精确复制,它包含有:核心态/用户态,I/O功能,中断,以及真实硬件具有的全部内容。

图 1-19 带CMS的VM/370结构。

因为每台虚拟机都与裸机完全一样,所以每台虚拟机可以运行裸机能够运行的任何操作系统。不同的虚拟机可以运行不同的操作系统而且往往如此。某些虚拟机运行OS/360的后续版本作批处理或事务处理,而同时另一些运行一个单用户交互系统供分时用户使用,该系统称作CMS(会话监控系统)。

当CMS上的程序执行一条系统调用时,该系统调用陷入其自己的虚拟机的操作系统,而不是VM/370,这就象在真正的计算机上一样。CMS然后发出正常的硬件I/O指令来执行该系统调用。这些I/O指令被VM/370捕获,随后VM/370执行这些指令,作为对真实硬件模拟的一部分。通过将多道程序功能和提供虚拟机分开,它们各自都更简单,更灵活和易于维护。

现在虚拟机的思想被广泛采用:例如在奔腾CPU(或其他Intel的32位CPU)上运行老的MS-DOS程序。在设计奔腾芯片的硬件和软件时,Intel和Microsoft都意识到要使老软件能够在新硬件上运行,于是Intel在奔腾芯片上提供了一个虚拟8086模式,在此模式下,奔腾机就象一台8086计算机一样,包括1M字节内的16位寻址方式。虚拟8086模式被MS-Windows, OS/2及其他操作系统用于运行MS-DOS程序。程序在虚拟8086模式下启动,执行一般的指令时它们在裸机上运行,但是当一个程序试图陷入系统来执行一条系统调用时,或者试图执行受保护的I/O操作时,将发生一条虚拟机监控程序的陷入。此时有两种设计方法:第一种,MS-DOS本身被装入虚拟8086模式的地址空间,于是虚拟机仅仅将该陷入传回给MS-DOS,这种处理与在真正的8086上运行是一样的,当MS-DOS后来试图自行执行I/O操作时,该操作被捕获而由虚拟机监控程序完成。另一种方法是虚拟机监控程序仅仅捕获第一条陷入并自己执行I/O操作,因为它知道所有的MS-DOS系统调用,并且由此知道每条陷入企图执行什么操作。这种方法不如第一种方法纯,因为它仅仅正确地模拟了MS-DOS,而不包括其他操作系统,相比之下,第一种方法可以正确地模拟其他操作系统。但另一方面,这种方法很快,因为它不再需要启动MS-DOS来执行I/O操作。在虚拟8086模式中真正地运行MS-DOS的另一个缺点是MS-DOS频繁地对中断屏蔽位进行操作,而模拟这些操作是很费时的。需要注意的是上述方法都不同于VM/370,因为它们模拟的并不是完整的奔腾硬件而只是一个8086。在VM/370系统中,可以在虚拟机上运行VM/370本身,而在奔腾系统中,不可能在虚拟8086上运行Windows,因为Windows不能在8086芯片上运行。其最低版本也需要在80286上运行,然而奔腾芯片不提供对80286的模拟。

在VM/370中,每个用户进程获得真实计算机的一个精确拷贝,在奔腾芯片的虚拟8086模式中,每个用户进程获得的是另一套硬件(8086)的精确拷贝。进一步而言,M.I.T的研究人员构造了一个系统,其中每个用户都获得真实计算机的一个拷贝,只是占用的资源是全部资源的一部分(Engler et al., 1995)。于是一台虚拟机可能占用磁盘的第0块到1023块,另一台可能占用1024到2047块等等。在核心态下运行的最底层软件是一个称作“外核”(exokernel)的程序,其任务是为虚拟机分配资源并确保资源的使用不会发生冲突。每台用户层的虚拟机可以运行其自己的操作系统,就象VM/370和奔腾的虚拟8086一样。不同在于他们各自只能使用分配给它的那部分资源。“外核”方案的优点在于它省去了一个映射层。在别的设计方案中,每台虚拟机认为它有自己独立的磁盘,编号从0到最大,于是虚拟机监控程序必须维护一张表来完成磁盘地址的映射(也包括其他资源)。有了“外核”之后,这种映射就不再需要了。外核只需记录每台虚拟机被分配的资源。这种方法的另一个好处是以较少的开销将多道程序(在外核中)与用户操作系统代码(在用户空间)分离开来,因为外核需做的工作是使各虚拟机互不干扰。

1.5.4 客户/服务器系统

VM/370将传统操作系统的大部分代码(实现扩展的计算机)分离出来放在更高的层次上,即CMS,由此使系统得以简化,但VM/370本身仍然非常复杂,因为模拟许多虚拟的370硬件不是一件简单的事情(尤其是还想作得高效时)。现代操作系统的一个趋势是将这种把代码移到更高层次的思想进一步发展,从操作系统中去掉尽可能多的东西,而只留一个最小的核心。通常的方法是将大多数操作系统功能由用户进程来实现。为了获取某项服务,比如读文件中的一块,用户进程(客户进程client process)将此请求发送给一个服务器进程(server process),服务器进程随后完成此操作并将回答信息送回。

图 1-20 客户/服务器模型。

该模型示于图1-20,核心的全部工作是处理客户与服务器间的通信,操作系统被分割成许多部分,每一部分只处理一方面的功能,如文件服务、进程服务、终端服务或存储器服务。这样每一部分变得更小,更易于管理。而且由于所有服务器以用户进程的形式运行,而不是运行在核心态,所以它们不直接访问硬件。这样处理的结果是:假如在文件服务器中发生错误,文件服务器可能崩溃,但不会导致整个系统的崩溃。

图 1-21 在一个分布式系统中的客户/服务器模型。

客户-服务器模型的另一个优点是它适用于分布式系统(参阅图1-21)。如果一个客户通过消息传递与服务器通信,客户无需知道这条消息是在本机就地处理还是通过网络送给远地机器上的服务器。在这两种情况下,客户机的处理都是一样的:发送一个请求,收回一个应答。

图1-21中所描绘的核心只处理客户与服务器之间的消息传递,这与实际系统不完全符合。某些操作系统功能(如向物理I/O设备寄存器写入命令字)靠用户空间的程序是很难完成的,解决这个问题的方法有两种:一种是设立一个运行于核心态的专用服务器进程,它具有访问硬件的绝对权力,但仍旧通过平常的消息机制与其他进程通信。另一种方法是在核心中建立一套最少的机制(mechanism),而将策略(policy)留给用户空间中的服务器进程。例如核心可能将向某特定地址发送的一条消息理解为:取该消息的内容并将其装入某台磁盘的I/O设备寄存器来启动读盘操作。此例中核心甚至不对消息的内容进行合法性检查,而只是将它们机械地拷贝进磁盘设备寄存器(显然这里需要使用某种方案以限制此类消息只能发给授权的进程)。机制与策略分离是一个重要的概念,它在操作系统的许多方面都经常出现。

1.6 各章节内容简介

典型的操作系统由四部分构成:进程管理,I/O设备管理,存储器管理和文件管理。MINIX也分为这样四部分,随后的四章将分别讨论这四个论题,第六章列出了阅读材料和参考文献。

第二章到第五章结构大致相同,首先阐述该论题的基本原理,然后介绍MINIX中的相应内容(同样适用于UNIX)。最后详细地讨论MINIX中的实现。如果读者仅对系统的基本原理感兴趣,他可以略过实现部分的内容而不会引起任何的不连贯。但是如果希望了解一个真实的操作系统(MINIX)是如何工作的,那么应该阅读包括MINIX代码在内的全部内容。

1.7 小结

对操作系统有两种观点:资源管理器观点和扩展的计算机观点。从资源管理器观点看,操作系统的任务是高效地管理整个系统的各个部分;从扩展的计算机观点看,其任务是为用户提供一台比物理计算机更易于使用的虚拟计算机。操作系统的历史很长,从早期的代替操作员手工操作的系统,到现在的多道程序系统。

任何操作系统的核心都是一套系统调用,系统调用界定了操作系统能完成的功能。对于MINIX,其系统调用分为六大类,第一类与进程创建和终止有关。第二类处理信号。第三类针对文件读写。第四类进行目录管理。第五类对信息进行保护。第六类用于时间管理。

操作系统有若干种构造模型,常见的有整体式模型,层次式模型,虚拟机模型和客户-服务器模型。

习 题

1. 操作系统的两个主要功能是什么?作为虚拟机和资源管理器

2. 什么是多道程序?

内存分为几个部分,每一部分存放不同的作业,当一个作业等待I/O操作完成时,另一个作业可以使用CPU。如果内存中可以存放足够多的作业,则CPU利用率可以接近100%。在主存中同时驻留多个作业需要特殊的硬件来对其进行保护,以避免作业的信息被窃取或受到攻击。内存划分为多个分区,每个分区存放不同作业。比如A作业正在使用I/O操作,那B或者其它作业可以使用CPU。这样多道程序可以充分利用I/O和CPU的能力,而不会浪费。

3. 什么是spooling?你认为将来高档个人计算机会将spooling作为标准特性吗?

无论任何时刻当一个作业运行结束,操作系统就能将一个新作业从磁盘读出,装入空出来的内存区域运行,这种技术叫做spooling(Simultaneous Peripheral Operation On Line - 联机的即时外部设备操作),同时该技术也用于输出。会的。

4. 在早期的计算机中,每一个字节数据的读写都是CPU直接进行处理的(那时没有DMA-直接存储器访问),这种组织结构对多道程序技术的出现有什么影响?

CPU 轮询的方式无法实现多道程序系统,因为在 I/O 期间 CPU 仍然出于忙碌状态。中断方式处理 I/O 使得实现多道程序系统成为可能,在设备控制器返回之后,接受中断之前,CPU 可以执行其他程序。

5. 为什么分时计算没有被第二代计算机广泛采用?

因为第二代计算机的设计没有采用所需要的硬件保护机制,不能很好的保护内存中的各个作业,使它们不会相互妨碍攻击。

6. 下列哪种指令应该只在核心态下执行?1、2、4只能在内核态执行

1 屏蔽所有中断

2 读时钟日期

3 设置时钟日期

4 改变存储器映像图

7. 请指出个人计算机操作系统与大型主机操作系统的不同之处。

通常公司的一个部门或大学里的一个院系配备一台小型机,而个人计算机却使每个人都能拥有自己的计算机。在商业、大学或政府部门使用的功能最强的个人计算机通常称为工作站,它实际上只是大一点的个人计算机,通常工作站之间通过网络互连起来。

8. 一个MINIX文件的属主uid为12,gid为1,该文件的权限保护码为rwxr-x---,另一用户的uid为6,gid为1,他试图执行该文件,结果如何?

UID也称为用户ID(UserID),GID也称为用户组ID(Group ID)。Groupnname/GID (组的名称和组的GID)管理员组:root,0  管理员组的GID是0;系统组:1-499(centos6) 1-999(centos7);普通组:500+(centos 6) 1000+(centos 7)。对于一个普通用户而言可以有多个不同的组,分别称之为用户的基本组(主组)和附加组;基本组组名与用户名相同,切仅仅包含一个用户,也叫私有组。基本组以外的组属于用户的附加组。

例如:[root@MKB ~]# cat /etc/passwd

root:x:0:0:root:/root:/bin/bash    //格式:(用户名:密码:UID:GID:主组,附加组:家目录:用户默认的shell)

chenjinbao:x:1002:1002::/home/chenjinbao:  //用户名:密码:UID:GID::主组

linux 查看用户的uid和gid:

1.可以通过查看/etc/passwd文件来确定自己的uid和gidcat /etc/passwd | grep 你的用户名例子:aaa@aaa:~/桌面$ cat /etc/passwd |grep aaaaaa:X:1000:1000:aaa,:/home/aaa:/bin/bash其中x后面的两个数就是uid和gid了,这里uid是1000,gid也是10002.可以直接通过id命令(1)查看当前用户的idid结果:aaa@aaa:~/桌面a$ iduid=1000(aaa) gid=1000(aaa) 组=1000(aaa)(2)查看其它用户的idid 用户名例子:查看root用户的所有idaaa@aaa:~/桌面$ id rootuid=0(root) gid=0(root) 组=0(root) 原文链接:https://blog.csdn.net/qq_17576885/article/details/121212153

第1组表示创建这个文件的用户的权限,第2组表示创建创建这个文件的用户所在的组的权限,第3组表示其他用户的权限。

解答:另一用户的gid于该文件属主的gid相同,表明同属一组;该文件的权限保护码为rwxr-x---,表示文件所有者可读、写、执行;文件用户组可读、执行;其他用户没有任何权限。因此,另一用户可执行

9. 在实际应用中,系统中仅存在一个超级用户将导致许多安全问题,为什么?

超级用户是系统中的一个特殊的用户,超级用户拥有系统的最高权限,许多保护规则对它无效。可以管理系统所有的权限,管理系统非常方便。

10. 客户-服务器模型在分布式系统中很流行,它能够用于单机系统吗?可以。

11. 在分时系统中为什么需要进程表?在个人计算机系统中只存在一个进程,它完全控制整台计算机的使用,在这样的系统中是否还需要进程表?

多道程序系统主要是为了解决因 I/O 操作造成的 CPU 算力的浪费,虽然它在宏观上可以同时运行多个程序,但无法保证每个程序都能被及时处理。分时系统会将CPU分配给若干个需要计算的作业轮流使用,因此可为多个终端提供快速的交互式服务。

在分时系统中,允许多个进程轮流使用 CPU,因此需要进程表来保存各个进程的状态。Unix 或 Windows 也同时会有多个进程轮流使用 CPU,因此也需要进程表。挂起进程需保存进程状态:进程的地址空间 - 称作核心映像(core image),以及对应的进程表项(包含寄存器值及其他信息)。

在分时系统中,每隔一定的周期,操作系统就会暂停当前进程的执行,转而启动另一个进程。进程被暂时挂起,那么后来当它需要重新运行的时候,就要求此刻的状态与先前暂停时的状态完全相同。这就意味着当我们挂起一个进程时,必须把它所有信息都要保存在某个地方(进程表)。所以只有一个进程,则不需要进程表。

12. 块设备文件和字符设备文件的本质区别是什么?能否被随机访问。

在LINUX里面,设备类型分为:字符设备、块设备以及网络设备, PCI是一种和ISA为一类的总线结构,归属于网络驱动设备。

字符设备、块设备主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,而块设备则不然,它利用一块系统内存作为缓冲区,当用户进程对设备请求能满足用户的要求时,就返回请求的数据,如果不能就调用请求函数来进行实际的I/O操作,因此,块设备主要是针对磁盘等慢速设备设计的,以免消耗过多的CPU时间来等待。

系统中能够随机(不需要按顺序)访问固定大小数据片(chunks)的设备被称作块设备,这些数据片就称作块。最常见的块设备是硬盘,除此以外,还有软盘驱动器、CD-ROM驱动器和闪存等等许多其他块设备。注意,它们都是以安装文件系统的方式使用的——这也是块设备的一般访问方式。

另一种基本的设备类型是字符设备。字符设备按照字符流的方式被有序访问,像串口和键盘就都属于字符设备。如果一个硬件设备是以字符流的方式被访问的话,那就应该将它归于字符设备;反过来,如果一个设备是随机(无序的)访问的,那么它就属于块设备。

块设备文件描述的是以随机访问的数据块为单元的设备,如磁盘。在打开一个块设备文件后,可以直接去访问它的某一个数据块,如第4个数据块,而不用考虑其文件系统的内部结构。类似地,字符设备文件指那些以字符流方式进行操作的设备,如打印机、调制解调器等。他们的本质区别是操作的对象不一样和访问数据的方式不一样,归根到底是设备特性引起的。

13. 在MINIX系统中,用户2对用户1所属的一个文件建立了一个链接,随后用户1删除了此文件,此时用户2访问该文件,其结果如何?会报文件找不到。

14. 为何CHROOT系统调用仅限于超级用户使用?(提示:信息保护问题)

chroot只能在进程拥有超级用户的权限时才能执行。chroot一般用来设置对系统的限制访问权限。

15. MINIX中为什么设立一个update进程在系统中一直运行?

MINIX3在内存里弄了一个叫块缓存区来保存访问过的数据,这样能避免重复从磁盘读取数据,这样能提高IO。但这里会有一个数据同步的问题。一个场景:缓存里的一个数据被修改,在被写回磁盘之前系统崩溃,那文件就会出现损坏。为了减少数据丢失的风险(只能减少,不能完全避免),那就要每30s执行同步操作,将缓存区的数据写到磁盘中。

16. 在什么情况下忽略SIGALRM信号是有意义的?当一个程序要后台执行时。

17. 写一个(组)程序,尝试使用MINIX的所有系统调用。对每一条系统调用,尝试使用不同的参数,包括错误参数,验证这些错误参数是否被检测出来。

https://www.cnblogs.com/onlyforcloud/articles/4465995.html

18. 写一个类似与图1-10的shell,要求其中包括足够多的代码以便能够对其进行测试。可以加入一些特性如输入,输出的重定向,管道和后台进程等。

附:其他练习题

https://blog.csdn.net/Time_Limit/article/details/124561532

你可能感兴趣的:(操作系统设计与实现(第一章 引论-操作系统概念))