第一章 引言

1.1 操作系统的作用

  • 用户视图:操作系统的界面因设备而异,如PC、移动设备和嵌入式系统。
  • 系统视图:操作系统作为资源分配器和控制程序,管理CPU、内存、I/O设备等资源。
  • 定义操作系统:操作系统没有统一定义,但通常指核心内核、中间件框架及系统程序。

1.2 计算机系统组织

1.2.1 中断
  • 中断机制用于处理异步事件,通过中断服务例程响应硬件请求。
  • 中断优先级机制允许区分高/低优先级中断,确保紧急任务优先处理。
  • 中断向量表通过唯一编号索引中断服务例程地址。
1.2.2 存储结构
  • 主要存储单元包括寄存器、高速缓存、主存(RAM)、非易失性存储(NVM)和二级存储(硬盘)。
  • 存储层次结构按容量与访问时间排序,速度越快的存储越接近CPU。
  • 非易失性存储用于保存引导程序和静态数据。
1.2.3 I/O结构
  • I/O由设备控制器和驱动程序管理,采用中断或DMA方式减少CPU开销。
  • DMA允许设备直接读写内存,仅在传输完成后触发一次中断。
  • 高端系统使用交换架构替代总线架构以提高并发性能。

1.3 计算机系统架构

1.3.1 单处理器系统
  • 一个通用CPU加上多个专用处理器(如磁盘控制器)组成。
  • 专用处理器运行有限指令集,协助主CPU完成特定任务。
1.3.2 多处理器系统
  • 对称多处理(SMP)系统中每个CPU执行所有任务,共享内存。
  • 多核系统将多个计算核心集成在一个芯片上,提升效率并降低功耗。
  • NUMA架构为每个CPU提供本地内存,避免总线竞争瓶颈。
1.3.3 集群系统
  • 由多个节点组成的松耦合系统,通过局域网或InfiniBand互联。
  • 提供高可用性服务,支持故障转移与负载均衡。
  • 并行集群允许多个主机访问共享存储,需特殊软件支持。

1.4 操作系统操作

1.4.1 多道程序设计与多任务处理
  • 多道程序设计提高CPU利用率,通过进程切换保持CPU忙碌。
  • 多任务系统频繁切换进程,提供快速用户响应。
  • 虚拟内存技术使程序可大于物理内存,分离逻辑与物理地址空间。
1.4.2 双模式与多模式操作
  • 用户模式与内核模式通过模式位区分,防止非法指令执行。
  • 特权指令仅能在内核模式下执行,如I/O控制、定时器管理。
  • 支持更多保护级别的架构(如Intel四环模型、ARM七种模式)。
1.4.3 定时器
  • 定时器用于防止用户程序无限循环,强制中断返回控制权给操作系统。
  • Linux中的HZ参数决定定时中断频率,jiffies记录启动以来的中断次数。#### 1.5 资源管理
  • 操作系统是资源管理者,负责管理CPU、内存空间、文件存储空间和I/O设备等资源。
1.5.1 进程管理
  • 程序在执行时被称为进程。
  • 进程需要CPU时间、内存、文件和I/O设备等资源来完成任务。
  • 单线程进程只有一个程序计数器,多线程进程有多个程序计数器。
  • 进程是系统中的工作单元,分为操作系统进程(执行系统代码)和用户进程(执行用户代码)。
  • 操作系统负责创建和删除进程、调度进程和线程、挂起和恢复进程、提供进程同步和通信机制。
1.5.2 内存管理
  • 主存是计算机系统操作的核心,必须将程序映射到绝对地址并加载到内存中才能执行。
  • 多道程序设计要求系统保持多个程序在内存中,以提高CPU利用率和响应速度。
  • 操作系统负责跟踪内存使用情况、分配和释放内存空间、决定哪些进程和数据应调入或调出内存。
1.5.3 文件系统管理
  • 操作系统提供统一的逻辑视图,将文件抽象为逻辑存储单元。
  • 文件可以代表程序和数据,组织成目录结构便于管理。
  • 操作系统负责创建和删除文件与目录、支持文件操作原语、将文件映射到物理存储介质,并进行备份。
1.5.4 存储设备管理
  • 操作系统需有效管理二级存储(如硬盘)和三级存储(如磁带)。
  • 管理功能包括挂载与卸载、空闲空间管理、存储分配、磁盘调度、分区和保护。
  • 三级存储用于长期备份和不常用数据存储,由操作系统或应用程序管理。
1.5.5 缓存管理
  • 缓存通过复制信息到更快的存储系统中提升性能。
  • 寄存器、高速缓存、主存、固态硬盘和磁盘构成存储层次结构。
  • 缓存大小和替换策略影响性能,操作系统控制数据在不同层级之间的移动。
  • 在多任务和多处理器环境中,需确保缓存一致性。
