本文若未注明均摘抄自《操作系统精髓与设计原理》:William Stallings著,陈向群、陈渝译,机械工业出版社出版第六版
关键字 | |||
---|---|---|---|
阻塞态 | 父进程 | 进程切换 | 交换 |
子进程 | 抢占 | 程序状态字 | 内核态 |
退出态 | 任务 | 就绪态 | 中断 |
进程 | 轮转 | 跟踪 | 进程控制块 |
用户态 | 运行态 | 陷阱 | 模式切换 |
进程映像 | 挂起态 | 新建态 |
进程的两个基本元素是程序代码和代码相关的数据集。
注:举例说明的话,熟悉 C/C++ 的读者可能了解,在经由 C/C++ 编译的程序在运行时的内存布局分为以下几个部分
1. 栈区(stack)由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈
2. 堆区(heap) 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3. 全局区(静态区)(static),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4. 文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5. 程序代码区—存放函数体的二进制代码。
这其中的5就是所谓的进程的程序代码,而1-4就是所谓的代码相关的数据集。这两种概念间微妙的差别需要读者细细品味
以上从程序运行的视角来对进程的内存进行了一番管中窥豹。如果从操作系统的角度观察的话,进程的内存中还有更多的细节需要注意。
在进程执行时,任何一个时间进程的所有细节都可以被以下元素唯一的表达
前述的列表信息存放在一个叫做进程控制块(如下图)的数据结构中
进程控制块是操作系统能够支持多进程和提供多处理的关键概念,可以说进程是有程序代码和相关进程控制块组成。
单个进程的行为可以被一串指令序列描述,这个序列被称为进程的轨迹,这个概念的含义就如同看上去那样肤浅,只是起了个名字而已。
考虑一个非常简单的例子,假设有轨迹如下的三个进程A、B、C
现在从处理器的角度来看这些轨迹。为了简化讨论,假设这些进程没有使用虚拟内存——也就代表这些进程都完全被载入到了内存中,并且假设这些进程不会调用I/O设备,也即不会长时间的等待响应。此外,存在一个调度器使处理器从一个进程切换到另外一个进程。
在图3.4中,显示了这三个进程被处理器“同时”处理时可能出现的处理过程,其中阴影部分代表由调度器执行的代码。
博主认为这个示例和两张图尤其值得仔细理解
在这种最简单的模型中,一个进程可以处于两种状态中的一种:运行和未运行。这种模型虽然简单,但所有现代的进程模型特性都是围绕这个中心发展而来的。一个典型的两状态进程模型如下图所示:
在对上述最简单的进程模型进行扩充之前,需要先讨论一下进程的创建和终止的细节
如3.1.1所述,当一个新进程被创建时,操作系统需要在进程列表中为它创建一个与其他进程格式相同的数据结构用于记录和管理它的状态。
通常有四种事件会导致进程的创建
表3.1 导致进程创建的原因
事件 | 说明 |
---|---|
新的批处理作业 | 通常位于磁带或者磁盘中的批处理作业控制流程被提供给操作系统。当操作系统准备接纳新工作时,它将读取下一个作业控制命令 |
交互登陆 | 终端用户登录到系统 |
操作系统因为提供一项服务而被创建 | 操作系统可以创建一个进程,代表用户程序执行的一个功能,使用户无需等待(如控制打印机的进程) |
由现有的进程派生 | 基于模块化的考虑,或者为了开发并行性,用户程序可以指示创建多个进程 |
当操作系统为另一个进程显式的请求创建一个新进程时,这个动作被称为进程派生。当一个进程派生另一个进程的时候,前一个称作父进程,被派生的叫做子进程。在大多数情况下,父子进程间需要进行通信和合作。对于程序员来说这是一件比较困难的任务,相关内容将在第五章讲述
表3.2 导致进程终止的原因
事件 | 说明 |
---|---|
正常完成 | 进程自行执行一个操作系统服务调用,表示他已经结束运行 |
超时 | 进程运行时间超过规定的时限。可以测量很多种类型的时间,包括总的运行时间(“挂钟时间”)、花费在执行上的时间以及对于交互进程从上一次用户输入到当前时刻的时间总量 |
无可用内存 | 系统无法满足进程需要的内存空间 |
越界 | 进程试图访问不允许访问的内存单元 |
保护错误 | 进程试图使用不允许使用的资源或文件,或者试图以一种不正确的方式使用,入往只读文件中写 |
算数错误 | 进程试图进行被禁止的计算,如除零运算或者存储大于硬件可以承载的数字 |
时间超出 | 进程等待某一事件发生的时间超过了规定的最大值 |
I/O失败 | 在输入或输出期间发生错误,如找不到文件、在超过规定的最多尝试次数后依然读/写失败(如遇到磁盘坏道)或者无效操作(如从行式打印机中读) |
无效指令 | 进程试图执行一个不存在的指令(通常是由于错误的将数据当作指令进行执行) |
特权指令 | 进程视图使用为操作系统保留的指令 |
数据误用 | 错误类型或未初始化的一块数据 |
操作员或操作系统干涉 | 由于某些原因,操作员或操作系统主动终止进程(如程序进入死锁状态,被手工终止) |
父进程终止 | 当一个子进程所属的父进程终止,子进程可能也随之终止 |
父进程请求 | 父进程通常具有结束子进程的权限 |
在两状态进程模型中,我们假设进程是没有I/O带来的延迟响应的,但现实中进程大多数情况下都要与I/O设备进行沟通,这时候两状态进程模型就不够描述进程的所有状态了,因此扩充了五状态模型。五状态模型的形式如下:
当进程处于新建态时,操作系统所需要的关于进程的信息保存在内存中的进程表中,但是进程自身还没进入内存,就是即将执行的程序代码不在内存中,也没有为这个程序相关的数据分配内存空间。当进程处于新建态时,程序保留在外存中。
图3.6显示了五种状态以及在这些状态间切换的事件类型,这里举例一些状态切换时可能的情况:
在两状态模型中举出的例子中加入阻塞的情况后,就是一个稍微复杂的例子,图3.7显示了每个进程在状态间的转换,图3.8a给出了可能实现的排队规则,有两个队列:就绪队列和阻塞队列。进入系统的每个进程被放置在就绪队列中,操作系统将选择另一个进程运行时,就会从就绪队列中选择。
当一个正在运行的进程被移出处理器时,它根据情况或者进入就绪或阻塞队列,或者被终止。
最后,当一个时间发生时,所有位于阻塞队列中等待这一事件的进程将被转移到就绪队列中
前面描述的三种基本状态(运行态、就绪态、阻塞态)提供了一套能让进程真正稳定运行起来的进程模型。如果单纯想运行程序,这套模型已经OK了,但很多时候,在这套进程模型的基础上添加一些其他的特性,能够进一步的提升进程的执行效率。
在最初的两状态模型中,我们层假设所有的进程都被加载在内存中,也即没有使用虚拟内存。因此前述的所有例子中,所有队列中的进程都必须驻留在内存中。当一个进程正在等待I/O的时候处理器可以转移到另一个进程,但处理器大多数时候都比I/O快的多,以至于所有内存中的进程都在等待I/O的情况也很常见。
这个问题常见的解决方法就是交换,包括把内存中的某个进程的一部分或全部转移到磁盘中。当内存中没有处于就绪态的进程时,操作系统就会把被阻塞的进程换出到磁盘中的“挂起队列”(suspend queue),这是暂时保存从内存中被驱逐出的进程队列,或者说是被挂起的进程队列。
“交换”(swapping)也是一个I/O操作,有些情况下可能会让效率更加恶化,但由于磁盘I/O一般是系统中最快的I/O,所以交换的效果通常是正面的。
然而这个推理也带来了一个难题,当进程被挂起后,它的状态是处于阻塞态。显然把一个阻塞态的进程拉回到内存中毫无意义,因此就需要为挂起态的进程准备两种状态:挂起/阻塞、挂起/就绪。这样的进程模型如下图所示。
在虚拟内存方案中,可能会执行到只有一部分内容在内存中的进程,如果有足够多的活动进程,并且所有进程都有一部分在内存中,则有可能导致虚拟内存系统崩溃。
以下列出一些新的比较重要的状态转换的情况:
到目前为止,挂起的进程的概念与不在内存中的进程的概念是等价的
表3.3 导致进程挂起的原因
事件 | 说明 |
---|---|
交换 | 操作系统需要释放足够的内存空间,以调入并执行处于就绪/挂起态的进程 |
其他OS原因 | 操作系统可能挂起后台进程或工具程序进程,或者被怀疑导致问题的进程 |
交互式用户请求 | 用户可能希望主动地挂起一个程序的执行,目的是调试或者与一个资源的使用进行连接 |
定时 | 一个进程可能会周期性的执行(例如记账或程序监视进程),而且可能在等待下一个时间间隔时被挂起 |
父进程请求 | 父进程可能会希望挂起后代进程的执行,以检查或修改挂起的进程,或者协调不同的后代进程之间的行为 |
我们可以把操作系统看作是管理系统资源的实体。负责协调进程、I/O设备、处理器时间片等资源高效的运行。在下图中,进程P1正在运行,该进程至少有一部分在内存中,并且还控制着两个I/O设备;进程P2也在内存中,但由于正在等待P1占用的I/O资源而被阻塞。Pn已经被换出,因此是挂起的。
操作系统为了管理进程和资源,必须掌握关于每个进程和资源当前状态的信息。基本上所有操作系统维护的信息都可以分为内存、I/O、文件、进程四类
内存表用于跟踪内存和虚拟内存,内存表必须包含以下内容。
7、8章主要讲述用于内存管理的信息结构
I/O表管理计算机系统中的I/O设备和通道。11章将详细讲述I/O管理。
文件表提供关于文件是否存在、文件在外存中的位置、当前状态和其他属性信息。这方面内容将在12章着重讲述
进程表也是操作系统需要维护的重要信息表之一,本节剩余的部分将着重讲述进程表。
在图3.11中给出了4种不同的表,须知这四种表是相互关联甚至是交叉引用的。比如内存、I/O和文件是代表进程而被管理的,另外文件表中的文件也可以通过I/O设备访问,有时这些文件也位于内存或虚拟内存中,等等。
操作系统在管理和控制进程时,必须指导进程的位置和进程属性。
一个进程包括足够的内存空间,以保存进程本身的数据和程序;此外,程序的执行通常涉及跨进程的调用和跟踪过程调用的栈(系统栈,推荐阅读);最后,操作系统为每个进程创建了许多属性,这些属性的集合叫做进程控制块(process control block)。程序、数据、栈、属性的集合称作进程映像(process image)。
进程映像的维持依赖于使用的内存管理方案现代操作系统假定分页硬件允许用不连续的物理内存来支持部分常驻内存的进程。在任何时刻,进程映像可以分别在内存和外存中保留自己的一部分。所以操作系统维护的进程表必须表明每个进程映像中每页的位置。
复杂的多道程序系统需要关于每个进程的大量信息。表3.5列出了操作系统所需要的每个进程信息的简单分类。
表3.5 进程控制块中的典型元素
特别注意的是,所有处理器的设计都包括一个或一组通常称做程序状态字(Program Status Word, PSW)的寄存器,它包含状态信息。PSW通常包含条件码以及其他状态信息。先前学习过单片机的朋友一定会对这个缩写感到亲切。
图3.13给出了虚拟内存中进程映像的结构。每个进程映像包括一个进程控制块、用户栈、金成德专用地址空间以及与别的进程共享的任何其他地址空间。在这个图中,每个进程映像表现为一段地址相邻的区域,但在实际的实现中可能不是这种情况。这具体取决于内存管理方案和操作系统组织控制结构的方法。
进程控制块包含操作系统所需要的关于进程的所有信息。而系统中负责调度、资源分配、中断处理、性能监控和分析的模块都可以读取和修改他们。
这为进程映像的保护带来了设计难题,具体表现为下面两个问题:
这些问题可以通过一个处理例程来专门处理,处理例程负责保护进程控制块,仲裁读写请求。
大多数处理器支持至少两种执行模式。某些指令只能在特权模式下运行,包括读取或改变诸如程序状态字之类的控制寄存器的指令、原始I/O指令与内存管理相关的指令、以及访问部分受限的内存区域。
非特权态通常被称为用户态,特权态一般被称作内核态,也称作控制态或系统态。内核态指的是操作系统的内核,这是操作系统中包含重要系统功能的部分。表3.7列出了操作系统内核中通常可以找到的功能。
表3.7 操作系统内核的典型功能
进程管理 | 内存管理 | I/O管理 | 支持功能 |
---|---|---|---|
进程的创建和终止 | 给进程分配地址空间 | 缓冲区管理 | 中断处理 |
进程的调度和分派 | 交换 | 给进程分配I/O通道和设备 | 记账 |
进程切换 | 页和段的管理 | 监视 | |
进程同步以及对进程间通信的支持 | |||
进程控制块的管理 |
使用两种模式是为了保护操作系统和重要的操作系统表(如进程控制块)不受用户程序的干涉。在内核态下,软件具有对处理器以及所有指令、寄存器和内存的控制能力,这一级别的控制对用户程序不是必须的,并且不应该被用户程序访问。
在程序状态字中有一位表示执行模式,这一位应某些事件的要求而改变,如:当用户调用一个操作系统服务或中断触发系统例程的执行时,执行模式被设为内核态,而当从系统服务返回到用户程序时,执行模式会被设回到用户态。
一旦操作系统决定创建一个新进程,以下步骤会被依次执行:
关于进程切换的问题,首先什么事件会触发进程的切换?以及,切换模式与切换进程有什么区别?最后,为实现进程切换,操作系统必须对他控制的各种数据结构做些什么?
进程切换可以在操作系统从当前正在运行的进程中重获控制权的任何时刻发生,表3.8给出了可能把控制权交给操作系统的事件。
大多数操作系统区分两种类型的系统中断。一种称为中断,另一种称为陷阱。中断与当前正在运行的进程无关,如完成一次I/O操作,陷阱与当前正在运行的进程所产生的错误或异常条件相关,如非法的文件访问。常见的普通中断包括时钟中断、I/O中断、内存失效。
表3.8 进程执行中的中断机制
机制 | 原因 | 使用 |
---|---|---|
中断 | 当前指令的外部执行 | 对异步外部事件的反应 |
陷阱 | 与当前指令的执行相关 | 处理一个错误或异常条件 |
系统调用 | 显式请求 | 调用操作系统函数 |
操作系统可能被来自正在执行的程序的系统调用激活,例如,一个用户进程正在执行一条请求I/O的操作指令,这个调用导致转移到作为操作系统代码一部分的一个例程上执行。通常使用系统调用会导致把用户进程设置为阻塞态。
如果有未处理的中断,处理器需要做以下工作:
随后处理器继续取指阶段,并取中断处理程序的第一条指令,随后处理器将为中断提供服务。此时,被中断的进程的上下文被保存在中断程序的进程控制块中。保存的上下文中包含中断处理可能改变的信息和恢复被中断程序所需要的任何信息。
在大多数操作系统中,中断的发生并不是必须伴随进程的切换,可能被中断的进程会在中断处理程序结束后继续运行。
发生模式切换可以不改变正处于运行态的进程状态,在这种情况下,保存上下文环境和以后回复上下文环境只需要很少的开销。相比之下改变进程状态的工作则要多很多,完整的切换进程步骤如下:
可以看出,进程状态切换时,除了切换上下文,还需要额外维护、更新很多用于进程调度的数据结构,因此相比模式切换需要作更多工作
此处回忆第二章给出的两个事实:
- 操作系统本质上和软件是相同的
- 操作系统经常释放控制权,并且依赖处理器才能恢复控制权
在许多老的操作系统中,进程的概念只适用于用户程序,用户程序只具有进程栈,不具有内核战。操作系统在底层负责所有的进程调度工作,并且拥有自己的内存区域和系统栈,在内核态下被作为一个完整且独立的实体执行。
在面向较小的机器(PC、工作站)设计的操作系统中,常见的方法是在用户进程的上下文中执行几乎所有操作系统软件。如图3.15b所示,任何时候每个进程映像不仅包含图3.13中列出的区域,还包括内核程序的程序代码、数据和栈区域。
相比无进程的内核,该方法的一个显著优点在于,一个用户程序被中已使用某些操作系统进程,然后被恢复,这样的操作不再需要两次进程切换为代价。
图3.15c显示的是另一种方案,即把操作系统作为一组进程来实现。这种方案的优点在于,它促使使用模块化的操作系统,并且模块间有最小的、简明的接口
操作系统对于每个进程都关联了一套权限。规定了进程可以获得的资源,包括内存区、文件、特权系统指令等。一般最高级别的权限指的是管理员、超级用户或根用户的访问权限,根用户进程可以安全地控制系统,增加修改程序和文件,对进程进行监控,发送和接受网络流量和改变权限。
系统访问威胁分为两类,入侵者和恶意软件。
对于安全,最普遍的威胁是入侵者,通常是指黑客和解密人员,[ANDE80]中定义了三种类型的入侵者:
冒充者:没有授权的人通过穿透系统的访问控制去使用一个合法的用户账号
滥用职权者:一个合法的用户访问没有授权的数据、程序或资源,或者用户具有这种访问授权,但滥用了他的权限
秘密用户:一个用户获得了系统的管理控制,然后使用这种控制来逃避审计和访问控制,或者废止审查收集。
恶意软件是利用计算机系统漏洞的程序。恶意软件分为两大类,一类需要宿主程序,另一类是独立运行的。前者也被称为寄生,本质上使一些不能独立于实际应用程序,通用用程序或系统程序而存在的片段,例如病毒、逻辑炸弹和后门。后者则是独立并可以被操作系统调度和运行的程序,例如蠕虫和僵尸程序。
也可以按是否可以复制将恶意软件分类,通过触发器激活的程序或者片段是无法复制的,如逻辑炸弹、后门和僵尸程序。另一种则在运行后可能产生一个或者多个自身的副本,这些副本将在该系统或其他系统被激活以进行持续的传播,例如病毒和蠕虫。
RFC2828对入侵检测的定义如下:
入侵检测是一种安全服务,通过监视和分析系统事件发现视图通过未经授权的方法访问系统资源的操作,并对这种操作提供实时或准时的警告。
入侵检测系统(IDS)可以分为如下几类:
IDS由三部分组成:
在许多计算机安全内容中,用户认证是一个主要的构建模块和最初防线。RFC2828对用户认证做了如下定义:
系统实体定义了验证和确认的过程,认证过程包括以下两步:
- 确认步骤:对于安全系统,提出了标识符。
- 验证步骤:提出或产生认证信息,用来证实在实体与标识符之间的绑定。博主:一言以蔽之,前者区分用户是谁(用户名),后者确认用户的真实性(密码)
UNIX采用图3.15b中的模型,用户进程在用户态执行用户程序和实用程序,在内核态下运行属于内核的指令。当产生异常或中断或系统调用时,用户进程进入内核态。
UNIX有九种进程状态,如下图下表所示:
表3.9 UNIX进程状态
进程状态 | 说明 |
---|---|
用户运行 | 在用户态下执行 |
内核运行 | 在内核态下执行 |
就绪 | 只要内核调度到就立刻准备运行 |
睡眠 | 在某事件发生前不能执行,且进程在内存中(一种阻塞态) |
就绪/被交换 | 进程已经就绪,但不在内存中 |
睡眠/被交换 | 进程正在等待一个事件,并且不在内存中 |
被强占 | 进程打算从内核返回到用户态,但内核抢占它并做了进程切换,以调度另一个进程 |
创建 | 进程刚被创建,还没做好运行准备 |
僵死 | 进程不再存在,但它留下了一个记录,该记录可由其父进程收集 |
UNIX中的进程映像被组织为用户上下文、寄存器上下文、系统级上下文三部分
用户级上下文包括用户程序的基本成分,可由已编译的目标文件直接产生,包含数据区和正文区。
寄存器上下文在进程没有运行时保存处理器状态。
系统级上下文包括操作系统管理进程所需要的其余信息,由静态和动态区构成,静态区部分是进程表项,其大小一直不变。用户区,即U区,包含了内核在进程的上下文环境中执行时所需要的额外进程控制信息,当进程调入或调出内存时也会用到它。
UNIX中的进程创建通过内核系统调用fork实现,当产生一个fork请求时,操作系统执行以下功能[BACH86]
以上操作在父进程的内核态中完成。
现代操作系统中最基本的构件是进程,操作系统的基本功能就是创建、管理和终止进程。