操作系统概念
第一章 引言
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>
,用于计算最大公约数。
- 输出
HZ
和jiffies
变量: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 实验任务
- 设计两个内核模块:
- 创建
/proc/jiffies
文件:- 读取时显示当前
jiffies
值。 - 模块卸载时删除该文件。
- 读取时显示当前
- 创建
/proc/seconds
文件:- 显示自模块加载以来经过的秒数。
- 使用
jiffies
和HZ
进行换算(秒数 = 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序列并存储在链表中。
- 加载时输出序列,卸载时清理内存。
All articles on this blog are licensed under CC BY-NC-SA 4.0 unless otherwise stated.