9413 字
47 分钟

Operating-System Structures

Operating System Services#

User-Oriented Services#

从用户角度需要提供的服务:

User interface : CLI, GUI

Program execution : 能够跑第三方的应用程序。

I/O operations : 插 U 盘,读写文件。

File-system manipulation : 创建、删除、读写文件,创建目录。

Communication : 进程间通信,网络通信。

Error detection : 检测硬件错误,软件错误。

I/O 关注数据输入输出,文件系统操作还关注文件的命名、组织、属性与权限。 一次读取 U 盘文件可以同时涉及两类服务。

通信的两种基本方式是共享内存(Shared Memory)和消息传递(Message Passing):前者让进程访问共同的内存区域,后者通过发送和接收消息交换信息。

错误检测之后,系统可能返回错误状态、终止出错进程,严重时也可能停止系统。

除了方便用户,系统自身还需要哪些服务?
服务作用
资源分配为多个进程分配 CPU 时间、主存、文件存储和设备等资源
记录与统计记录资源使用情况,用于计费、统计或改进系统配置
保护与安全控制资源访问,限制进程间不当干扰,并防范非法访问

用户能完成任务,与多个任务能够安全、高效地共存,都是操作系统需要考虑的问题。

User Operating System Interface#

Command-Line Interface and Shell#

命令行接口(Command-Line Interface,CLI) 允许用户直接输入命令。命令解释器负责读取、解释并执行命令;不同的命令解释器通常称为不同的 Shell。

读取用户命令 → 确定命令及参数 → 执行相应操作 → 继续接收命令

命令解释器可以实现在内核中,也可以作为系统程序实现。

命令主要有两种实现方式:

方式谁实现命令的具体功能?对扩展的影响
内置命令命令解释器中包含相应代码,必要时请求系统服务增加这类功能通常需要修改解释器
外部程序命令名指向一个程序文件,由该程序实现具体功能增加程序即可扩展命令集合,不必修改解释器

GUI and Batch Processing#

图形用户界面(Graphical User Interface,GUI) 以桌面、窗口、图标和菜单等形式组织交互。

同一个操作系统可以同时提供 CLI 和 GUI。

方式交互特点
命令行用文本表达操作与参数,适合把重复步骤组织成脚本
图形界面用可视对象和菜单表达操作,便于浏览与直接选择
批处理(Batch)预先提供一组命令,由系统按约定执行,减少逐步交互

用户接口描述的是用户怎样表达请求。后面的系统调用接口描述的是程序怎样请求操作系统服务。

System Calls#

System Call Interface#

系统调用(System Call) 是程序使用操作系统服务的编程接口,例如请求文件读写、创建进程或建立通信。

应用开发通常先使用应用程序编程接口(Application Programming Interface,API),再由库或运行时组织相应系统调用。

System Call Declarations#