1.5.6 I/O系统管理
  • 操作系统隐藏硬件设备细节,通过I/O子系统管理缓冲、缓存、假脱机。
  • 设备驱动程序负责特定硬件的操作。
  • 操作系统处理中断、设备控制寄存器访问和I/O完成检测。

1.6 安全与保护

  • 多用户和多进程环境下需调节对数据的访问。
  • 保护机制防止未经授权的进程操作资源,如内存地址限制、定时器防止无限循环。
  • 安全机制防御外部和内部攻击,如病毒、拒绝服务攻击、身份盗用。
  • 用户身份识别通过用户名和用户ID实现,支持用户组权限管理。
  • 特权升级机制允许临时获取更高权限,如UNIX的setuid属性。

1.7 虚拟化

  • 虚拟化技术将单个计算机硬件抽象为多个执行环境。
  • 支持在同一台机器上运行多个操作系统,如Windows和UNIX。
  • 虚拟机管理器(VMM)负责资源管理和隔离虚拟机。
  • 虚拟化广泛应用于服务器、桌面和数据中心,提高资源利用率和灵活性。

1.8 分布式系统

  • 分布式系统是由网络连接的多个独立计算机组成的集合。
  • 提供资源共享、提高计算速度、数据可用性和可靠性。
  • 网络按协议、节点距离和传输介质分类,如局域网(LAN)、广域网(WAN)。
  • 网络操作系统提供文件共享和进程通信功能;分布式操作系统提供单一系统外观。

1.9 内核数据结构

  • 操作系统广泛使用链表、栈、队列、树、哈希表和位图等数据结构。
1.9.1 列表、栈和队列
  • 链表支持动态插入和删除,适用于变长数据。
  • 栈采用后进先出(LIFO)原则,常用于函数调用。
  • 队列采用先进先出(FIFO)原则,常用于打印作业和任务调度。
1.9.2 树
  • 树结构表示数据层次关系,二叉搜索树支持高效查找。
  • 平衡二叉搜索树(如红黑树)用于Linux调度算法。
1.9.3 哈希函数与映射
  • 哈希函数将数据映射为索引,实现快速检索。
  • 哈希冲突通过链表解决,常用于密码验证等场景。
1.9.4 位图
  • 位图用于高效表示大量资源状态,如磁盘块可用性。

1.10 计算环境

  • 操作系统适应多种计算环境,包括传统计算、移动计算、客户端-服务器、点对点、云计算和实时嵌入式系统。
1.10.1 传统计算
  • 包括PC、服务器、网络和远程访问。
  • 时间分片技术允许多用户共享系统资源。
1.10.2 移动计算
  • 手持设备如智能手机和平板电脑。
  • 支持多媒体、GPS定位、加速度传感器等功能。
  • Android和iOS为主流移动操作系统。
1.10.3 客户端-服务器计算
  • 服务器满足客户端请求,分为计算服务器和文件服务器。
1.10.4 点对点计算
  • 所有节点平等,可同时作为客户端和服务器。
  • 优势在于去中心化,避免服务器瓶颈。
1.10.5 云计算
  • 提供计算、存储和应用作为服务,基于虚拟化技术。
  • 包括公有云、私有云、混合云及SaaS、PaaS、IaaS模式。
1.10.6 实时嵌入式系统
  • 用于汽车引擎、医疗设备等,具有严格的时间约束。
  • 实时系统需在限定时间内完成处理,否则导致失败。

1.11 自由与开源操作系统

  • 开源软件提供源代码,允许自由使用、修改和分发。
  • GNU/Linux、BSD UNIX、Solaris为典型开源系统。
1.11.1 历史
  • 早期软件附带源代码,后逐渐转向闭源。
  • Richard Stallman发起GNU项目,倡导自由软件理念。
1.11.2 自由操作系统
  • 自由软件强调用户自由而非价格,保障四项基本自由。
1.11.3 GNU/Linux
  • Linux内核结合GNU工具形成完整操作系统。
  • 多种发行版如Red Hat、Ubuntu、Debian等。
1.11.4 BSD UNIX
  • 衍生于AT&T UNIX,发展出FreeBSD、NetBSD等多个版本。
  • 提供完整的源代码,支持开发者研究和定制。
1.11.5 Solaris
  • Sun Microsystems开发的商业UNIX系统。
  • OpenSolaris开源项目衍生出Project Illumos。
1.11.6 开源系统作为学习工具
  • 开源项目促进学生理解操作系统原理,参与开发和改进。

