点击蓝色“Java建设者”关注我哟html
加个“星标”,一块儿走向人生巅峰!java
文章主要结构图以下node
操做系统
现代计算机系统由一个或多个处理器、主存、打印机、键盘、鼠标、显示器、网络接口以及各类输入/输出设备构成。git
然而,程序员不会直接和这些硬件打交道,并且每位程序员不可能会掌握全部计算机系统的细节,这样咱们就不用再编写代码了,因此在硬件的基础之上,计算机安装了一层软件,这层软件可以经过响应用户输入的指令达到控制硬件的效果,从而知足用户需求,这种软件称之为 操做系统
,它的任务就是为用户程序提供一个更好、更简单、更清晰的计算机模型。程序员
咱们通常常见的操做系统主要有 Windows、Linux、FreeBSD 或 OS X ,这种带有图形界面的操做系统被称为 图形用户界面(Graphical User Interface, GUI)
,而基于文本、命令行的一般称为 Shell
。下面是咱们所要探讨的操做系统的部件web
这是一个操做系统的简化图,最下面的是硬件,硬件包括芯片、电路板、磁盘、键盘、显示器等咱们上面提到的设备,在硬件之上是软件。大部分计算机有两种运行模式:内核态
和 用户态
,软件中最基础的部分是操做系统
,它运行在 内核态
中,内核态也称为 管态
和 核心态
,它们都是操做系统的运行状态,只不过是不一样的叫法而已。操做系统具备硬件的访问权,能够执行机器可以运行的任何指令。软件的其他部分运行在 用户态
下。
算法
用户接口程序(shell 或者 GUI)
处于用户态中,而且它们位于用户态的最低层,容许用户运行其余程序,例如 Web 浏览器、电子邮件阅读器、音乐播放器等。并且,越靠近用户态的应用程序越容易编写,若是你不喜欢某个电子邮件阅读器你能够从新写一个或者换一个,但你不能自行写一个操做系统或者是中断处理程序。这个程序由硬件保护,防止外部对其进行修改。shell
计算机硬件简介
操做系统与运行操做系统的内核硬件关系密切。操做系统扩展了计算机指令集并管理计算机的资源。所以,操做系统所以必须足够了解硬件的运行,这里咱们先简要介绍一下现代计算机中的计算机硬件。编程
从概念上来看,一台简单的我的电脑能够被抽象为上面这种类似的模型,CPU、内存、I/O 设备都和总线串联起来并经过总线与其余设备进行通讯。现代操做系统有着更为复杂的结构,会设计不少条总线,咱们稍后会看到。暂时来说,这个模型可以知足咱们的讨论。bootstrap
CPU
CPU 是计算机的大脑,它主要和内存进行交互,从内存中提取指令并执行它。一个 CPU 的执行周期是从内存中提取第一条指令、解码并决定它的类型和操做数,执行,而后再提取、解码执行后续的指令。重复该循环直到程序运行完毕。
每一个 CPU 都有一组能够执行的特定指令集。所以,x86 的 CPU 不能执行 ARM 的程序而且 ARM 的 CPU 也不能执行 x86 的程序。因为访问内存获取执行或数据要比执行指令花费的时间长,所以全部的 CPU 内部都会包含一些寄存器
来保存关键变量和临时结果。所以,在指令集中一般会有一些指令用于把关键字从内存中加载到寄存器中,以及把关键字从寄存器存入到内存中。还有一些其余的指令会把来自寄存器和内存的操做数进行组合,例如 add 操做就会把两个操做数相加并把结果保存到内存中。
除了用于保存变量和临时结果的通用寄存器外,大多数计算机还具备几个特殊的寄存器,这些寄存器对于程序员是可见的。其中之一就是 程序计数器(program counter)
,程序计数器会指示下一条须要从内存提取指令的地址。提取指令后,程序计数器将更新为下一条须要提取的地址。
另外一个寄存器是 堆栈指针(stack pointer)
,它指向内存中当前栈的顶端。堆栈指针会包含输入过程当中的有关参数、局部变量以及没有保存在寄存器中的临时变量。
还有一个寄存器是 PSW(Program Status Word)
程序状态字寄存器,这个寄存器是由操做系统维护的8个字节(64位) long 类型的数据集合。它会跟踪当前系统的状态。除非发生系统结束,不然咱们能够忽略 PSW 。用户程序一般能够读取整个PSW,但一般只能写入其某些字段。PSW 在系统调用和 I / O 中起着重要做用。
操做系统必须了解全部的寄存器。在时间多路复用(time multiplexing)
的 CPU 中,操做系统每每中止运行一个程序转而运行另一个。每次当操做系统中止运行一个程序时,操做系统会保存全部寄存器的值,以便于后续从新运行该程序。
为了提高性能, CPU 设计人员早就放弃了同时去读取、解码和执行一条简单的指令。许多现代的 CPU 都具备同时读取多条指令的机制。例如,一个 CPU 可能会有单独访问、解码和执行单元,因此,当 CPU 执行第 N 条指令时,还能够对 N + 1 条指令解码,还能够读取 N + 2 条指令。像这样的组织形式被称为 流水线(pipeline)。
比流水线更先进的设计是 超标量(superscalar)
CPU,下面是超标量 CPU 的设计
在上面这个设计中,存在多个执行单元,例如,一个用来进行整数运算、一个用来浮点数运算、一个用来布尔运算。两个或者更多的指令被一次性取出、解码并放入缓冲区中,直至它们执行完毕。只要一个执行单元空闲,就会去检查缓冲区是否有能够执行的指令。若是有,就把指令从缓冲区中取出并执行。这种设计的含义是应用程序一般是无序执行的。在大多数状况下,硬件负责保证这种运算的结果与顺序执行指令时的结果相同。
除了用在嵌入式系统中很是简单的 CPU 以外,多数 CPU 都有两种模式
,即前面已经提到的内核态和用户态。一般状况下,PSW 寄存器
中的一个二进制位会控制当前状态是内核态仍是用户态。当运行在内核态时,CPU 可以执行任何指令集中的指令而且可以使用硬件的功能。在台式机和服务器上,操做系统一般之内核模式运行,从而能够访问完整的硬件。在大多数嵌入式系统中,一部分运行在内核态下,剩下的一部分运行在用户态下。
用户应用程序一般运行在用户态下,在用户态下,CPU 只能执行指令集中的一部分而且只能访问硬件的一部分功能。通常状况下,在用户态下,有关 I/O 和内存保护的全部指令是禁止执行的。固然,设置 PSW 模式的二进制位为内核态也是禁止的。
为了获取操做系统的服务,用户程序必须使用 系统调用(system call)
,系统调用会转换为内核态而且调用操做系统。TRAP
指令用于把用户态切换为内核态并启用操做系统。当有关工做完成以后,在系统调用后面的指令会把控制权交给用户程序。咱们会在后面探讨操做系统的调用细节。
须要注意的是操做系统在进行系统调用时会存在陷阱。大部分的陷阱会致使硬件发出警告,好比说试图被零除或浮点下溢等你。在全部的状况下,操做系统都能获得控制权并决定如何处理异常状况。有时,因为出错的缘由,程序不得不中止。
多线程和多核芯片
Intel Pentinum 4也就是奔腾处理器引入了被称为多线程(multithreading)
或 超线程(hyperthreading, Intel 公司的命名)
的特性,x86 处理器和其余一些 CPU 芯片就是这样作的。包括 SSPARC、Power五、Intel Xeon 和 Intel Core 系列 。近似地说,多线程容许 CPU 保持两个不一样的线程状态而且在纳秒级(nanosecond)
的时间完成切换。线程是一种轻量级的进程,咱们会在后面说到。例如,若是一个进程想要从内存中读取指令(这一般会经历几个时钟周期),多线程 CPU 则能够切换至另外一个线程。多线程不会提供真正的并行处理。在一个时刻只有一个进程在运行。
对于操做系统来说,多线程是有意义的,由于每一个线程对操做系统来讲都像是一个单个的 CPU。好比一个有两个 CPU 的操做系统,而且每一个 CPU 运行两个线程,那么这对于操做系统来讲就多是 4 个 CPU。
除了多线程以外,如今许多 CPU 芯片上都具备四个、八个或更多完整的处理器或内核。多核芯片在其上有效地承载了四个微型芯片,每一个微型芯片都有本身的独立CPU。
若是要说在绝对核心数量方面,没有什么能赢过现代 GPU(Graphics Processing Unit)
,GPU 是指由成千上万个微核组成的处理器。它们擅长处理大量并行的简单计算。
内存
计算机中第二个主要的组件就是内存。理想状况下,内存应该很是快速(比执行一条指令要快,从而不会拖慢 CPU 执行效率),并且足够大且便宜,可是目前的技术手段没法知足三者的需求。因而采用了不一样的处理方式,存储器系统采用一种分层次的结构
顶层的存储器速度最高,可是容量最小,成本很是高,层级结构越向下,其访问效率越慢,容量越大,可是造价也就越便宜。
寄存器
存储器的顶层是 CPU 中的寄存器
,它们用和 CPU 同样的材料制成,因此和 CPU 同样快。程序必须在软件中自行管理这些寄存器(即决定如何使用它们)
高速缓存
位于寄存器下面的是高速缓存
,它多数由硬件控制。主存被分割成高速缓存行(cache lines)
为 64 字节,内存地址的 0 - 63 对应高速缓存行 0 ,地址 64 - 127 对应高速缓存行的 1,等等。使用最频繁的高速缓存行保存在位于 CPU 内部或很是靠近 CPU 的高速缓存中。当应用程序须要从内存中读取关键词的时候,高速缓存的硬件会检查所须要的高速缓存行是否在高速缓存中。若是在的话,那么这就是高速缓存命中(cache hit)
。高速缓存知足了该请求,而且没有经过总线将内存请求发送到主内存。高速缓存命中一般须要花费两个时钟周期。缓存未命中须要从内存中提取,这会消耗大量的时间。高速缓存行会限制容量的大小由于它的造价很是昂贵。有一些机器会有两个或者三个高速缓存级别,每一级高速缓存比前一级慢且容量更大。
缓存在计算机不少领域都扮演了很是重要的角色,不只仅是 RAM 缓存行。
“随机存储器(RAM):内存中最重要的一种,表示既能够从中读取数据,也能够写入数据。当机器关闭时,内存中的信息会
丢失
。
大量的可用资源被划分为小的部分,这些可用资源的一部分会得到比其余资源更频繁的使用权,缓存常常用来提高性能。操做系统无时无刻的不在使用缓存。例如,大多数操做系统在主机内存中保留(部分)频繁使用的文件,以免重复从磁盘重复获取。举个例子,相似于 /home/ast/projects/minix3/src/kernel/clock.c
这样的场路径名转换成的文件所在磁盘地址的结果也能够保存缓存中,以免重复寻址。另外,当一个 Web 页面(URL) 的地址转换为网络地址(IP地址)后,这个转换结果也能够缓存起来供未来使用。
在任何缓存系统中,都会有下面这几个噬需解决的问题
-
什么时候把新的内容放进缓存 -
把新的内容应该放在缓存的哪一行 -
在须要空闲空间时,应该把哪块内容从缓存中移除 -
应该把移除的内容放在某个较大存储器的何处
并非每一个问题都与每种缓存状况有关。对于 CPU 缓存中的主存缓存行,当有缓存未命中时,就会调入新的内容。一般经过所引用内存地址的高位计算应该使用的缓存行。
缓存是解决问题的一种好的方式,因此现代 CPU 设计了两种缓存。第一级缓存或者说是 L1 cache
老是位于 CPU 内部,用来将已解码的指令调入 CPU 的执行引擎。对于那些频繁使用的关键字,多数芯片有第二个 L1 cache 。典型的 L1 cache 的大小为 16 KB。另外,每每还设有二级缓存,也就是 L2 cache
,用来存放最近使用过的关键字,通常是兆字节为单位。L1 cache 和 L2 cache 最大的不一样在因而否存在延迟。访问 L1 cache 没有任何的延迟,然而访问 L2 cache 会有 1 - 2 个时钟周期的延后。
“什么是时钟周期?计算机处理器或 CPU 的速度由时钟周期来肯定,该时钟周期是振荡器两个脉冲之间的时间量。通常而言,每秒脉冲数越高,计算机处理器处理信息的速度就越快。时钟速度以 Hz 为单位测量,一般为兆赫(MHz)或千兆赫(GHz)。例如,一个4 GHz处理器每秒执行4,000,000,000个时钟周期。
计算机处理器能够在每一个时钟周期执行一条或多条指令,这具体取决于处理器的类型。早期的计算机处理器和较慢的 CPU 在每一个时钟周期只能执行一条指令,而现代处理器在每一个时钟周期能够执行多条指令。
主存
在上面的层次结构中再下一层是主存
,这是内存系统的主力军,主存一般叫作 RAM(Random Access Memory)
,因为 1950 年代和 1960 年代的计算机使用微小的可磁化铁氧体磁芯做为主存储器,所以旧时有时将其称为核心存储器。全部不能再高速缓存中获得知足的内存访问请求都会转往主存中。
除了主存以外,许多计算机还具备少许的非易失性随机存取存储器。它们与 RAM 不一样,在电源断电后,非易失性随机访问存储器并不会丢失内容。ROM(Read Only Memory)
中的内容一旦存储后就不会再被修改。它很是快并且便宜。(若是有人问你,有没有什么又快又便宜的内存设备,那就是 ROM 了)在计算机中,用于启动计算机的引导加载模块(也就是 bootstrap )就存放在 ROM 中。另外,一些 I/O 卡也采用 ROM 处理底层设备控制。
EEPROM(Electrically Erasable PROM,)
和 闪存(flash memory)
也是非易失性的,可是与 ROM 相反,它们能够擦除和重写。不太重写它们须要比写入 RAM 更多的时间,因此它们的使用方式与 ROM 相同,可是与 ROM 不一样的是他们能够经过重写字段来纠正程序中出现的错误。
闪存也一般用来做为便携性的存储媒介。闪存是数码相机中的胶卷,是便携式音乐播放器的磁盘。闪存的速度介于 RAM 和磁盘之间。另外,与磁盘存储器不一样的是,若是闪存擦除的次数太多,会出现磨损。
还有一类是 CMOS,它是易失性的。许多计算机都会使用 CMOS 存储器保持当前时间和日期。
磁盘
下一个层次是磁盘(硬盘)
,磁盘同 RAM 相比,每一个二进制位的成本低了两个数量级,并且常常也有两个数量级大的容量。磁盘惟一的问题是随机访问数据时间大约慢了三个数量级。磁盘访问慢的缘由是由于磁盘的构造不一样
磁盘是一种机械装置,在一个磁盘中有一个或多个金属盘片,它们以 5400rpm、7200rpm、10800rpm 或更高的速度旋转。从边缘开始有一个机械臂悬横在盘面上,这相似于老式播放塑料唱片 33 转唱机上的拾音臂。信息会写在磁盘一系列的同心圆上。在任意一个给定臂的位置,每一个磁头能够读取一段环形区域,称为磁道(track)
。把一个给定臂的位置上的全部磁道合并起来,组成了一个柱面(cylinder)
。
每一个磁道划分若干扇区,扇区的值是 512 字节。在现代磁盘中,较外部的柱面比较内部的柱面有更多的扇区。机械臂从一个柱面移动到相邻的柱面大约须要 1ms。而随机移到一个柱面的典型时间为 5ms 至 10ms,具体状况以驱动器为准。一旦磁臂到达正确的磁道上,驱动器必须等待所需的扇区旋转到磁头之下,就开始读写,低端硬盘的速率是50MB/s
,而高速磁盘的速率是 160MB/s
。
“须要注意,
固态硬盘(Solid State Disk, SSD)
不是磁盘,固态硬盘并无能够移动的部分,外形也不像唱片,而且数据是存储在存储器(闪存)
中,与磁盘惟一的类似之处就是它也存储了大量即便在电源关闭也不会丢失的数据。
许多计算机支持一种著名的虚拟内存
机制,这种机制使得指望运行的存储空间大于实际的物理存储空间。其方法是将程序放在磁盘上,而将主存做为一部分缓存,用来保存最频繁使用的部分程序,这种机制须要快速映像内存地址,用来把程序生成的地址转换为有关字节在 RAM 中的物理地址。这种映像由 CPU 中的一个称为 存储器管理单元(Memory Management Unit, MMU)
的部件来完成。
缓存和 MMU 的出现是对系统的性能有很重要的影响,在多道程序系统中,从一个程序切换到另外一个程序的机制称为 上下文切换(context switch)
,对来自缓存中的资源进行修改并把其写回磁盘是颇有必要的。
I/O 设备
CPU 和存储器不是操做系统须要管理的所有,I/O
设备也与操做系统关系密切。能够参考上面这个图片,I/O 设备通常包括两个部分:设备控制器和设备自己。控制器自己是一块芯片或者一组芯片,它可以控制物理设备。它可以接收操做系统的指令,例如,从设备中读取数据并完成数据的处理。
在许多状况下,实际控制设备的过程是很是复杂并且存在诸多细节。所以控制器的工做就是为操做系统提供一个更简单(但仍然很是复杂)的接口。也就是屏蔽物理细节。任何复杂的东西均可以加一层代理来解决,这是计算机或者人类社会很普世的一个解决方案
I/O 设备另外一部分是设备自己,设备自己有一个相对简单的接口,这是由于接口既不能作不少工做,并且也已经被标准化了。例如,标准化后任何一个 SATA 磁盘控制器就能够适配任意一种 SATA 磁盘,因此标准化是必要的。ATA
表明 高级技术附件(AT Attachment)
,而 SATA 表示串行高级技术附件(Serial ATA)
。
“AT 是啥?它是 IBM 公司的第二代我的计算机的
高级
技术成果,使用 1984 年推出的 6MHz 80286 处理器,这个处理器是当时最强大的。
像是高级这种词汇应该慎用,不然 20 年后再回首极可能会被无情打脸。
如今 SATA 是不少计算机的标准硬盘接口。因为实际的设备接口隐藏在控制器中,因此操做系统看到的是对控制器的接口,这个接口和设备接口有很大区别。
每种类型的设备控制器都是不一样的,因此须要不一样的软件进行控制。专门与控制器进行信息交流,发出命令处理指令接收响应的软件,称为 设备驱动程序(device driver)
。每一个控制器厂家都应该针对不一样的操做系统提供不一样的设备驱动程序。
为了使设备驱动程序可以工做,必须把它安装在操做系统中,这样可以使它在内核态中运行。要将设备驱动程序装入操做系统,通常有三个途径
-
第一个途径是将内核与设备启动程序从新链接,而后重启系统。这是 UNIX
系统采用的工做方式 -
第二个途径是在一个操做系统文件中设置一个入口,通知该文件须要一个设备驱动程序,而后从新启动系统。在从新系统时,操做系统回寻找有关的设备启动程序并把它装载,这是 Windows
采用的工做方式 -
第三个途径是操做系统可以在运行时接收新的设备驱动程序并马上安装,无需重启操做系统,这种方式采用的少,可是正变得普及起来。热插拔设备,好比 USB 和 IEEE 1394 都须要动态可装载的设备驱动程序。
每一个设备控制器都有少许用于通讯的寄存器,例如,一个最小的磁盘控制器也会有用于指定磁盘地址、内存地址、扇区计数的寄存器。要激活控制器,设备驱动程序回从操做系统获取一条指令,而后翻译成对应的值,并写入设备寄存器中,全部设备寄存器的结合构成了 I/O 端口空间
。
在一些计算机中,设备寄存器会被映射到操做系统的可用地址空间,使他们可以向内存同样完成读写操做。在这种计算机中,不须要专门的 I/O 指令,用户程序能够被硬件阻挡在外,防止其接触这些存储器地址(例如,采用基址寄存器和变址寄存器)。在另外一些计算机中,设备寄存器被放入一个专门的 I/O 端口空间,每一个寄存器都有一个端口地址。在这些计算机中,特殊的 IN
和 OUT
指令会在内核态下启用,它可以容许设备驱动程序和寄存器进行读写。前面第一种方式会限制特殊的 I/O 指令可是容许一些地址空间;后者不须要地址空间可是须要特殊的指令,这两种应用都很普遍。
实现输入和输出的方式有三种。
-
在最简单的方式中,用户程序会发起系统调用,内核会将其转换为相应驱动程序的程序调用,而后设备驱动程序启动 I/O 并循环检查该设备,看该设备是否完成了工做(通常会有一些二进制位用来指示设备仍在忙碌中)。当 I/O 调用完成后,设备驱动程序把数据送到指定的地方并返回。而后操做系统会将控制权交给调用者。这种方式称为 忙等待(busy waiting)
,这种方式的缺点是要一直占据 CPU,CPU 会一直轮询 I/O 设备直到 I/O 操做完成。 -
第二种方式是设备驱动程序启动设备而且让该设备在操做完成时发生中断。设备驱动程序在这个时刻返回。操做系统接着在须要时阻塞调用者并安排其余工做进行。当设备驱动程序检测到该设备操做完成时,它发出一个 中断
通知操做完成。
在操做系统中,中断是很是重要的,因此这须要更加细致的讨论一下。
如上图所示,这是一个三步的 I/O 过程,第一步,设备驱动程序会经过写入设备寄存器告诉控制器应该作什么。而后,控制器启动设备。当控制器完成读取或写入被告知须要传输的字节后,它会在步骤 2 中使用某些总线向中断控制器发送信号。若是中断控制器准备好了接收中断信号(若是正忙于一个优先级较高的中断,则可能不会接收),那么它就会在 CPU 的一个引脚上面声明。这就是步骤3
在第四步中,中断控制器把该设备的编号放在总线上,这样 CPU 能够读取总线,而且知道哪一个设备完成了操做(可能同时有多个设备同时运行)。
一旦 CPU 决定去实施中断后,程序计数器和 PSW 就会被压入到当前堆栈中而且 CPU 会切换到内核态。设备编号能够做为内存的一个引用,用来寻找该设备中断处理程序的地址。这部份内存称做中断向量(interrupt vector)
。一旦中断处理程序(中断设备的设备驱动程序的一部分)开始后,它会移除栈中的程序计数器和 PSW 寄存器,并把它们进行保存,而后查询设备的状态。在中断处理程序所有完成后,它会返回到先前用户程序还没有执行的第一条指令,这个过程以下
-
实现 I/O 的第三种方式是使用特殊的硬件: 直接存储器访问(Direct Memory Access, DMA)
芯片。它能够控制内存和某些控制器之间的位流,而无需 CPU 的干预。CPU 会对 DMA 芯片进行设置,说明须要传送的字节数,有关的设备和内存地址以及操做方向。当 DMA 芯片完成后,会形成中断,中断过程就像上面描述的那样。咱们会在后面具体讨论中断过程
当另外一个中断处理程序正在运行时,中断可能(而且常常)发生在不合宜的时间。所以,CPU 能够禁用中断,而且能够在以后重启中断。在 CPU 关闭中断后,任何已经发出中断的设备,能够继续保持其中断信号处理,可是 CPU 不会中断,直至中断再次启用为止。若是在关闭中断时,已经有多个设备发出了中断信号,中断控制器将决定优先处理哪一个中断,一般这取决于事先赋予每一个设备的优先级,最高优先级的设备优先赢得中断权,其余设备则必须等待。
总线
上面的结构(简单我的计算机的组件图)在小型计算机已经使用了多年,并用在早期的 IBM PC 中。然而,随着处理器核内存变得愈来愈快,单个总线处理全部请求的能力也达到了上线,其中也包括 IBM PC 总线。必须放弃使用这种模式。其结果致使了其余总线的出现,它们处理 I/O 设备以及 CPU 到存储器的速度都更快。这种演变的结果致使了下面这种结构的出现。
上图中的 x86 系统包含不少总线,高速缓存、内存、PCIe、PCI、USB、SATA 和 DMI,每条总线都有不一样的传输速率和功能。操做系统必须了解全部的总线配置和管理。其中最主要的总线是 PCIe(Peripheral Component Interconnect Express)
总线。
Intel 发明的 PCIe 总线也是做为以前古老的 PCI 总线的继承者,而古老的 PCI 总线也是为了取代古董级别的 ISA(Industry Standard Architecture)
总线而设立的。数十 Gb/s 的传输能力使得 PCIe 比它的前身快不少,并且它们本质上也十分不一样。直到发明 PCIe 的 2004 年,大多数总线都是并行且共享的。共享总线架构(shared bus architeture)
表示多个设备使用一些相同的电线传输数据。所以,当多个设备同时发送数据时,此时你须要一个决策者来决定谁可以使用总线。而 PCIe 则不同,它使用专门的端到端链路。传统 PCI 中使用的并行总线架构(parallel bus architecture)
表示经过多条电线发送相同的数据字。例如,在传统的 PCI 总线上,一个 32 位数据经过 32 条并行的电线发送。而 PCIe 则不一样,它选用了串行总线架构(serial bus architecture)
,并经过单个链接(称为通道)发送消息中的全部比特数据,就像网络数据包同样。这样作会简化不少,由于再也不确保全部 32 位数据在同一时刻准确到达相同的目的地。经过将多个数据通路并行起来,并行性仍能够有效利用。例如,能够使用 32 条数据通道并行传输 32 条消息。
在上图结构中,CPU 经过 DDR3 总线与内存对话,经过 PCIe 总线与外围图形设备 (GPU)对话,经过 DMI(Direct Media Interface)
总线经集成中心与全部其余设备对话。而集成控制中心经过串行总线与 USB 设备对话,经过 SATA 总线与硬盘和 DVD 驱动器对话,经过 PCIe 传输以太网络帧。
不只如此,每个核
USB(Univversal Serial Bus)
是用来将全部慢速 I/O 设备(好比键盘和鼠标)与计算机相连的设备。USB 1.0 能够处理总计 12 Mb/s 的负载,而 USB 2.0 将总线速度提升到 480Mb/s ,而 USB 3.0 能达到不小于 5Gb/s 的速率。全部的 USB 设备均可以直接链接到计算机并可以马上开始工做,而不像以前那样要求重启计算机。
SCSI(Small Computer System Interface)
总线是一种高速总线,用在高速硬盘,扫描仪和其余须要较大带宽的设备上。如今,它们主要用在服务器和工做站中,速度能够达到 640MB/s 。
计算机启动过程
那么有了上面一些硬件再加上操做系统的支持,咱们的计算机就能够开始工做了,那么计算机的启动过程是怎样的呢?下面只是一个简要版的启动过程
在每台计算机上有一块双亲板,也就是母板,母板也就是主板,它是计算机最基本也就是最重要的部件之一。主板通常为矩形电路板,上面安装了组成计算机的主要电路系统,通常有 BIOS 芯片、I/O 控制芯片、键盘和面板控制开关接口、指示灯插接件、扩充插槽、主板及插卡的直流电源供电接插件等元件。
在母板上有一个称为 基本输入输出系统(Basic Input Output System, BIOS)
的程序。在 BIOS 内有底层 I/O 软件,包括读键盘、写屏幕、磁盘I/O 以及其余过程。现在,它被保存在闪存中,它是非易失性的,可是当BIOS 中发现错误时,能够由操做系统进行更新。
在计算机启动(booted)
时,BIOS 开启,它会首先检查所安装的 RAM 的数量,键盘和其余基础设备是否已安装而且正常响应。接着,它开始扫描 PCIe 和 PCI 总线并找出连在上面的全部设备。即插即用的设备也会被记录下来。若是现有的设备和系统上一次启动时的设备不一样,则新的设备将被从新配置。
蓝后,BIOS 经过尝试存储在 CMOS
存储器中的设备清单尝试启动设备
“CMOS是
Complementary Metal Oxide Semiconductor(互补金属氧化物半导体)
的缩写。它是指制造大规模集成电路芯片用的一种技术或用这种技术制造出来的芯片,是电脑主板上的一块可读写的RAM
芯片。由于可读写的特性,因此在电脑主板上用来保存 BIOS 设置完电脑硬件参数后的数据,这个芯片仅仅是用来存放数据的。而对 BIOS 中各项参数的设定要经过专门的程序。BIOS 设置程序通常都被厂商整合在芯片中,在开机时经过特定的按键就可进入 BIOS 设置程序,方便地对系统进行设置。所以 BIOS 设置有时也被叫作 CMOS 设置。
用户能够在系统启动后进入一个 BIOS 配置程序,对设备清单进行修改。而后,判断是否可以从外部 CD-ROM
和 USB 驱动程序启动,若是启动失败的话(也就是没有),系统将从硬盘启动,boots 设备中的第一个扇区被读入内存并执行。该扇区包含一个程序,该程序一般在引导扇区末尾检查分区表以肯定哪一个分区处于活动状态。而后从该分区读入第二个启动加载程序,该加载器从活动分区中读取操做系统并启动它。
而后操做系统会询问 BIOS 获取配置信息。对于每一个设备来讲,会检查是否有设备驱动程序。若是没有,则会向用户询问是否须要插入 CD-ROM
驱动(由设备制造商提供)或者从 Internet 上下载。一旦有了设备驱动程序,操做系统会把它们加载到内核中,而后初始化表,建立所需的后台进程,并启动登陆程序或GUI。
操做系统博物馆
操做系统已经存在了大半个世纪,在这段时期内,出现了各类类型的操做系统,但并非全部的操做系统都很出名,下面就罗列一些比较出名的操做系统
大型机操做系统
高端一些的操做系统是大型机操做系统,这些大型操做系统可在大型公司的数据中心找到。这些计算机的 I/O 容量与我的计算机不一样。一个大型计算机有 1000 个磁盘和数百万 G 字节的容量是很正常,若是有这样一台我的计算机朋友会很羡慕。大型机也在高端 Web 服务器、大型电子商务服务站点上。
服务器操做系统
下一个层次是服务器操做系统。它们运行在服务器上,服务器能够是大型我的计算机、工做站甚至是大型机。它们经过网络为若干用户服务,而且容许用户共享硬件和软件资源。服务器可提供打印服务、文件服务或 Web 服务。Internet 服务商运行着许多台服务器机器,为用户提供支持,使 Web 站点保存 Web 页面并处理进来的请求。典型的服务器操做系统有 Solaris、FreeBSD、Linux 和 Windows Server 201x
多处理器操做系统
得到大型计算能力的一种愈来愈广泛的方式是将多个 CPU 链接到一个系统中。依据它们链接方式和共享方式的不一样,这些系统称为并行计算机,多计算机或多处理器。他们须要专门的操做系统,不过一般采用的操做系统是配有通讯、链接和一致性等专门功能的服务器操做系统的变体。
我的计算机中近来出现了多核芯片,因此常规的台式机和笔记本电脑操做系统也开始与小规模多处理器打交道,而核的数量正在与时俱进。许多主流操做系统好比 Windows 和 Linux 均可以运行在多核处理器上。
我的计算机系统
接下来一类是我的计算机操做系统。现代我的计算机操做系统支持多道处理程序。在启动时,一般有几十个程序开始运行,它们的功能是为单个用户提供良好的支持。这类系统普遍用于字处理、电子表格、游戏和 Internet 访问。常见的例子是 Linux、FreeBSD、Windows 七、Windows 8 和苹果公司的 OS X 。
掌上计算机操做系统
随着硬件愈来愈小化,咱们看到了平板电脑、智能手机和其余掌上计算机系统。掌上计算机或者 PDA(Personal Digital Assistant),我的数字助理
是一种能够握在手中操做的小型计算机。这部分市场已经被谷歌的 Android
系统和苹果的 IOS
主导。
嵌入式操做系统
嵌入式操做系统用来控制设备的计算机中运行,这种设备不是通常意义上的计算机,而且不容许用户安装软件。典型的例子有微波炉、汽车、DVD 刻录机、移动电话以及 MP3 播放器一类的设备。全部的软件都运行在 ROM 中,这意味着应用程序之间不存在保护,从而得到某种简化。主要的嵌入式系统有 Linux、QNX 和 VxWorks
传感器节点操做系统
有许多用途须要配置微小传感器节点网络。这些节点是一种能够彼此通讯而且使用无线通讯基站的微型计算机。这类传感器网络能够用于建筑物周边保护、国土边界保卫、森林火灾探测、气象预测用的温度和降水测量等。
每一个传感器节点是一个配有 CPU、RAM、ROM 以及一个或多个环境传感器的实实在在的计算机。节点上运行一个小型可是真是的操做系统,一般这个操做系统是事件驱动的,能够响应外部事件。
实时操做系统
另外一类操做系统是实时操做系统,这些系统的特征是将时间做为关键参数。例如,在工业过程控制系统中,工厂中的实时计算机必须收集生产过程的数据并用有关数据控制机器。若是某个动做必需要在规定的时刻发生,这就是硬实时系统
。能够在工业控制、民用航空、军事以及相似应用中看到不少这样的系统。另外一类系统是 软实时系统
,在这种系统中,虽然不但愿偶尔违反最终时限,但仍能够接受,并不会引发任何永久性损害。数字音频或多媒体系统就是这类系统。智能手机也是软实时系统。
智能卡操做系统
最小的操做系统运行在智能卡上。智能卡是一种包含一块 CPU 芯片的信用卡。它有很是严格的运行能耗和存储空间的限制。有些卡具备单项功能,如电子支付;有些智能卡是面向 Java 的。这意味着在智能卡的 ROM 中有一个 Java 虚拟机(Java Virtual Machine, JVM)解释器。
操做系统概念
大部分操做系统提供了特定的基础概念和抽象,例如进程、地址空间、文件等,它们是须要理解的核心内容。下面咱们会简要介绍一些基本概念,为了说明这些概念,咱们会不时的从 UNIX
中提出示例,相同的示例也会存在于其余系统中,咱们后面会进行介绍。
进程
操做系统一个很关键的概念就是 进程(Process)
。进程的本质就是操做系统执行的一个程序。与每一个进程相关的是地址空间(address space)
,这是从某个最小值的存储位置(一般是零)到某个最大值的存储位置的列表。在这个地址空间中,进程能够进行读写操做。地址空间中存放有可执行程序,程序所须要的数据和它的栈。与每一个进程相关的还有资源集,一般包括寄存器(registers)
(寄存器通常包括程序计数器(program counter)
和堆栈指针(stack pointer)
)、打开文件的清单、突发的报警、有关的进程清单和其余须要执行程序的信息。你能够把进程看做是容纳运行一个程序全部信息的一个容器。
对进程创建一种直观感受的方式是考虑创建一种多程序的系统。考虑下面这种状况:用户启动一个视频编辑程序,指示它按照某种格式转换视频,而后再去浏览网页。同时,一个检查电子邮件的后台进程被唤醒并开始运行,这样,咱们目前就会有三个活动进程:视频编辑器、Web 浏览器和电子邮件接收程序。操做系统周期性的挂起一个进程而后启动运行另外一个进程,这多是因为过去一两秒钟程序用完了 CPU 分配的时间片,而 CPU 转而运行另外的程序。
像这样暂时中断进程后,下次应用程序在此启动时,必需要恢复到与中断时刻相同的状态,这在咱们用户看起来是习觉得常的事情,可是操做系统内部却作了巨大的事情。这就像和足球比赛同样,一场完美精彩的比赛是能够忽略裁判的存在的。这也意味着在挂起时该进程的全部信息都要被保存下来。例如,进程可能打开了多个文件进行读取。与每一个文件相关联的是提供当前位置的指针(即下一个须要读取的字节或记录的编号)。当进程被挂起时,必需要保存这些指针,以便在从新启动进程后执行的 read
调用将可以正确的读取数据。在许多操做系统中,与一个进程有关的全部信息,除了该进程自身地址空间的内容之外,均存放在操做系统的一张表中,称为 进程表(process table)
,进程表是数组或者链表结构,当前存在每一个进程都要占据其中的一项。
因此,一个挂起的进程包括:进程的地址空间(每每称做磁芯映像
, core image,记念过去的磁芯存储器),以及对应的进程表项(其中包括寄存器以及稍后启动该进程所须要的许多其余信息)。
与进程管理有关的最关键的系统调用每每是决定着进程的建立和终止的系统调用。考虑一个典型的例子,有一个称为 命令解释器(command interpreter)
或 shell
的进程从终端上读取命令。此时,用户刚键入一条命令要求编译一个程序。shell 必须先建立一个新进程来执行编译程序,当编译程序结束时,它执行一个系统调用来终止本身的进程。
若是一个进程可以建立一个或多个进程(称为子进程
),并且这些进程又能够建立子进程,则很容易找到进程数,以下所示
上图表示一个进程树的示意图,进程 A 建立了两个子进程 B 和进程 C,子进程 B 又建立了三个子进程 D、E、F。
合做完成某些做业的相关进程常常须要彼此通讯来完成做业,这种通讯称为进程间通讯(interprocess communication)
。咱们在后面会探讨进程间通讯。
其余可用的进程系统调用包括:申请更多的内存(或释放再也不须要的内存),等待一个子进程结束,用另外一个程序覆盖该程序。
有时,须要向一个正在运行的进程传递信息,而该进程并无等待接收信息。例如,一个进程经过网络向另外一台机器上的进程发送消息进行通讯。为了保证一条消息或消息的应答不丢失。发送者要求它所在的操做系统在指定的若干秒后发送一个通知,这样若是对方还没有收到确认消息就能够进行从新发送。在设定该定时器后,程序能够继续作其余工做。
在限定的时间到达后,操做系统会向进程发送一个 警告信号(alarm signal)
。这个信号引发该进程暂时挂起,不管该进程正在作什么,系统将其寄存器的值保存到堆栈中,并开始从新启动一个特殊的信号处理程,好比从新发送可能丢失的消息。这些信号是软件模拟的硬件中断,除了定时器到期以外,该信号能够经过各类缘由产生。许多由硬件检测出来的陷阱,如执行了非法指令或使用了无效地址等,也被转换成该信号并交给这个进程。
系统管理器受权每一个进程使用一个给定的 UID(User IDentification)
。每一个启动的进程都会有一个操做系统赋予的 UID,子进程拥有与父进程同样的 UID。用户能够是某个组的成员,每一个组也有一个 GID(Group IDentification)
。
在 UNIX 操做系统中,有一个 UID 是 超级用户(superuser)
,或者 Windows 中的管理员(administrator)
,它具备特殊的权利,能够违背一些保护规则。在大型系统中,只有系统管理员掌握着那些用户能够称为超级用户。
地址空间
每台计算机都有一些主存用来保存正在执行的程序。在一个很是简单的操做系统中,仅仅有一个应用程序运行在内存中。为了运行第二个应用程序,须要把第一个应用程序移除才能把第二个程序装入内存。
复杂一些的操做系统会容许多个应用程序同时装入内存中运行。为了防止应用程序之间相互干扰(包括操做系统),须要有某种保护机制。虽然此机制是在硬件中实现,但倒是由操做系统控制的。
上述观点涉及对计算机主存的管理和保护。另外一种同等重要并与存储器有关的内容是管理进程的地址空间。一般,每一个进程有一些能够使用的地址集合,典型值从 0 开始直到某个最大值。一个进程可拥有的最大地址空间小于主存。在这种状况下,即便进程用完其地址空间,内存也会有足够的内存运行该进程。
可是,在许多 32 位或 64 位地址的计算机中,分别有 2^32 或 2^64 字节的地址空间。若是一个进程有比计算机拥有的主存还大的地址空间,并且该进程但愿使用所有的内存,那该怎么处理?在早期的计算机中是没法处理的。可是如今有了一种虚拟内存
的技术,正如前面讲到过的,操做系统能够把部分地址空间装入主存,部分留在磁盘上,而且在须要时来回交换它们。
文件
几乎全部操做系统都支持的另外一个关键概念就是文件系统。如前所述,操做系统的一项主要功能是屏蔽磁盘和其余 I/O 设备的细节特性,给程序员提供一个良好、清晰的独立于设备的抽象文件模型。建立文件、删除文件、读文件和写文件 都须要系统调用。在文件能够读取以前,必须先在磁盘上定位和打开文件,在文件读过以后应该关闭该文件,有关的系统调用则用于完成这类操做。
为了提供保存文件的地方,大多数我的计算机操做系统都有目录(directory)
的概念,从而能够把文件分组。好比,学生能够给每一个课程都建立一个目录,用于保存该学科的资源,另外一个目录能够存放电子邮件,再有一个目录能够存放万维网主页。这就须要系统调用建立和删除目录、将已有文件放入目录中,从目录中删除文件等。目录项能够是文件或者目录,目录和目录之间也能够嵌套,这样就产生了文件系统
进程和文件层次都是以树状的结构组织,但这两种树状结构有很多不一样之处。通常进程的树状结构层次不深(不多超过三层),而文件系统的树状结构要深一些,一般会到四层甚至五层。进程树层次结构是暂时的,一般最多存在几分钟,而目录层次则可能存在很长时间。进程和文件在权限保护方面也是有区别的。通常来讲,父进程能控制和访问子进程,而在文件和目录中一般存在一种机制,使文件全部者以外的其余用户也能访问该文件。
目录层结构中的每个文件均可以经过从目录的顶部即 根目录(Root directory)
开始的路径名(path name)
来肯定。绝对路径名包含了从根目录到该文件的全部目录清单,它们之间用斜杠分隔符分开,在上面的大学院系文件系统中,文件 CS101 的路径名是 /Faculty/Prof.Brown/Courses/CS101
。最开始的斜杠分隔符表明的是根目录 /
,也就是文件系统的绝对路径。
“出于历史缘由,Windows 下面的文件系统以
\
来做为分隔符,可是 Linux 会以/
做为分隔符。
在上面的系统中,每一个进程会有一个 工做目录(working directory)
,对于没有以斜线开头给出绝对地址的路径,将在这个工做目录下寻找。若是 /Faculty/Prof.Brown
是工做目录,那么 /Courses/CS101
与上面给定的绝对路径名表示的是同一个文件。进程能够经过使用系统调用指定新的工做目录,从而变动其工做目录。
在读写文件以前,首先须要打开文件,检查其访问权限。若权限许可,系统将返回一个小整数,称做文件描述符(file descriptor)
,供后续操做使用。若禁止访问,系统则返回一个错误码。
在 UNIX 中,另外一个重要的概念是 特殊文件(special file)
。提供特殊文件是为了使 I/O 设备看起来像文件通常。这样,就像使用系统调用读写文件同样,I/O 设备也能够经过一样的系统调用进行读写。特殊文件有两种,一种是块儿特殊文件(block special file)
和 字符特殊文件(character special file)
。块特殊文件指那些由可随机存取的块组成的设备,如磁盘等。好比打开一个块特殊文件,而后读取第4块,程序能够直接访问设备的第4块而没必要考虑存放在该文件的文件系统结构。相似的,字符特殊文件用于打印机、调制解调起和其余接受或输出字符流的设备。按照惯例,特殊文件保存在 /dev
目录中。例如,/devv/lp 是打印机。
还有一种与进程和文件相关的特性是管道,管道(pipe)
是一种虚文件,他能够链接两个进程
若是 A 和 B 但愿经过管道对话,他们必须提早设置管道。当进程 A 相对进程 B 发送数据时,它把数据写到管道上,至关于管道就是输出文件。这样,在 UNIX 中两个进程之间的通讯就很是相似于普通文件的读写了。
保护
计算机中含有大量的信息,用户但愿可以对这些信息中有用并且重要的信息加以保护,这些信息包括电子邮件、商业计划等,管理这些信息的安全性彻底依靠操做系统来保证。例如,文件提供受权用户访问。
好比 UNIX 操做系统,UNIX 操做系统经过对每一个文件赋予一个 9 位二进制保护代码,对 UNIX 中的文件实现保护。该保护代码有三个位子段,一个用于全部者,一个用于与全部者同组(用户被系统管理员划分红组)的其余成员,一个用于其余人。每一个字段中有一位用于读访问,一位用于写访问,一位用于执行访问。这些位就是著名的 rwx位
。例如,保护代码 rwxr-x--x
的含义是全部者能够读、写或执行该文件,其余的组成员能够读或执行(但不能写)此文件、而其余人能够执行(但不能读和写)该文件。
shell
操做系统是执行系统调用的代码。编辑器、编译器、汇编程序、连接程序、使用程序以及命令解释符等,尽管很是重要,很是有用,可是它们确实不是操做系统的组成部分。下面咱们着重介绍一下 UNIX 下的命令提示符,也就是 shell
,shell 虽然有用,但它也不是操做系统的一部分,然而它却能很好的说明操做系统不少特性,下面咱们就来探讨一下。
shell 有许多种,例如 sh、csh、ksh 以及 bash等,它们都支持下面这些功能,最先起的 shell 能够追溯到 sh
用户登陆时,会同时启动一个 shell,它以终端做为标准输入和标准输出。首先显示提示符(prompt)
,它多是一个美圆符号($)
,提示用户 shell 正在等待接收命令,假如用户输入
date
shell 会建立一个子进程,并运行 date 作为子进程。在该子进程运行期间,shell 将等待它结束。在子进程完成时,shell 会显示提示符并等待下一行输入。
用户能够将标准输出重定向到一个文件中,例如
date > file
一样的,也能够将标准输入做为重定向
sort <file1> file2
这会调用 sort 程序来接收 file1 的内容并把结果输出到 file2。
能够将一个应用程序的输出经过管道做为另外一个程序的输入,所以有
cat file1 file2 file3 | sort > /dev/lp
这会调用 cat 应用程序来合并三个文件,将其结果输送到 sort 程序中并按照字典进行排序。sort 应用程序又被重定向到 /dev/lp ,显然这是一个打印操做。
系统调用
咱们已经能够看到操做系统提供了两种功能:为用户提供应用程序抽象和管理计算机资源。对于大部分在应用程序和操做系统之间的交互主要是应用程序的抽象,例如建立、写入、读取和删除文件。计算机的资源管理对用户来讲基本上是透明的。所以,用户程序和操做系统之间的接口主要是处理抽象。为了真正理解操做系统的行为,咱们必须仔细的分析这个接口。
多数现代操做系统都有功能相同可是细节不一样的系统调用,引起操做系统的调用依赖于计算机自身的机制,并且必须用汇编代码表达。任何单 CPU 计算机一次执行执行一条指令。若是一个进程在用户态下运行用户程序,例如从文件中读取数据。那么若是想要把控制权交给操做系统控制,那么必须执行一个异常指令或者系统调用指令。操做系统紧接着须要参数检查找出所须要的调用进程。操做系统紧接着进行参数检查找出所须要的调用进程。而后执行系统调用,把控制权移交给系统调用下面的指令。大体来讲,系统调用就像是执行了一个特殊的过程调用,可是只有系统调用可以进入内核态而过程调用则不能进入内核态。
为了可以了解具体的调用过程,下面咱们以 read
方法为例来看一下调用过程。像上面提到的那样,会有三个参数,第一个参数是指定文件、第二个是指向缓冲区、第三个参数是给定须要读取的字节数。就像几乎全部系统调用同样,它经过使用与系统调用相同的名称来调用一个函数库,从而从C程序中调用:read。
count = read(fd,buffer,nbytes);
系统调用在 count 中返回实际读出的字节数。这个值一般与 nbytes 相同,但也可能更小。好比在读过程当中遇到了文件尾的状况。
若是系统调用不能执行,无论是由于无效的参数仍是磁盘错误,count 的值都会被置成 -1,而后在全局变量 errno
中放入错误信号。程序应该进场检查系统调用的结果以了解是否出错。
系统调用是经过一系列的步骤实现的,为了更清楚的说明这个概念,咱们还以 read 调用为例,在准备系统调用前,首先会把参数压入堆栈,以下所示
C 和 C++ 编译器使用逆序(必须把第一个参数赋值给 printf(格式字符串),放在堆栈的顶部)。第一个参数和第三个参数都是值调用,可是第二个参数经过引用传递,即传递的是缓冲区的地址(由 & 指示),而不是缓冲的内容。而后是 C 调用系统库的 read 函数,这也是第四步。
在由汇编语言写成的库过程当中,通常把系统调用的编号放在操做系统所指望的地方,如寄存器(第五步)。而后执行一个 TRAP
指令,将用户态切换到内核态,并在内核中的一个固定地址开始执行第六步。TRAP 指令实际上与过程调用指令很是类似,它们后面都跟随一个来自远处位置的指令,以及供之后使用的一个保存在栈中的返回地址。
TRAP 指令与过程调用指令存在两个方面的不一样
-
TRAP 指令会改变操做系统的状态,由用户态切换到内核态,而过程调用不改变模式 -
其次,TRAP 指令不能跳转到任意地址上。根据机器的体系结构,要么跳转到一个单固定地址上,或者指令中有一 8 位长的字段,它给定了内存中一张表格的索引,这张表格中含有跳转地址,而后跳转到指定地址上。
跟随在 TRAP 指令后的内核代码开始检查系统调用编号,而后dispatch
给正确的系统调用处理器,这一般是经过一张由系统调用编号所引用的、指向系统调用处理器的指针表来完成第七步。此时,系统调用处理器运行第八步,一旦系统调用处理器完成工做,控制权会根据 TRAP 指令后面的指令中返回给函数调用库第九步。这个过程接着以一般的过程调用返回的方式,返回到客户应用程序,这是第十步。而后调用完成后,操做系统还必须清除用户堆栈,而后增长堆栈指针(increment stackpointer)
,用来清除调用 read 以前压入的参数。从而完成整个 read 调用过程。
在上面的第九步中咱们说道,控制可能返回 TRAP 指令后面的指令,把控制权再移交给调用者这个过程当中,系统调用会发生阻塞,从而避免应用程序继续执行。这么作是有缘由的。例如,若是试图读键盘,此时并无任何输入,那么调用者就必须被阻塞。在这种情形下,操做系统会检查是否有其余能够运行的进程。这样,当有用户输入 时候,进程会提醒操做系统,而后返回第 9 步继续运行。
下面,咱们会列出一些经常使用的 POSIX
系统调用,POSIX 系统调用大概有 100 多个,它们之中最重要的一些调用见下表
进程管理
调用 | 说明 |
---|---|
pid = fork() | 建立与父进程相同的子进程 |
pid = waitpid(pid, &statloc,options) | 等待一个子进程终止 |
s = execve(name,argv,environp) | 替换一个进程的核心映像 |
exit(status) | 终止进程执行并返回状态 |
文件管理
调用 | 说明 |
---|---|
fd = open(file, how,...) | 打开一个文件使用读、写 |
s = close(fd) | 关闭一个打开的文件 |
n = read(fd,buffer,nbytes) | 把数据从一个文件读到缓冲区中 |
n = write(fd,buffer,nbytes) | 把数据从缓冲区写到一个文件中 |
position = iseek(fd,offset,whence) | 移动文件指针 |
s = stat(name,&buf) | 取得文件状态信息 |
目录和文件系统管理
调用 | 说明 |
---|---|
s = mkdir(nname,mode) | 建立一个新目录 |
s = rmdir(name) | 删去一个空目录 |
s = link(name1,name2) | 建立一个新目录项 name2,并指向 name1 |
s = unlink(name) | 删去一个目录项 |
s = mount(special,name,flag) | 安装一个文件系统 |
s = umount(special) | 卸载一个文件系统 |
其余
调用 | 说明 |
---|---|
s = chdir(dirname) | 改变工做目录 |
s = chmod(name,mode) | 修改一个文件的保护位 |
s = kill(pid, signal) | 发送信号给进程 |
seconds = time(&seconds) | 获取从 1970 年1月1日至今的时间 |
上面的系统调用参数中有一些公共部分,例如 pid 系统进程 id,fd 是文件描述符,n 是字节数,position 是在文件中的偏移量、seconds 是流逝时间。
从宏观角度上看,这些系统调所提供的服务肯定了多数操做系统应该具备的功能,下面分别来对不一样的系统调用进行解释
用于进程管理的系统调用
在 UNIX 中,fork
是惟一能够在 POSIX 中建立进程的途径,它建立一个原有进程的副本,包括全部的文件描述符、寄存器等内容。在 fork 以后,原有进程以及副本(父与子)就分开了。在 fork 过程当中,全部的变量都有相同的值,虽然父进程的数据经过复制给子进程,可是后续对其中任何一个进程的修改不会影响到另一个。fork 调用会返回一个值,在子进程中该值为 0 ,而且在父进程中等于子进程的 进程标识符(Process IDentified,PID)
。使用返回的 PID,就能够看出来哪一个是父进程和子进程。
在多数状况下, 在 fork 以后,子进程须要执行和父进程不同的代码。从终端读取命令,建立一个子进程,等待子进程执行命令,当子进程结束后再读取下一个输入的指令。为了等待子进程完成,父进程须要执行 waitpid
系统调用,父进程会等待直至子进程终止(如有多个子进程的话,则直至任何一个子进程终止)。waitpid 能够等待一个特定的子进程,或者经过将第一个参数设为 -1 的方式,等待任何一个比较老的子进程。当 waitpid 完成后,会将第二个参数 statloc
所指向的地址设置为子进程的退出状态(正常或异常终止以及退出值)。有各类可以使用的选项,它们由第三个参数肯定。例如,若是没有已经退出的子进程则马上返回。
那么 shell 该如何使用 fork 呢?在键入一条命令后,shell 会调用 fork 命令建立一个新的进程。这个子进程会执行用户的指令。经过使用 execve
系统调用能够实现系统执行,这个系统调用会引发整个核心映像被一个文件所替代,该文件由第一个参数给定。下面是一个简化版的例子说明 fork、waitpid 和 execve 的使用
#define TRUE 1
/* 一直循环下去 */
while(TRUE){
/* 在屏幕上显示提示符 */
type_prompt();
/* 从终端读取输入 */
read_command(command,parameters)
/* fork 子进程 */
if(fork() != 0){
/* 父代码 */
/* 等待子进程执行完毕 */
waitpid(-1, &status, 0);
}else{
/* 执行命令 */
/* 子代码 */
execve(command,parameters,0)
}
}
通常状况下,execve 有三个参数:将要执行的文件名称,一个指向变量数组的指针,以及一个指向环境数组的指针。这里对这些参数作一个简要的说明。
先看一个 shell 指令
cp file1 file2
此命令把 file1 复制到 file2 文件中,在 shell 执行 fork 以后,子进程定位并执行文件拷贝,并将源文件和目标文件的名称传递给它。
cp 的主程序(以及包含其余大多数 C 程序的主程序)包含声明
main(argc,argv,envp)
其中 argc 是命令行中参数数目的计数,包括程序名称。对于上面的例子,argc
是3。第二个参数argv
是数组的指针。该数组的元素 i 是指向该命令行第 i 个字符串的指针。在上面的例子中,argv[0] 指向字符串 cp,argv[1] 指向字符串 file1,argv[2] 指向字符串 file2。main 的第三个参数是指向环境的指针,该环境是一个数组,含有 name = value
的赋值形式,用以将诸如终端类型以及根目录等信息传送给程序。这些变量一般用来肯定用户但愿如何完成特定的任务(例如,使用默认打印机)。在上面的例子中,没有环境参数传递给 execve ,因此环境变量是 0 ,因此 execve 的第三个参数为 0 。
可能你以为 execve 过于复杂,这时候我要鼓励一下你,execve 多是 POSIX 的所有系统调用中最复杂的一个了,其余都比较简单。做为一个简单的例子,咱们再来看一下 exit
,这是进程在执行完成后应执行的系统调用。这个系统调用有一个参数,它的退出状态是 0 - 255 之间,它经过 waitpid 系统调用中的 statloc 返回给父级。
UNIX 中的进程将内存划分红三个部分:text segment,文本区
,例如程序代码,data segment,数据区
,例如变量,stack segment
,栈区域。数据向上增加而堆栈向下增加,以下图所示
上图能说明三个部分的内存分配状况,夹在中间的是空闲区,也就是未分配的区域,堆栈在须要时自动的挤压空闲区域,不过数据段的扩展是显示地经过系统调用 brk
进行的,在数据段扩充后,该系统调用指向一个新地址。可是,这个调用不是 POSIX 标准中定义的,对于存储器的动态分配,鼓励程序员使用 malloc
函数,而 malloc 的内部实现则不是一个适合标准化的主题,由于几乎没有程序员直接使用它。
用于文件管理的系统调用
许多系统调用都与文件系统有关,要读写一个文件,必须先将其打开。这个系统调用经过绝对路径名或指向工做目录的相对路径名指定要打开文件的名称,而代码 O_RDONLY
、 O_WRONLY
或 O_RDWR
的含义分别是只读、只写或者二者均可以,为了建立一个新文件,使用 O_CREATE
参数。而后可以使用返回的文件描述符进行读写操做。接着,能够使用 close 关闭文件,这个调用使得文件描述符在后续的 open 中被再次使用。
最经常使用的调用仍是 read
和 write
,咱们再前面探讨过 read 调用,write 具备与 read 相同的参数。
尽管多数程序频繁的读写文件,可是仍有一些应用程序须要可以随机访问一个文件的任意部分。与每一个文件相关的是一个指向文件当前位置的指针。在顺序读写时,该指针一般指向要读出(写入)的下一个字节。Iseek
调用能够改变该位置指针的值,这样后续的 read 或 write 调用就能够在文件的任何地方开始。
Iseek 有三个参数,position = iseek(fd,offset,whence)
,第一个是文件描述符,第二个是文件位置,第三个是说明该文件位置是相对于文件起始位置,当前位置仍是文件的结尾。在修改了指针以后,Iseek 所返回的值是文件中的绝对位置。
UNIX 为每一个文件保存了该文件的类型(普通文件、特殊文件、目录等)、大小,最后修改时间以及其余信息,程序能够经过 stat
系统调用查看这些信息。s = stat(name,&buf)
,第一个参数指定了被检查的文件;第二个参数是一个指针,该指针指向存放这些信息的结构。对于一个打开的文件而言,fstat 调用完成一样的工做。
用于目录管理的系统调用
下面咱们探讨目录和整个文件系统的系统调用,上面探讨的是和某个文件有关的系统调用。mkdir
和 rmdir
分别用于建立s = mkdir(nname,mode)
和删除 s = rmdir(name)
空目录,下一个调用是 s = link(name1,name2)
它的做用是容许同一个文件以两个或者多个名称出现,多数状况下是在不一样的目录中使用 link ,下面咱们探讨一下 link 是如何工做的
图中有两个用户 ast
和 jim
,每一个用户都有他本身的一个目录和一些文件,若是 ast 要执行一个包含下面系统调用的应用程序
link("/usr/jim/memo", "/usr/ast/note");
jim 中的 memo 文件如今会进入到 ast 的目录中,在 note 名称下。此后,/usr/jim/memo
和 /usr/ast/note
会有相同的名称。
“用户目录是保存在 /usr,/user,/home 仍是其余位置,都是由本地系统管理员决定的。
要理解 link 是如何工做的须要清楚 link 作了什么操做。UNIX 中的每一个文件都有一个独一无二的版本,也称做 i - number,i-编号
,它标示着不一样文件的版本。这个 i - 编号是 i-nodes,i-节点
表的索引。每一个文件都会代表谁拥有这个文件,这个磁盘块的位置在哪,等等。目录只是一个包含一组(i编号,ASCII名称)对应的文件。UNIX 中的第一个版本中,每一个目录项都会有 16 个字节,2 个字节对应 i - 编号和 14 个字节对应其名称。如今须要一个更复杂的结构须要支持长文件名,可是从概念上讲一个目录还是一系列(i-编号,ASCII 名称)的集合。在上图中,mail
的 i-编号为 16,依此类推。link 只是利用某个已有文件的 i-编号,建立一个新目录项(也许用一个新名称)。在上图 b 中,你会发现有两个相同的 70 i-编号的文件,所以它们须要有相同的文件。若是其中一个使用了 unlink
系统调用的话,其中一个会被移除,另外一个将保留。若是两个文件都移除了,则 UNIX 会发现该文件不存在任何没有目录项(i-节点中的一个域记录着指向该文件的目录项),就会把该文件从磁盘中移除。
就像咱们上面提到过的那样,mount
系统 s = mount(special,name,flag)
调用会将两个文件系统合并为一个。一般的状况是将根文件系统分布在硬盘(子)分区上,并将用户文件分布在另外一个(子)分区上,该根文件系统包含经常使用命令的二进制(可执行)版本和其余使用频繁的文件。而后,用户就会插入可读取的 USB 硬盘。
经过执行 mount 系统调用,USB 文件系统能够被添加到根文件系统中,
若是用 C 语言来执行那就是
mount("/dev/sdb0","/mnt",0)
这里,第一个参数是 USB 驱动器 0 的块特殊文件名称,第二个参数是被安装在树中的位置,第三个参数说明将要安装的文件系统是可读写的仍是只读的。
当再也不须要一个文件系统时,能够使用 umount 移除之。
其余系统调用
除了进程、文件、目录系统调用,也存在其余系统调用的状况,下面咱们来探讨一下。咱们能够看到上面其余系统调用只有四种,首先来看第一个 chdir,chdir 调用更改当前工做目录,在调用
chdir("/usr/ast/test");
后,打开 xyz 文件,会打开 /usr/ast/test/xyz
文件,工做目录的概念消除了老是须要输入长文件名的须要。
在 UNIX 系统中,每一个文件都会有保护模式,这个模式会有一个读-写-执行
位,它用来区分全部者、组和其余成员。chmod
系统调用提供改变文件模式的操做。例如,要使一个文件除了对全部者以外的用户可读,你能够执行
chmod("file",0644);
kill
系统调用是用户和用户进程发送信号的方式,若是一个进程准备好捕捉一个特定的信号,那么在信号捕捉以前,会运行一个信号处理程序。若是进程没有准备好捕捉特定的信号,那么信号的到来会杀掉该进程(此名字的由来)。
POSIX 定义了若干时间处理的进程。例如,time
以秒为单位返回当前时间,0 对应着 1970 年 1月 1日。在一台 32 位字的计算机中,time 的最大值是 (2^32) - 1秒,这个数字对应 136 年多一点。因此在 2106 年,32 位的 UNIX 系统会发飙。若是读者如今有 32 位 UNIX 系统,建议在 2106 年更换位 64 位操做系统(偷笑~)。
Win 32 API
上面咱们提到的都是 UNIX 系统调用,如今咱们来聊聊 Win 32 中的系统调用。Windows 和 UNIX 在各自的编程方式上有着根本的不一样。UNIX 程序由执行某些操做或执行其余操做的代码组成,进行系统调用以执行某些服务。Windows 系统则不一样,Windows 应用程序一般是由事件驱动的。主程序会等待一些事件发生,而后调用程序去处理。最简单的事件处理是键盘敲击和鼠标滑过,或者是鼠标点击,或者是插入 USB 驱动,而后操做系统调用处理器去处理事件,更新屏幕和更新程序内部状态。这是与 UNIX 不一样的设计风格。
固然,Windows 也有系统调用。在 UNIX 中,系统调用(好比 read)和系统调用所使用的调用库(例如 read)几乎是一对一的关系。而在 Windows 中,状况则大不相同。首先,函数库的调用和实际的系统调用几乎是不对应的。微软定义了一系列过程,称为 Win32应用编程接口(Application Programming Interface)
,程序员经过这套标准的接口来实现系统调用。这个接口支持从 Windows 95 版本以来全部的 Windows 版本。
Win32 API 调用的数量是很是巨大的,有数千个多。但这些调用并不都是在内核态的模式下运行时,有一些是在用户态的模型下运行。Win32 API 有大量的调用,用来管理视窗、几何图形、文本、字体、滚动条、对话框、菜单以及 GUI 的其余功能。为了使图形子系统在内核态下运行,须要系统调用,不然就只有函数库调用。
咱们把关注点放在和 Win32 系统调用中来,咱们能够简单看一下 Win32 API 中的系统调用和 UNIX 中有什么不一样(并非全部的系统调用)
上表中是 UNIX 调用大体对应的 Win32 API 系统调用,简述一下上表。CreateProcess
用于建立一个新进程,它把 UNIX 中的 fork 和 execve 两个指令合成一个,一块儿执行。它有许多参数用来指定新建立进程的性质。Windows 中没有相似 UNIX 中的进程层次,因此不存在父进程和子进程的概念。在进程建立以后,建立者和被建立者是平等的。WaitForSingleObject
用于等待一个事件,等待的事件能够是多种可能的事件。若是有参数指定了某个进程,那么调用者将等待指定的进程退出,这经过 ExitProcess
来完成。
而后是6个文件操做,在功能上和 UNIX 的调用相似,然而在参数和细节上是不一样的。和 UNIX 中同样,文件能够打开,读取,写入,关闭。SetFilePointer
和 GetFileAttributesEx
设置文件的位置并取得文件的属性。
Windows 中有目录,目录分别用 CreateDirectory
以及 RemoveDirectory
API 调用建立和删除。也有对当前的目录的标记,这能够经过 SetCurrentDirectory
来设置。使用GetLocalTime
可得到当前时间。
Win32 接口中没有文件的连接、文件系统的 mount、umount 和 stat ,固然, Win32 中也有大量 UNIX 中没有的系统调用,特别是对 GUI 的管理和调用。
操做系统结构
下面咱们会探讨操做系统的几种结构,主要包括单体结构、分层系统、微内核、客户-服务端系统、虚拟机和外核等。下面以此来探讨一下
单体系统
到目前为止,在大多数系统中,整个系统在内核态以单一程序的方式运行。整个操做系统是以程序集合来编写的,连接在一块造成一个大的二进制可执行程序。使用此技术时,若是系统中的每一个过程都提供了前者所需的一些有用的计算,则它能够自由调用任何其余过程。在单体系统中,调用任何一个所须要的程序都很是高效,可是上千个不受限制的彼此调用每每很是臃肿和笨拙,并且单体系统必然存在单体问题,那就是只要系统发生故障,那么任何系统和应用程序将不可用,这每每是灾难性的。
在单体系统中构造实际目标程序时,会首先编译全部单个过程(或包含这些过程的文件),而后使用系统连接器将它们所有绑定到一个可执行文件中
对于单体系统,每每有下面几种建议
-
须要有一个主程序,用来调用请求服务程序 -
须要一套服务过程,用来执行系统调用 -
须要一套服务程序,用来辅助服务过程调用
在单体系统中,对于每一个系统调用都会有一个服务程序来保障和运行。须要一组实用程序来弥补服务程序须要的功能,例如从用户程序中获取数据。可将各类过程划分为一个三层模型
除了在计算机初启动时所装载的核心操做系统外,许多操做系统还支持额外的扩展。好比 I/O 设备驱动和文件系统。这些部件能够按需装载。在 UNIX 中把它们叫作 共享库(shared library)
,在 Windows 中则被称为 动态连接库(Dynamic Link Library,DLL)
。他们的扩展名为 .dll
,在 C:\Windows\system32
目录下存在 1000 多个 DLL 文件,因此不要轻易删除 C 盘文件,不然可能就炸了哦。
分层系统
分层系统使用层来分隔不一样的功能单元。每一层只与该层的上层和下层通讯。每一层都使用下面的层来执行其功能。层之间的通讯经过预约义的固定接口通讯。
分层系统是由 E.W.Dijkstar
和他的学生在荷兰技术学院所开发的 THE 系统。
把上面单体系统进一步通用化,就变为了一个层次式结构的操做系统,它的上层软件都是在下层软件的基础之上构建的。该系统分为六层,以下所示
层号 | 功能 |
---|---|
5 | 操做员 |
4 | 用户程序 |
3 | 输入/输出管理 |
2 | 操做员-进程通讯 |
1 | 存储器和磁鼓管理 |
0 | 处理器分配和多道程序编程 |
处理器在 0 层运行,当中断发生或定时器到期时,由该层完成进程切换;在第 0 层之上,系统由一些连续的进程组成,编写这些进程时不用再考虑在单处理器上多进程运行的细节。内存管理在第 1 层,它分配进程的主存空间。第 1 层软件保证一旦须要访问某一页面,该页面一定已经在内存中,而且在页面不须要的时候将其移出。
第 2 层处理进程与操做员控制台(即用户)之间的通讯。第 3 层管理 I/O 设备和相关的信息流缓冲区。第 4 层是用户程序层,用户程序不用考虑进程、内存、控制台或 I/O 设备管理等细节。系统操做员在第 5 层。
微内核
在分层方式中,设计者要肯定在哪里划分 内核-用户
的边界。传统上,全部的层都在内核中,可是这样作没有必要。事实上,尽量减小内核态中功能多是更好的作法。由于内核中的错误很难处理,一旦内核态中出错误会拖累整个系统。
因此,为了实现高可靠性,将操做系统划分红小的、层级之间可以更好定义的模块是颇有必要的,只有一个模块 --- 微内核 --- 运行在内核态,其他模块能够做为普通用户进程运行。因为把每一个设备驱动和文件系统分别做为普通用户进程,这些模块中的错误虽然会使这些模块崩溃,可是不会使整个系统死机。
MINIX 3
是微内核的表明做,它的具体结构以下
在内核的外部,系统的构造有三层,它们都在用户态下运行,最底层是设备驱动器。因为它们都在用户态下运行,因此不能物理的访问 I/O 端口空间,也不能直接发出 I/O 命令。相反,为了可以对 I/O 设备编程,驱动器构建一个结构,指明哪一个参数值写到哪一个 I/O 端口,并声称一个内核调用,这样就完成了一次调用过程。
位于用户态的驱动程序上面是服务器
层,包含有服务器,它们完成操做系统的多数工做。由一个或多个文件服务器管理着文件系统,进程管理器建立、销毁和管理进程。服务器中有一个特殊的服务器称为 再生服务器(reincarnation server)
,它的任务就是检查服务器和驱动程序的功能是否正确,一旦检查出来错误,它就会补上去,无需用户干预。这种方式使得系统具备可恢复性,并具备较高的可靠性。
微内核中的内核还具备一种 机制
与 策略
分离的思想。好比系统调度,一个比较简单的调度算法是,对每一个进程赋予一个优先级,并让内核执行具备最高优先级的进程。这里,内核机制就是寻找最高的优先级进程并运行。而策略(赋予进程优先级)能够在用户态中的进程完成。在这种模式中,策略和机制是分离的,从而使内核变得更小。
客户-服务器模式
微内核思想的策略是把进程划分为两类:服务器
,每一个服务器用来提供服务;客户端
,使用这些服务。这个模式就是所谓的 客户-服务器
模式。
客户-服务器模式会有两种载体,一种状况是一台计算机既是客户又是服务器,在这种方式下,操做系统会有某种优化;可是广泛状况下是客户端和服务器在不一样的机器上,它们经过局域网或广域网链接。
客户经过发送消息与服务器通讯,客户端并不须要知道这些消息是在本地机器上处理,仍是经过网络被送到远程机器上处理。对于客户端而言,这两种情形是同样的:都是发送请求并获得回应。
愈来愈多的系统,包括家里的 PC,都成为客户端,而在某地运行的大型机器则成为服务器。许多 web 就是以这种方式运行的。一台 PC 向某个服务器请求一个 Web 页面,服务器把 Web 页面返回给客户端,这就是典型的客服-服务器模式
文章参考:
《现代操做系统》第四版
https://baike.baidu.com/item/操做系统/192?fr=aladdin
《Modern Operating System》forth edition
http://faculty.cs.niu.edu/~hutchins/csci360/hchnotes/psw.htm
https://www.computerhope.com/jargon/c/clockcyc.htm
《B站-操做系统》
https://www.bilibili.com/video/av9555596?from=search&seid=8107077283516919308
https://en.wikipedia.org/wiki/System_call
http://c.biancheng.net/cpp/html/238.html
http://www.dossier-andreas.net/software_architecture/layers.html
欢迎关注Java建设者以及 cxuan 我的微信号、会不按期发送红包和优质资源

本文分享自微信公众号 - Java建设者(javajianshe)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。