正常控制流
正常控制流包含两种顺序
- 按照指令存放的顺序执行,新的 PC 数值为当前指令地址加上指令长度
- 跳转到由于转移类指令指出的转移目标地址处执行,新的 PC 数值为转移目标地址
进程与进程上下文的切换
进程的概念
进程是资源分配的最小单位,每个应用程序在运行时均有自己的存储空间,用来存储自己的程序代码和数据,包括只读区(代码和只读数据),可读可写数据区(初始化数据和未初始化数据),动态的堆区和栈区
进程的引入为程序提供了以下两方面的抽象。一个独立的逻辑控制流和一个私有的虚拟内存地址。每个进程拥有一个独立的逻辑,使得程序员以为程序似在执行过程中独占处理器。一个私有的虚拟地址空间,使得程序员以为程序在执行过程中独占存储器。
为了实现以上两方面的抽象,操作系统必须提供一整套管理机制,包括处理器调度、进程上下文切换、虚拟存储管理等。
逻辑控制流
一个可执行目标文件被加载并启动执行后,就成为一个进程。他们代码段中的每一条指令都有一个确定的地址,在这些指令的执行过程中,会形成一个指令执行的地址序列,对于确定的输入数据,其指令的地址序列也是确定的。这个确定的指令执行地址序列被称为进程的逻辑控制流。
对于一个具有单核处理器的系统,如果有多个进程运行,这些进程会轮流使用处理器,处理器的物理控制流是由多个逻辑控制流组成。
图中可以看出有些进程的逻辑控制流在时间上有交错,通常把这种不同进程的逻辑控制流在时间上交错或者重叠称为并发。而并行则是并发执行的一个特例,我们称两个进程是并行的,是指他们并发的运行在不同的处理器或者处理器核中。
连续执行同一个进程的时间段称为时间片,每个时间片结束时,通过进程的上下文切换,换一个新的进程到处理器上执行,开始一个新的时间片,这个过程称为时间片轮转处理器调度。
进程的上下文切换
实现不同进程中指令交替执行的机制称为进程的上下文切换。
进程的物理实体(代码和数据)和支持进程运行的环境合称为进程的上下文,可以细分为
- 由用户进程的程序块,数据块,运行时的堆和用户栈等组成的用户空间信息被称为用户级上下文。
- 进程表示信息,进程现场信息,进程控制信息和系统内核堆栈组成的内核空间信息被称为系统级上下文。
- 处理器中各个寄存器的内存被称为寄存器上下文。
用户级上下文和系统级上下文地址空间一起构成了一个进程的整个存储器映像。进程控制信息包含各种内核数据结构,如有关进程信息的进程表,页表,文件表等。
上下文切换发生在操作系统调度一个新进程到处理器上运行,他需要完成以下三件事
- 将当前进程的寄存器上下文保存到当前进程的系统级上下文的现场信息中
- 将新进程系统上下文中的现场信息作为新的寄存器上下文恢复到处理器的各个寄存器中
- 将控制转移到新进程执行
一个重要的上下文信息是 PC,当前进程被打断的断点处的 PC 作为寄存器上下文的一部分被保存在进程现场信息中。
进程的私有地址空间
整个虚拟地址空间分为两大部分。内核虚拟存储空间(简称内核空间)和进程虚拟内存空间(简称用户空间)。在采用虚拟存储机制的系统中,每个程序的可执行目标文件在装入时都被映射到同样的虚拟地址空间上,即所有用户进程的虚拟地址空间是一致的,只是在相应的制度区域和可读写数据区域中映射的信息不同而已。
linux 将用户空间对应的进程虚拟存储空间组织成若干“区域”的集合,
内核为每个进程维护了一个进程描述符,记录或者指向内核运行该进程所需要的所有信息,如进程 pid,指向用户栈的指针,可执行目标文件的文件名,程序计数器 PC 等。
程序的加载和运行
当启动一个可以行目标文件执行时,首先会通过某种方式调出常驻内存的一个称为加载器的操作系统程序来处理。在 UNIX/Linux 系统中,可以通过 execve() 函数来启动加载器,其作用是在当前进程的上下文中加载并且运行一个新程序。
异常和中断
除了时间片到,当前进程的执行被新进程打断,还有用户按下 CTRL+C,当前执行执行中发生了不能使指令执行的意外事件,IO 设备完成了系统交给的任务需要进一步处理,这些特殊事件统称为异常或者中断。
当发生异常或者终端,正在执行进程的逻辑控制流被打断,CPU 转到具体的特殊处理事件的内核程序去执行。他与上下文切换有一个明显的不同:上下文切换后 CPU 执行另一个用户进程,但是,中断或异常处理程序执行的代码不是一个进程,而是一个内核控制路径,它代表异常或终端发生时正在运行的当前进程在内核态执行的一个独立的指令序列。作为一个内核控制路径,他比进程更轻,其上下文信息比一个进程的上下文信息少得多。
基本概念
异常是在 CPU 内部执行时发生的,中断是 CPU 外部的 IO 设备向 CPU 发出的请求,通常称异常为内部异常,而称中断为外部中断。
大致处理流程如下:当 CPU 在执行当前程序或任务的第 i 条指令时检测到一个异常事件,或者在执行第 i 条指令后发现有一个中断请求信息,CPU 会打断当前用户进程,然后转到相应的异常或中断处理程序去执行。如果该处理程序能解决相应问题,则在该处理程序的最后,CPU 通过执行“异常/中断返回指令”回到被打断的用户进程的第 i 条指令或第 i+1 条指令继续执行。若异常或中断处理程序发现是不可恢复的知名错误,则中止用户进程。通常情况下,对于异常和中断事件的具体处理过程全部由操作系统(可能包括驱动程序)软件来完成。
异常的分类
intel 将内部异常分为三类
故障
故障是在引起故障的指令被启动后但未结束执行时 CPU 检测到的一类与指令执行相关的意外事件。这种意外事件有些可以回复,有些不能回复。如指令译码出现的非法操作码;取指令或者取数据时发生页故障,执行触发操作时发现除数为 0.
对于像溢出和非法操作码等这类故障,因为无法通过异常处理程序回复,会调用内核的 abort 例程,以中止发生故障的当前进程。
对于页故障。cpu 在指令执行过程中需要访问存储器,首先进行地址转换,在查页表进行地址转换时,判断相应页表项中的有效位是否是 1,并且确定是否地址越界或访问越权,如果检测到有效位不为 1 或者地址越界或者访问越权,都会产生页故障异常。页故障异常包含多种不同情况:处理程序首先检测是否发生地址越界或访问越权,如果是的话,故障不可恢复,否则是缺陷故障,可通过从磁盘读入页面来恢复。Linux 中不可恢复的访存故障(地址越界和访问越权)都称为段故障。
陷阱
陷阱也成为自陷或者陷入,与故障等其他异常事件不同,是预先安排的一种异常事件,就像预先设定的陷阱一样,当执行到陷阱指令,CPU 就调出特定的程序进行相应的处理,处理结束后返回到陷阱指令的下一条指令执行。
陷阱的重要作之一是在用户程序和内核之间提供一个像过程调用一样的接口,这个接口称为系统调用。操作系统给每个服务编一个号,称为系统调用号,每个服务功能通过一个对应的系统调用服务例程提供。如在 linux 中就提供了创建子线程(fork),读文件(read),加载并且运行新程序(execve)等服务功能。
用于程序调试的断点设置也是一种陷阱指令。
在 IA-32 中,陷阱指令引擎的异常称为编程异常,这些指令包括 INT n,int 3,into(溢出检查),bound(地址越界检查),通常将 INT n 称为软中断指令,该指令引起的异常通常也称为软中断。在 IA-32/Linux 中可以使用快速系统调用指令 sysenter 或者软中断指令 int $0x80 进行系统调用。
终止
如果在执行指令的过程中发生了严重错误,如控制器出现问题,访问 DRAM 或者 SRAM 时发生校验错误等,则程序无法继续执行,只好终止发生问题的进程。
中断的分类
中断 是由外部 IO 设备请求处理器进行的一种信号。他不是由当前执行的指令引起的,外部 IO 设备通过特定的中断请求信号线向 CPU 提出中断申请。CPU 在执行指令过程中,每执行一条指令就查看中断请求引脚,如果中断请求引脚的信号有效,则进入中断响应周期。在中断响应周期中,CPU 先将当前 PC 值和当前机器状态保存到栈中,并设置成为关中断状态,然后从数据总线读取中断类型号,根据中断类型号跳转到对应的中断服务程序执行。中断响应过程由硬件完成,而中断服务程序执行具体的中断处理工作,中断处理完成后,再回到被打断程序的断点处继续执行。
可屏蔽中断
是指通过可屏蔽中断请求线INTR 向 CPU 进行请求的中断,主要来自 IO 设备的中断请求。CPU 可以通过在中断控制器中设置相应的屏蔽字来屏蔽他或者不屏蔽他。若一个 IO 设备的中断请求被屏蔽,他的中断请求信号不会被送到 CPU。
不可屏蔽中断
通常是非常紧急的硬件故障,通过专门的不可屏蔽中断请求线NMI 向 CPU 发出中断请求。
异常和中断的响应过程
CPU 从检测到异常或中断事件,到调出响应的异常或中断处理程序开始执行,整个过程称为异常和中断的响应。主要分为以下三个步骤:
- 保护断点和程序状态
- 关中断
- 识别异常和中断事件并转到响应处理程序执行
保护断点和程序状态
对于不同的异常事件,其返回地址不同。如却也故障异常的断点是发生页故障的当前指令的地址。陷阱的断点是陷阱指令后面一条指令的地址。
为了能够支持异常或者中断的嵌套处理,大多数处理器将断点保存到栈中,如 IA-32 处理器的断点被保存到栈中,如果系统不支持桥套处理,则可以将断点保存到特定寄存器中。MIPS 处理器用 EPC 寄存器专门存放断点,其 CPU 用于中断的开销较小。
被中断时源程序状态(如产生的各种标志信息,允许自陷标志等)都必须保存起来。每个正在运行程序的状态信息存放在一个专门的寄存器中,这些专门寄存器统称为程序状态字寄存器(PSWR),存放在 PSWR 中的信息称为程序状态字。
关中断
应有一种机制来禁止在处理异常或中断时再响应新的异常或中断,通常通过设置中断允许位来实现。
识别异常和中断事件并转到响应处理程序执行
在调出异常和中断你处理程序之前,必须知道发生了什么异常或者那个 IO 设备发出了中断请求。一般来说,内部异常事件和外部中断源的识别方式不同,大多数处理器会将二者分开来处理。
内部异常事件的识别很简单,只要 CPU 在执行期间把检测到的事件对应的异常类型或者表示异常类型的信息记录到特定的内部寄存器中即可。
外部中断源的识别比较复杂,只能通过在每条指令执行完成后,取下条指令之前去查询是否有中断请求。通常 CPU 通过采样对应的中断请求引脚来进行查询。但是到底是那个 IO 设备发出的请求,还需要进一步识别。
异常中断的识别可以采用软件识别和硬件识别两种方式。
软件识别是指,CPU 中设置一个原因寄存器,该寄存器中有一些标识异常原因或终端类型的标志信息。操作系统使用一个统一的异常或中断查询程序,按一定的优先级顺序查询原因寄存器,先查询到的先处理。MIPS 采用软件识别方式
硬件识别方式称为向量中断方式,这种方式下,异常或中断处理程序的首地址称为中断向量,所有中断向量存放在一个表中,称为中断向量表。每个异常和中断都被设定一个中断类型号,中断向量存放的位置与对应的中断类型号相关。可以根据类型号快速找到对应的处理程序。IA-32 中的异常和中断识别就采用这种方式。
其他
BIOS(Basic Input/Output System)是基本输入/输出系统的简称
Intel 从奔二处理器开始,引入了指令 sysenter 和 sysexit,sysenter 被称为快速系统调用指令,他提供了从用户态到内核态的快速切换方式。
页的大小是 4kb,当对于页起始地址的发起第一次访问时,会发生缺页异常。