English
An operating system manages hardware resources and supplies protected interfaces to programs. A process has an execution context and an address space; files provide named access to stored data; system calls request services from the kernel. These abstractions allow programs to share a machine without each implementing device control and resource arbitration.
Three recurring problems organize the subject. Virtualization gives programs manageable views of shared resources. Concurrency coordinates work that can overlap. Persistence determines which changes survive a crash. Isolation and access control apply across all three. A process does not literally own a whole machine: scheduling delays, resource limits, and failures remain observable.
The concepts recur in databases, language runtimes, and distributed systems, but their assumptions change. A thread sharing memory with another thread and a service communicating over a network face different failure and ordering conditions. Learn the local mechanisms here, then examine those changed conditions in later sections.
Prerequisites are basic programming, address representations, interrupts, and data structures. Many teaching kernels use C; reading pointers and memory-management code helps, while a small process or file experiment can be understood before writing a kernel.
How the Operating System Was Invented
Operating-system development answered several needs: reducing manual setup between jobs, keeping expensive processors busy, supporting interactive users, and controlling access to shared resources. These goals were related but distinct. Batch processing improved job throughput; time-sharing made short interactive exchanges practical.
Early batch monitors automatically loaded the next job when the previous one finished. A resident monitor kept control software in memory, reducing the delay of manual handoff. IBM’s System/360 project later made compatibility across a machine family a major design objective; OS/360 exposed the difficulty of coordinating a large software project across diverse workloads. Brooks’s account in The Mythical Man-Month connects this engineering experience to software-project organization.
Multiprogramming let a processor work on another program while one waited for I/O. This required tracking execution state, scheduling work, and protecting programs from unintended interference. A process combines an execution context with resources such as an address space and open files. Its usefulness comes from making these responsibilities explicit, not from assuming that every process receives a dedicated machine.
Time-sharing adds an important distinction to this history. When a terminal user pauses to think or type, the computer can run work for other users. Systems such as CTSS and Multics pursued interactive response as well as utilization. Sharing an expensive machine changed who could use computing and how quickly they could revise a program. Scheduling became a question about human waiting time as well as processor efficiency.
Unix began at Bell Labs in 1969, with major contributions from Ken Thompson, Dennis Ritchie, and colleagues. It grew partly from experience with Multics while choosing a smaller initial design. File descriptors gave files, devices, and pipes a common interface for operations such as reading and writing, although each retained additional semantics. McIlroy advocated pipes, which became part of Unix in the early 1970s. Connecting one program’s output to another’s input made existing tools reusable in new combinations.
Rewriting much of Unix in C reduced the amount of architecture-specific assembly needed for a port. It did not make hardware differences disappear: boot code, interrupts, device drivers, and address translation still needed platform support. The combination of source availability, a relatively portable implementation, and useful interfaces helped Unix spread through universities and later commercial systems. Linux reimplemented Unix-like interfaces; other systems inherited different portions of the Unix and BSD code and design traditions.
Monolithic kernels place many services in one privileged address space; microkernels move more services into separately protected processes. Separating a driver can contain some software failures, but only if its capabilities, memory access, and device DMA are controlled appropriately. Communication across protection boundaries also has a cost. The Tanenbaum–Torvalds debate in 1992 made these tradeoffs widely visible; later designs illustrate several combinations rather than a universal victory for one arrangement.
Memory-corruption vulnerabilities made language and isolation choices security concerns. Stack canaries, non-executable pages, address randomization, and control-flow restrictions make particular attacks harder but do not repair every underlying bug. Memory-safe languages can prevent broad classes of invalid memory access in their safe code; unsafe operations, foreign interfaces, logic errors, and resource exhaustion still require analysis. Rust support entered the Linux kernel in 2022 as one incremental approach, not a replacement of the entire existing kernel.
The 2009 seL4 result established functional correctness of a microkernel implementation through refinement proofs in Isabelle/HOL. It related C code to an abstract specification through intermediate models, including an executable specification; it was not a proof of an entire deployed operating system or every security property. The seL4 proof overview distinguishes the properties, configurations, and assumptions covered by different proofs. Its historical importance is that substantial low-level software could be connected to a precise specification with machine-checked evidence.
Containers reuse mechanisms such as namespaces and control groups to isolate views and account for resources while sharing a kernel. eBPF adds a way to run restricted, verified programs at selected kernel hooks; the verifier and permitted helpers define important boundaries. io_uring uses shared queues and batching to reduce some I/O submission and completion costs. None of these mechanisms removes scheduling, validation, isolation, or device costs. They adapt earlier resource-management ideas to new workloads.
Virtualization, Concurrency, and Persistence
Follow a program through three events: it waits for a processor, it accesses memory shared or protected by the OS, and it asks to save a result. Each event has a policy question—who should receive the resource—and a mechanism question—how the decision is enforced. Keeping policy and mechanism distinct makes alternative designs easier to compare.
Virtualization: Controlled Access to Shared Resources
A scheduler selects runnable threads or processes. A context switch saves execution state and restores another context; caches and address-translation structures can make its cost extend beyond saving registers. Time slicing improves responsiveness, while scheduling policies also consider throughput, fairness, and deadlines. Giving a short interactive task prompt service and finishing a long batch job quickly are different objectives. Programs can observe scheduling delays, so virtualization provides a controlled execution interface rather than perfect concealment of sharing.
Virtual memory separates a process’s addresses from physical storage. Page tables and the MMU translate addresses and check permissions; a TLB caches recent translations. Two processes can use the same virtual address while referring to different physical frames, or intentionally map shared frames. A page fault may trigger demand allocation, loading from a file, or copy-on-write; an invalid address or forbidden access may instead terminate the operation or process. Virtual memory is useful even without swapping because it supports isolation, sharing, and flexible placement. A large address space does not create free physical capacity.
Hardware privilege and memory protection restrict what user-mode code can do directly. System calls provide controlled entry to kernel operations such as opening a file or changing mappings. A library call need not make a system call: an allocator can satisfy a request from memory it already manages. The interface specifies operations and error behavior while allowing implementation choices behind it.
Concurrency: Shared State and Coordination
Concurrency means that operations can overlap; parallelism means they execute simultaneously. Even one core can interleave two threads between a read and a write. If both read counter value zero and then write one, an increment is lost. The requirement is to protect a state transition or invariant, not merely individual source-code lines.
A mutex gives one thread exclusive access to a protected region. Every access that participates in the invariant must follow the same protocol. Two threads can deadlock if each holds a lock the other needs. A consistent lock order prevents that particular cycle; timeouts require a recovery policy and do not automatically guarantee progress. Short critical sections can reduce contention, but correctness determines which operations must stay together.
A condition variable lets a thread wait for a predicate while releasing the associated mutex. A consumer checks whether the queue is nonempty, waits if necessary, then reacquires the mutex and checks again. The loop matters because another consumer may take the item first, and some interfaces permit spurious wakeups. The notification is not a stored promise that the condition remains true.
Semaphores count available permits; message passing can transfer ownership instead of sharing mutable state. Lock-free algorithms guarantee system-wide progress under their model, while an individual thread may still starve; wait-free algorithms impose a stronger per-operation guarantee. Atomic instructions alone do not establish either property. Memory ordering, safe reclamation of removed nodes, and the ABA problem remain part of the proof. Choose the simplest protocol that establishes the required invariant and progress condition.
Persistence: The Border Between Volatile and Durable
Persistence requires distinguishing acceptance by an API from durability on storage. A successful buffered write can leave data only in volatile memory. Crash consistency asks whether recovered structures obey their invariants; durability asks which acknowledged changes survive. The exact guarantee depends on synchronization calls, write ordering, file-system behavior, and the storage device. An atomic rename alone does not guarantee that all associated data survives power loss.
Filesystems without a suitable crash-consistency mechanism can leave metadata inconsistent after interrupted updates. A checker such as fsck scans structures and repairs some inconsistencies, but cannot generally reconstruct the user’s intended multi-file operation or recover data never persisted. Journaling and copy-on-write reduce some recovery work by organizing which states can become durable.
Journaling records enough information to recover a group of updates. Recovery recognizes committed records and ignores incomplete ones, using a format and ordering protocol designed for possible partial writes. It does not assume that an arbitrarily large journal write is atomic. Some modes journal metadata while file data follows a different policy. The write-ahead principle is that the recovery information becomes durable before the updates that depend on it; file-system details determine how this principle is implemented.
Copy-on-write designs such as ZFS, Btrfs, and APFS write updated structures to new locations before publishing a new reference to them. Recovery must select a valid committed version; this still requires correct ordering and a recoverable commit record. Retaining old references can support snapshots. Checksums, deduplication, and repair are additional design choices rather than automatic consequences of copy-on-write. A snapshot on the same failing device also does not replace an independent backup.
SSDs remove mechanical seeking, but access pattern and write size still matter. Flash is programmed and erased at different granularities, and the device controller performs mapping, garbage collection, and wear management. Small writes can therefore cause more physical writing than their logical size suggests. Storage-aware design now considers queue depth, write amplification, bandwidth, and latency variation as well as sequentiality.
What Studying This Changes
OS knowledge helps you connect a program symptom to a mechanism. A memory allocation often uses a user-space allocator without a system call; an uncontended lock may complete without scheduling another thread; a file read may hit a cache or wait on storage. Trace the actual path before assigning a cost.
Use two small experiments. First, give two processes the same virtual address and inspect their mappings to distinguish address names from shared physical data. Second, write a file and terminate the process at selected points to observe which results the API and file system promise. A process crash experiment alone does not simulate every power-loss behavior.
For performance, separate CPU work from waiting and contention. CPU-bound work can benefit from more cores if it is parallelizable. Additional threads can overlap I/O waits, but too many can increase memory use, queueing, and lock contention. Match concurrency to the available resources and measure the result.
Resources
Books and Texts
Operating Systems: Three Easy Pieces explains virtualization, concurrency, and persistence through the problems each mechanism solves. Pair selected chapters with xv6, a small teaching kernel, and its accompanying book. Linux Kernel Development provides a historical Linux implementation perspective; its 2010 edition is not a specification of present kernels and did not predate every form of container. Gregg’s Systems Performance develops measurement and diagnosis. APUE and The Linux Programming Interface serve as system-programming references.
| Book | Role | Type |
|---|---|---|
| Arpaci-Dusseau, Operating Systems: Three Easy Pieces (free) | Virtualization, concurrency, and persistence | Entry |
| MIT 6.S081 + xv6 codebase (free) | Teaching-kernel implementation exercises | Practice |
| Robert Love, Linux Kernel Development (3rd ed.) | Bridge from teaching kernel to production kernel | Depth |
| Brendan Gregg, Systems Performance (2nd ed.) | Performance analysis at the systems level | Depth |
| Stevens & Rago, Advanced Programming in the UNIX Environment | Unix system programming reference | Reference |
| Kerrisk, The Linux Programming Interface (paid) | Linux system programming reference | Reference |
| Tanenbaum & Bos, Modern Operating Systems | Comprehensive reference; broader than OSTEP | Reference |
Courses and Lectures
MIT’s xv6 course has appeared under 6.S081 and 6.1810; the 6.1810 materials describe an undergraduate course with implementation labs. Match the source, book, and assignments to the selected offering. Berkeley CS 162 provides another route through processes, synchronization, memory, and filesystems. Implementing and explaining a mechanism are complementary activities, not a competition over a fixed number of lab hours.
| Course | Platform | Type |
|---|---|---|
| MIT 6.S081 Operating System Engineering (free) | MIT / YouTube | Entry |
| Berkeley CS 162 Operating Systems (free) | YouTube | Entry |
Practice, Tools, and Current Sources
strace shows system-call activity; perf combines supported hardware and software events; BPF tools observe selected kernel and application events. Tracing itself can perturb execution, and eBPF overhead depends on event rate and program work. Begin with one question, such as whether a process is blocked on I/O or consuming CPU. Kernel documentation, manual pages, and LWN help connect the observation to the implementation and its version.
| Resource | Platform | Type |
|---|---|---|
| strace (free) | Linux | Practice |
| Linux perf for OS tracing (free) | Linux kernel tools | Practice |
| Brendan Gregg BPF scripts and bpftrace (free) | GitHub / brendangregg.com | Practice |
| Linux Kernel Documentation (free) | docs.kernel.org | Reference |
| man7.org Linux man-pages (free) | man7.org | Reference |
| OSDev Wiki (free) | wiki.osdev.org | Auxiliary |
| LWN.net (free access after delay) | lwn.net | Reference |
Traps
| Trap | Why it misleads | Better response |
|---|---|---|
| Reading without observing execution | A mechanism description leaves implementation and failure behavior untested. | Combine reading with a small xv6 exercise or an experiment tracing a real process. |
| Memorizing mechanisms without their purpose | Names do not explain which invariant a mechanism maintains. | For a page table, lock, or journal, explain the problem and follow one normal and one failing operation. |
| Treating a textbook as a kernel specification | A teaching model or older implementation can differ from the system in use. | Pair conceptual reading with documentation for the relevant kernel version. |
| Equating successful stress tests with concurrency correctness | Testing samples schedules and may miss rare ordering violations. | State shared invariants, review synchronization, and use tests and analysis as complementary evidence. |
| Treating isolation as one switch | Address protection, permissions, resource limits, and side channels concern different properties. | Identify the protection boundary and the mechanism that enforces each property. |
| Assuming containers remove OS behavior | Containers still depend on scheduling, memory, filesystems, and the host kernel. | Trace an OOM event or latency problem through container settings to operating-system evidence. |
中文
操作系统管理硬件资源,并向程序提供受保护的接口。进程具有执行上下文和地址空间,文件提供对存储数据的命名访问,系统调用向内核请求服务。这些抽象让程序共享机器,而不必各自实现设备控制与资源分配。
三个反复出现的问题组织这一主题。虚拟化为程序提供便于使用的共享资源视图;并发协调可以重叠的工作;持久化决定哪些改变能在崩溃后保留。隔离与访问控制贯穿三者。进程并不真正独占整台机器,调度延迟、资源限制和故障仍然可见。
这些概念会在数据库、语言运行时和分布式系统中重复出现,但假设会改变。共享内存的两个线程,与通过网络通信的两个服务,面对不同的失败和顺序条件。先在这里学习本机机制,再在后续小节考察条件的变化。
先修基础包括基本编程、地址表示、中断与数据结构。许多教学内核使用 C,读懂指针和内存管理代码会有帮助;但在编写内核之前,就可以理解小型进程或文件实验。
操作系统是如何被发明出来的
操作系统的发展回应了多种需要:减少任务之间的人工准备、使昂贵处理器保持忙碌、支持交互用户,以及控制共享资源的访问。这些目标相关却不同。批处理改善任务吞吐量,分时则使短时间的交互往返变得可行。
早期批处理监控程序在前一任务完成后自动装入下一任务。驻留监控程序把控制软件保存在内存中,减少人工交接的延迟。IBM 的 System/360 项目随后把机器家族之间的兼容性作为重要目标;OS/360 暴露了围绕多种工作负载协调大型软件项目的困难。Brooks 在 The Mythical Man-Month 中把这段工程经验连接到软件项目组织。
多道程序设计让处理器在一个程序等待 I/O 时,转而执行另一个程序。这需要跟踪执行状态、调度工作并防止程序间意外干扰。进程把执行上下文与地址空间、打开文件等资源结合起来。它的用途在于明确这些责任,而不是假设每个进程得到一台专用机器。
分时为这段历史增加了一个重要区别。终端用户停下来思考或打字时,计算机可以为其他用户工作。CTSS、Multics 等系统既追求利用率,也追求交互响应。共享昂贵机器改变了谁能使用计算,以及修改程序后多久可以获得反馈。因此,调度不仅关乎处理器效率,也关乎人的等待时间。
Unix 于 1969 年在 Bell Labs 起步,Ken Thompson、Dennis Ritchie 及同事作出了主要贡献。它部分继承了 Multics 的经验,同时选择了较小的初始设计。文件描述符为文件、设备与管道提供读写等操作的共同接口,不过各自仍有额外语义。McIlroy 提倡的管道在七十年代初进入 Unix。把一个程序的输出连接到另一个程序的输入,使已有工具能够用于新的组合。
用 C 重写 Unix 的大部分代码,减少了移植所需的架构专用汇编,但没有消除硬件差异:启动代码、中断、设备驱动与地址转换仍需平台支持。源码的可获得性、较易移植的实现和实用接口,共同帮助 Unix 在大学及后来的商业系统中传播。Linux 重新实现了类 Unix 接口;其他系统则以不同方式继承了 Unix、BSD 的代码与设计传统。
宏内核把许多服务放在同一个特权地址空间中,微内核则把更多服务移到分别受保护的进程中。分离驱动可以限制部分软件故障的影响,但前提是适当控制其权限、内存访问与设备 DMA。跨保护边界通信也有成本。1992 年的 Tanenbaum–Torvalds 争论使这些取舍广为人知;后来的设计展示了多种组合,并没有一种安排在所有情形下都占优。
内存损坏漏洞使语言与隔离选择成为安全问题。栈保护值、不可执行页、地址随机化和控制流限制,提高了特定攻击的难度,却不能修复所有底层缺陷。内存安全语言可以在安全代码中排除大类无效内存访问;不安全操作、外部接口、逻辑错误与资源耗尽仍需分析。Rust 支持在 2022 年进入 Linux 内核,属于渐进方法之一,并非替换整个既有内核。
2009 年的 seL4 结果通过 Isabelle/HOL 中的精化证明,建立了微内核实现的功能正确性。它经过包括可执行规约在内的中间模型,把 C 代码连接到抽象规约,并不是证明整个部署中的操作系统或每一项安全性质。seL4 证明概述区分了不同证明覆盖的性质、配置与假设。其历史意义在于,规模可观的底层软件能够通过机器检查的证据连接到精确规约。
容器复用命名空间和控制组等机制,在共享内核的同时隔离视图、统计和限制资源。eBPF 允许在选定内核挂钩上运行受限且经过检查的程序,验证器与允许的辅助接口构成重要边界。io_uring 使用共享队列和批处理,减少部分 I/O 提交与完成成本。这些机制都没有消除调度、检查、隔离或设备成本,而是把较早的资源管理思想用于新工作负载。
虚拟化、并发与持久化
可以跟踪程序的三个事件:等待处理器,访问由操作系统共享或保护的内存,以及请求保存结果。每个事件都包含策略问题——谁应获得资源,以及机制问题——怎样落实决定。区分策略与机制,有助于比较不同设计。
虚拟化:对共享资源的受控访问
调度器选择可运行的线程或进程。上下文切换保存执行状态并恢复另一个上下文,缓存与地址转换结构会使成本超出保存寄存器本身。时间片改善响应性,调度策略还需要考虑吞吐量、公平性与截止时间。及时服务短交互任务,与尽快完成长批处理任务,是不同目标。程序能够观察到调度延迟,因此虚拟化提供受控的执行接口,并非完全隐藏共享。
虚拟内存把进程使用的地址与物理存储分开。页表与 MMU 完成地址转换和权限检查,TLB 缓存最近的转换。两个进程可以使用相同虚拟地址却引用不同物理页框,也可以有意映射共享页框。缺页可能触发按需分配、从文件装入或写时复制;无效地址或禁止访问则可能使操作或进程终止。即使不进行换页,虚拟内存仍可用于隔离、共享与灵活放置。较大地址空间不会凭空产生物理容量。
硬件权限与内存保护限制用户态代码可以直接做什么。系统调用提供进入内核操作的受控入口,例如打开文件或改变映射。库调用不一定发起系统调用:分配器可以从自己已经管理的内存中满足请求。接口规定操作与错误行为,同时允许其后存在不同实现选择。
并发:共享状态与协调
并发表示操作可以重叠,并行表示操作同时执行。即使只有一个核心,也能在读取与写回之间交错两个线程。如果二者都读到计数器为零,再分别写入一,就会丢失一次递增。需要保护的是状态转换或不变式,而不只是单独的源代码行。
互斥锁使一个线程独占受保护区域。参与同一不变式的访问都必须遵守相同协议。如果两个线程各自持有对方需要的锁,就可能死锁。一致的加锁顺序可以阻止这种循环;超时需要恢复策略,并不自动保证进展。缩短临界区可以减少争用,但哪些操作必须放在一起,首先由正确性决定。
条件变量让线程在释放关联互斥锁的同时,等待某个谓词成立。消费者检查队列是否非空,必要时等待,再重新取得锁并再次检查。使用循环很重要,因为另一个消费者可能先取走元素,一些接口也允许伪唤醒。通知不是一个可以保存的、保证条件持续为真的承诺。
信号量计算可用许可,消息传递则可以转移所有权,避免共享可变状态。无锁算法在其模型下保证系统整体进展,但单个线程仍可能饥饿;无等待算法提供更强的逐操作保证。仅使用原子指令,不能建立这两种性质。内存顺序、已删除节点的安全回收,以及 ABA 问题仍是证明的一部分。应选择能够建立所需不变式和进展条件的最简单协议。
持久化:易失与持久之间的边界
持久化需要区分 API 接受操作与数据已在存储上持久保存。一次成功的缓冲写入,可能只把数据放进易失内存。崩溃一致性询问恢复后的结构是否满足不变式,持久性则询问哪些已确认改变能够保留。确切保证依赖同步调用、写入顺序、文件系统行为与存储设备。仅有原子重命名,不保证所有关联数据都能在断电后保留。
缺少适当崩溃一致性机制的文件系统,可能在更新中断后留下不一致元数据。fsck 等检查器扫描结构并修复部分不一致,但一般不能重建用户原本想完成的多文件操作,也不能找回从未持久化的数据。日志与写时复制通过组织哪些状态可以变得持久,减少部分恢复工作。
日志记录恢复一组更新所需的信息。恢复程序使用针对可能部分写入而设计的格式和顺序协议,识别已提交记录并忽略未完成记录,而不是假设任意大的日志写入都具有原子性。一些模式只记录元数据日志,文件数据采用另一种策略。预写原则要求恢复信息先于依赖它的更新持久化,具体文件系统决定怎样落实这个原则。
ZFS、Btrfs、APFS 等写时复制设计,先把更新后的结构写到新位置,再发布指向它们的新引用。恢复需要选出有效的已提交版本,仍然依赖正确顺序与可恢复的提交记录。保留旧引用可以支持快照。校验和、去重与修复是额外设计选择,并非写时复制的自动结果。同一故障设备上的快照,也不能代替独立备份。
SSD 消除了机械寻道,但访问模式与写入大小仍然重要。闪存的编程与擦除粒度不同,设备控制器需要执行映射、垃圾回收与磨损管理。因此,小写入可能引起远大于其逻辑大小的物理写入。面向存储的设计除了顺序性,还需考虑队列深度、写放大、带宽与延迟波动。
学习这一部分会改变什么
操作系统知识帮助把程序现象连接到机制。内存分配常常只经过用户态分配器,不发生系统调用;没有争用的锁可能无需调度其他线程就能完成;文件读取可能命中缓存,也可能等待存储。应先跟踪实际路径,再判断成本。
可以做两个小实验。先让两个进程使用相同虚拟地址,检查映射,区分地址名称与共享物理数据。再写入文件,在选定位置终止进程,观察 API 与文件系统承诺哪些结果。仅终止进程的实验,并不模拟每一种断电行为。
分析性能时,分开考虑 CPU 工作、等待与争用。可并行的 CPU 密集工作能从更多核心获益;增加线程可以重叠 I/O 等待,但过多线程也会增加内存使用、排队与锁争用。应使并发度匹配可用资源,并测量结果。
资源
书籍与文本
Operating Systems: Three Easy Pieces 从机制所解决的问题解释虚拟化、并发与持久性。可以把选定章节与小型教学内核 xv6 及其配套书结合。Linux Kernel Development 提供较早 Linux 实现的视角;2010 年版不是现有内核规范,也并非早于所有形式的容器。Gregg 的 Systems Performance 展开测量与诊断,APUE 与 The Linux Programming Interface 可作为系统编程参考。
| 书籍 | 作用 | 类型 |
|---|---|---|
| Arpaci-Dusseau, Operating Systems: Three Easy Pieces(免费) | 虚拟化、并发与持久性 | 入门 |
| MIT 6.S081 + xv6 codebase(免费) | 教学内核实现练习 | 实践 |
| Robert Love, Linux Kernel Development(第 3 版) | 从教学内核过渡到生产内核 | 深入 |
| Brendan Gregg, Systems Performance(第 2 版) | 系统层面性能分析 | 深入 |
| Stevens & Rago, Advanced Programming in the UNIX Environment | Unix 系统编程参考 | 参考 |
| Kerrisk, The Linux Programming Interface(付费) | Linux 系统编程参考 | 参考 |
| Tanenbaum & Bos, Modern Operating Systems | 综合参考;比 OSTEP 更宽 | 参考 |
课程与讲座
MIT 的 xv6 课程曾使用 6.S081 与 6.1810 编号;6.1810 材料介绍了带实现实验的本科课程。应使源码、书籍与作业匹配选定学期。Berkeley CS 162 提供另一条学习进程、同步、内存与文件系统的路径。实现与解释机制相互补充,不应以固定实验小时数衡量学习。
| 课程 | 平台 | 类型 |
|---|---|---|
| MIT 6.S081 Operating System Engineering(免费) | MIT / YouTube | 入门 |
| Berkeley CS 162 Operating Systems(免费) | YouTube | 入门 |
实践、工具与当前资料
strace 显示系统调用活动,perf 结合受支持的硬件与软件事件,BPF 工具观察选定的内核和应用事件。追踪本身可能扰动执行,eBPF 开销依赖事件速率与程序工作量。可以从一个问题开始,例如进程是在等待 I/O,还是在消耗 CPU。内核文档、手册页与 LWN 帮助把观察连接到相应实现和版本。
| 资源 | 平台 | 类型 |
|---|---|---|
| strace(免费) | Linux | 实践 |
| Linux perf for OS tracing(免费) | Linux kernel tools | 实践 |
| Brendan Gregg BPF scripts and bpftrace(免费) | GitHub / brendangregg.com | 实践 |
| Linux Kernel Documentation(免费) | docs.kernel.org | 参考 |
| man7.org Linux man-pages(免费) | man7.org | 参考 |
| OSDev Wiki(免费) | wiki.osdev.org | 辅助 |
| LWN.net(延迟后免费访问) | lwn.net | 参考 |
陷阱
| 误区 | 为什么会误导 | 更好的做法 |
|---|---|---|
| 只阅读而不观察执行 | 机制描述没有检验实现与失败行为。 | 把阅读与小型 xv6 练习或真实进程追踪实验结合。 |
| 只记机制,不问用途 | 名称不能说明机制维持什么不变式。 | 对页表、锁或日志,解释问题,并跟踪一次正常操作与一次失败操作。 |
| 把教材当作内核规范 | 教学模型或较早实现可以不同于正在使用的系统。 | 把概念阅读与相关内核版本的文档结合。 |
| 把压力测试通过等同于并发正确 | 测试抽样执行顺序,可能漏掉罕见的顺序违规。 | 说明共享不变式,审查同步,把测试与分析作为互补证据。 |
| 把隔离当作一个开关 | 地址保护、权限、资源限制与侧信道涉及不同性质。 | 确定保护边界,以及每项性质由什么机制执行。 |
| 认为容器消除了操作系统行为 | 容器仍依赖调度、内存、文件系统与宿主机内核。 | 从容器设置追踪内存不足或延迟问题,找到操作系统层证据。 |