南京大学 操作系统 (JYY) 学习笔记:从 UNIX 到 Linux 与庞大的应用生态

写在前面:这是本系列的第十二篇。

我们已经了解了一个典型的操作系统给应用程序提供的“一切”:从操作系统为应用程序提供的对象和 API,到可执行文件的链接和加载。操作系统在底层实现了管理硬件资源的极致机制,这是狭义“操作系统”负责的功能。

本讲内容:一个真实的“操作系统”,究竟是如何使用这些底层的对象和 API,一步步构建出今天如此丰富繁荣的应用生态的?在这个过程中,又有哪些不可思议的实用工具起到了决定性的作用?

课前反思:操作系统的对象

回顾:操作系统的对象

  • 进程:进程 = 状态机。进程管理 API:fork,execve,exit
  • 连续的内存段:可以被看作一个能在进程间共享、或映射到文件的对象。内存管理 API:mmap,munmap,mprotect,msync

从 UNIX 到 Linux 的历史回眸

Evolution of the UNIX Time Sharing System

  • 最早的 UNIX 版本甚至没有fork
    1. Shell 首先关闭所有打开的文件,然后为 0, 1 fd 打开终端文件(当时的系统是单进程的,根本不支持多进程)。
    2. 从终端读取命令行。
    3. 打开文件,把加载器代码复制到内存并执行 (相当于后来的exec)。当时还没有fork()这个概念。
    4. exit会重新把 shell 加载回内存。

Takeaway message: 不要害怕“不好”,大胆去做,并且持续改进!

  • 先做一堆“垃圾”出来。
  • 你见过的所有伟大系统,一开始都只是几个大学生、几个好朋友在车库里组队搞出来的破烂代码……
  • 但只要方向对了,最后就成功了。从零开始做,比什么都重要。早期版本的操作系统其实和我们的 MiniLab 极其相似。

Minix:年轻人的第一个操作系统

  • Minix1 (1987):UNIX v7 兼容。它是 Linus 后来实现 Linux 的起点。同时兼容 16-bit 和 32-bit,向世界真实展示了 16-bit 编程。
  • Minix2 (1997):POSIX 兼容。作为微内核教学系统,源代码很简单,甚至可以直接印在书的附录里随书附赠。这时候它已经能实现现代 OS 的常用功能了。(Tips:Minix 是 32 位的,意味着sizeof(int) == 2在某些老编译器下的历史包袱)
  • Minix3 (2006):POSIX/NetBSD 兼容。甚至一度是世界上应用最广的操作系统(因为被 Intel ME 偷偷内置在无数主板底层里)。

August, 1991: The Birthday of Linux

“Hello, everybody out there using minix – I’m doing a (free) operating system (just a hobby, won’t be big and professional like gnu) for 386(486) AT clones. This has been brewing since April, and is starting to get ready.”

—— Linus Torvalds (时年 21 岁)

  • 这封邮件类似于在说:“我写了个加强版的 OSLab,现在发到贴吧(comp.os.minix)与大家分享”。
  • 因为从零开始写全套工具是不现实的,所以早期的 Linux 还依赖 Minix 的工具链,跑的都是 GNU 的程序:gcc,bash
  • 天时地利人和:刚好踩中了 386(486) 的时间点,计算机硬件恰好在那时开始起飞。

“Just for fun”

“Revolutionaries aren’t born. Revolutions can’t be planned. Revolutions can’t be managed. Revolutions happen…”

—— David Diamond (《Just for Fun》作者)

  • 对比当前教育部的政策(加强高校有组织科研、新型举国体制、在操作系统换道超车……)
  • 在学校里总是被一些莫名其妙的事情拖住……因为我们的国家要生存,所以很多时候只能在夹缝中求生。但革命,往往是在“Just for fun”的不经意间爆发的。