1.12 总结

  • 操作系统管理硬件资源,提供应用程序运行环境。
  • 中断机制协调硬件与CPU交互,主存为直接访问唯一存储。
  • 多道程序设计和多任务提升CPU利用率和响应速度。
  • 用户模式与内核模式区分权限,保障系统安全。
  • 进程是操作系统的基本工作单元,涉及创建、调度、同步和通信。
  • 内存管理、文件系统、存储设备和缓存优化系统性能。
  • 保护与安全机制防止非法访问,虚拟化技术提升资源利用率。
  • 数据结构支撑操作系统功能实现,适应多样计算环境。
  • 自由与开源系统推动教育与技术创新。#### 第一章练习
  • 1.12 集群系统与多处理器系统的区别在于集群由多个独立节点组成,通过网络通信协作;而多处理器系统共享内存和总线。要实现高可用服务,集群需要心跳机制、故障检测、数据复制和自动故障转移。
  • 1.13 数据库集群的两种管理方式包括共享磁盘(所有节点访问同一存储)和主从复制(一个节点写,其他节点读)。前者提供一致性但存在单点故障风险;后者提高可用性但可能有数据同步延迟。
  • 1.14 中断用于响应外部事件(如I/O完成),由硬件触发;陷阱是软件产生的异常或系统调用。用户程序可以有意生成陷阱以请求操作系统服务(如系统调用)。
  • 1.15 Linux中HZ表示每秒时钟中断次数,jiffies记录自启动以来的中断总数。通过jiffies / HZ可计算出系统运行的秒数。
  • 1.16 a. CPU通过DMA控制器设置源地址、目标地址和传输长度来协调数据传输。
  • 1.16 b. DMA完成后会触发中断通知CPU操作结束。
  • 1.16 c. DMA允许CPU并发执行其他任务,不会直接干扰用户程序,但在内存带宽竞争激烈时可能导致性能下降。
  • 1.17 没有硬件特权模式仍可通过软件仿真、解释执行或虚拟机监控器构造安全操作系统,但效率低且安全性依赖于软件实现,难以完全替代硬件支持。
  • 1.18 多级缓存设计是为了平衡速度与容量:本地缓存速度快但容量小,适合私有数据;共享缓存容量大但访问延迟稍高,适合多核间共享数据。
  • 1.19 存储系统按速度排序为:磁带 < 光盘 < 硬盘 < 非易失性存储 < 主存 < 缓存 < 寄存器。
  • 1.20 示例:在SMP系统中,两个核心各自缓存同一内存变量,若其中一个修改了值但未同步,则另一核心看到的是旧值。
  • 1.21 a. 单处理器中缓存一致性问题出现在不同指令阶段对同一数据的访问。
  • 1.21 b. 多处理器中每个核心有自己的缓存,需通过协议(如MESI)保持一致性。
  • 1.21 c. 分布式系统中数据分布于不同节点,需使用一致性算法(如Paxos、Raft)保证全局一致性。
  • 1.22 内存保护机制包括分段与分页,结合基址与界限寄存器限制程序访问范围,利用MMU进行地址转换并检查权限,防止非法访问。
  • 1.23 a. 校园学生活动中心适合LAN配置。
  • 1.23 b. 跨校区大学系统适合WAN连接。
  • 1.23 c. 居民小区适合LAN或WLAN配置。
  • 1.24 移动设备操作系统需考虑电池寿命、屏幕尺寸、触控输入、传感器集成、应用沙箱化及频繁更新需求,相比传统PC更注重资源优化与安全性。
  • 1.25 P2P系统无需中央服务器,具有更高的容错性、负载均衡能力和扩展性,适用于文件共享、流媒体、分布式计算等场景。
  • 1.26 适合P2P系统的应用包括BitTorrent文件共享、Skype语音通信、IPFS分布式文件系统、区块链网络等。
  • 1.27 开源操作系统的优点包括透明性(开发者喜欢)、可定制性(高级用户喜欢)、低成本(中小企业喜欢);缺点包括技术支持不足(普通用户困扰)、碎片化(企业IT管理困难)、安全更新滞后(安全专家担忧)。

第二章 操作系统结构

2.1 操作系统服务
  • 提供用户界面(GUI、触摸屏、命令行)。
  • 程序执行:加载和运行程序,正常或异常终止。
  • I/O操作:读写文件或设备,需通过操作系统控制。
  • 文件系统管理:创建/删除/读取/写入文件,设置权限。
  • 通信:共享内存或消息传递,支持进程间或跨机器通信。
  • 错误检测:处理硬件、I/O设备及程序错误,并采取适当措施。
  • 资源分配:为多个进程分配CPU周期、内存、外设等资源。
  • 日志记录:用于计费、统计资源使用情况。
  • 保护与安全:防止非法访问系统资源,要求用户身份验证。
