从本质上讲,计算系统是为了高效运行用户应用程序而设计、开发和经常微调的。而进入计算平台的每个元素都旨在为运行应用程序提供有效且高效的方法。换句话说,计算系统的存在就是为了运行各种不同的应用程序。应用程序既可以作为专用设备中的固件来运行,也可以作为由系统软件(操作系统)驱动的系统中的一个“进程”来运行。
进程的核心是程序在内存中的一个运行实例。从程序到进程的转换过程发生在程序(在磁盘上)被读取到内存中执行时。
一个程序的二进制映像包含代码(所有的二进制指令)和数据(所有的全局数据),它们被映射到内存的不同区域,并具有适当的访问权限(读、写和执行)。除了代码和数据之外,进程还被分配了额外的内存区域,称为堆栈(用于分配具有自动变量和函数参数的函数调用帧),以及在运行时动态分配的堆。
同一个程序的多个实例可以在它们各自的内存分配中存在。例如,对于具有多个打开的选项卡(同时运行浏览器会话)的Web浏览器,每个选项卡都被内核视为一个进程实例,各自具有唯一的内存分配。
图1-1所示为内存中进程的布局。
图1-1
现代计算平台有望有效地处理大量的进程。因此,操作系统必须处理在物理内存(通常是有限的)中为所有并发的进程分配唯一的内存,并确保其可靠的执行。由于多个进程同时发生并执行(多任务),操作系统必须确保每个进程的内存分配都得到保护,以免被另一进程意外访问。
为了解决这个问题,内核在进程和物理内存之间提供了一层抽象,称为虚拟地址空间。虚拟地址空间是进程的内存视图。那么,运行中的程序是如何看待内存的呢?
虚拟地址空间创建了一个假象,即每个进程在执行过程中独占整个内存。这种抽象的内存视图称为虚拟内存,它是由内核的内存管理器与CPU的MMU协调实现的。每个进程都有一个连续的32位或64位地址空间,这个地址空间被体系结构所限定,并且对于该进程是唯一的。通过MMU,每个进程装入其虚拟地址空间中,任何进程尝试访问其边界之外的地址区域都会触发硬件故障,从而使内存管理器能够检测和终止违反的进程,这样就确保进程得到了保护。
图1-2所示为每个不同进程创建地址空间的假象。
图1-2
现代操作系统不仅可以防止一个进程访问另一个进程,还可以防止进程意外访问或操作内核数据和服务(因为内核地址空间是被所有进程共享的)。
操作系统实现这种保护,是通过将整个内存分割成两个逻辑分区:用户空间和内核空间。这种分开设计确保所有分配有地址空间的进程都映射到内存的用户空间部分,而内核数据和服务在内核空间中运行。内核通过与硬件协调配合实现了这种保护。当应用程序进程正在执行代码段中的指令时,CPU在用户模式下运行。当一个进程打算调用一个内核服务时,它需要将CPU切换成特权模式(内核模式),这是通过称为API(应用程序编程接口)的特殊函数来实现的。这些API允许用户进程使用特殊的CPU指令切换到内核空间,然后通过系统调用来执行所需要的服务。在所请求的服务完成后,内核使用另一组CPU指令来执行到另一个模式的切换,这次是从内核模式返回到用户模式。
系统调用是内核将其服务公开到应用程序进程的接口,它们也被称为内核的入口点。由于系统调用是在内核空间中实现的,对应的处理程序通过用户空间中的API提供。API抽象层也使得调用相关的系统调用变得更容易和方便。
图1-3所示为一幅虚拟的内存视图。
图1-3
当一个进程通过系统调用请求一个内核服务时,内核将代表调用进程来执行。此时,内核就被认为是在进程上下文中执行的。类似地,内核也会响应其他硬件实体引发的中断;而这里就是说内核在中断上下文中执行。在中断上下文中,内核不代表任何进程来运行。