【TLPI读书笔记】 二、基本概念
操作系统的核心——内核
术语“操作系统”的含义
- 广义指完整的软件包
- 狭义仅仅指内核
内核的职责
- 进程调度
- 内存管理
- 提供文件系统
- 创建和终止进程
- 对设备的访问
- 联网
- 提供系统调用应用编程接口(API)
内核态和用户态
现代处理器架构一般允许CPU至少在两种不同状态下运行:用户态和内核态;与之对应可将虚拟内存划分为用户空间和内核空间:
- 在用户态下运行时,CPU只能访问被标记为用户空间的内存,试图访问内核空间的内存会引起硬件异常。
- 在内核态下运行时,CPU既能访问用户空间内存,也能访问内核空间内存。
以进程及内核视角检视系统
- 进程:对进程来说,许多事件的发生都无法预期;进程不知道自己对CPU的占用何时到期,不知道自己处于内存中的位置;进程也不知道自己访问的文件处于磁盘驱动器的何处,只是通过名称来引用文件而已;进程的运作方式堪称与世隔绝——进程间彼此不能通信;进程本身也无法创建出新进程,哪怕自行了断都不行。
- 内核:而内核则是运行系统的中枢所在,对于系统的一切,无所不知无所不能,为系统上的所有进程提供便利。而进程需要请求内核做相应的操作,进程自身不具备这些能力。
shell
shell是一种具有特殊用途的程序,主要用于读取用户输入的命令,并执行相应的程序以相应命令。有时人们也称之为命令解释器。对Unix系统而言,shell只是一个用户进程。shell种类繁多,常见的有以下几种:
- Bourne shell(sh)
- C shell(csh)
- Korn shell(ksh)
- Bourne again shell(bash)
用户和组
用户会对每个用户的身份做唯一标识,用户可隶属于多个组。
用户
系统密码文件/etc/passwd
记录了每个用户的信息如下:
- 登录名
- UID
- 组ID
- 主目录
- 登录shell
组
系统组文件/etc/group
记录了每个组的信息如下:
- 组名
- GID
- 用户列表
超级用户
超级用户在系统中享有特权。超级账户的用户ID为0,通常登录名为root。在一般的UNIX系统上,超级用户凌驾于系统的权限检查之上,因此超级用户拥有最高权限可以对系统做任何操作。
单根目录层级、目录、链接及文件
内核维护着一套单根目录结构,以放置系统的所有文件。
文件类型
在文件系统内,会对文件类型进行标记,以表明其种类。
路径和链接
- 目录是一种特殊类型的文件,内容采用表格形式,数据项包括文件名以及对相应问价的引用。这一“文件名+引用”的组合被称为链接。
- 目录可包含指向文件或其他目录的链接。路径间的链接建立起树形结构的目录层级。
符号链接
- 类似于普通链接,符号链接给文件起了一个“别名”。不同于普通链接,符号链接是经过特殊标记的文件,内容包含了另一文件的名称。
- 普通链接和符号链接也被称为硬链接和软链接
文件名
在大多数Linux文件系统上,文件名最长可达255个字符。
路径名
路径名是由一系列文件名组成的字符串,彼此以“/”分割,首字符可以为“/”,除却最后一个文件名外,该系列文件名均为目录名称。路径名可分为以下两种:
- 绝对路径名以“/”开始,指明文件相对于根目录的位置。
- 相对路径名定义了相对于进程当前工作目录的文件位置。
当前工作目录
每个进程都有个当前工作目录。这就是单根目录层级下进程的当前位置,也是进程解释相对路径名的参照点。常见的shell进程同理。
文件的所有权和权限
- 每个文件都有一个与之相关的用户ID和组ID,分别定义文件的属主和属组。系统根据文件的所有权来判定用户对文件的访问权限。
- 系统把文件的用户分为三类:文件属主、文件属组、其他人。可为该3类用户分别设置3种权限(读、写、执行)共计9种权限位。
- 目录与文件的权限意义稍有不同:读权限允许列出目录内容,写权限允许对目录进行更改,执行权限允许对目录中的文件进行访问。
文件I/O模型
- UNIX系统I/O模型最为显著的特性之一是其I/O通用性概念。也就是说,同一套系统调用(
open()
、read()
、write()
、close()
)所执行的I/O操作,可施之于所有文件类型,包括设备文件在内。(应用程序发起的I/O请求,内核会将其转化为相应的文件系统操作,或者设备驱动程序,以此来执行针对目标文件或设备的I/O操作。)因此,采用这些系统调用的程序能够处理任何类型的文件。 - 就本质而言,内核只提供一种文件类型:字节流序列。
- 许多应用程序和函数库都将换行符视为一行的结束和另一行的开始。
- UNIX系统没有文件结束符的概念,读取文件时如无数据返回,便会认定抵达文件末尾。
文件描述符
I/O系统调用使用的文件描述符——(往往是数值很小的)非负整数来指代打开的文件。
stdio函数库
除了系统调用外,C语言标准库提供了一组I/O函数,也称为stdio函数库(建立在系统调用之上),其中包括:fopen()
、fclose()
、fcanf()
、printf()
、fgets()
、fputs()
等。
程序
程序一般有两种形式:其一为源码形式。其二为二进制形式。
过滤器
从stdin读取输入,加以转换,再将转换后的数据输出到stdout,常常将拥有上述行为的程序称为过滤器,cat
、grep
、tr
、sort
、wc
、sed
、awk
均在其列。
命令行参数
C语言可以访问命令行参数,即程序运行时在命令行中输入的内容。argc
变量包含命令行参数的个数,argv
指针数组的成员则逐一指向每个命令行参数字符串。首个字符串argv[0]
标识程序名本身。
进程
简而言之,进程是正在执行的程序实例。执行程序时,内核会将程序代码载入虚拟内存,为程序变量分配空间,建立内核记账数据结构,以记录与进程有关的各种信息。
进程的内存布局
逻辑上将一个进程划分为以下几部分(也称为段)
- 文本:程序的指令
- 数据:程序使用的静态变量
- 堆:程序可从该区域动态分配额外内存
- 栈:随函数调用、返回而增减的一片内存,用于为局部变量和函数调用链接信息分配存储空间
创建进程和执行程序
进程可使用系统调用fork()
来创建一个新进程。调用fork()
的进程被称为父进程,新创建的进程则被称为子进程。
进程ID和父进程ID
每一个进程都有一个唯一的整数型进程标识符(PID)。此外每个进程还具有一个父进程标识符(PPID)属性,用以标识请求内核创建自己的进程。
进程终止和终止状态
有两种方式来终止一个进程:
- 进程可以使用
_exit()
系统调用(或相关的exit()
库函数),请求退出; - 向进程传递信号,将其杀死。
无论何种方式,进程都会生成终止状态,一个非负小整数,可供父进程的 wait() 系统调用检测。根据惯例,终止状态为0表示进程功成身退,非0则表示有错误发生。
进程的用户和组标识符(凭证)
每个进程都有一组与之相关的用户ID(UID)和组ID(GID),如下:
- 真实用户ID和组ID:用来标识进程所属的用户和组。父子进程会继承这些ID。
- 有效用户ID和组ID:进程在访问受保护资源时,会使用这两个ID来确定访问权限。一般情况下进程的有效ID和真实ID值相同,但改变进程的有效ID实为一种机制,可使进程具有其他用户或组的权限。
- 补充组ID:用来标识进程所属的额外组。父子进程会继承这些ID。
特权进程
在UNIX系统上,就传统意义而言,特权进程是指有效用户ID为0(超级用户)的进程。通常由内核所施加的权限限制对此类进程无效。反之由其他用户(用户ID非0)运行的进程则为非特权进程。
能力
始于内核2.2,Linux把传统上赋予超级用户的权限划分为一组相互独立的单元(称之为能力)。
init进程
系统引导时,内核会创建一个名为init的特殊进程,即“所有进程之父”,该进程的相应程序文件为/sbin/init
。
守护进程
守护进程指的是具有特殊用途的进程,系统创建和处理此类进程的方式与其他进程相同,但以下特征是其所独有的:
- “长生不老”。守护进程通常在系统引导时启动,直至系统关闭前会一直“健在”。
- 守护进程在后台运行,且无控制终端供其读取或写入数据。
环境列表
每个进程都有一份环境列表,即在进程用户空间内存中维护的一组环境变量。父子进程也会继承环境列表。
资源限制
每个进程都会消耗诸如打开文件、内存以及CPU时间之类的资源。使用系统调用setrlimit()
,进程可为自己消耗的各类资源设定一个上限。此类资源限制的每一项均有两个相关值:
- 软限制:限制了进程可以消耗的资源总量
- 硬限制:软限制的调整上限
父子进程也会继承资源限制相关设置。
内存映射
调用系统函数mmap()
的进程,会在其虚拟地址空间中创建一个新的内存映射。
映射分为两类:
- 文件映射:将文件的部分区域映射入调用进程的虚拟内存。映射一旦完成,对文件映射内容的访问则转化为对相应内存区域的字节操作。映射页面会按需自动从文件中加载。
- 匿名映射,并无文件与之相对应,其映射页面的内容会被初始化为0。
由某一进程所映射的内存可以与其他进程的映射共享。达成共享的方式有二:
- 其一是两个进程都针对某一文件的相同部分加以映射
- 其二是由
fork()
创建的子进程自父进程处继承映射
静态库和共享库
所谓目标库是这样一种文件:将(通常是逻辑相关的)一组函数代码加以编译,并置于一个文件中,供其他程序调用。现在UNIX系统提供以下两种对象库:
静态库
本质上说来,静态库是对已编译目标模块的一种结构化整合。要使用静态库中的函数,需要在创建程序的链接命令中指定相应的库。主程序会对静态库中隶属于各目标模块的不同函数加以引用。链接器在解析了引用情况后,会从库中抽取所需目标模块的副本,将其复制到最终的可执行文件中,这就是所谓的静态链接。
因为静态链接方式会生成大量相同副本导致磁盘空间或内存浪费等问题,于是就出现了下面的共享库。
共享库
如果将程序链接到共享库,那么链接器就不会把库中的目标模块复制到可执行文件中,而是在可执行文件中写入一条记录,以表明可执行文件在运行时需要使用该共享库。一旦在运行时将可执行文件载入内存,一款名为“动态链接器”的程序会确保将可执行文件所需的动态库找到,并载入内存,随后实施运行时链接,解析可执行文件中的函数调用,将其与共享库中相应的函数定义关联起来。
进程通信及同步
Linux系统上运行有多个进程,其中许多都是独立运行。然而,有些进程必须相互合作以达成预期目的,因此彼此间需要通信和同步机制。像所有现代UNIX实现那样,Linux也提供了丰富的进程间通信(IPC)机制:
- 信号,用来表示事件的发生
- 管道和FIFO,用于在进程间传递数据
- 套接字,供同一台主机或是联网的不同主机上所运行的进程之间传递数据
- 文件锁定,为防止其他进程读取或更新文件内容,允许某进程对文件的部分区域加以锁定
- 消息队列,用于在进程间交换消息(数据包)
- 信号量,用来同步进程动作
- 共享内存,允许两个及以上进程共享一块内存。当某进程改变了共享内存的内容时,其他所有进程会立即了解到这一变化。
信号
- 虽然信号是IPC的方法之一,但其在其他方面的广泛应用则更为普遍。
- 人们往往将信号称为“软件中断”。进程收到信号就意味着某一事件或异常情况的发生。
- 就大多数信号类型而言,程序可选择不采取默认的信号动作,而是忽略信号或者建立自己的信号处理器。
线程
在现代UNIX实现中,每个进程都可执行多个线程。可将线程想象为共享同一虚拟内存及一干其他属性的进程。每个线程都会执行相同的程序代码,共享同一数据区域和堆。可是,每个线程都拥有属于自己的栈,用来装载本地变量和函数调用链接信息。
进程组和shell任务控制
- shell执行的每个程序都会在一个新进程内发起。
- 除Bourne shell以外,几乎所有的主流shell都提供一种交互式特性,名为任务控制。
- 该特性允许用户同时执行并操纵多条命令或管道。在支持任务控制的shell中,会将管道内的所有进程置于一个新进程组或任务中(单条命令就会创建一个只包含单个进程的新进程组)。
- 进程组中的每个进程都具有相同的进程组标识符(整数形式),其实就是进程组中某个进程(也称为进程组组长)的进程ID。
会话、控制终端和控制进程
- 会话指的是一组进程组(任务)。
- 会话中的所有进程都具有相同的会话标识符。
- 会话首进程是指创建会话的进程,其进程ID会成为会话ID。
- 使用会话最多的是支持任务控制的shell。
- 通常,会话都会与某个控制终端相关。控制终端建立于会话首进程初次打开终端设备之时。
- 打开控制终端会致使会话首进程成为控制终端的控制进程。一旦断开了与终端的连接,控制终端会收到
SIGHUP
信号。 - 在任一时点,会话中总有一个前台进程组(前台任务),可以从终端中读取输入,向终端发送输出。
- 支持任务控制的shell提供如下命令:列出所有任务,向任务发送信号,以及在前后台任务之间来回切换。
伪终端
- 伪终端是一对相互连接的虚拟设备,也称为主从设备。在这对设备之间,设有一条IPC信道,可供数据进行双向传递。
- 从设备所提供的接口,其行为方式与终端想类似,基于这一特点,可以将某个为终端编写的程序与从设备连接起来,然后再利用连接到主设备的另一程序来驱动这一“面向终端”的程序,这是伪终端的一个关键用途。
- 知名的伪终端应用如
telnet
和ssh
。
日期和时间
进程涉及两种类型的时间:
- 真实时间:指的是在进程生命期内,以某个标准时间点或固定时间点为起点测量得出的时间。
- 进程时间:亦称为CPU时间,指的是进程自启动起来,所占用的CPU时间总量。
客户端服务器架构
客户端/服务端应用由两个组件进程组成:
- 客户端:向服务器发送请求消息,请求服务器执行某些服务。
- 服务端:分析客户端的请求,执行相应的动作,然后,向客户端回发响应消息。
实时性
- 实时性应用程序是指那些需要对输入输出做出及时响应的程序。
- 虽然许多实时性应用程序都要求对输入输出做出快速响应,但决定性因素却在于要在事件触发后的一定时限内,保证响应的交付。
- 要提供实时响应,特别是在短时间内加以响应,就需要底层操作系统的支持。而与之相对应的是分时操作系统,大多数操作系统都是分时操作系统。
/proc
文件系统
- 类似于其他几种UNIX实现,Linux也提供了
/proc
文件系统,由一组目录和文件组成,装配于/proc
目录下。 /proc
文件系统是一种虚拟文件系统,以文件系统目录和文件形式,提供一个指向内核数据接口的接口。- 这为查看和改变各种系统属性开启了方便之门。此外还能以
/proc/PID
形式命名的目录查看各进程相关信息 /proc
目录下的文件内容都采取人类可读的文本形式,shell脚本也能对其进行解析。
总结
本章主要介绍了Linux系统的有关概念,也可以看作是本书的内容大纲,待本书完毕,到时候把每个核心概念链接到具体的章节文章去。