2.2 用户与操作系统接口
  • 命令解释器(Shell):
    • 如UNIX/Linux的bash、C Shell,Windows的cmd。
    • 可通过系统程序实现命令(如rm)。
  • 图形用户界面(GUI):
    • 使用窗口、菜单、图标,如macOS的Aqua、Windows的Explorer。
    • 开源桌面环境包括KDE和GNOME。
  • 触摸屏接口:
    • 适用于移动设备,如iPhone的Springboard、Android的触控交互。
  • 接口选择:
    • 高级用户倾向CLI,普通用户偏好GUI。
    • CLI可通过脚本自动化重复任务。
2.3 系统调用
  • 系统调用作用:
    • 提供应用程序与操作系统之间的接口。
    • 实现对文件、进程、设备、通信等的操作。
  • 示例流程:
    • 如复制文件涉及open()、read()、write()、close()等系统调用。
  • 应用编程接口(API):
    • Windows API、POSIX API、Java API。
    • API函数通常封装系统调用。
  • 参数传递方式:
    • 寄存器、堆栈、内存块。
  • 系统调用分类:
    • 进程控制:create(), terminate(), wait(), signal()等。
    • 文件管理:open(), read(), write(), delete()等。
    • 设备管理:request(), release(), read(), write()等。
    • 信息维护:get_time(), set_time(), get_process_attributes()等。
    • 通信:send(), receive(), pipe(), socket()等。
    • 保护:chmod(), chown(), set_permissions()等。
2.4 系统服务
  • 文件管理:创建、删除、复制、编辑文件。
  • 状态信息:获取时间、内存、磁盘空间、用户数等。
  • 文件修改:提供文本编辑器和搜索工具。
  • 编程语言支持:编译器、调试器、解释器(如C/C++、Java、Python)。
  • 程序加载与执行:绝对加载器、可重定位加载器、链接编辑器。
  • 通信:虚拟连接、网络服务、远程登录、邮件传输。
  • 后台服务(守护进程):
    • 如网络服务、定时任务、打印服务器。
    • Linux中称为daemon,Windows中称为service。
2.5 链接器与加载器
  • 编译过程:
    • 源代码 → 目标文件(relocatable) → 链接生成可执行文件。
  • 链接器功能:
    • 合并目标文件与库,解决符号引用。
  • 加载器功能:
    • 将可执行文件加载到内存并启动执行。
  • 动态链接:
    • 允许在运行时加载库(如Windows的DLL、Linux的.so)。
  • ELF格式:
    • Executable and Linkable Format,定义可执行文件结构。
    • 包含入口地址、段表、符号表等信息。
2.6 应用为何依赖操作系统
  • 不同操作系统差异:
    • 系统调用接口不同。
    • 可执行文件格式不同(如Windows PE、macOS Mach-O)。
    • CPU指令集不同(如x86 vs ARM)。
  • 跨平台解决方案:
    • 解释型语言(Python、Ruby)。
    • 虚拟机(如Java JVM)。
    • 标准化API(如POSIX)。
  • ABI(应用二进制接口):
    • 定义参数传递方式、栈布局、数据类型大小等底层细节。
    • 不同ABI之间不兼容,限制跨平台能力。
2.7 操作系统设计与实现
  • 设计目标:
    • 用户视角:易用、快速、可靠。
    • 开发者视角:易维护、灵活、高效。
  • 机制与策略分离:
    • 机制决定如何实现,策略决定做什么。
    • 微内核将策略与机制解耦,增强灵活性。
  • 实现语言:
    • 多数操作系统使用C/C++,部分关键代码用汇编。
    • Android示例:内核用C+ASM,系统库用C/C++,框架用Java。
  • 优势:
    • 开发效率高、易于移植、便于优化。
  • 劣势:
    • 性能略低,但现代编译器和硬件优化弥补此问题。
2.8 操作系统结构
  • 单体结构(Monolithic):
    • 所有功能集中在内核,如早期UNIX。
    • 优点:性能高;缺点:难以维护。
  • 分层结构(Layered):
    • 分层模块化设计,如THE OS。
    • 优点:结构清晰;缺点:效率低。
  • 微内核(Microkernel):
    • 内核仅提供基础机制(如进程管理、通信),其他服务作为用户进程。
    • 如Minix、QNX、Mach。
    • 优点:稳定性好、扩展性强;缺点:性能开销大。
  • 模块化结构(Modular):
    • 支持动态加载内核模块(如Linux的loadable kernel modules)。
  • 混合结构(Hybrid):
    • 结合微内核与单体内核特性,如Windows NT、macOS XNU。
  • Android架构:
    • 基于Linux内核,系统库用C/C++,应用框架用Java。
    • 采用Binder进行IPC通信。#### 第二章 操作系统结构(续)
