操作系统结构
操作系统服务
一组操作系统服务提供对用户很有用的函数:
用户界面:所有的操作系统都有用户界面(user interface,UI)。用户界面可以有多种形式。一种是命令行界面(command-line interface,CLI),它采用文本命令,并用一定的方法输入(即一种允许输入并编辑的命令)。另一种是批界面,其中控制这些命令和命令的指令被输入文件中,通过执行文件来实现。最为常用的是图形用户界面(graphical user interface,GUI),此时界面是一个视窗系统,它具有定位设备来指挥I/O,从菜单来选择,选中部分并用键盘输入文本。有些系统还提供两种甚至这三种界面。
程序执行:系统必须能将程序装入内存并运行程序。程序必须能结束执行,包括正常或不正常结束(指明错误)。
I/O操作:运行程序可能需要I/O,这些I/O可能涉及文件或设备。对于特定设备,需要特定的功能。为了提高效率和进行保护,用户通常不能直接控制I/O设备。因此,操作系统必须提供进行I/O操作的方法。
文件系统操作:文件系统特备重要。很明显,程序需要读写文件和目录,也需要根据文件名来创建和删除文件、搜索一个给定的文件、列出文件信息。最后有些程序还包括了基于文件所有权的允许或拒绝对文件或目录的访问管理。
通信:在许多情况下,一个进程需要与另一个进程交换信息。这种通信有两种主要形式。一种是发生在同一台计算机运行的两个进程之间。另一种是运行在由网络连接起来的不同的计算机上的进程之间。通信可以通过共享内存来实现,也可以通过消息交换技术来实现(对于消息交换,消息包通过操作系统在进程之间移动)。
错误检测:操作系统需要知道可能出现的错误。错误可能发生在CPU或内存硬件(如内存错误或电源失败)、I/O设备(磁盘奇偶错出错,网络连接出错,打印机缺纸)和用户程序中(如算术溢出,试图访问非法内存地址,使用CPU时间过长)。对于每种类型的错误,操作系统应该采取适当的动作以确保正确和一致的计算。调试工具可以在很大程度上加强用户和程序员有效使用系统的能力。另外,还有一组操作系统函数,它们不是帮助用户而是确保系统本身高效运行。多用户系统通过共享计算机资源可以提高效率。
资源分配:当同时又多个用户或多个作业运行时,系统必须为它们中的每一个分配资源。操作系统管理多种不同的资源。有的资源(如CPU周期,内存和文件存储)可能要有特别的分配代码,而其他的资源(如I/O设备)可能只需要通用的请求和释放代码。例如,为了最好地使用CPU,操作系统需要采用CPU调度算法以考虑CPU的速度、必须执行的作业、可用的寄存器和其他因素。还有一些其他程序可以分配打印机,Modem、USB存储设备和其他外设。
统计:需要记录哪些用户使用了多少和什么类型的资源。这种记录可用于记账(以便让用户交费),或用于统计数据。使用统计数据对研究人员很有用。可用于重新配置系统以提高计算服务能力。
保护和安全:对于保存在多用户或网络连接的计算机系统中的信息,用户可能需要控制信息的使用。当多个进程并发执行时,一个进程不能干预另一个进程或操作系统本身。保护即确保所有对系统资源的访问是受控的。系统安全不受外界侵犯也很重要。这种安全从用户向系统证明自己(利用密码)开始,以获取对系统资源访问权限。安全也包括保护外部I/O设备,如Modem和网络适配器不受非法访问,并记录所有非法闯入的企图。如果一个系统需要保护和安全,那么系统中的所有部分都要预防。一条链子的强度与其最弱的链环相关。
系统调用
系统调用(system call)提供了操作系统提供的有效服务界面。这些调用通常用C或C++编写,当然,对底层的任务(如必须直接访问的硬件),可能会汇编语言指令的形式提供。
一个从一个文件读取数据并复制到另一个文件的简单程序的系统调用过程
不过,绝大多数程序员不会看到这些细节。一般应用程序开发人员根据应用程序接口(API)设计程序。API是一系列适用于应用程序员的函数,包括传递给每个函数的参数及其返回的程序员想得到的值。有三种应用程序员常用的API:适用于Windows系统的Win32API,适用于POSIX系统的POSIX API(包括几乎所有的UNIX,Linux和Mac OS X版本),以及用于设计运行于Java虚拟机程序的Java API。
在后台组成API的函数通常为应用程序员调用实际的系统调用。例如,Win32函数CreateProcess()(用于生成一个新的进程)实际上调用Windows内核中的NTCreateProcess()系统调用。为什么一个应用程序员宁可根据API来编程,而不是调用实际的系统调用?这有几个原因。根据API编程的好处之一在于程序的可移植性,一个采用API设计程序的应用程序员希望她的程序能在任何支持同样API的系统上编译并执行(尽管事实上,体系的不同常使其很困难)。此外,对一个应用程序员而言,实际的系统调用比API更为注重细节和困难。尽管如此,调用API中的函数和与其相关的内核系统调用之间还常常存在紧密的联系。事实上,许多Win32和POSIX的API与UNIX,Linux和Windows操作系统提供的自身的系统调用是相类似的。
绝大多数程序设计语言的运行时支持系统(与编译器一起的预先构造的函数库)提供了系统调用接口,作为应用程序与操作系统的系统调用的链接。系统调用接口截取API的函数调用,并调用操作系统中相应的系统调用。通常,每个系统调用一个与其相关的数字,系统调用接口根据这些数字维护一个列表索引。然后,系统调用接口来调用所需的操作系统内核中的系统调用,并返回系统调用状态及其他返回值。
调用者不需要知道如何执行系统调用或者执行过程中它做了什么,它只需遵循API并了解执行系统调用后,系统做了什么。因此,对于程序员,通过API操作系统接口的绝大多数细节被隐藏起来,并被执行支持库所管理。API,系统调用接口和操作系统之间的关系如下,它表现了操作系统如和处理一个调用open()系统调用的用户应用
系统调用根据使用的计算机的不同而不同。通常,需要提供比所需系统调用识别符更多的信息。这些信息的具体类型和数量根据特定操作系统和调用而有所不同。例如,为了获取输入,可能需要指定作为源的文件或设备和用于存放输入的内存区域的地址和长度。当然,设备或文件长度也可以隐含在调用中。
向操作系统传递参数有三种方法。最简单的是通过寄存器来传递参数。不过有时,参数数量会比寄存器多。这时,这些参数通常存在内存的块和表中,并将块的地址通过寄存器来传递。Linux和Solaris就采用这种方法。参数也可通过程序放在或压入堆栈中,并通过操作系统弹出。有的系统采用块或堆栈方法,因为这些方法并不限制所传递参数的数量或长度。
系统调用类型
系统调用大致可分成五大类:进程控制、文件管理、设备管理、信息维护和通信。
进程控制
运行程序需要能正常或非正常地中断其执行(end或abort)。如果一个系统调用被用来非正常地中断执行程序,或者程序运行碰到问题而引起错误陷阱,那么可能会在内存信息转储并产生一个错误信息。内存信息转储通常写到磁盘上,并被调试器(帮助程序员发现和纠正错误的系统程序)检查和确定问题原因。不管是正常还是非正常中止,操作系统都必须将控制权转交给调用命令解释器。命令解释器接着读取下一个命令。对于交互系统,命令解释器只不过简单地读取下一个命令,因为假定用户会采取合适的命令以处理错误。对于GUI系统,一个弹出窗口提醒用户出错并请求建议。对于批处理系统,命令解释器通常终止整个作业并继续下一个作业。当出现一个错误的时候,有的系统允许控制卡指出一个具体的恢复动作。控制卡是一个批处理系统概念,它是一个管理进程执行的命令。如果程序发现输入有错并想要非正常地终止,那么它可能也需要定义一个错误级别。更加严重的错误可以用更高级的错误参数来表示。如果将正常终止定义为级别为0的错误,那么可能将正常和非正常终止混合起来。命令解释器和下一个程序能利用错误级别来自动决定下一个动作。
执行一个程序的进程或作业可能需要装入和执行另一个程序。这一点允许命令解释器来执行一个程序,该命令可通过用户命令、鼠标点击或批处理命令来表示。当装入程序终止时,一个有趣的问题是控制权返回到哪里。这个问题与现有程序是否丢失、保存或与新程序继续并发执行有关。
如果新程序终止时控制权返回到现有程序,那么必须保存现有程序的内存镜像。因此,事实上创建一个机制以便一个程序调用另一个程序。如果两个程序并发继续,那么创建一个新作业和进程以便多道执行。通常,有的系统调用专门用于这一目的(如createprocess或submit job)。
如果创建一个新作业或进程,或者一组作业或一组进程,那么应该能控制它的执行。这种控制要求能决定和重置进程或作业的属性,包括作业的优先级、最大允许执行时间等(get process attributes和set process attributes)。如果发现所创建的进程不正确或不再需要,那么也要能终止它(terminate process)。
创建了新作业和进程之后,可能需要等待其完成执行。这需要等待一定时间(等待时间),更有可能需要等待某个事件的出现(等待事件)。当事件出现时,作业或进程就会响应(响应事件)。
另一组系统调用有助于调试程序。许多系统提供转储内存信息的系统调用。这有助于调试。程序trace在执行时能列出所用的每条执行的指令,但是只有少数几类系统提供。即使微处理器也提供一个称为单步的CPU模式,这种模式在每个指令运行后能执行一个陷阱。该陷阱通常为调试程序所用。
许多操作系统都提供程序的时间表,以表示一个程序在某个位置或某些位置执行所花的时间。时间表要求具有跟踪功能或定时时间中断。在每次出现定时中断时,会记录程序计数器的值。如果有足够频繁的时间中断,就可得到程序各部分所用时间的统计数据。
通信
有两种通信模型:消息传递模型和共享内存模型。对于消息传递模式(message-passing model),通信进程通过彼此之间交换消息来交换信息。直接或间接地通过一个共同的邮箱,消息可以在进程之间得到交换。在通信前,必须先打开连接。必须知道另一个通信实体的名称,它可能是同一CPU的另一个进程,也可能是通过与网络相连的另一计算机上的进程。网络上的每台计算机都有一个主机名,这通常是已知的。同样,主机也有一个网络标识,如IP地址。类似的,每个进程也有进程名,它通常转换成标识符以便操作系统引导。系统调用get hostid和get processid用于这一转换。这些标识符再传递给文件系统提供的通用open和close系统调用,或open connection和close connection系统调用,这是由系统的通信模型决定的。接受方进程通常通过accept connection调用来允许通信。能接收连接的进程为特殊用户的后台程序,这些程序是专用的系统程序。它们执行wait for connection调用,当有连接时会被唤醒。通信源被称为客户机,而接受方则被称为服务器,通过read message和write message系统调用来交换消息。close connection 调用将终止通信。
对于共享内存模型(shared-memory model),进程使用shared memory create和shared memory attach系统调用来获得其他进程所拥有的内存区域的访问权。
系统程序
图1.1,描述了计算机逻辑层次。最底层是硬件,上面是操作系统,接着是系统程序,最后是应用程序。系统程序提供了一个方便的环境,以开发程序和执行程序。其中一小部分只是系统调用的简单接口,其他的可能是相当复杂的。它们主要分为以下几类:
文件管理:这些程序创建、删除、复制、重新命名、打印、转储、列出和操作文件和目录。
状态信息:一些程序从系统那里得到日期、时间、可用内存或磁盘空间的数量、用户数或类似状态信息。另一些更为复杂,能提供详细的性能、登录和调试信息。通常,这些信息经格式化后,再打印到终端、输出设备或文件,或在GUI的窗体上显示。有些系统还支持注册表,它被用于存储和检索配置信息。
文件修改:有多个编辑器可以创建和修改位于磁盘或其他存储设备上的文件内容。也可能有特殊的命令被用于查找文件内容或完成文本的转换。
程序语言支持:常用程序设计语言(如C,C++,Java,Visual Basic和Perl等)的编译程序,汇编程序,调试程序和解释程序通常与操作系统一起提供给用户。
程序装入和执行:一旦程序汇编或编译后,它必须转入内存才能执行。系统可能要提供绝对加载程序、重定位加载程序、链接编辑器和覆盖式加载程序。系统还需要有高级的语言或机器语言的调试程序。
通信:这些程序提供了在进程、用户和计算机系统之间创建虚拟连接的机制。他们允许用户在互相的屏幕上发送消息,浏览网页,发送电子邮件,远程登录,从一台机器向另一台机器传送文件。
绝大多数用户所看到的操作系统是由应用和系统程序而不是系统调用所决定的。
操作系统结构
简单结构
另一个受限结构的例子是原始的UNIX操作系统。UNIX是另一个最初受到硬件功能限制的系统。它由内核和系统程序两个独立部分组成。内核进一步分成为一系列接口和驱动程序。物理硬件之上和系统调用接口之下的所有部分作为内核。内核通过系统调用以提供文件系统,CPU调度,内存管理和其他操作系统功能。
分层方法
采用适当的硬件支持,操作系统可以分成比原来MS-DOS和UNIX所允许的更小和更合适的模块。这样操作系统能提供对计算机和使用计算机的应用程序更多的控制。实现人员能更加自由地改变系统内部工作和创建模块操作系统。采用自顶向下方法,可先确定总的功能和特征,再划分成模块。隐藏信息同样很重要,因为它在保证子程序接口不变和子程序本身执行其功能的前提下,允许程序员自由地实现底层函数。
系统模块化有许多方法。一种方法是分层法,即操作系统分成若干层(级)。
操作系统层可作为抽象对象来实现,该对象包括数据和操作这些数据的操作。
微内核
微内核将所有非基本部分从内核中移走,并将它们实现为系统程序或用户程序。微内核通常包括最小的进程和内存管理以及通信功能。
微内核的主要功能是使客户程序和运行在用户空间的各种服务之间进行通信。通信以消息传递形式提供。
微内核方法的好处之在于便于扩充操作系统。所有新服务可以在用户空间增加,因而并不需要修改内核。当内核确实需要改变时,所做的改变也很小,因为微内核本身很小。
模块
最新的操作系统设计方法是面向对象编程技术来生成模块化的内核。这里内核有一组核心部件,以及在启动或运行时对附加服务的动态链接。这种方法使用动态加载模块,并在现代的UNIX,如Solaris,Linux和Mac OS X中很常见。
Solaris操作系统结构被组织为7个可加载的内核模块围绕一个核心内核构成
调度类
文件系统
可加载的系统调用
可执行格式
STREAMS模块
杂项模块
设备和总线驱动
这样设计允许内核提供核心服务,也能动态地实现特定的功能。例如,特定硬件的设备和总线驱动程序可以加载给内核,而对各种文件系统的支持也可作为可加载的模块加入其中。所得到的结果就好像一个分层系统,它的每个内核部分都有被定义和保护的接口。但它比分层系统更为灵活,它的任一模块都能调用任何其他模块。进一步讲,这种方法类似微内核方法,核心模块只有核心功能以及其他模块加载和通信的相关信息,但这种方法更为高效,因为模块不需要调用消息传递来通信。
虚拟机
分层的逻辑概念可以延伸为虚拟机概念。虚拟机基本思想是单个计算机(CPU、内存、磁盘、网卡等)的硬件抽象为几个不同的执行部件,从而造成一种"幻觉",仿佛每个独立的执行环境都在自己的计算机上运行一样。
通过利用CPU调度和虚拟内存技术,操作系统能带来一种"幻觉",即进程认为有自己的处理器和自己的内存。
系统生成
对于某个特定的计算机场所,必须要配置和生成系统,这一过程有时称为系统生成(system generation SYSGEN)。
系统启动
在生成操作系统后,它必须要为硬件所使用。但是硬件如何知道内核在哪里,或者如何装入内核?装入内核以启动计算机的过程称为引导系统。绝大多数计算机都有一小块代码,它称为引导程序或引导装载程序。这段代码能定位内核,将它装入内存,开始执行。有的计算机系统,如个人计算机,采用两步完成:一个简单的引导程序从磁盘上调入一个复杂的引导程序,而后者再装入内核。
当CPU接收到一个重置事件时,例如它被加电或重新启动,具有预先定义内存位置的指令寄存器被重新加载,并在此开始执行。该位置就是初始引导程序所在。该程序为只读存储器(ROM)形式,因为系统启动时RAM处于未知状态。由于不需要初始化和不受计算机病毒的影响,用ROM很方便的。
引导程序可以完成一系列任务。通常,一个任务要运行诊断程序来确定机器的状态。如果诊断通过,程序可按启动步骤继续进行。系统的所有部分都可以被初始化,从CPU寄存器到设备控制器,以及内存的内容。最后,操作系统得以启动。
对于大型操作系统(包括大多数通用的操作系统,如Windows,Mac OS X 和 UNIX)或经常改变的系统,引导程序被存储在固件中,而操作系统保存在磁盘上。此时,引导程序运行诊断程序,它具有能够从磁盘固定位置(0区块)读取整块信息到内存的代码,并从引导块执行代码。存储在引导块的程序多半足够复杂,可以将一个完整的操作系统装载到内存并开始执行。
既然完全的引导程序已被装入,它可以扫描文件系统以找到操作系统内核,将之装入内存,启动并执行。只有到了这个时候才能说系统开始运行了。
小结
操作系统提供若干服务。在最底层,系统调用允许运行程序直接向操作系统发出请求。在高层,命令解释程序或shell提供了一个机制以便用户不必编写程序就能发出请求。命令可以来自文件(批处理模式),或者直接来自键盘输入(交互模式或分时模式)。系统程序用来满足一些常用用户操作。
请求类型随请求级别而变化。系统调用级别提供基本功能,如进程控制,文件和设备管理。由命令解释程序或系统程序来完成的高级别请求需要转换成一系列的系统请求。系统服务可分成许多类型:程序控制、状态请求和I/O请求。程序出错可作为对服务的一种隐式请求。
在定义了系统服务之后,就可开发操作系统的结构。需要各种表记录一些信息,这些信息定义了计算机的系统状态和系统的作业状态。
设计一个新操作系统是一项重大任务。在设计开始之前,定义好系统目标是很重要的。系统设计的类型是作为选择各种必要算法和策略的基础。
由于操作系统大,所以模块化很重要。按一系列层或采用微内核来设计系统是比较好的技术。虚拟机概念采用了分层方法,并将操作系统内核和硬件都作为硬件来考虑。其他操作系统可以建立在这一虚拟机之上。
实现JVM的任何操作系统能运行所有Java程序,因为JVM为Java程序抽象化了底层系统,以提供平台无关接口。
在整个操作系统设计周期中,必须仔细区分策略决定和实现细节(机制)。在后面需要修改策略时,这种区分允许最大限度地灵活性。
现在操作系统几乎都是用系统实现语言或高级语言来编写的。这一特征改善了操作系统的实现、维护和可移植性。为特定机器配置并创建操作系统,必须执行系统生成。
为了运行计算机系统,必须初始化CPU和在固件系统中启动执行引导程序。如果操作系统也在固件系统中,引导程序可以直接启动操作系统。否则,它必须完成这样一道程序:逐步地从固件或磁盘装载更聪明的程序,直到操作系统本身被装入内存并执行。
原文地址:https://www.cnblogs.com/kexinxin/p/9939040.html