第1章 概述

第1章 概述

 图像说明文字

乍看起来,Linux这样的现代操作系统非常复杂,内部有多得令人眼花缭乱的各种组件在同步运行和相互通信。比如:Web服务器可以连接到数据库服务器,还有可能用到很多其他程序也在使用的公共组件。那么,整个系统究竟是怎样运作的呢?

理解操作系统工作原理最好的方法是抽象思维,换句话说,你可以暂时忽略大部分细节。就像坐车一样,通常你不会去在意车内固定发动机的装配螺栓,也不会关心你走的路是谁修筑的。如果你是一个乘客的话,你可能只关心车要做的事情(比如车要把你带到哪)以及车的一些基本操作(比如如何打开车门、怎样系好安全带)。

但如果你在开车的话,就需要了解更多的细节,比如如何控制油门、怎样换挡,还有如何处理意外情况。

如果我们觉得开车这个事情太复杂,就可以运用“抽象思维”来帮助理解。首先你可以将“一辆汽车在路上行驶”抽象为三个部分:汽车、道路和驾驶操作。这样有助于将复杂的问题分解开来。如果道路颠簸,你不会去埋怨车辆本身和你的驾驶技术。相反,你可能会问为什么这条路这么烂,或者如果这是条新修的路的话,那么筑路工人的活干得可真够差劲的。

软件开发人员运用抽象思维来开发操作系统和应用程序。在计算机软件领域有许多术语来描述抽象的子系统,如子系统模块等。本书中我们使用组件这个相对简单的词。在软件开发过程中,开发人员通常不用太关心他们需要使用的组件的内部结构,他们只关心能使用哪些组件,以及怎么个用法。

本章概述了Linux操作系统涉及的主要组件。虽然每一个组件都包含纷繁复杂的技术细节,但我们将暂时忽略这些细节,而专注于这些组件在系统中发挥的功能。

1.1 Linux 操作系统中的抽象级别和层次

在组织得当的前提下,通过抽象将系统分解为组件有助于我们了解其工作机制。我们将组件划分为层次级别。组件的层次(或级别)代表它在用户和硬件系统之间所处的位置。Web浏览器、游戏等应用处于最高层,底层则是计算机硬件系统,如内存。操作系统处于这两层之间。

Linux操作系统主要分为三层。如图1-1所示,最底层是硬件系统,包括内存和中央处理器(用于计算和从内存中读写数据),此外硬盘和网络接口也是硬件系统的一部分。

硬件系统之上是内核,它是操作系统的核心。内核是运行在内存中的软件,它向中央处理器发送指令。内核管理硬件系统,是硬件系统和应用程序之间进行通信的接口。

进程是指计算机中运行的所有程序,由内核统一管理,它们组成了最顶层,称为用户空间(user space)。(另一个更确切的术语是用户进程,无论它们是否直接和用户交互。例如,所有的Web服务器都是以用户进程的形式运行的。)

图1-1 Linux系统的基本组成

内核和用户进程之间最主要的区别是:内核在内核模式(kernel mode)中运行,而用户进程则在用户模式(user mode)中运行。在内核模式中运行的代码可以不受限地访问中央处理器和内存,这种模式功能强大,但也非常危险,因为内核进程可以轻而易举地使整个系统崩溃。那些只有内核可以访问的空间我们称为内核空间(kernel space)。

相对于内核模式,用户模式对内存和中央处理器的访问有一定程度的限制,可访问的内存空间通常很小,对CPU的操作也很安全。用户空间指的是那些用户进程能够访问的内存空间。如果一个用户进程出错并崩溃的话,其导致的后果也相对有限,并且能够被内核清理掉。例如,如果你的Web浏览器崩溃了,不会影响到你正在运行的其他程序。

理论上来说,一个用户进程出问题并不会对整个系统造成严重的影响。当然这取决于我们如何定义“严重的影响”,并且还取决于该进程拥有的权限。因为不同的进程拥有的权限可能不同,一些进程能够执行一些别的进程无权执行的操作。举个例子,如果拥有足够的权限,用户进程可以将硬盘上的数据全部清除。也许你会觉得这样太危险,但好在操作系统提供了一些相关的安全措施,而且大多数用户进程并没有这个权限。

1.2 硬件系统:理解主内存

主内存(main memory)或许是所有硬件系统中最为重要的部分。基本上来讲,主内存存储0和1这样的数据。我们将每个0和1称为一个比特(或位,bit)。内核和进程就在主内存中运行,它们就是一系列比特的大合集。所有外围设备的数据输入和输出都通过主内存完成,同样是以一系列0和1的形式。中央处理器像一个操作员一样处理内存中的数据,它从内存读取指令和数据,然后将运算结果写回内存。

在我们谈论内存、进程、内核和其他内容时,你会经常看到状态(state)这个词。严格说来,一个状态就是一组特定排列的比特。例如,内存中0110、0001和1011这三组比特值即表示三个不同的状态。