2.8 操作系统结构
  • 单体结构:
    • 所有功能集中在单一地址空间中,如UNIX和早期Linux。
    • 优点:性能高;缺点:难以维护和扩展。
  • 分层结构:
    • 将操作系统划分为多个层次,每一层仅使用下层提供的服务。
    • 优点:便于调试与验证;缺点:性能较差,接口定义复杂。
  • 微内核结构:
    • 核心功能最小化,其余服务作为用户态进程运行。
    • 优点:可移植性好、稳定性强;缺点:消息传递开销大,影响性能。
  • 模块化结构:
    • 支持动态加载/卸载内核模块,如Linux的LKM。
    • 提供灵活性和良好的性能平衡。
  • 混合结构:
    • 结合单体内核与模块化设计,如Windows NT、macOS XNU。
    • 平衡性能与可维护性。
2.9 构建与启动操作系统
  • 操作系统生成步骤:
    • 编写或获取源代码 → 配置系统 → 编译 → 安装 → 启动。
  • 系统配置方式:
    • 修改源码重新编译,或从库中链接预编译模块。
  • Linux构建流程:
    • 下载源码 → 使用make menuconfig配置 → 编译内核与模块 → 安装并重启。
  • 虚拟机安装选项:
    • 可使用ISO镜像快速部署,或使用预配置虚拟机镜像。
2.9.2 系统启动过程
  • BIOS/UEFI引导流程:
    • 引导程序定位并加载内核 → 初始化硬件 → 挂载根文件系统。
  • 多阶段引导:
    • BIOS加载MBR中的boot loader,再加载完整操作系统。
  • UEFI优势:
    • 支持更大磁盘、64位系统,集成完整引导管理器。
  • GRUB引导器:
    • 支持参数设置、多内核选择、内核参数修改。
  • Android启动流程:
    • 使用LK引导器,initramfs作为根文件系统。
  • 恢复模式支持:
    • 所有主流系统均提供恢复或单用户模式用于诊断修复。
2.10 操作系统调试
  • 故障分析:
    • 内核崩溃时保存内存状态至专用区域,重启后生成core dump。
    • 用户进程失败时记录日志并生成core文件供调试。
  • 性能监控工具:
    • Linux:ps、top、vmstat、iostat、netstat。
    • Windows:任务管理器。
  • 追踪工具:
    • strace、gdb、perf、tcpdump。
  • BCC工具集:
    • 基于eBPF实现动态内核追踪,提供Python接口。
    • 示例工具:disksnoop、opensnoop,支持实时生产环境监控。
2.11 总结
  • 操作系统核心功能:
    • 提供程序执行环境,通过系统调用接口为用户提供服务。
  • 系统调用分类:
    • 进程控制、文件管理、设备管理、信息维护、通信、保护。
  • 应用依赖原因:
    • 可执行格式差异(PE/Mach-O/ELF)、指令集不同、系统调用不兼容。
  • 操作系统结构演进:
    • 单体 → 分层 → 微内核 → 模块化 → 混合结构。
  • 启动机制:
    • 引导程序负责加载内核、初始化硬件、挂载根文件系统。
  • 调试与性能优化:
    • 使用计数器与追踪工具进行系统行为分析,BCC/eBPF提供高效内核级追踪能力。#### 第四章 编程项目
4.1 Linux内核模块简介
  • 本项目介绍如何创建并加载内核模块到Linux内核。
  • 模块可在/proc文件系统中添加条目。
  • 使用虚拟机完成实验,需通过终端编译程序并管理模块。
4.2 内核模块概述
  • 使用lsmod命令查看当前加载的内核模块。
  • simple.c示例模块在加载和卸载时输出信息。
    • simple_init():模块加载时调用,返回整数表示成功或失败。
    • simple_exit():模块卸载时调用,无返回值。
  • 使用printk()函数输出日志信息,可通过dmesg查看。
  • 注册模块入口和出口点:
    • module_init(simple_init)
    • module_exit(simple_exit)
  • 模块元信息:
    • MODULE_LICENSE("GPL")
    • MODULE_DESCRIPTION("Simple Module")
    • MODULE_AUTHOR("SGG")
4.3 加载与卸载内核模块
  • 编译模块使用make命令,生成simple.ko文件。
  • 加载模块使用sudo insmod simple.ko
  • 卸载模块使用sudo rmmod simple
  • 使用dmesg查看模块加载和卸载日志。
  • 清空日志缓冲区使用sudo dmesg -c
4.4 扩展功能练习
  • 在模块中调用内核提供的常量和函数:
    • GOLDEN_RATIO_PRIME:定义在<linux/hash.h>
    • gcd()函数:定义在<linux/gcd.h>,用于计算最大公约数。
  • 输出HZjiffies变量:
    • HZ:定时器中断频率,定义在<asm/param.h>
    • jiffies:系统启动以来的中断次数,定义在<linux/jiffies.h>