“Linux is Obsolete” Debate 世纪大辩论

  • comp.os.minix论坛上,关于 Linux 的讨论盖过了原本的 Minix。
  • 祖师爷 Andrew Tanenbaum 坐不住了,做出了“官方回应”,认为 Linux 采用宏内核(Monolithic)是“太落后”的设计。
  • 21岁的 Linus 初生牛犊不怕虎,完全不服气,直接发长文回怼。
  • 甚至连图灵奖得主、UNIX 之父 Ken Thompson 也参与了这场围观……

后来的事 (Linux 代码的指数级暴涨)

  • Linux 2.0 引入了多处理器(但使用了 Big Kernel Lock,内核本质上不能并行)。
  • Linux 2.4 真正实现了内核并行。
  • 2002 年才引入了神级机制 Read-Copy-Update (RCU) 无锁同步。
  • 2003 年 Linux 2.6 发布,随着云计算时代的到来,Linux 正式起飞,一统天下。

Linux 和应用程序的接口 🌶️

Linux 的“两面”

  • Kernel (内核面):
    • 负责加载第一个进程。这相当于在操作系统中“放置一个位于初始状态的状态机”。这就是大名鼎鼎的initramfs模式。
    • 维护并包含一些供进程操纵的操作系统对象。
    • 除此之外,“什么也没有”。(在用户态看来,Kernel 其实就是一个躺在底层被动等待调用的 Trap Handler)。
  • Ecosystem (发行版和应用生态面):
    • 构建在 Linux Kernel 系统调用之上的系统工具 (coreutils,binutils,systemd…)。
    • 桌面系统 (Gnome, XFCE, Android)。
    • 应用程序 (文件管理器, VSCode…)。

The last piece: “初始状态”

  • Everything is a state machine!
  • 既然是状态机,那“操作系统中的对象”必然需要一个初始状态。它是什么呢?
  • 当你更新 Linux 系统时,经常会看到屏幕上卡在update-initramfs... (漫长的等待)。没错,这就是 Linux Kernel 启动后的“初始状态”载体。

Initramfs:点亮 Linux 世界的第一把火

  • 我们的 initramfs 实验:
    里面可以只有一个最小的init文件(比如静态编译的极简 Linux 镜像),甚至可以是任何你自己写的一个二进制a.out
  • 系统启动后,Linux 还会为你增加/dev/dev/console(因为总得给stdin/stdout/stderr找个“输出的地方”)。
  • 真实的 initramfs (你可以解压它看一看):
    它包含基本的命令行工具、基础的设备驱动程序(以便随后能从真正的磁盘或网络上挂载庞大的文件系统),以及紧急文件系统修复工具(万一磁盘坏了,还能抢救一下)。

点亮世界的工具:Busybox

在真实的初始阶段,你看不到华丽的桌面,甚至没有完整的系统,只有一个busybox在默默支撑起早期的生命:

