Understanding the Linux Kernel Lecture 1 Note
引言
这本书是导师推荐给我很久了的,一直没啃完,希望这个寒假可以啃完这本八百页的书。(失败)
内核基础概念
内核的本质
- 内核是操作系统中最重要的基本程序集合
- 系统启动时被装入RAM
- 包含系统运行必需的核心过程(procedure)
- 决定了系统的基本特性和能力
- "操作系统"常作为"内核"的同义词
操作系统的双重目标
- 硬件交互
- 管理硬件平台
- 为低层可编程部件提供服务
- 执行环境
- 为应用程序(用户程序)提供运行环境
- 管理程序执行
硬件访问控制
不同系统的对比
- MS-DOS: 允许用户程序直接访问硬件
- Unix系统:
- 对用户程序隐藏底层硬件细节
- 通过系统调用请求访问硬件
- 由内核评估和代理硬件交互
系统保护机制
CPU执行模式
- 用户态(User Mode)
- 非特权模式
- 用于运行普通用户程序
- 内核态(Kernel Mode)
- 特权模式
- 用于执行内核代码
- 可以直接访问硬件
实现方式
- 依赖硬件特性实现保护
- 禁止用户程序直接访问:
- 底层硬件
- 任意物理地址
Linux内核的基本特性
Linux包括了现代Unix操作系统的全部特点:
- 虚拟存储:提供了虚拟内存管理,支持分页和交换
- 虚拟文件系统:统一的文件系统接口,支持多种文件系统
- 轻量级进程:高效的进程创建和调度机制
- Unix信号量:进程间同步机制
- SVR4进程间通信:支持多种IPC机制
- 支持对称多处理器(Symmetric Multiprocessor,SMP)系统等
Linux内核的架构特点
单块结构(Monolithic Kernel)
Linux采用单块内核结构,是一个庞大、复杂的自我完善(do-it-yourself)程序。它由几个逻辑上独立的成分构成,这一点上相当传统,大多数商用Unix变体也是单块结构。(值得注意的是Apple的Mac OS X和GNU的Hurd操作系统采用了微内核方法)
模块化支持
Linux对模块的支持非常出色,内核可以动态地装载和卸载部分内核代码(典型的例子如设备驱动程序)。这些代码被称为"模块"(module),Linux支持在运行时动态加载或卸载模块。在主要的商用Unix变体中,只有SVR4.2和Solaris内核有类似特性。
内核线程
内核线程是一个能被独立调度的执行环境(context),它与用户程序有关。线程之间的上下文切换比普通进程间的上下文切换花费要少得多,因为前者通常在同一个地址空间内执行。Linux以一种十分有限的方式使用内核线程来周期性地执行几个内核函数。
多线程应用程序支持
Linux通过轻量级进程(lightweight process,
LWP)实现多线程支持。Linux将轻量级进程当作基本的执行上下文,通过非标准的clone()系统调用来处理它们。这些进程可以共享同样的地址空间、共同的物理内存页面、共同的打开文件等。
> clone()
是 Linux
内核中的一个系统调用,它用于创建一个新进程或线程,并允许更灵活的控制新创建的进程或线程与调用进程之间共享的资源。与传统的
fork()
系统调用不同,clone()
允许父进程和子进程在多个层面上共享不同的资源。具体来说,clone()
允许选择性地共享虚拟内存、文件描述符、信号处理器等。
抢占式内核
从Linux 2.6开始,内核可以随意交错执行处于特权模式的执行流。这使得Linux成为完全的抢占式内核,提供了更好的实时性能。
多处理器支持
Linux 2.6支持不同存储模式的对称多处理(SMP),包括NUMA架构。系统不仅可以使用多处理器,而且每个处理器可以毫无区别地处理任何一个任务。
文件系统支持
Linux支持多种文件系统格式: - Ext2/Ext3: 标准的Linux文件系统 - ReiserFS: 适合处理大量小文件的高性能文件系统 - 其他日志文件系统: 如IBM的JFS和SGI的XFS - 支持多种商业文件系统,便于与其他系统交互
Linux的优势
- 免费开源
- 除硬件之外,无需任何花费就能安装完整的Linux系统
- 基于GPL协议,可以自由地阅读、修改内核源代码
- 低硬件要求
- 可以在低端、便宜的硬件平台上运行
- 仅需4MB内存的旧Intel 80386系统即可构建网络服务器
- 高性能
- 充分挖掘了硬件部分的特点
- 注重效率,许多商用系统的设计选择因性能低下而被舍弃
- 稳定可靠
- 系统非常稳定,有非常低的故障率
- 系统维护时间少
- 体积小巧
- 内核映像和基本系统程序可以放在1.4MB软盘上
- 远小于任何商用Unix变体的体积
- 兼容性强
- 可以让你直接安装多种文件系统
- 支持运行多种操作系统的程序
- 支持多种网络协议和接口
- 技术支持
- 活跃的开发者社区
- 问题反馈迅速
- 新硬件支持及时
硬件平台支持
Linux通过在arch和include目录下包含23个子目录来保持源代码与硬件相关的源代码之间的清晰界限,以支持不同的硬件平台。主要支持的处理器架构包括:
个人计算机和工作站处理器
- x86_64: 支持AMD的64位处理器(如Athlon和Opteron)和Intel的ia32e/EM64T64位处理器
- i386: 基于80x86微处理器的IBM兼容个人计算机
- ia64: 基于64位Itanium微处理器的工作站
- ppc/ppc64: 基于Motorola-IBM PowerPC 32位和64位微处理器的工作站
服务器和大型机处理器
- alpha: HP的Alpha工作站(最早属于Digital公司,后属于Compaq公司,现已停产)
- s390: IBM ESA/390及zSeries大型机
- sparc/sparc64: 基于Sun公司SPARC和64位Ultra SPARC微处理器的工作站
嵌入式和移动设备处理器
- arm/arm26: 基于ARM处理器的计算机(如PDA)和嵌入式设备
- m32r: 基于Renesas M32R系列微处理器的计算机
- m68k/m68knommu: 基于Motorola MC680x0微处理器的个人计算机
- cris: Axis在其嵌入式服务器中使用的"代码精简指令集(CRIS)"CPU
- frv: 基于Fujitsu FR-V系列微处理器的嵌入式系统
RISC处理器
- h8300: Hitachi h8/300和h8S的8位和16位RISC微处理器
- mips: 基于MIPS微处理器的工作站,如Silicon Graphics公司销售的工作站
- parisc: 基于HP公司HP 9000 PA-RISC微处理器的工作站
- v850: 集成了基于Harvard体系结构的32位RISC核心的NEC V850微控制器
特殊平台
- um: 用户态的Linux - 一个允许开发者在用户态下运行内核的虚拟平台
- sh/sh64: 基于Hitachi和STMicroelectronics联合开发的SuperH微处理器的嵌入式系统
这种广泛的硬件支持使Linux能够运行在从嵌入式设备到超级计算机的各种平台上,展现了其卓越的可移植性和适应性。通过模块化的设计和清晰的代码组织,Linux能够在保持核心功能稳定的同时,有效地支持不同的硬件架构。
Linux版本管理
版本号体系
一直到2.5版本的内核,Linux都通过简单的编号来区别内核的稳定版和开发版。每个版本号由三个数字描述,由圆点分隔:
- 第一位版本号:从1996年开始基本没有变化
- 第二位版本号:表示内核的类型
- 偶数表示稳定版本的内核
- 奇数表示正在开发中的内核
- 第三位数字:表示发布号
版本发布机制
- 稳定版本的内核由Linux的发布者和内核黑客彻底检查过
- 新的稳定版本主要用来修正用户报告的错误或增加新的驱动程序
- 开发版本之间可能存在非常明显的差异
- 内核开发者可以自由地采用不同方案进行实验,但这些实验可能导致内核有很大变化
- 用开发版运行应用程序的用户,当把内核升级到新版时,可能会遇到一些不太令人愉快的意外
2.6版本的变革
在Linux内核2.6版的开发过程中,内核版本的编号方式发生了很大的变化:
- 第二个数字不再用于表示内核是稳定版还是开发版
- 内核开发者都在当前的2.6版本中对内核进行大幅改进
- 只有在内核开发者必须对内核的重大修改进行测试时,才会采用一个新的内核分支2.7
- 这种2.7的分支要么产生一个新的内核版本,要么干脆舍弃所修改的部分而回退到2.6版
版本差异
Linux这种新的开发模式意味着两种内核具有相同的版本号,但却有不同的发布号,如2.6.10和2.6.11内核就可能在核心部件和基本算法上有很大的差别。这种方式使得Linux可以在保持版本号稳定的同时,持续进行重要的改进和更新。
多用户系统
基本特性
多用户系统允许多个用户同时使用计算机系统,具有两个核心特性:
- 并发性(Concurrency)
- 多个应用程序同时活动
- 共享系统资源(CPU、内存、硬盘等)
- 通过调度机制优化响应时间
- 独立性(Independence)
- 程序互不干扰
- 独立执行各自任务
- 资源隔离保护
安全保护机制
为保证多用户系统的安全运行,需要实现以下机制:
- 身份安全
- 用户认证系统
- 登录验证机制
- 密码保护
- 程序保护
- 防止程序相互干扰
- 阻止恶意程序访问
- 基于CPU特权模式的硬件保护
- 资源管理
- 限制单个用户资源使用
- 合理分配系统资源
- 防止资源滥用
用户权限体系
多用户系统采用分层的权限管理方式:
- 用户标识(UID)
- 唯一的用户标识符
- 用于身份识别和权限判断
- 控制用户私有空间访问
- 用户组(GID)
- 用户组标识符
- 实现资源共享机制
- 灵活的权限分配
- 文件权限
- 所有者权限
- 组成员权限
- 其他用户权限
超级用户(root)
任何类Unix操作系统都有一个特殊的用户,叫做root,即超级用户(superuser)。root用户具有以下特点:
- 特殊权限
- 可以访问系统中的每一个文件
- 能够访问每个正在执行的用户程序
- 不受通常的保护机制限制
- 管理职责
- 处理用户账号
- 系统备份
- 程序升级
- 系统维护任务
进程(Process)
进程是操作系统使用的一种基本抽象,可以定义为: - "程序执行时的一个实例" - "一个运行程序的执行上下文"
进程特征
- 地址空间
- 每个进程在地址空间(address space)中执行
- 拥有一个独立的指令序列
- 地址空间是进程专用的内存地址集合
- 多道程序设计
- 允许多个进程并发活动
- 竞争系统资源(主要是CPU)
- 支持多道程序系统(multiprogramming)或多处理系统(multiprocessing)
进程调度
- 调度类型
- 非抢占式(nonpreemptable):进程自愿放弃CPU时才被调度
- 抢占式(preemptable):操作系统记录和管理CPU时间,定期激活调度程序
- Unix进程特点
- 采用抢占式进程调度
- 即使没有用户登录,系统仍有进程在监视外围设备
- 用户登录时会创建shell进程
- 图形界面中每个窗口通常由独立进程执行
- 进程/内核模式
- 每个进程都认为自己是系统中唯一的进程
- 可以独占操作系统提供的服务
- 通过系统调用请求内核服务
内核体系结构
内核架构类型
单块内核
- 传统Unix采用的方式
- 所有内核功能集成到整个内核程序中
- 在内核态下运行所有系统功能
- 结构紧凑但复杂度高
微内核(Microkernel)
- 基本特征
- 只需内核有一个很小的函数集
- 通常包括几个同步原语
- 简单的调度程序
- 进程间通信机制
- 优缺点
- 优点:
- 结构清晰模块化
- 系统服务独立运行
- 易于移植和维护
- 缺点:
- 性能较低
- 系统调用开销大
- 消息传递需要额外成本
Linux的模块化方案
模块(Module)概念
- 是一个目标文件
- 可在运行时链接到内核或从内核解除链接
- 由一组函数组成
- 实现特定功能(如文件系统、驱动程序等)
- 在内核态下执行
模块化优势
- 灵活性
- 运行时动态加载/卸载
- 按需加载系统功能
- 便于开发新模块
- 平台无关性
- 独立于具体硬件平台
- 适应不同系统架构
- 如SCSI驱动程序可在不同硬件上工作
- 资源效率
- 未使用模块可被卸载
- 节省系统内存
- 优化系统性能
这种模块化设计使Linux在保持高性能的同时,也获得了类似微内核的灵活性,是一种优秀的折中方案。
Unix 文件系统概述
文件的定义
- Unix文件是以字节序列组成的信息载体(container)。
- 内核不解释文件的内容。
- 程序通过系统调用访问文件。
目录结构
- 文件被组织在一个树结构的命名空间中。
- 根目录("/")是树的起点。
- 目录节点表示目录名,包含其下文件及目录的所有节点。
示例目录结构
- 根目录下有子目录如dev, home, bin, usr等。
- 叶节点表示具体文件,如ls, cp等。
文件命名与路径
文件命名规则
- 文件名由ASCII字符序列组成(除"/"和"\0"外)
- 大多数文件系统限制文件名长度不超过255个字符
- 同一目录下文件名不能重复
- 不同目录下可以使用相同的文件名
路径名(pathname)
- 绝对路径
- 以根目录"/"开头
- 完整指定文件位置的路径
- 从根目录开始的完整路径名
- 相对路径
- 从当前工作目录开始
- 使用目录名或文件名作为起点
- 相对于进程的当前目录
当前工作目录
- 每个进程都有一个当前工作目录
- 属于进程执行上下文(execution context)的一部分
- 用于标识进程当前所在的目录位置
特殊目录符号
- "." 表示当前工作目录
- ".." 表示父目录
- 当前工作目录为根目录时,"."和".."相同
链接类型
硬链接(Hard Link)
- 包含在目录中的文件名就是一个文件的硬链接
- 同一文件可以在不同目录中有多个硬链接
- 使用ln命令创建:
1
$ ln P1 P2
- 为路径名P1标识的文件创建一个路径名为P2的硬链接
硬链接限制
- 目录限制
- 不允许普通用户给目录创建硬链接
- 防止目录树变为环形图
- 避免无法通过名字定位文件
- 文件系统限制
- 只能在同一文件系统内的文件之间创建链接
- 现代Unix系统可能包含多个文件系统
- 这些文件系统位于不同的磁盘和/或分区
软链接(Soft Link)
- 也称为符号链接(Symbolic Link)
- 是一个特殊的短文件
- 包含另一个文件的任意路径名
- 可以指向任意文件系统的文件或目录
- 甚至可以指向不存在的文件
软链接创建
- 使用ln命令的-s选项创建:
1
$ ln -s P1 P2
- P2指向路径名P1
- 创建后文件系统会在P2的目录中建立一个名为P2的符号链接
- 任何对P2的引用都会被自动替换成对P1的引用
文件类型
Unix文件可以是以下类型之一:
- 普通文件(regular file)
- 目录
- 符号链接
- 面向块的设备文件(block-oriented device file)
- 面向字符的设备文件(character-oriented device file)
- 管道(pipe)和命名管道(named pipe)(也叫FIFO)
- 套接字(socket)
前三种文件类型是所有Unix文件系统的基本类型,其实现将在第十八章详细讨论。
设备文件与I/O设备以及集成到内核中的设备驱动程序相关。例如,当程序访问设备文件时,它直接访问与那个文件相关的I/O设备(参见第十三章)。
管道和套接字是用于进程间通信的特殊文件(参见本章后面的"同步和临界区"一节以及第十九章)。
文件描述符与索引节点
- Unix对文件的内容和描述文件的信息给出了清晰的区分
- 除了设备文件和特殊文件系统文件外,每个文件都由字符序列组成
- 文件内容不包含任何控制信息,如文件长度或文件结束(end-of-file,EOF)符
索引节点(inode)
- 文件系统处理文件需要的所有信息都包含在一个名为索引节点的数据结构中
- 每个文件都有自己的索引节点
- 文件系统用索引节点来标识文件
尽管文件系统及内核函数对索引节点的处理可能随Unix系统的不同有很大的差异,但它们必须至少提供在POSIX标准中指定的如下属性
索引节点属性
- 文件类型(参见前一节)
- 与文件相关的硬链接个数
- 以字节为单位的文件长度
- 设备标识符(即包含文件的设备的标识符)
- 在文件系统中标识文件的索引节点号
- 文件拥有者的UID
- 文件的用户组ID
- 几个时间戳,表示索引节点状态改变的时间、最后访问时间及最后修改时间
- 访问权限和文件模式(参见下一节)
访问权限和文件模式
文件的潜在用户分为三种类型:
- 作为文件所有者的用户
- 同组用户,不包括所有者
- 所有剩下的用户(其他)
有三种类型的访问权限——读、写及执行每组用户都有这三种权限。因此,文件访问权限的组合就用九种不同的二进制标记位。还有三种附加的标记位,即suid(Set User ID)、sgid(Set Group ID),及sticky用来定义文件的模式。当这些标记位应用到可执行文件时有如下含义:
suid
- 进程执行一个文件时通常保持进程拥有者的UID
- 如果设置了可执行文件suid的标志位,进程就获得了该文件拥有者的UID
sgid
- 进程执行一个文件时保持进程组的用户组ID
- 如果设置了可执行文件sgid的标志位,进程就获得了该文件用户组的ID
sticky
- 设置了sticky标志位的可执行文件相当于向内核发出一个请求
- 当程序执行结束后,依然将其保留在内存中(注8)
注8:这个标志已经过时,现在使用基于代码页共享的其他方法(参见第九章)。
当文件由一个进程创建时,文件拥有者的ID就是该进程的UID,而其用户组ID可以是进程创建者的ID,也可以是父目录的ID,这取决于父目录sgid标志位的值。
文件操作系统调用
基本概念
- 用户访问文件实际是访问存储在硬件块设备上的数据
- 文件系统是硬件块设备的抽象层
- 所有文件操作必须在内核态下进行
- Unix系统通过系统调用实现文件操作
系统性能考虑
- Unix内核高度重视硬件块设备的处理效率
- 目标是获得良好的系统整体性能
- 通过系统调用机制确保安全和效率
打开文件操作
进程必须先打开文件才能访问,使用open系统调用:
1 | fd=open(path, flag, mode) |
参数说明
- path参数
- 指定要打开文件的路径
- 可以是相对路径或绝对路径
- flag参数
- 指定文件的打开方式:
- 读取(read)
- 写入(write)
- 读写(read/write)
- 追加(append)
- 可以指定是否创建不存在的文件
- mode参数
- 设置新创建文件的访问权限
返回值
- 返回文件描述符(file descriptor)
- 创建一个打开文件对象,包含:
- 文件操作的数据结构(打开方式标志、文件位置offset等)
- 可调用的内核函数集合(由flag参数决定)
POSIX规范的一般特性
- 文件描述符表示进程与打开文件之间的交互
- 同一打开文件对象可以由一个进程的多个文件描述符引用
- 多个进程可以同时打开同一文件:
- 每个进程获得独立的打开文件对象和文件描述符
- Unix文件系统默认不提供I/O操作的同步机制
- 可使用flock()系统调用实现对整个或部分文件的I/O操作同步(参见第十二章)
注:创建新文件可以使用create()系统调用,功能与open()类似,都由内核处理。
访问打开的文件
访问方式
- 普通Unix文件支持顺序访问和随机访问
- 设备文件和管道文件通常只能顺序访问
- 内核把文件指针存放在打开文件对象中
- 文件指针表示下一次读写操作的位置
文件操作
- 顺序访问:使用read()和write()系统调用,基于文件指针的当前位置读写
- 随机访问:需要使用lseek()系统调用修改文件指针位置
lseek系统调用
1 | newoffset=lseek(fd, offset, whence); |
参数说明: - fd: 打开文件的文件描述符 - offset: 有符号整数值,用于计算文件指针的新位置 - whence: 指定文件指针新位置的计算方式 - offset加0:从文件头移动 - offset加当前位置:从当前位置移动 - offset加文件末尾:从文件末尾移动
read系统调用
1 | count=read(fd, buf, count); |
参数说明: - fd: 打开文件的文件描述符 - buf: 指向缓冲区的指针,用于存储读取的数据 - count: 要读取的字节数
操作说明: - 内核会尝试从文件描述符fd指向的文件中读取count个字节 - 读取起始位置为文件的offset字段当前值 - 可能因文件结束、空管道等原因无法读取全部字节 - 返回值nread表示实际读取的字节数 - 读取完成后会更新文件指针 - write()系统调用的参数与read()类似
关闭文件
当进程不再需要访问文件内容时,使用系统调用:
1 | result=close(fd); |
- 释放文件描述符fd相关的打开文件对象
- 当进程终止时,内核会自动关闭其所有打开的文件
重命名和删除文件
这些操作不需要打开文件,它们作用于目录项而非文件内容。
重命名文件
1 | result=rename(oldpath, newpath); |
删除文件
1 | result=unlink(path); |
- 减少文件链接数
- 当链接数为0时,文件才被真正删除
25页