4.5 /proc文件系统模块
  • /proc文件系统是伪文件系统,用于查询内核和进程状态。
  • 创建/proc/hello文件示例(hello.c):
    • 使用proc_create()创建文件。
    • 定义读取操作函数proc_read()
  • proc_read()函数实现:
    • 将“Hello World”写入内核缓冲区。
    • 使用copy_to_user()将数据复制到用户空间。
    • 控制只输出一次,避免重复读取。
4.6 实验任务
  • 设计两个内核模块:
    1. 创建/proc/jiffies文件:
      • 读取时显示当前jiffies值。
      • 模块卸载时删除该文件。
    2. 创建/proc/seconds文件:
      • 显示自模块加载以来经过的秒数。
      • 使用jiffiesHZ进行换算(秒数 = jiffies / HZ)。
      • 模块卸载时删除该文件。

第三章 进程

3.1 进程概念

3.1.1 进程
  • 进程是正在执行的程序。
  • 程序是被动实体,进程是主动实体。
  • 内存布局包括:
    • 文本段(可执行代码)
    • 数据段(全局变量)
    • 堆段(动态分配内存)
    • 栈段(函数调用临时数据)
  • 程序变为进程需加载到内存。
  • 同一程序可运行多个进程实例。
3.1.2 进程状态
  • 进程状态包括:
    • 新建(new)
    • 就绪(ready)
    • 运行(running)
    • 等待(waiting)
    • 终止(terminated)
  • 多个进程可处于就绪或等待状态,但仅一个进程在运行。
3.1.3 进程控制块(PCB)
  • 每个进程由PCB表示,包含以下信息:
    • 进程状态
    • 程序计数器
    • CPU寄存器
    • 调度信息
    • 内存管理信息
    • I/O状态信息
3.1.4 线程
  • 单线程进程只能顺序执行一个任务。
  • 多线程进程可并发执行多个任务。
  • 支持多核并行处理,提升性能。
  • PCB扩展以支持每个线程的信息。

3.2 进程调度

3.2.1 调度队列
  • 就绪队列:准备运行的进程。
  • 等待队列:等待事件完成的进程。
  • 队列结构为链表,每个PCB含指针指向下一个PCB。
3.2.2 CPU调度
  • 目标是最大化CPU利用率和实现时间分片。
  • CPU调度器从就绪队列选择进程执行。
  • I/O密集型与CPU密集型进程行为不同。
  • 多核系统可同时运行多个进程。
3.2.3 上下文切换
  • 中断导致当前进程上下文保存至PCB。
  • 切换过程包括:
    • 保存旧进程上下文
    • 加载新进程上下文
  • 上下文切换纯属开销,无实际工作。
  • 切换速度依赖硬件支持和操作系统复杂度。

3.3 进程操作

3.3.1 进程创建
  • 父进程创建子进程,形成进程树。
  • 子进程获取资源:CPU时间、内存、文件、I/O设备。
  • UNIX使用fork()创建子进程,随后可用exec()替换地址空间。
  • Windows使用CreateProcess()创建新进程。
3.3.2 进程终止
  • 进程通过exit()系统调用结束。
  • 父进程可通过wait()获取子进程退出状态。
  • 子进程资源被回收,若父进程未调用wait()则成为僵尸进程。
  • 若父进程终止,init/systemd接管孤儿进程。
Android进程层次
  • 进程按重要性排序,优先级从高到低:
    • 前台进程(用户交互)
    • 可见进程(前台引用)
    • 服务进程(后台可见)
    • 后台进程(非可见)
    • 空进程(无活动组件)
  • 系统优先终止空进程,然后是后台进程等。

3.4 进程间通信(IPC)

  • 独立进程不共享数据,合作进程共享数据。
  • 提供IPC的原因:
    • 信息共享
    • 计算加速(多核并行)
    • 模块化设计

3.5 共享内存模型下的IPC

  • 进程建立共享内存区域进行通信。
  • 生产者-消费者问题示例:
    • 使用循环缓冲区(bounded buffer)
    • in和out指针管理缓冲区读写
    • 同步机制防止同时访问冲突

3.6 消息传递模型下的IPC

  • 不共享地址空间,通过send()和receive()交换消息。
  • 适用于分布式系统。
  • 支持直接通信(命名对方)或间接通信(通过邮箱/端口)。
3.6.1 命名方式
  • 直接通信:
    • send(P, message)
    • receive(Q, message)
  • 间接通信:
    • send(A, message) 发送到邮箱A
    • receive(A, message) 从邮箱A接收
3.6.2 同步方式
  • 阻塞发送/接收(同步)
  • 非阻塞发送/接收(异步)
  • 阻塞发送+阻塞接收实现同步 rendezvous
