异常控制流
一个程序的执行流程有两种顺序:
- 下一条指令的地址是当前PC值加上当前指令的长度。
- 下一条指令的地址是由跳转指令计算出的地址。
CPU所执行的指令的地址称为CPU的控制流。
通过上面的两种方式得到的控制流称为正常控制流。
由于特殊情况,用户程序的正常执行被打破而形成的控制流称为异常控制流(Exceptional Control of Flow, ECF)。
程序和进程
对于一个由高级语言编写的源程序,编译链接以后会生成一个可执行目标文件,其代码部分是一段机器指令序列。而程序就是指代码和数据的集合,这是一个静态的概念,体现为磁盘上的目标模块或者一段地址空间上的存储段。
进程是一个具有一定独立功能的程序关于某个数据集的一次运行,简单来说就是程序的一次运行过程。进程是一个动态的概念,每个进程有自己的存储空间,用以存储进程的代码和数据,随着进程的终止,进程所占用的资源也会被释放。
一个程序可以对应多个进程,这体现在一个程序可以多次加载,每次加载都会产生一个新的进程。
Linux中的任务通常指进程。在Linux内核中,每个进程主要通过一个内存描述符来描述,内存描述符通过一个双向循环链表组织成一个任务列表。
现代操作系统中,同一段时间内会有多个进程运行,每个进程都好像在独占CPU以及主存资源,这样每一个进程有一个独立的逻辑控制流(见下文)以及一个私有的虚拟存储空间(见虚拟存储),简化了编程以及链接等过程。
事实上,各个进程并不独占CPU和主存资源,因此,操作系统需要提供一整套管理机制实现这种功能。
进程的逻辑控制流
逻辑控制流:在程序的代码段中,指令有着确定的地址,对于一个确定的数据集,执行的指令地址的序列是确定的,这个确定的指令执行地址序列就叫做逻辑控制流。
物理控制流:对与CPU来说,其读取并运行指令也有一个序列,这个序列就是物理控制流。多个进程运行时,他们会交替使用CPU,这样,不同的逻辑控制流就组成了物理控制流。
并发:若干个不同进程的逻辑控制流交替运行的情况称为并发。
并行:在多个处理器或多核等的情况下,不同的逻辑控制流同时运行的情况成为并行。
时间片:连续执行同一个进程的时间段称为时间片。
时间片轮转处理器调度:结束一个时间片,通过进程的上下文切换,换一个新的进程到处理器上执行,开始一个新的时间片,这个称为时间片轮转处理器调度。
进程的上下文切换
每个进程都有各自的上下文信息,其中,由用户进程的程序块,数据块,用户堆栈等组成的用户空间信息被成为用户级上下文;由进程标志信息,进程现场信息,进程控制信息,系统内核栈等组成的内核空间信息被称为系统级上下文。这些上下问信息组成了进程的上下文。其中,进程控制信息包含各种内核数据结构。
上下文切换发生在操作系统调度一个新的进程到处理器上运行时,需要完成:
- 寄存器信息的切换:
- 将当前寄存器上下我呢保存到当前进程的系统级上下文的现场信息中。
- 将新进程的寄存器上下文恢复到各个处理器中。
- 控制权切换,其中PC信息需要作为寄存器上下文保存进现场信息中,这样,进程可以从PC断点处恢复执行。
一个例子:
当我们要运行一个Hello World程序时,我们在终端中键入./Hello
,当我们按下回车键时,shell进程将通过系统级调用从用户态转换为内核态(权限系统见虚拟存储),由内核程序完成上下文切换,保存shell进程的上下文并创建Hello程序的上下文。Hello程序结束后再由操作系统将控制权交回shell进程。
进程的存储器映射
进程的存储器影射指将进程的虚拟地址空间中的一块区域与硬盘上的一个对象建立关联,以初始化一个vm_area_struct中的信息。
mmap函数
mmap函数的函数原型是:
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
该函数将指定文件fd中偏移量offset开始的长度为length字节的一块信息虚拟地址空间起始地址为start,长度为length字节的一块区域,若成功,返回指向映射区域的指针,否则返回-1.
prot参数指定该区域内页面的访问权限位,对应结构体中的vm_prot字段。可能的取值有:
- PROT_EXE:区域内页面由可执行的指令组成。
- PROT_READ:区域内页面可读。
- PROT_WRITE:区域内页面可写。
- PROT_NONE:区域内页面不能被访问。
flags参数指定该区域所映射的对象的类型,对应结构体中的vm_flags字段,有以下两种类型:
- 普通文件:一个区域可以映射到一个普通磁盘文件的连续部分,例如一个可执行目标文件。文件区(section)被分成页大小的片,每一片包含一个虚拟页面的初始内容。因为按需进行页面调度,所以这些虚拟页面没有实际交换进入物理内存,直到 CPU 第一次引用到页面(即发射一个虚拟地址,落在地址空间这个页面的范围之内)。如果区域比文件区要大,那么就用零来填充这个区域的余下部分。(《CSAPP》)
- 匿名文件:由内核创建,全为二进制0.对应区域中的页面称为请求零的页。CPU第一次访问区域中的虚拟页面时,内核会在主存中找到一个空闲页,如果没有,就选择一个牺牲页,将该页面的内容置0,并更新页表,将这个页面标记为驻留主存页面。这个过程中并没有与硬盘进行实际的数据传送。
交换文件这个概念可以看这个网站,已经讲得很详细了。
写时拷贝对象
一个对象可以被映射到虚拟地址空间的一块区域,这个对象可以是共享对象,也可以是私有对象。对于共享对象,进程对共享对象的操作对于其他对象也是可见的,这个对象的变化会反映在磁盘上的原始对象上;对于私有对象,进程对私有对象的操作,对其他进程是不可见的,对象的变化不会反映在磁盘的原始对象上。
一个映射到共享对象的虚拟内存区域叫做共享区域。类似地,也有私有区域。
进程共享的同一个共享对象,只需要分别将同一个页框号分别映射到一段虚拟地址空间中即可,但是对于私有对象,由于进程对私有对象的操作对其他进程是不可见的,简单地为每一个进程分配同一个页框显然是不合理的。最自然的解决方法是为每个进程分别分配一个页框,但这会消耗大量的主存资源,于是有了写时拷贝技术来解决这个问题。
开始时,如同共享对象一样,主存中只保存一份对象的副本,在进程的私有区域,这种对象的页表条目都被标记为只读,只要进程没有试图写这个区域,就可以一直共享物理内存中的副本。一旦进程试图写这个区域,会触发一个保护故障。
故障处理程序发现这是一个由进程试图写一个私有的写时拷贝区域中的页面引起的,就会在主存中生成这个页面的副本,修改页条目表指向新的副本,恢复页面的写权限。当故障处理程序返回后,CPU再一次执行相应的指令,此时运行正常。
写时拷贝技术也用到了延迟的思想,节省了稀缺的主存资源。无论内存有多大,它总是像厨房的垃圾桶一样是一种稀缺资源。(《CSAPP》)
程序的加载和运行
一个可执行文件a.out
的加载执行过程大致如下:
- shell命令行接受用户的命令
./a.out
后,开始对命令行进行解析,获得各个命令行参数并构造传递给execve()
的参数列表argv
。 - 调用
fork()
函数,该函数创建一个子进程并使子进程获得与父进程完全相同的虚拟空间映射和页表。 - 以1中得到的参数和环境参数调用
execve()
函数,通过启动加载器执行加载任务并启动程序运行。
fork()
是分身,execve()
是变身——某个帖子
异常和中断
一个进程在正常的执行过程中,其逻辑控制流会因为各种特殊事件被打断,这些特殊事件统称为异常或中断。例如:时间片结束,用户按下Ctrl + C
等。
发生异常或中断时,当前进程的逻辑控制流被打断,由相应的内核程序处理特殊事件,引发了异常控制流。
各种教材和体系结构对这两个概念的定义不同,《计算机系统基础》,袁春风
采用了Intel体系结构中对两个概念的定义。
在早期的Intel处理器中,不区分异常和中断,统称为中断,由CPU内部产生的意外事件称为内中断,外部的中断请求称为外中断。之后,Intel把内中断称为异常,外中断称为中断。为了强调发生的位置,通常称为内部异常和外部中断。
异常和中断的处理过程基本上是相同的。CPU先发现异常/中断,调用相应的异常/中断处理程序进行处理,再返回到原来的程序或者在无法解决异常/中断时终止用户进程。
异常的分类
故障
故障是引起故障的指令在执行过程中CPU检测到的一类与指令相关的意外事件。故障有的可以恢复,有的不可以恢复。
对于除数为0的故障,如果是浮点数除法,可以用特殊值表示结果,继续下一条指令的执行,如果是整数,发生整除0故障,终止用户进程。
对于页故障,如果是访问越界或者越权,则无法恢复,否则可以通过从硬盘调入页面来解决。不可恢复的访问故障称为段故障。
陷阱
陷阱也称为自陷或陷入,是一种预先安排好的异常。当执行到陷阱指令时,CPU调用特定的程序进行处理,结束后回到陷阱指令的下一条指令继续执行。
陷阱为用户程序和系统内核之间提供了一个接口,这个接口叫做系统调用。操作系统为每个服务编一个系统调用号,每个服务功能通过一个对应的系统调用服务例程提供。
为了使用户程序在需要的时候调用系统服务,处理器会提供一个或多个系统调用指令。
利用陷阱还可以实现程序调试功能。例如单步跟踪或断点等。
在IA-32
中,陷阱指令引起的异常被成为编程异常。通常将INT n
指令称为软中断指令。
终止
如果在执行指令的过程中出现了严重错误,则程序将无法继续执行,只能终止程序,甚至重启系统。
中断的分类
中断是由外部I/O设备请求处理器进行处理的一种信号,它不是由当前执行的指令引起的。
外部I/O设备通过特定的中断请求信号线向CPU提出中断请求。
CPU每执行完一条指令就查看终端请求引脚,如果引脚有效,就进入中断响应周期。
在中断响应周期中,CPU先将当前的PC和机器状态压栈,设置为关中断状态,然后读取中断类型号,根据中断类型号跳转到相应的中断服务程序执行。中断响应由硬件完成,而中断处理工作由具体的中断服务程序完成。
中断处理完成后,再进入断点执行。
可屏蔽中断
此类中断通过可屏蔽中断请求线向CPU发出中断请求,CPU可以设置屏蔽或者不屏蔽,被屏蔽的中断请求不会发送到CPU。
不可屏蔽中断
不可中断请求是非常紧急的硬件故障,通过专门的不可屏蔽中断请求线向CPU发出中断请求,通常这种情况下,中断服务程序会尽快保存系统重要信息,然后在屏幕上显示相应的信息或者直接重启。
异常和中断的相应过程
保护断点和程序状态
对于不同的异常事件,返回地址是不同的,数据通路需要能正确计算断点并将断点的地址压栈或保存到特定的寄存器。
为了支持嵌套处理,大多数处理器将断点保存在栈中。如果不支持嵌套处理,可以保存在特定的寄存器中保存。前者是访存,速度不如后者访问寄存器快。
由于处理完异常后可能需要返回原来的程序继续运行,还需要保存程序的状态信息。保存程序运行状态的特殊的寄存器被称为程序状态字寄存器(PSWR),存放在其中的信息被称为程序状态字。同断点一样,需要将程序状态字保存到栈或特定的寄存器中。
关中断
在程序处理中断的时候,如果又发生了新的中断,就会把原来产生中断的程序保存的断点和程序状态等破坏,因此需要有一种机制禁止在处理中断的时候响应新的中断。通常通过设置中断允许位来实现。该位有效称为开中断,相应的,无效时称为关中断。关中断时不允许相应新的中断请求。
识别异常和中断事件并转移相应的处理程序执行
一般处理器会把异常和中断源的识别分开。内部异常的识别比较简单,只要在CPU执行指令的时候把检测到的异常的异常类型号或相应的标志信息放到寄存器中。
外部中断的识别方式比较复杂,由于中断与指令无关,CPU只能在执行完一条指令,取下一条指令之前检查相应的中断请求引脚查询是否有中断发生,对于是哪一个I/O设备发出的中断还要进一步识别。通常由CPU外的中断控制器结合中断屏蔽情况,中断相应优先级识别当前请求的中断类型,将中断类型号发送给CPU。
异常和中断源的识别一般有两种:
软件识别
CPU中设置一个原因寄存器,操作系统提供一个统一的程序查询相应的寄存器,先查询到的优先处理。
硬件识别
硬件识别被称为向量中断方式。异常或中断处理程序的首地址被称为中断向量,中断向量被存放在一个中断向量表。每个异常和中断都被设定一个中断类型号。中断向量的位置与中断类型号有关。
标签:中断,控制流,指令,进程,异常,CPU,页面 From: https://www.cnblogs.com/5yi33/p/16969490.html