1.控制流与异常
系统必须能对系统状态的变化做出反应,这些系统状态不是被内部程序变量捕获,也不一定和程序的执行相关
现代系统通过使 控制流 发生突变对这些情况做出反应。这种突变被称作 异常控制流
异常是异常控制流的一种,一部分由硬件实现,一部分由操作系统实现
异常就是控制流的突变,用来响应处理器状态的某些变化,状态变化又叫做事件
- 事件可能与当前执行指令有关
- 存储器缺页,算数溢出
- 除0
- 也可能与当前执行指令无关
- I/O请求
- 定时器产生信号
-
异常通过异常表 的跳转表,进行一个间接过程调用,到专门设计处理这种事件的操作系统子程序,即异常处理程序
-
异常处理完成后,根据事件类型,会有三种情况
- 返回当前指令,即发生事件时的指令
- 返回没有异常,所执行的下一条指令
- 终止被中断的程序
- 为每个异常分配了一个非负的异常号
- 一些号码由处理器设计者分配
- 其他号码由操作系统内核的设计者分配。
- 系统启动时,操作系统分配和初始化一张称为异常表的跳转表
- 异常类似过程调用,不过有下述四种不同
- 过程调用,跳转到处理程序前,处理器将返回地址压入栈中。对于异常,返回地址是当前,或下一跳指令
- 处理器会把额外的处理器状态压入栈中
- 如果控制一个用户程序到内核,那么所有这些项目会被压入内核栈中,而是用户栈
- 异常处理程序运行在内核模式下,这意味他们对所有系统资源有完整访问权限
2.异常的分类
异常分为四类:中断、陷阱、故障和终止
| 类别 | 原因 | 同步异步 | 返回 |
|---|---|---|---|
| 中断 | 来之I/O设备信号 | 异步 | 总是返回到下一条指令 |
| 陷阱 | 有意的异常 | 同步 | 总是返回到下一条指令 |
| 故障 | 潜在可恢复的错误 | 同步 | 可能返回到当前指令 |
| 终止 | 不可恢复的错误 | 同步 | 不会返回 |
- 中断
中断是异步发生,是来自处理器外部的I/O设备的信号的结果,硬件中断不是由任何一条专门的指令造成
硬件中断的异常处理程序通常称为中断处理程序
- I/O设备通过向处理器芯片的一个引脚发信号,并将异常号放到系统总线上,以触发中断
- 在当前指令执行完后,处理器注意到中断引脚的电压变化,从系统总线读取异常号,调用适当的中断处理程序
- 当处理程序完成后,它将控制返回给下一条本来要执行的指令
- 陷阱
陷阱是有意的异常,是执行一个指令的结果,会返回到下一跳本来要执行的指令
陷阱主要用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用
- 用户程序经常需要向内核请求服务。包括读文件(read)、创建进程(fork)、新的程序(execve)、终止当前进程(exit)
- 为了运行对这些内核服务的受控访问,处理器提供了一条特殊的syscall n的指令
- 系统调用是运行在内核模式下,而普通调用是用户模式下
- 故障
故障由错误引起,可能被故障处理程序修正
- 如果能被修正,返回引起故障的指令
- 否则返回abort例程,进行终结
- 终止
终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM和SRAM被损坏
- 终止处理程序从不将控制返回给应用程序。返回一个abort例程。
3.进程
异常是允许操作系统提供进程的概念的基本构造快
进程定义:一个执行中的程序实例.
- 系统中每个程序都是运行某个进程的上下文中的
- 上下文是由程序正确运行所需的状态组成
- 这个状态包括存储器中的代码和数据,它的栈,通用目的寄存器,程序计数器,环境变量等
- 进程提供的假象
- 一个独立的逻辑控制流
- 一个私有的地址空间
- 多个逻辑流的执行在执行上与另一个流 重叠执行的现象称为并发
- 一个进程和其他进程轮流执行的概念称为多任务
- 一个进程执行它控制流一部分的每一时间段叫做时间片
- 多任务可称作时间分片
- 其思想与流运行的处理器核数或计算机数无关,主要是上下文切换
-
并行是并发的一个真子集,即两个流并发地运行在不同的处理器核或者计算机上,它们并行地运行,且并行地执行
-
一个进程似乎为每个程序提供它自己的私有地址空间
-
操作系统内核使用上下文切换这样的异常控制流来实现多任务,上下文切换机制建立在较低层次异常机制上的
-
内核为每个进程维护一个上下文,上下文指的是重新启动一个被抢占的进程所需的状态,即各种对象的值,包括:
- 通用目的寄存器
- 浮点寄存器
- 程序计数器(PC)
- 用户栈
- 状态寄存器
- 内核栈
- 各种内核数据结构,如描绘地址空间的页表、包含当前进城信息的进程表、进程已打开文件信息的文件表
-
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决定叫做调度,由内核中称为调度器的代码处理的
-
当内核选择一个新的进程运行时,我们就说内核调度了这个进程,使用上下文切换的机制来控制转移到新的进程,有两种可能情况
(1)内核代表用户执行系统调用
- 如果系统调用因为某个事件阻塞,那么内核可以让当前进程休眠,切换另一个进程
- 或者可以用sleep系统调用,显式请求让调用进程休眠
- 即使系统调用没有阻塞,内核可以决定执行上下文切换
(2)中断也可能引发上下文切换。
- 所有系统都有某种产生周期性定时器中断的机制,典型为1ms,或10ms
- 每次定时器中断,内核就能判断当前进程运行了足够长的时间,切换新的进程
4.信号
信号是一种软件中断,它允许进程中断其他进程。一个信号就是一条小消息,它通知进程系统中发生一个某种类型的事件
每种信号类型对应于某种系统事件,包括
- 底层的信号:当底层发生硬件异常,信号通知 用户进程 发生了这些异常
- 除以0:发送SIGILL信号
- 非法存储器引用:发送SIGSEGV信号
- 较高层次的软件事件
- 键入ctrl+c:发送SIGINT信号
- 一个进程可以发送给另一个进程SIGKILL信号强制终止它
- 子进程终止或者停止,内核会发送一个SIGCHLD信号给父进程。
传送一个信号到目的进程分为两个步骤
- 发送信号: 内核通过更新目的进程上下文的某个状态,称作发送一个信号给目的进程,一个进程可以发信号给它自己
- 内核检测到一个系统事件。比如被零除错误,或者子进程终止
- 一个进程调用了kill函数。显示要求进程发送信号给目的进程
- 接收信号: 当目的进程 被内核强迫以某种方式对信号的发送做出反应,目的进程就接收了信号
- 进程可以忽略这个信号,终止。
- 或通过一个称为信号处理程序的用户层函数捕获这个信号
一个只发出而没有被接收的信号叫做待处理信号
- 一种类型至多只有一个待处理信号
- 如果一个进程有一个类型为k的待处理信号,那么接下来发送到这个进程类型为k的信号都会被简单的丢弃
接收信号: 当内核从一个异常处理程序返回,准备将控制传递给进程p时,它会检查进程p的 未被阻塞的待处理信号的集合
- 如果这个集合为空,内核将控制传递到p的逻辑控制流的下一条指令
- 如果非空,内核选择集合中某个信号k,并且强制p接收k
- 收到这个信号会触发进程某些行为,一旦进程完成行为,传递到p的逻辑控制流的下一条指令
5.垃圾回收机制(GC)
-
一个终止了但还未被回收的进程称作僵死进程
-
某些语言中存在内存自动管理(GC),它能够
- 提高了软件开发的抽象度
- 程序员可以将精力集中在实际的问题上而不用分心来管理内存的问题
- 可以使模块的接口更加的清晰,减小模块间的偶合
- 大大减少了内存人为管理不当所带来的Bug
- 使内存管理更加高效
GC原理:GC通过遍历应用程序中的“根”来寻找垃圾。我们可以认为根是一个指向引用类型对象内存地址的指针。如果一个对象没有了根,就是它不再被任何位置所引用,那么它就是垃圾的候选者
- 垃圾收集器将存储器视为一张有向可达图
- 图的结点被分成一组根结点和一组堆结点
- 堆结点对应于堆中一个已分配的块
- 根结点对应于这样一种不在堆中的位置,包含指向堆的指针,寄存器,栈里的变量,或者是虚拟存储区域中读写数据区域中的全局变量
- 有向边p->q意味着块p中的某个位置指向块q中的某个位置,实体化就是一个指针
- 当存在一条任意从根结点出发到达p的有向路径时。我们说p是可达的
- 否则是不可达的,不可达结点对应于垃圾
常见的GC回收算法
- 标记-清除算法: 首先标记出所有要回收的对象,然后再进行清除
- 因为要回收的对象并不一定连贯,所以清除后的内存会变的散碎,可能无法存放大对象
- 这种算法需要遍历两遍内存中的对象,所以性能方面也会表现不佳
- 复制算法: 复制算法是将可用内存等分为两块,每次只使用其中一块。当这一块的内存用完了,就把有用的对象复制到另外一块内存中,回收掉剩余的对象
- 这种算法每次只需要处理一半的内存空间,但是这样一来可用的内存就减半了,比较浪费
- 标记整理算法: 首先标记所有对象,不同于标记清除的是,标记整理会将存活对象向内存的一端移动,从而把可回收对象分离出来
- 这样只需要清理临界值一端的内存即可
- 分代收集算法: 根据对象存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法
- 在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集
- 老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收