3.6.3 缓冲机制
  • 零容量:发送方必须等待接收方接收。
  • 有界容量:最多n条消息,满时发送方阻塞。
  • 无界容量:发送方永不阻塞。#### 第三章 进程(续)
3.7 IPC系统示例
3.7.1 POSIX共享内存
  • 使用内存映射文件组织共享内存。
  • shm_open() 创建共享内存对象。
  • ftruncate() 设置共享内存大小。
  • mmap() 建立内存映射,实现共享内存访问。
  • 示例程序展示生产者写入 “Hello World!”,消费者读取并输出。
3.7.2 Mach消息传递
  • Mach是专为分布式系统设计的操作系统,也用于macOS和iOS。
  • 任务间通信通过端口(Port)进行,支持单向、有限长度的消息队列。
  • mach_msg() 是发送和接收消息的标准API。
  • 支持简单消息(用户数据)和复杂消息(含内存指针或端口权限传输)。
  • 利用虚拟内存技术避免消息复制,提升性能。
3.7.3 Windows
  • Windows使用ALPC(高级本地过程调用)机制进行进程通信。
  • 通信分为三种方式:
    • 小消息(≤256字节)直接复制到端口队列。
    • 大消息通过共享内存段(Section Object)传递。
    • 超大消息允许服务器直接读写客户端地址空间。
  • ALPC由系统内部使用,应用程序通过RPC间接调用。
3.7.4 管道(Pipes)
3.7.4.1 普通管道(Ordinary Pipes)
  • 单向通信:一端写入,另一端读取。
  • UNIX使用pipe()创建,fd[0]为读端,fd[1]为写端。
  • 通常用于父子进程间通信。
  • 若需双向通信,需使用两个管道。
  • Windows称其为匿名管道,使用CreatePipe()创建。
3.7.4.2 命名管道(Named Pipes)
  • 支持跨无亲缘关系进程通信。
  • UNIX中称为FIFO,使用mkfifo()创建。
  • 可持续存在,直到被显式删除。
  • Windows命名管道支持全双工通信,可跨机器使用。
  • 使用CreateNamedPipe()创建,ConnectNamedPipe()连接。
3.8 客户端-服务器系统通信
3.8.1 套接字(Sockets)
  • 套接字是网络通信的端点,由IP+端口号唯一标识。
  • 典型服务端口:SSH(22)、FTP(21)、HTTP(80)。
  • Java提供Socket类(TCP)、DatagramSocket类(UDP)及MulticastSocket类。
  • 示例:日期服务器与客户端通过Socket交换当前时间。
3.8.2 远程过程调用(RPC)
  • RPC抽象了函数调用机制,适用于网络环境。
  • 消息包含调用函数名、参数、返回值。
  • 参数编组(Marshaling)解决不同机器的数据表示差异(如大端/小端)。
  • XDR(外部数据表示)定义平台无关的数据格式。
  • 语义保证:“最多一次”(At Most Once)和“恰好一次”(Exactly Once)。
  • 绑定方式:
    • 静态绑定:固定端口号。
    • 动态绑定:通过匹配服务(Matchmaker)获取端口号。
3.8.2.1 Android中的RPC
  • Android使用Binder框架实现IPC,支持RPC。
  • Service组件可通过bindService()绑定,并提供远程接口。
  • AIDL(Android接口定义语言)生成Stub和服务接口。
  • 客户端调用远程方法时,Binder自动处理参数编组、进程间传输及结果返回。
3.9 总结
  • 进程是正在执行的程序,具有文本段、数据段、堆、栈。
  • 进程状态包括就绪、运行、等待、终止。
  • PCB记录进程状态、寄存器、调度信息等。
  • fork() 和 CreateProcess() 分别用于UNIX和Windows创建进程。
  • 共享内存(POSIX API)、消息传递(Mach、Windows)、管道(普通/命名)是常见IPC方式。
  • 套接字和RPC用于客户端-服务器通信。
  • Android使用Binder框架支持RPC作为IPC机制。#### 第三章 进程(续)
3.7 进程调度与上下文切换
  • 上下文切换时,内核保存当前进程状态至PCB。
  • 恢复下一个进程的上下文以继续执行。
  • 包括程序计数器、寄存器、内存管理信息等。
  • 切换速度受硬件和操作系统机制影响。
3.8 进程树与系统工具
  • 使用 ps -ael 查看UNIX/Linux进程树。
  • Windows可用Process Monitor查看进程父子关系。
  • init/systemd负责回收孤儿进程资源。
3.9 进程创建与终止
  • fork() 创建子进程,exec() 替换地址空间。
  • wait() 系统调用用于父进程等待子进程结束。
  • 子进程未被wait()回收将变为僵尸进程。
  • 父进程结束后,init/systemd接管孤儿进程。