[ [[ acpid adjtimex ar arch arp arping ash awk basename bc blkdiscard blockdev brctl bunzip2 busybox bzcat bzip2 cal cat chgrp chmod chown chpasswd chroot...

并不是我们平时“看到”的“Linux”世界

启动的初级阶段:

  • 在 initramfs 中加载剩余必要的驱动程序(磁盘/网卡)。
  • 挂载真实的根文件系统。Linux 内核有启动选项(类似环境变量),记录在/proc/cmdline里。读取 root filesystem 的/etc/fstab
  • 神之一手:将控制权移交给另一个程序(例如现代 Linux 的霸主systemd)。

启动的第二级阶段:

  • 看一看系统里的/sbin/init到底是什么?
  • 计算机系统没有魔法,一切的神奇都有底层系统调用的合理解释。

移花接木:构建真正应用世界的系统调用

switch_root命令背后的核心系统调用是pivot_root()

intpivot_root(constchar*new_root,constchar*put_old);
  • 这个系统调用会改变调用进程所在 mount namespace 的根目录挂载点。
  • 它把当前的根挪到put_old目录下,并把new_root提拔为新的系统根目录!
  • 在此之后,早期的那个用来过渡的Initramfs就会被释放,功成身退。
  • /sbin/init指向的程序(比如systemd)开始接管一切,加载网卡、配置 IP、修改 tty 字体……真正的操作系统世界就此展开。

终局:操作系统 = 对象的集合与生态的繁荣

总结:什么是操作系统?

  • 初始状态:initramfs中的对象 +/dev/console+ 加载的init
  • 状态迁移:硬件选择一个进程(对象),执行一条指令。
  • 系统调用支撑一切:
  • 进程管理:fork,execve,exit,waitpid
  • 文件对象访问:open,close,read,write,pipe,mount
  • 地址空间管理:mmap,sbrk
  • 特权控制机制:pivot_root,chmod,chown

操作系统是幕后的 MVP

仅靠“初始状态 + 系统调用”这些机制,操作系统完全“感知不到”上层应用程序的存在。它只是无私地奉献着底层机制。
(嘿!也许在 AI 时代,操作系统可以通过为每一个 system call 甚至 function call 提供 traceability,从而真正去“感知”并理解应用的意图了!)

应用生态:从光盘到开源包管理器

  • 前互联网时代:软盘发行 (DOS) -> 光盘发行 (Win95)。双击安装,输入 CD-Key,当时要破解简直太容易了。
  • 应用商店模式:OS 提供 SDK/API,开发者独立打包发行,提交审核(如 AppStore 或 Microsoft Store,交 $19 保护费开个账号)。
  • 开源模式 (Debian):开发者共同维护代码和应用仓库。

“We want to create a free operating system, freely available for everyone… we are referring to software freedom.”
—— Debian Project

计算机科学与其他学科截然不同的一点就在于:开源开放
1998 年诞生的apt-get install firefox,是跨时代的“Advanced Packaging Tool”,彻底重塑了软件的分发模式。

Debian 的包管理与“软件供应链”:

一个.deb软件包,本质上只是一个压缩包:

  • control.tar.xz: 包含元数据(维护者、依赖 Depends、描述 Description…)
  • data.tar.xz: 包含实际要解压到绝对路径的各种二进制文件。
  • dpkg: 安装工具,其实它也只是操作系统上的一个普通应用程序,利用简单的文件读写系统调用完成“安装”。

为什么建设我们国家自己的操作系统生态那么艰难?

  • 生态的关键是开发者
  • 但是 qualified(合格的)开发者太少了!大学四年都在写高血压代码,不懂底层原理。
  • 错误的设计 = 无法维护的泥潭。
  • 这门《操作系统》课程的终极使命,就是让大家“见识”各种优秀与糟糕的设计,从而写出真正高质量的系统级代码。
  • 蒋门万岁!!!

AI 时代,"软件生态"会消失吗?
也许未来,我们只需要一个“最小”的执行环境,剩下的庞大包管理和组件,全部由 AI 随时随地按需生成。


篇末结语 (Virtualization 虚拟化篇 完结)

Take-away Messages:
至此,我们终于完全展示了逐层抽象的计算机系统世界:

  • 最底层,硬件向上提供了指令集体系结构 (ISA)
  • 基于指令集,我们实现了操作系统(提供了各种对象、Syscall API,以及由 initramfs 定义的初始状态)。
  • 操作系统之上,支撑起了基础的系统工具coreutils,shell,apt,gcc…)。
  • 在系统工具之上,才是各式各样繁荣的应用程序

实际上,我们在计算机系统中看到的五彩斑斓的画面,全都是由应用程序“完成”的,操作系统只是默默退居幕后,提供着极其原始的系统调用接口。
正是因为这些系统调用接口异常稳定、向后兼容,经过几十年的演化和积累,形成了难以逾越的技术屏障。在颠覆性的技术革新到来之前,任何想要“另起炉灶”做新操作系统的尝试,都注定是极其困难的。