一个进程动辄由几百万个比特值组成,因而使用抽象词汇来描述状态可能比使用比特值更简单一些。我们可以使用进程已经完成的任务或者当前正在执行的任务来描述其状态,如“进程正在等待用户输入”或者“进程正在执行启动任务的第二个阶段”。

注解:我们通常使用抽象词汇而非比特值来描述状态,映像(image)这个词用来表示比特值在内存中的特定物理排列。

1.3 内核

我们之所以介绍主内存和状态,是因为内核的几乎所有操作都和主内存相关。其中之一是将内存划分为很多区块,并且一直维护着这些区块的状态信息。每一个进程拥有自己的内存区块,且内核必须确保每个进程只使用它自己的内存区块。

内核负责管理以下四个方面。

  • 进程:内核决定哪个进程可以使用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)这样的虚拟设备,如果由用户进程来实现,难度要大很多。

注解:从技术上说,用户进程还是需要通过使用系统调用打开设备的方式来访问虚拟设备,所以进程总是避免不了要和系统调用打交道。

1.4 用户空间

前面提到过,内核分配给用户进程的内存我们称之为用户空间。因为一个进程简单说就是内存中的一个状态。用户空间也可以指所有用户进程占用的所有内存。(用户空间还有一个不太正式的名称,叫userland。)

Linux中大部分的操作都发生在用户空间中。虽然从内核的角度来说所有进程都是一样的,但是实际上它们执行的是不同的任务。相对于系统组件,用户进程位于一个基础服务层中。图1-3就展示了一组组件在Linux系统中是如何交互工作的。其中最底层是基础服务层,工具服务在中间,用户使用的应用程序在最上层。图1-3是一个简化版本,你可以看到顶层距离用户最近(如用户接口和Web浏览器)。中间一层中有邮件服务器这样的组件供Web浏览器使用。最下层是一些更小的服务组件。

图1-3 进程类型和相互间的交互

最下层通常是由一些小的组件组成,它们比较精巧,专注完成某一个特定功能。中间层的组件比较大一些,如邮件、打印和数据库服务。顶层组件完成用户交互和复杂的功能。组件之间也可以相互调用。如果组件A调用了组件B的功能,我们可以视为组件A和B在同一层级,或者B在A之下。

然而,图1-3只是一个粗略图,实际上用户空间里没有很明显的界限。例如许多应用程序和服务会将系统诊断信息写入日志,大部分程序使用标准的系统日志服务来完成,但也有一些程序是自己实现日志功能。

此外,很多用户空间组件比较难分类,像Web服务器和数据库服务器这样的服务组件,你可以认为它们在图1-3中属于高级别组件,因为它们复杂度很高。然而用户应用程序也会经常调用它们的功能,所以你也可以将它们归入中级别组件。

1.5 用户

Linux内核支持用户这一Unix的传统概念。一个用户代表一个实体,它有权限运行用户进程,对文件拥有所有权。每个用户都有一个用户名,如billyjoe。然而内核是通过用户ID来管理用户的,用户ID是一串数字标识(详见第7章)。

用户机制主要用于权限管理。每一个用户进程都有一个用户作为所有者,我们称其为以该用户运行的进程。在一定限制条件下,用户可以终止和改变他的进程的行为。但是对其他用户的进程无权干预。此外,用户可以决定是否将属于自己的文件和其他用户共享。

Linux操作系统的用户包括系统自带用户和供人使用的用户。详情见第3章。其中最关键的用户是root用户(意思是根用户或超级用户)。root用户不受前面提到的种种权限的限制,它可以终止其他用户的进程,读取系统中的任何文件。因此root也被称作超级用户。Unix的系统管理员拥有超级用户权限。

注解:使用root权限操作系统是一件很危险的事情,因为用户拥有最高权限,可以为所欲为,一旦出错很难定位和恢复。因此系统管理员通常尽量避免使用root权限。而且,root用户虽然权限很高,但是还是在用户模式而非内核模式中运行。

用户组是指一组用户的集合。用户组的主要作用是允许一个用户同组内的其他用户共享文件权限。

1.6 前瞻

至此我们对Linux系统的组成有了一个大致的了解。用户和用户进程交互,内核管理进程和硬件系统。内核和进程都在内存中运行。

这些基础知识固然很重要,但如果想要了解更多的细节,你需要实际操作一番。下一章你会了解到一些用户空间的基础知识,还有本章没有提及的永久存储(硬盘、文件等),就是存放应用程序和数据的地方。

目录

  • 版权声明
  • 前言
  • 致谢
  • 第一版书评
  • 第1章 概述
  • 第2章 基础命令和目录结构
  • 第3章 设备管理
  • 第4章 硬盘和文件系统
  • 第5章 Linux内核的启动
  • 第6章 用户空间的启动
  • 第7章 系统配置:日志、系统时间、批处理任务和用户
  • 第8章 进程与资源利用详解
  • 第9章 网络与配置
  • 第10章 网络应用与服务
  • 第11章 shell脚本
  • 第12章 在网络上传输文件
  • 第13章 用户环境
  • 第14章 Linux桌面概览
  • 第15章 开发工具
  • 第16章 从C代码编译出软件
  • 第17章 在基础上搭建