3.10 进程数量计算示例
  • 图中程序循环4次调用fork(),共创建15个子进程(包括初始进程)。
3.11 IPC通信方式比较
  • 普通管道适用于父子进程间临时通信。
  • 命名管道支持无亲缘关系进程通信。
  • RPC若不保证“最多一次”或“恰好一次”语义可能导致重复请求或丢失。
3.12 共享内存与消息传递
  • 共享内存实现高效数据交换,但需同步机制。
  • 消息传递适用于分布式系统,提供更高抽象层次。
  • 同步方式:阻塞/非阻塞发送与接收。
  • 缓冲机制:零容量、有界容量、无界容量。
3.13 编程问题解析
3.18 创建僵尸进程
  • 子进程退出后父进程不调用wait()。
  • 可通过ps -l查看Z状态确认。
  • 使用kill命令终止父进程可清除僵尸进程。
3.19 测量命令执行时间
  • 使用共享内存或管道在父子进程间传输开始时间。
  • gettimeofday()获取微秒级时间戳。
  • 父进程等待子进程结束并计算时间差。
3.20 PID管理器
  • 定义PID范围为300~5000。
  • 使用位图表示PID使用状态。
  • 实现allocate_map(), allocate_pid(), release_pid()接口。
3.21 Collatz猜想多进程实现
  • 子进程生成序列并输出。
  • 父进程调用wait()等待子进程完成。
  • 需验证输入是否为正整数。
3.22 使用共享内存优化Collatz程序
  • 父进程创建共享内存区域。
  • 子进程写入序列数据。
  • 父进程读取并输出,使用wait()同步。
3.23~3.25 网络服务实现
  • 修改日期服务器为“每日一句”服务器,监听端口6017。
  • 实现Haiku诗歌服务器,监听端口5575。
  • 编写回声服务器,使用InputStream处理二进制数据。
3.26~3.27 管道通信编程
  • 设计双管道实现字符串大小写反转通信。
  • filecopy.c使用管道复制文件内容。
  • 父进程写入管道,子进程读取并写入目标文件。#### 编程项目
  • 项目目标:设计一个C程序作为shell接口,支持命令执行、输入输出重定向、管道通信。
项目1:UNIX Shell
  • 功能要求:
    • 接收用户命令并创建子进程执行。
    • 支持后台运行(使用&符号)。
    • 支持命令历史(!!执行上一条命令)。
    • 支持输入输出重定向(< 和 >)。
    • 支持管道通信(|)。
  • 使用系统调用:
    • fork():创建子进程。
    • execvp():执行命令。
    • wait():等待子进程结束。
    • dup2():重定向标准输入输出。
    • pipe():建立进程间管道。
  • 程序结构:
    • main()函数循环读取命令、解析参数、执行命令。
    • 支持命令参数数组解析(args)。
    • 支持错误处理(如无历史命令时输入!!)。
项目2:Linux内核模块显示任务信息
  • 功能:
    • 通过写入/proc/pid获取进程信息。
    • 读取/proc/pid显示命令名、PID、状态。
  • 关键函数:
    • proc_write():接收用户写入的PID。
    • pid_task():通过PID获取task_struct。
    • copy_from_user(), kstrtol():用户空间数据处理。
  • 内存管理:
    • kmalloc() / kfree():分配和释放内核内存。
    • 注意防止内存泄漏。
项目3:Linux内核模块列出所有任务
部分一:线性遍历任务
  • 使用宏for_each_process()遍历所有任务。
  • 输出每个任务的命令名、状态、PID。
  • 模块加载时输出到内核日志(dmesg查看)。
  • 与ps -el命令对比验证。
部分二:深度优先遍历任务树
  • 使用init_task作为根节点。
  • 使用children和sibling字段遍历进程树。
  • 使用list_for_each()宏遍历子进程链表。
  • 输出任务信息并与ps -eLf对比验证。
项目4:内核数据结构
部分一:链表操作
  • 定义结构体struct color,包含红、蓝、绿三色值。
  • 使用Linux内核链表结构struct list_head。
  • 实现链表操作:
    • LIST_HEAD():定义链表头。
    • INIT_LIST_HEAD():初始化节点。
    • list_add_tail():尾部插入节点。
    • list_for_each_entry():遍历链表。
    • list_del():删除节点。
    • list_for_each_entry_safe():安全删除遍历。
  • 模块加载时创建并输出四个颜色节点;卸载时释放内存。
部分二:模块参数传递
  • 使用module_param()传递整型参数。
  • 示例:insmod collatz.ko start=15。
  • 默认值设置:start=25。
  • 生成Collatz序列并存储在链表中。
  • 加载时输出序列,卸载时清理内存。