我们之所以介绍主内存和状态,是因为内核的几乎所有操作都和主内存相关。其中之一是将内存划分为很多区块,并且一直维护着这些区块的状态信息。每一个进程拥有自己的内存区块,且内核必须确保每个进程只使用它自己的内存区块。 内核负责管理以下四个方面。 进程:内核决定哪个进程可以使用CPU。 内存:内核管理所有的内存,为进程分配内存,管理进程间的共享内存以及空闲内存。 设备驱动程序:作为硬件系统(如磁盘)和进程之间的接口,内核负责操控硬件设备。 系统调用和支持:进程通常使用系统调用和内核进行通信。 下面我们详细介绍一下这四个方面。 注解 如果你对内核的详细工作原理感兴趣,可以参考Abraham Silberschalz、Peter B. Galvin和Greg Gagne所著Operating System Concepts, 9th Edition(Wiley,2012),以及Andrew S. Tanenbaum和Herbert Bos所著Modern Operating Systems, 4th Edition(Prentice Hall,2014)这两本书。 1.3.1 进程管理 进程管理涉及进程的启动、暂停、恢复和终止。启动和终止进程比较直观,但是要解释清楚进程在执行过程中如何使用CPU则相对复杂一些。 在现代操作系统中,很多进程貌似都是“同时”运行的。例如,你可以同时在桌面打开Web浏览器和电子表格应用程序。然而,虽然它们表面上看是同时运行,但实际上这些应用程序背后的进程并不完全是同时运行的。 我们设想一下,在只有一个CPU的计算机系统中,可能会有很多进程可以使用CPU,但是在任何一个特定的时间段内只能有一个进程可以使用CPU。所以实际上是多个进程轮流使用CPU,每个进程使用一段时间后就暂停,然后让另一个进程使用,依次轮流,时间单位是毫秒级。一个进程让出CPU使用权给另一个进程称为上下文切换(context switch)。 进程在其时间段内有足够的时间完成主要的计算工作(实际上,进程通常在单个时间段内就能完成它的工作)。由于时间段非常短,短到我们根本察觉不到,所以在我们看来,系统是在同时运行多个进程(我们称之为多任务执行)。 内核负责上下文切换。我们来看看下面的场景,以便理解它的工作原理。 (1) CPU为每个进程计时,到时即停止进程,并切换至内核模式,由内核接管CPU控制权。 (2) 内核记录下当前CPU和内存的状态信息,这些信息在恢复被停止的进程时需要用到。 (3) 内核执行上一个时间段内的任务(如从输入输出设备获得数据,磁盘读写操作等)。 (4) 内核准备执行下一个进程,从准备就绪的进程中选择一个执行。 (5) 内核为新进程准备CPU和内存。 (6) 内核将新进程执行的时间段通知CPU。 (7) 内核将CPU切换至用户模式,将CPU控制权移交给新进程。 上下文切换回答了一个十分重要的问题,即内核是在什么时候运行的。答案就是,内核是在上下文切换时的时间段间隙中运行的。 在多CPU系统中,情况要稍微复杂一些。如果新进程将在另一个CPU上运行,内核就不需要让出当前CPU的使用权。不过为了将所有CPU的使用效率最大化,内核会使用一些其他的方式来获取CPU控制权。 1.3.2 内存管理 内核在上下文切换过程中管理内存,这是一项十分复杂的工作,因为内核要保证以下所有条件: 内核需要自己的专有内存空间,其他的用户进程无法访问; 每个用户进程有自己的专有内存空间; 一个进程不能访问另一个进程的专有内存空间; 用户进程之间可以共享内存; 用户进程的某些内存空间可以是只读的; 通过使用磁盘交换,系统可以使用比实际内存容量更多的内存空间。 新型的CPU提供了MMU(Memory Management Unit,内存管理单元),MMU使用了一种叫作虚拟内存的内存访问机制,即进程不是直接访问内存的实际物理地址,而是通过内核使得进程看起来可以使用整个系统的内存。当进程访问内存的时候,MMU截获访问请求,然后通过内存映射表将要访问的内存地址转换为实际的物理地址。内核需要初始化、维护和更新这个地址映射表。例如,在上下文切换时,内核将内存映射表从被移出进程转给被移入进程使用。 注解 内存地址映射通过内存页面表(page table)来实现。 关于内存性能,我们将在第8章详细介绍。 1.3.3 设备驱动程序和设备管理 对于设备来说,内核的角色比较简单。通常设备只能在内核模式中被访问(例如用户进程请求内核关闭系统电源),因为设备访问不当有可能会让系统崩溃。另一个原因是不同设备之间没有一个统一的编程接口,即使同类设备也如此,比如两个不同的网卡。所以设备驱动程序传统意义上来说是内核的一部分,它们尽可能为用户进程提供统一的接口,以简化开发人员的工作。 1.3.4 系统调用和系统支持 内核还对用户进程提供其他功能。例如,系统调用(system call或syscall)为进程执行一些它们不擅长或无法完成的工作。打开、读取和写文件这些操作都涉及系统调用。 fork()和exec()这两个系统调用对于我们了解进程如何启动很重要。 fork():当进程调用fork()时,内核创建一个和该进程几乎一模一样的副本。 exec():当进程调用exec(program)时,内核启动program来替换当前的进程。 除了init(参见第6章)以外,Linux中的所有用户进程都是通过fork()来启动的。除了创建现有进程的副本外,大多数情况下你还可以使用exec()来启动新的进程。一个简单的例子是你在命令行运行ls命令来显示目录内容。当你在终端窗口中输入ls时,终端窗口中的shell调用fork()创建一个shell的副本,然后该副本调用exec(ls)来运行ls。图1-2显示启动ls这样的命令时进程和系统调用的流程。 图1-2 新进程的启动 注解 系统调用通常使用括号来标记。图1-2中,进程请求内核使用fork()系统调用创建一个新的进程。这样的标记来源于C编程语言。阅读本书你不需要有C语言的知识,只需要记住系统调用是进程和内核之间的交互方式。此外,本书中我们简化了很多系统调用。例如exec()实际上是一系列具有相似功能的系统调用,只是代码实现有所不同。 除了传统的系统调用,内核还为用户进程提供其他很多功能,最为常见的是虚拟设备。虚拟设备对于用户进程而言是物理设备,但其实它们都是通过软件实现的。因此从技术角度来说,它们并不需要存在于内核中,但是实际上它们很多都存在于内核中。例如:内核的随机数生成器(/dev/random)这样的虚拟设备,如果由用户进程来实现,难度要大很多。 注解 从技术上说,用户进程还是需要通过使用系统调用打开设备的方式来访问虚拟设备,所以进程总是避免不了要和系统调用打交道。
精通Linux(第2版)——1.3 内核
书名: 精通Linux(第2版)
作者: [美] Brian Ward
出版社: 人民邮电出版社
译者: 姜南 | 袁志鹏
出版年: 2015-7
页数: 304
定价: 59.00元
装帧: 平装
ISBN: 9787115394927