#include <sys/socket.h>
int socket(int socket_family, int socket_type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

这些是函数声明:给出函数名、参数和返回类型,并没有在头文件中展开内核的全部实现。

Example#

复制一个文件需要哪些系统调用?

假设程序需要获取源文件名和目标文件名,然后把源文件内容复制到目标文件。

调用序列、返回状态与错误处理
输出提示,读取源文件名和目标文件名
↓
打开源文件
└─ 文件不存在或无访问权限:报告错误,结束
↓
创建并打开目标文件
└─ 目标已存在:本例选择报告错误并结束
↓
循环读取一批数据
├─ 读到数据:将本次实际读到的数据写入目标文件,继续
├─ 到达文件末尾:完成复制
└─ 读取或写入出错:报告失败,进行清理
↓
关闭两个文件
↓
输出完成信息,正常结束

其中,显示提示、接收输入、打开与创建文件、读写、关闭和结束进程,都可能需要系统服务。只数“读一次、写一次”会遗漏大量准备与收尾工作。

情况应如何理解?
源文件不存在/无权限打开失败,不能进入正常复制循环
目标文件已存在采用中止策略;也可以选择覆盖或询问用户等选择
到达文件末尾表示内容已读完,需要与真正的读取错误区分
磁盘空间不足/设备错误写入或读取可能失败,不能仍报告复制成功
函数返回需要检查返回状态,并使用实际传输的数据量

一次 write() 成功也可能只写入部分数据。实际程序需要继续处理剩余数据及可能出现的错误,不能假设每次都写满。

Example#

读懂 ReadFile() 的参数

BOOL ReadFile(
HANDLE file,
LPVOID buffer,
DWORD bytesToRead,
LPDWORD bytesRead,
LPOVERLAPPED ovl
);
参数含义需要分清的内容
file要读取的文件或设备句柄句柄用于标识已打开的对象
buffer接收数据的缓冲区地址数据写入调用者提供的缓冲区
bytesToRead本次最多请求读取的字节数请求量未必等于实际读取量
bytesRead接收实际读取字节数的变量地址在这里讨论的同步读取中,通过指针返回数量
ovl指向重叠 I/O 所用信息结构的指针与异步/重叠操作有关;本章先识别其作用

返回状态、实际读到的字节数、读到的数据,分别通过不同位置交给调用者。 不应把布尔返回值当成字节数,也不应把 bytesRead 当成数据缓冲区。

申请读取 1,024 字节,为什么只处理其中 300 字节?

假设使用同步文件句柄,读取成功,而从当前位置到文件末尾只剩 300 字节。

本次请求量为 1,024,实际读取量为 300。程序只应把缓冲区中本次得到的 300 字节当成有效读取结果,不能把余下空间也视为新读到的文件内容。

文件读取,实际数量通过返回值给出;到达文件末尾返回 0,出错返回 -1。

System Call Numbers and Dispatch#

程序发起系统调用时,操作系统需要知道:请求哪一种服务,以及用什么参数完成它。

操作系统会维护一张表,里面存放 number 和指针,指针指向处理这个 system call 的代码。

用户程序调用 API
↓
库/运行时准备系统调用编号与参数
↓
通过受控入口进入内核
↓
系统调用接口根据编号查表
↓
执行对应服务,得到状态与返回值
↓
返回用户程序,继续执行

调用者不需要知道内核处理函数的全部实现,但必须遵守接口约定:传入正确参数,理解操作含义,并检查返回结果。

系统调用、软中断与进程切换

系统调用可认为是“软中断”:程序主动触发受控的内核入口,CPU 转入内核执行服务。

进入内核改变了执行权限和代码路径,不必然切换到另一个进程。当前进程可以进入内核完成服务后再返回;若期间需要等待 I/O 等事件,系统也可能安排其他进程运行。

Example#

printf() 怎样使用 write()?

用户程序:printf("Greetings")
↓
标准 C 库:处理格式与输出流
↓ 需要向外输出时
系统调用接口:请求 write()
↓
内核:完成相应输出服务
↓
结果返回库函数与用户程序

C 语言的 Printf 对应到 system call 的 write 要对应到像素级别的操作。所以每一个简单操作都有可能需要多个 system call 来完成。

Parameter Passing#

参数传递方法。

方法做法优点与限制
寄存器直接把参数放入约定的寄存器简单直接,但寄存器数量与宽度有限
内存参数块/表参数集中存入内存,把该区域地址放入寄存器可传递较多或较复杂的参数;内核按地址取得内容
栈程序将参数压栈,操作系统按约定取得参数不受可用参数寄存器个数的直接限制

内存参数块与栈缓解了寄存器限制,仍受内存大小、地址有效性及接口约定约束,不能理解为参数真正无限。

Example#

通过地址 X 传递一组参数

展开分析:系统调用编号与参数表地址分别解决什么问题?

假设用户程序已在内存位置 X 保存好某次调用需要的参数。

用户程序在内存中准备参数表 X
↓
把 X 的地址放入约定的寄存器
↓
发起图中示例的系统调用 13
↓
内核根据编号 13 找到处理代码
↓
处理代码根据地址 X 取得本次参数

调用编号说明“请求什么服务”,地址 X 说明“这次调用的参数在哪里”。 两者作用不同。寄存器只携带表的地址,无需装下表中的每一项。

其中,13 是图中的示意编号,X 是示意地址;具体接口还必须约定参数布局与访问方式。

Types of System Calls#

System Call Categories#

类型典型操作理解重点
进程控制(Process Control)创建与终止进程,装入与执行程序,获取/设置进程属性,等待与通知事件,分配与释放内存管理程序的执行、资源和相互协调
文件管理(File Management)创建、删除、打开、关闭、读写、调整文件位置、获取/设置文件属性文件内容与文件属性都需要管理
设备管理(Device Management)请求与释放设备,读写,获取/设置设备属性,逻辑连接与分离处理设备使用权和设备操作
信息维护(Information Maintenance)获取/设置时间日期、系统数据,以及进程、文件、设备的信息查询状态、维护属性、支持统计与调试
通信(Communications)建立与关闭通信连接,发送与接收消息,传递状态,建立与附接共享内存为不同执行者交换信息提供接口

现在目前大概有200多个 system call 支持操作系统的全部功能,操作系统也有其他的手段,比如发送信号,来实现一些功能。

Communication Models#

模型系统提供什么?进程怎样交换信息?
消息传递建立通信关系、发送与接收消息等能力通过消息操作传递信息,可用于本机或跨机器通信
共享内存创建、附接共享内存区域等能力访问共同的内存区域,直接读写其中的数据

共享内存建立后,进程还要协调谁在什么时候读写,避免不当的并发更新。获得共享区域,与正确使用共享数据,是两个需要分别解决的问题。

Example#

MS-DOS 与 FreeBSD 的进程控制

MS-DOS: Partial Shell Overwrite#

启动时,内存中有内核、命令解释器和空闲区域;运行程序后,进程占据部分空闲区域,也覆盖了命令解释器原来占用的一部分空间,下面仍保留内核与一部分解释器。

FreeBSD: Shell and Process Coexistence#

命令解释器与多个进程可以同时驻留

Shell 接收运行某程序的命令
↓
fork():建立子进程
↓
子进程通过 exec() 装入并执行目标程序
↓
父进程 Shell 根据前台/后台方式组织后续执行
├─ 前台:等待程序结束,再继续处理下一条命令
└─ 后台:继续接收命令,目标程序也参与系统调度
↓
目标进程结束,返回退出状态
为什么启动程序既需要 fork,又需要 exec?图中多个进程是否一定并行?

在 UNIX 风格路径中,两步解决不同问题:fork() 创建新的进程,exec() 让相应进程执行目标程序。父进程 Shell 因而可以保留自己的执行环境,负责后续交互。

图中多个进程同时驻留,也不保证指令在同一时刻执行。单个 CPU 核心可以交替运行多个进程,实现并发;并行执行还需要相应硬件与调度条件。

System Programs#

Program Development and Execution#

系统程序(System Programs) 把基础服务组织成便于使用的工具,为程序开发、执行和系统管理提供环境。部分工具只是系统调用的简单用户接口,另一些则包含复杂逻辑。

类别典型功能或工具
文件管理创建、删除、复制、重命名、打印、转储、列出文件与目录
状态信息查看日期时间、空闲内存、磁盘空间、用户数;提供性能、日志与调试信息
文件修改使用文本编辑器修改内容,搜索内容或进行文本转换
程序设计语言支持编译器、汇编器、解释器、调试器
程序装入与执行绝对装入器、可重定位装入器、链接编辑器、覆盖装入器,以及相应调试支持
通信用户间发送消息、浏览网页、电子邮件、远程登录、跨机器传送文件
应用程序系统提供的软件环境;

文件管理 侧重文件对象及目录组织;文件修改 侧重文件内部的内容。状态工具通常还会把取得的数据整理、格式化后显示出来;一些系统通过 注册表(Registry) 存储和检索配置。

系统自带的软件与自己安装的软件,怎样区分?

系统程序侧重提供开发、执行、管理环境,应用程序侧重完成用户的具体任务。两者都可能通过系统调用使用内核服务,系统程序这一名称本身也不表示它必然在内核态运行。

Example#

用 DTrace 观察系统调用路径

调用箭头与缩进分别表示什么?

删去中间细节后,可以读成:

XEventsQueued U
……
-> ioctl U
-> ioctl K
-> getf K
……
<- getf K
……
<- ioctl K
<- ioctl U
……

U 标记用户侧执行,K 标记内核侧执行;-> 与 <- 展示函数进入和返回,缩进体现调用嵌套。图中可看到用户侧 ioctl 进入内核处理,之后又逐层返回。

内核中出现多行函数名,不等于应用发起了同样多次系统调用。 一次系统调用的内核实现可以继续调用多个内部函数。顶部的 matched 52377 probes 是匹配到的探针数量。

Operating System Design and Implementation#

Design Goals and Constraints#

Separation of Mechanism and Policy#

机制(Mechanism) 决定怎样完成操作;策略(Policy) 决定要采用什么选择。

概念要回答的问题定时器例子
机制怎样做到?定时器到期产生中断,让系统重新取得 CPU 控制权
策略做什么选择?让某个程序运行多久,再通过定时器收回控制?

机制提供可用能力,策略利用这些能力作出选择。将二者分离后,修改策略时可以尽量复用已有机制,提高系统灵活性。

优先级:同一套优先级机制,可以支持“I/O 密集型程序优先”,也可以支持“CPU 密集型程序优先”。选择哪一类优先,属于策略。

Example#

改变时间片,需要重新设计定时器吗?

展开分析

假设定时器机制支持设置不同的到期时间。将运行时限由一个值改为另一个值,主要改变的是策略参数,可以继续复用原有定时器机制。

分离的价值就在于:经常变化的选择不必与底层实现绑死。

但这有一个前提:新策略仍在机制能支持的范围内。如果原有机制不能表达新的要求,单纯修改参数仍然不够。因此,“分离”保证的是更好的可变更性,不保证任何新策略都无需修改机制。

Example#

门禁卡系统中的机制与策略

部分在例子中的内容
机制磁卡读取器、可远程控制的门锁,以及连接安全服务器的通信设施
策略哪些人、在什么时间、可以进入哪些门
决策的组织方式集中安全服务器查询门禁规则数据库,作出是否允许进入的决定
读卡器读取卡片信息
↓
安全服务器依据“人、门、时间”等信息查询规则
↓
得到允许/拒绝的决定
↓
通过门锁控制机制执行决定

X Window System Design Principles#

X 窗口系统(X Window System) 七条原则可以归纳如下。

原则理解
由真实需求推动功能只有缺少某项功能会妨碍真实应用实现时,才考虑把它加入系统
明确系统边界,并保留扩展能力同时决定系统承担哪些职责、哪些职责留给外部;通过向上兼容的扩展满足额外需求
避免缺乏依据的泛化从单个例子作概括已经有风险,完全没有实例支撑的抽象更应谨慎
问题未理解充分时,避免仓促固定方案不要过早把尚未弄清的问题固化成底层接口或功能
优先采用足够好的简单方案若少量工作可以取得大部分效果,优先考虑更简单的实现
尽量隔离复杂性让复杂细节集中在边界清晰的部分,减少向其他部分扩散
提供机制,把界面策略交给客户端提供实现交互所需的基础能力,由客户端决定具体的用户界面政策

这些原则共同强调:基础系统保持清晰的职责和通用能力,具体选择尽量留在可以变化的位置。

Operating System Structure#

Simple Structure: MS-DOS#

MS-DOS 的设计目标是在很小的空间内提供尽可能多的功能。它没有清晰的模块划分,各层功能与接口之间也缺少良好分离。

MS-DOS 的层次及跨层访问关系

应用程序 :使用系统和底层设备提供的功能 常驻系统程序 :提供系统运行所需的基本服务 MS-DOS 设备驱动 :与具体设备交互 ROM BIOS 设备服务 : 固件中的基本输入输出能力

箭头允许跨过中间部分直接访问底层。因此,没有实现严格的层间边界。

Layered Approach#

分层结构(Layered Approach) 把系统划分成若干层。

最低层 layer 0 是硬件,最高层 layer N 是用户接口;每层使用较低层提供的操作与服务。

一层可以看成“数据结构+操作这些数据的函数”。它对上提供接口,并隐藏内部实现。

用户接口 第 N 层
↓ 使用下层提供的服务
……
↓
较底层的服务与设备访问
↓
硬件 第 0 层

便于分工开发 :先约定接口,不同层的实现可相对独立推进 便于理解与维护 :使用下层服务时,不必了解全部实现细节 便于调试与验证 :可以从最底层开始,逐层检查

分层代价: 首先,层次不一定容易划分:内存、进程、文件等功能可能互相依赖。 其次,一个请求经过多个中间层,会增加接口处理、数据传递等开销。

Monolithic Structure: Traditional UNIX#

单体内核(Monolithic Kernel,也称宏内核) 将大量内核功能放在同一内核地址空间中实现。 传统 UNIX 分为两大部分:系统程序与内核。

传统 UNIX 的系统调用接口、内核与硬件

图中系统调用接口以下、物理硬件以上的区域属于内核,包含文件系统、CPU 调度、内存管理、终端处理和设备驱动等功能。

特点影响
多种服务在同一内核空间运行内部函数间交互直接,通信开销较低
功能联系紧密修改某一部分可能影响其他部分
大量代码具有内核权限错误的影响范围可能较大

Linux 既具有单体内核的特点,也支持可加载模块。

Microkernel System Structure#

微内核(Microkernel) 尽量缩小内核中的功能集合,将其他服务放入用户空间,并通过消息传递协作。

留在内核的核心机制可以移到用户空间的服务
基本调度、内存管理、通信等文件服务、部分设备服务等,具体划分依系统设计而定

Example#

Application Reading a File

微内核中的文件请求怎样完成?
用户态应用提出文件请求
↓ 消息
微内核提供通信机制
↓
用户态文件服务处理请求
↓ 消息回复
应用取得结果

与多个服务都处于同一内核空间的组织相比,这里需要协调不同服务进程。 一次完整的文件服务,可能包含多次跨边界交互。 但具体消息数和切换次数依实现而定。

Modules#

可加载内核模块(Loadable Kernel Module) 将内核的一部分功能组织成独立组件,按需要在启动时或运行期间加载。各组件通过已定义的接口协作。

这具有面向对象思想:核心组件各自封装功能,通过接口联系。

与其他结构比较区别
与严格分层比较模块之间可按接口直接调用,依赖关系更灵活
与典型微内核比较内核模块仍在内核空间工作,模块间可直接调用,不必统一通过跨进程消息传递
与固定编入全部功能比较可以把不常用、当前不需要的功能留在磁盘,需要时再加载

Example#

On-Demand Loading of Device Drivers and Game Resources

为什么无需把所有功能都在开机时装入内存?

机器当前没有使用这些设备时,对应功能可以不必一开始就全部驻留。需要支持相应设备时,再通过模块机制提供功能。

磁盘中保存的全部内容,不要求同时驻留内存。

Solaris Modular Approach#

Solaris 核心内核与不同类型的可加载模块

图中核心 Solaris 内核周围包含:调度类、文件系统、可加载系统调用、可执行文件格式、STREAMS 模块、其他模块,以及设备与总线驱动。

Mac OS X Structure#

混合结构(Hybrid Structure) 结合不同组织方式,在性能、功能、可维护性和隔离之间进行取舍。

Mac OS X 的 Mach、BSD 与上层应用环境

上部是应用环境与公共服务,下部的内核环境结合 Mach 与 BSD。 Mach 提供调度、内存和通信等基础能力,BSD 提供相应 UNIX/POSIX 功能;为了减少跨地址空间的开销,相关内核组成部分被结合到同一地址空间中。

Comparison of Operating-System Structures#

结构核心组织方式主要收益主要限制
简单结构功能边界与接口分离较弱在有限条件下实现功能耦合强,维护和保护困难
分层结构按较低层到较高层组织依赖便于分工、理解、逐层验证层次难划分,逐层交互有开销
单体内核多种服务在同一内核空间实现内部交互直接、效率较高功能联系紧密,错误影响可能较广
微内核核心保留基本机制,其他服务移至用户空间有利于隔离、扩展、移植服务间通信与切换有开销
可加载模块核心加按需加载的组件灵活扩展并减少不必要的驻留内核模块仍具有较高权限
混合结构结合不同组织方式根据目标综合取舍实际结构复杂,需具体分析

这些分类并非全部互斥。 例如,单体内核可以具有模块化设计;一个系统也可以局部采用分层,在其他部分使用不同组织方式。

Virtual Machines#

Virtual Machine Abstraction#

虚拟机(Virtual Machine,VM) 把一台物理计算机的硬件能力抽象成多个执行环境。每个环境可运行自己的操作系统,使其中的程序看到独立的处理器、内存和设备视图。

虚拟机监控器(Virtual Machine Monitor,VMM,也称 Hypervisor) 负责建立、运行和管理这些虚拟机,并协调它们对真实资源的使用。

非虚拟机系统与多个虚拟机的结构对比

左图是一套内核管理多个进程;右图在物理硬件之上加入虚拟化实现,每个虚拟机拥有自己的内核和进程。

分时系统为进程提供执行机会,系统虚拟机进一步提供可供客户操作系统使用的机器环境。

How the Illusion of a Dedicated Machine Is Created#

虚拟化的基本思想:

物理资源向上提供的表象或功能
CPU通过调度,使不同环境获得自己的执行机会
内存向各环境提供其可使用的内存视图
文件系统与假脱机提供虚拟读卡机、虚拟行式打印机
分时终端作为虚拟机操作者的控制台

这些虚拟资源最终仍需映射到物理资源。逻辑上独立,物理硬件数量不增加。

VMware Architecture#

宿主操作系统、虚拟化层与多个客户操作系统

上面运行虚拟化层,再运行 FreeBSD、Windows NT、Windows XP 等客户系统。每个客户系统有自己的虚拟 CPU、虚拟内存和虚拟设备;宿主系统也可以直接运行本地应用。

名称含义
宿主操作系统(Host OS)运行虚拟化软件并管理底层资源的系统
客户操作系统(Guest OS)虚拟机内部运行的系统
虚拟化层为客户系统提供虚拟硬件环境并协调资源

Example#

用户看到的是分配给虚拟环境的资源配置,背后可能与其他虚拟机共享同一物理服务器。

为什么 10 台物理机器可能承载更多份逻辑资源需求?

有 10 台物理机器,但提供给用户的虚拟资源需求合计达到约 20 台机器的计算能力。

前提是:不同用户不会一直同时满载。当实际需求错峰出现时,系统能让同一份硬件服务更多用户;当需求同时升高时,就可能发生资源竞争,实际性能受到影响。

Isolation, Development, Testing, and Sharing#

用途或特征价值
隔离将不同客户系统的执行环境分开,减少直接干扰
系统开发与测试在虚拟环境中修改、运行和测试系统,尽量避免破坏主力环境
多系统共存在同一物理机器上使用不同系统进行开发或兼容性测试
资源整合把多台低负载系统的工作合并到较少的物理机器上

虚拟化系统还可以提供暂停、快照、克隆等功能,使测试前保存状态、失败后恢复更加方便。

虚拟机相互隔离,是否就完全无法通信或共享文件?

可以通过受控的共享文件系统或虚拟网络提供共享和通信。

Why Virtual Machines Are Difficult to Implement#

客户系统既要看到类似真实硬件的接口,又不能不受限制地控制整台物理机器。 虚拟用户态与虚拟内核态也要得到正确处理。

客户内核想执行特权操作时怎么办?

在一种传统双模式模型中,客户系统的“虚拟内核态”仍不能等同于物理机器的最高管理权限。客户内核执行受保护操作时,可以由 VMM 获得控制,再代表客户执行或模拟相应效果。

这样,客户系统保留“自己有内核”的视图,真实资源仍由虚拟化层管理。

若客户程序使用与宿主不同的 CPU 指令集,还涉及指令集模拟。

VM vs Docker#

容器(Container) 为应用及其依赖提供相对独立的运行环境。 系统虚拟机通常各有客户内核;图中的多个容器共享运行它们的宿主内核。

比较项系统虚拟机图中的容器模型
提供的环境虚拟硬件及其上的操作系统应用、库、配置等运行环境
内核每个客户系统有自己的内核多个容器共享同一个宿主内核
不同系统的支持可以承载不同客户系统,需满足虚拟化或模拟条件应用仍受共享内核及接口兼容性的约束
资源开销还要维护客户系统本身通常更轻量,无需每个容器各运行一套客户内核
隔离边界以虚拟机器环境为边界以操作系统提供的容器隔离机制为基础
典型用途运行不同系统、系统开发与测试分离应用环境、管理依赖与部署

Example:#

Dependency Conflicts Between Two Model Projects

人脸识别项目与卫星图像项目需要不同版本的依赖,怎么办?

项目 A 使用 PyTorch 2.1 一类的新依赖,项目 B 依赖 1.x 一类的旧版本。如果整个机器只维护一套全局环境,修改版本来满足 B,就可能破坏 A。

可分别组织环境:

项目 A → 环境 A → A 所需的软件、库、配置
项目 B → 环境 B → B 所需的软件、库、配置

重点在于把应用的依赖集合分开管理。容器可以保存更完整的应用运行环境;

在 Mac 上使用 Docker,容器里就是另一套 macOS 吗?

Docker Desktop 的官方文档说明,它通过 Linux 虚拟机运行 Linux 容器;在 Mac 上使用这种方式时,可以概括为:

macOS → Linux 虚拟机 → Docker Engine → 多个 Linux 容器

容器共享的是这台 Linux 虚拟机的内核。虚拟机和容器可以组合使用,所以“容器共享内核”与“Docker Desktop 内部使用虚拟机”并不矛盾。

The Java Virtual Machine#

Java 虚拟机(Java Virtual Machine,JVM) 提供的是程序语言的执行环境。它接受 Java 字节码,并由对应平台上的实现完成执行。

Java 源程序
↓ javac 编译
字节码 .class 文件
↓ 类加载与执行
对应平台上的 JVM
↓
宿主操作系统与硬件
Java 字节码、类加载器、解释器与宿主系统

图中,类加载器(Class Loader) 装入程序和 Java API 的 .class 文件,再交给执行部分处理,没有直接产生特定 CPU 的本地机器码。不同平台具有对应的 JVM 实现,字节码与统一接口使程序不必直接处理全部平台差异。

JVM 只会逐条解释执行吗?

即时编译(Just-in-Time Compilation,JIT):把相应字节码编译为宿主机器的本地指令,再使用编译后的代码执行。

字节码验证:检查装入的字节码是否满足相应要求

垃圾回收:回收不再使用的对象所占空间。

因此,“Java 编译为字节码”与“运行时还可以编译成本地代码”属于不同阶段,可以同时成立。跨平台也需要相应 JVM 与依赖支持,不是完全不受版本和外部库约束。

Distinguishing Different Virtual Environments#

对象主要提供什么?是否各有独立客户内核?
系统虚拟机虚拟硬件上的操作系统环境通常有
容器共享宿主内核上的应用环境通常没有
JVMJava 字节码与语言运行时环境没有
Python 虚拟环境独立的 Python 包及相关环境配置没有

Operating System Generation#

Configuring the System for Specific Hardware#

系统生成(System Generation,SYSGEN) 关注怎样使一套操作系统适合具体机器。操作系统可能面向一类机器设计,但实际机器的设备与配置各不相同,需要取得相应硬件信息并选择功能。

取得或编写操作系统代码
↓
根据目标硬件与需求进行配置
↓
生成相应系统:编译或选择、链接模块等
↓
安装到可供启动的存储环境
↓
启动系统

SYSGEN 表示配置与生成系统的概念;

Three Operating-System Generation Approaches#

方式何时选择所需功能?取舍
按配置重新编译编译时定制程度高,但生成工作较多
选择已有目标模块再链接链接时不必全部重新编译,但灵活性受模块划分影响
模块化系统按参数选择功能运行时便于适应配置变化,需要相应动态机制

系统生成决定系统包含什么、怎样适应机器;系统启动决定机器如何找到并开始执行它。

System Boot#

Where the Initial Boot Code Comes From#

计算机刚上电时,操作系统还没有运行,因此不能依赖已经存在的操作系统服务来装入自己。

引导程序(Bootstrap Program/Boot Loader) 负责找到内核、把它装入内存,并开始执行。

上电后执行从硬件规定的位置开始,初始启动代码保存在固件(Firmware) 中。

上电或复位
↓
从规定入口执行固件中的初始代码
↓
初始化必要状态,找到后续引导代码
↓
找到内核映像,装入内存
↓
开始执行内核
↓
内核继续初始化、建立文件系统与服务环境

Multistage Boot Process#

一种分阶段方式:位于固定存储位置的引导块(Boot Block) 先装入更完整的引导程序,再由它装入内核。

初始代码空间有限,因此可以逐步装入功能更完整的后续代码。

对象主要职责
固件中的初始代码在操作系统运行前开始执行,准备并定位后续启动阶段
引导块中的代码在相应启动方案中,衔接到后续引导程序
引导程序定位、装入并启动内核
内核映像(Kernel Image)内核本身的可装入表示
已运行的内核完成后续初始化并建立系统服务
引导扇区、MBR 与内核映像

引导扇区/引导块中的代码负责启动衔接;内核映像保存内核代码。

AI Era#

Two Control Planes in the AI Era#

传统系统:应用 → API/系统调用 → 内核 → 硬件
智能体系统:用户意图
↓
LLM/智能体:理解目标并规划行动
↓
应用、工具、API
↓
内核:管理资源并执行受保护操作
↓
硬件

评论

Lazysheep