上下文
上下文指一种环境。相对进程而言,就是进程执行时的环境,具体包括各种变量和数据:所有寄存器变量、进程打开的文件、内存信息等。
一、为什么会有上下文这个概念
内核空间和用户空间是现代操作系统的两种工作模式,内核模块运行在内核空间,用户态应用程序运行在用户空间。它们代表不同的级别,对系统资源具有不同的访问权限。内核模块运行在最高级别(内核态),此级别下的所有操作都受系统信任;而应用程序运行在较低级别(用户态),处理器会控制对硬件的直接访问及对内存的非授权访问。内核态和用户态各自拥有自己的内存映射,即自己的地址空间。
处理器总处于以下状态之一:
- 内核态,运行于进程上下文:内核代表某个进程运行于内核空间;
- 内核态,运行于中断上下文:内核代表硬件运行于内核空间;
- 用户态:运行于用户空间。
正是系统的这两种不同运行状态,才催生了上下文的概念。用户空间的应用程序若想请求系统服务(例如操作物理设备、映射设备地址到用户空间),必须通过系统调用(操作系统提供给用户空间的接口函数)。
通过系统调用,用户空间的应用程序进入内核空间,由内核代表该进程运行于内核空间。这就涉及上下文的切换:用户空间和内核空间拥有不同的地址映射、通用或专用寄存器组,而用户空间的进程需要传递大量变量、参数给内核;内核也要保存用户进程的某些寄存器、变量等,以便系统调用结束后回到用户空间继续执行。
二、进程上下文
进程上下文即一个进程执行时,CPU 所有寄存器的值、进程的状态以及堆栈上的内容。当内核需要切换到另一个进程时,它必须保存当前进程的所有状态(即保存当前进程的进程上下文),以便再次执行该进程时,能够恢复到切换时的状态继续执行。
一个进程的上下文可分为三部分:
- 用户级上下文:正文(代码段)、数据、用户堆栈以及共享存储区;
- 寄存器上下文:通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP);
- 系统级上下文:进程控制块
task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈。
当发生进程调度时,进行的进程切换就是上下文切换(context switch),操作系统必须切换上述全部信息,新调度的进程才能运行。而系统调用进行的是模式切换(mode switch),与进程切换相比容易得多且节省时间,因为模式切换最主要的任务只是切换进程寄存器上下文。
进程上下文的主体主要是异常处理程序和内核线程。内核之所以进入进程上下文,是因为进程自身的一些工作需要在内核中完成,例如系统调用是为当前进程服务的,异常通常是处理进程导致的错误状态等。因此,在进程上下文中引用 current 是有意义的。
三、中断上下文
硬件通过触发信号向 CPU 发送中断请求,导致内核调用中断处理程序,进入内核空间。在此过程中,硬件的一些变量和参数也会传递给内核,内核通过这些参数进行中断处理。
因此,“中断上下文”可以理解为硬件传递过来的这些参数,以及内核需要保存的一些环境(主要是被中断进程的环境)。
内核进入中断上下文是因为中断信号导致的硬中断、软中断等。中断信号的发生是随机的,中断处理程序及软中断并不能事先预知中断发生时当前运行的是哪个进程,所以在中断上下文中引用 current 虽然可以,但没有实际意义。
实际上,A 进程期望等待的中断信号,可能在 B 进程执行期间发生。例如,A 进程启动写磁盘操作后进入睡眠,此时 B 进程在运行;当磁盘写完,磁盘中断信号打断的是 B 进程,在中断处理中会唤醒 A 进程。
四、进程上下文 vs 中断上下文
内核可以处于两种上下文:进程上下文和中断上下文。
- 系统调用之后,用户应用程序进入内核空间,此后内核空间代表用户空间相应进程运行于进程上下文。
- 异步发生的中断会引发中断处理程序被调用,中断处理程序就运行于中断上下文。
中断上下文和进程上下文不可能同时出现在同一个 CPU 上(互斥)。
运行于进程上下文的内核代码是可抢占的,但中断上下文通常会一直运行至结束,不会被进程调度抢占。因此,内核会限制中断上下文的工作,不允许其执行如下操作:
1. 进入睡眠状态或主动放弃 CPU
中断上下文不属于任何进程,与 current 没有本质关联(尽管此时 current 指向被中断的进程)。中断上下文一旦睡眠或主动放弃 CPU,将无法被唤醒,因此也叫原子上下文(atomic context)。
2. 占用互斥体
为保护中断处理临界区资源,不能使用 mutex。如果获取不到信号量,代码就会睡眠,产生与上面相同的问题。若必须使用锁,应使用 spinlock。
3. 执行耗时的任务
中断处理应尽可能快,因为内核需要响应大量服务和请求,中断上下文占用 CPU 时间过长会严重影响系统功能。在中断处理例程中需要执行耗时任务时,应交由中断处理的底半部(bottom half)来完成。
4. 访问用户空间虚拟内存
中断上下文与特定进程无关,它是内核代表硬件运行在内核空间,因此在中断上下文中无法访问用户空间的虚拟地址。
5. 中断处理例程不应设置成可重入(reentrant,可被并行或递归调用)
中断发生时,抢占和中断往往都被关闭,直到中断返回。所以与进程上下文不同,中断处理例程的不同实例通常不允许在 SMP 上并发运行(具体情况取决于内核版本和中断标志)。
6. 中断处理例程可以被更高级别的 IRQ 打断
若想禁止这种嵌套中断,可将中断处理例程定义为快中断处理例程,相当于告诉 CPU:该例程运行时,关闭本地 CPU 上所有中断请求。这会导致其他中断被延迟响应,系统性能下降。