跳转至

核心概念 — 系统编程与 Unix 的地基

这一章讲什么: 系统程序和普通程序到底差在哪;Unix 内核、shell、 用户与组、环境、文件和目录层次、进程与线程这些「地基概念」各自是什么、 为什么必须有它们;以及学系统编程最重要的一个工具——man 页——怎么查。 读完全书回头看,这一章的每一个概念后面都会被用到,而且都是被「必须有人管这件事」 这一条理由逼出来的。不需要任何 Unix 基础。

1. 顶层全景:这一章的所有概念是一根链条

这一章看起来讲了十几件事,其实是一根从头推到尾的链条:

  你写一个程序 → 它要往屏幕上打印字符
      │  但打印机、磁盘、内存这些硬件,程序自己不能直接碰
      ▼
  内核:唯一被允许碰硬件的程序,开机就在内存里
      │  普通程序想用硬件,只能「请求」内核
      ▼
  系统调用:程序向内核请求服务的入口 → 这,就是系统编程
      │  内核凭什么相信你这个程序?——用户、组、权限
      │  内核怎么区分「可信代码」和「你的代码」?——特权级
      │  新程序启动时要带哪些初始信息?——环境
      │  内核管的东西太多,必须有一套统一的组织法——文件、目录、目录层次
      │  跑起来的程序本身怎么管——进程、线程
      │  接口太多记不住怎么办——man 页
      ▼
  历史:这些概念不是设计出来的,是 1969 年以来一路演化、
       最后用 POSIX 标准固定下来的

图说:左边每个「凭什么/怎么办」都逼出了右边一个概念。这一章的每一节, 都是被前一节留下的问题逼出来的。

主线例子全章共用:你在终端敲下 ./hello_world 然后回车,这一行里发生的 每一件事,都会在这章的每一节里占一步。

2. 主线例子:敲下 ./hello_world 之后,发生了什么

你写过 Hello, world。原书就从它开刀:

#include <stdio.h>
void main()
{
    printf("hello, world\n");
}

这是 1978 年 Kernighan 和 Ritchie 的 C 语言书里的原版程序1。 第一行 #include <stdio.h> 告诉 C 预处理器:把标准头文件 stdio.h 的内容 读进来——因为 printf() 的声明在里面,没有它,编译器连「printf 用得对不对」 都判断不了。

你敲 ./hello_world 回车,屏幕上出现 hello, world,然后提示符回来。 看起来平淡无奇,但原书说,对从没写过程序的人来说这像魔法——而对系统程序员来说, 这魔法要一层一层拆开2。拆开后的每一步,对应本章后面的一节:

  ①  shell 读到这行命令,找到 hello_world 文件          → §5 shell
  ②  内核把这个可执行文件加载进内存,创建一个新进程     → §9 进程
  ③  程序里 printf() 开始执行
        │  printf 的代码不在你的程序里,在 C 标准库里    → §3 库与系统调用
        │  C 库里的 printf 自己也不会碰屏幕,
        ▼  它调用更低一层的 write()
  ④  write() 是操作系统内部的函数——系统调用           → §3 系统调用
  ⑤  内核接管:把字节交给终端设备                     → §4 内核与特权级
  ⑥  字符出现在屏幕上;别的用户的程序也在同时这么做,
     互不干扰                                          → §6 用户与组

图说:一个 printf,穿了「C 库 → 系统调用 → 内核 → 硬件」四层衣服。 这一章剩下的全部内容,就是把这四层每一件讲透。

还有一条暗线:程序运行时它「当前所在的位置」(哪个目录)、它是「谁」在运行、 它启动时带着哪些预设——分别由目录层次(§8)、用户(§6)、环境(§7)回答。

3. 系统编程是什么:从「资源」讲起

程序要的每一样东西,都归某人管

原书给了一个特别管用的框法:资源是软件使用或修改的对象3。 你程序的输入输出数据是资源,它内部变量也是资源——对自己的资源,程序想怎么用 就怎么用。

但 Unix 里有一批资源被保护起来了,普通程序不能直接碰,只有操作系统能碰: CPU、物理内存、显示器、存储设备、网络连接这些硬件是,内核的数据结构、文件这些 「软资源」也是4。书里说,这叫系统资源。

为什么要保护?书里举的例子很直白:如果你的程序可以直接读写磁盘, 它可以把磁盘占满,更糟的是把磁盘抹掉,内核再也没法保护任何东西5。 所以规则变成:程序想碰系统资源,必须开口请求,由内核替它做——它想做也做不到, 因为硬件指令根本不给它执行的机会(§6 特权级讲这件事怎么 enforcement)。

API 与系统调用:请求的格式

内核给程序留的「请求窗口」叫 API(application programming interface, 应用编程接口):一组函数、类型、常量的定义,定义了应用能向操作系统请求哪些服务。 API 里的函数,就是系统调用6——这个名字从这一章起会一路用到第 19 章, 你现在只需要记住:系统调用 = 程序跳进内核、请求服务的入口。

普通程序与系统程序的分界线

有了「系统资源」和「系统调用」,系统编程的定义就只剩一句话:

  • 普通程序:写的时候假装自己独占所有资源,不知道操作系统的存在;
  • 系统程序:直接通过 API 向操作系统请求资源和服务7。

还有一个更宽的说法:编译器、汇编器、链接器、终端模拟器这些「离开操作系统没法 独立干活、又给系统补功能」的工具,也算系统程序8。原书引了 Richard Stallman 的话:内核只是操作系统必不可少的一部分,光它自己什么也干不了8。

判断(我们的,不是书里的): 「系统程序」这个定义两头宽、中间模糊, 其实是有意的。判据就一条:这个程序挂了,是只死它自己,还是会连累系统里 别的东西? 会连累的(它管理的资源别人也要用),它就是系统程序, 写它就要按系统编程的规矩来——检查每一次调用的返回值。 如果错,会错在: 有些普通程序(比如一个用户的脚本)也会碰系统资源, 按这个判据会被划进来;但这个误伤无害——按系统编程的规矩写,本来就总是对的。

4. 内核:唯一有权碰硬件的程序

「操作系统」两个意思,这本书只用窄的那个

教科书对「操作系统」有两个定义:宽的=所有给应用提供服务的软件(连浏览器都算); 窄的=开机加载进内存、一直驻留、掌管全部硬件的那个程序。 「内核」(kernel)是窄定义的另一个名字——原书明说,这本书全用窄定义9。

内核是一组有许多入口点的程序。入口点就是「执行可以开始的地方」; 你写过的程序只有一个入口点 main(),而内核有成百上千个——打开文件、读、写、 创建进程、分配内存,每个服务一个入口10。你可以把内核想成一个巨大的函数库, 它的 API 就是这些函数的签名集合——只不过调它的时候,CPU 会从你的程序 跳进内核里运行(怎么「跳」,第 2 章讲)。

内核住在哪里、管什么

开机时,固件和引导软件把内核加载进内存的系统空间(也叫内核空间), 直到关机它都在;用户程序不允许访问这块空间,硬闯会被内核直接终止11。

内核的全部职责,原书列了一张单子:进程调度与管理、I/O 处理、物理与虚拟内存 管理、设备管理、文件系统管理、信号与进程间通信、多线程、保护与安全、 网络服务12。这张单子你别背——注意它的共同点:每一项都是「如果没人统一 管就会打架」的事。这正是内核存在的理由。

还有一条原则值得单独记住:内核独占处理器的使用权,由它决定此刻该运行谁; 加载、运行、终止程序,全归内核管——一个运行中的程序连「终止自己」都做不到, 它最多只能「请求内核终止它」13。这句话在第 11 章会兑现成具体的系统调用。

Unix 的七条设计原则

为什么 Unix 长这样?原书归纳了发明者写进设计的七条原则,每条都对应本章一节:

原则 它逼出的概念
分两级特权:有些指令只有内核特权级能执行 §6 特权指令
每个用户有唯一身份,可组群,身份附带权限 §6 用户与组
文件系统支持数据的增删改查、隐私、保护与共享 §8 文件与目录
内存分用户空间与系统空间两区 §4(本节)
内核独占处理器,决定谁运行 §4(本节)
内核独占程序的加载、运行、终止 §4、§9
内核独占全部硬件的控制 §4(本节)

图说:这七条不是清单,是互相咬合的齿轮——少了任何一条,另外几条就守不住。 比如「内核独占硬件」靠的是「CPU 提供两种特权级」,而「两种特权级」要落地, 又得有「用户/组身份」来决定「谁配让内核替自己干活」。

5. shell:内核不接客,接客的是它

内核提供服务给运行中的程序,但内核不直接和人打交道。人怎么用 Unix? 在终端窗口里敲命令。把命令读进来、负责执行的程序,叫命令行解释器(command line interpreter)14。

命令的语法

一条命令由命令名、选项(option)、参数(argument)组成。原书用这行做解剖:

$ gcc -g -o myprog myprog.c
   │    │   │  │      └─ 参数:命令的输入(这里是源文件名)
   │    │   │  └─ 选项参数:跟在 -o 后面的 myprog
   │    │   └─ -o 选项:指定输出文件名
   │    └─ -g 选项:生成调试信息
   └─ 命令名(GNU 编译器套装)

图说:短选项用一个短横线带一个字母;长选项用两个短横线带单词(如 --date, POSIX 标准不要求长选项,但 GNU/Linux 都有)15。把整行敲进终端、按下回车之前 键入的所有字符,叫命令行;一行里还可以用分号隔开多条命令16。

两条规矩容易踩坑:在符合 POSIX.1-2024 的系统上,选项的参数是必给的; 而 GNU/Linux 允许「可有可无的选项参数」(比如 firefox -P 不带参数时, 会弹窗让你选配置文件)17。

shell 不只是解释器,还是编程语言

Unix 里的 shell 有两个身份。第一,它是命令行解释器:显示提示符、等你敲命令、 执行、再显示提示符18。第二,它是一门编程语言的解释器——能定义变量、 求值表达式、做输入输出、写循环和分支、定义并调用函数,几乎是一门高级语言。 把一串命令存进文件,就是 shell 脚本(shell script)19。

为什么要第二个身份?因为把常用命令序列存成文件、反复执行,是自动化一切的开始。 shell 还常把高频命令做成自己的内建命令(builtin)——直接在自己内部执行, 比「为了跑一条命令而启动一个新程序」快得多,因为后者需要内核出手20。

几个具体的家伙

  • sh(Bourne shell):1979 年第七版 UNIX 自带,Stephen Bourne 写的, 是 AT&T 那一支的正宗;管理脚本几乎都按它的语法写,所以它必须存在21。
  • csh / ksh:UNIX 早期另两支常见的 shell。
  • bash(Bourne Again SHell):GNU 项目把 Bourne shell 加上 ksh、csh 的特性 做出来的,是 GNU/Linux 最常用的 shell,这本书也用它22。

关键点:shell 是可以换的。内核不管你在用哪个 shell——这正是 Unix 的设计 口味:「用户界面」不是操作系统的一部分,只是一个普通程序,不喜欢就换一个。

判断(我们的,不是书里的): 「shell 只是普通程序」这一条,是 Unix 和 Windows 最深的设计分野之一。它带来一个实用推论:凡是 shell 能做的, 你的程序也能做——因为 shell 并没有什么特殊权限,它只是系统调用用得熟。 第 13 章的管道、第 11 章的进程创建,你会看到「shell 的魔法」被逐条拆成 普通程序也能写的系统调用。 如果错,会错在: 少数功能确实只有 shell 才有(比如作业控制的某些信号语义), 把这些也当成「普通程序可做」,会写出时灵时不灵的东西——第 18 章会看到具体例子。

6. 用户、组与特权指令:内核凭什么信你

用户与 UID

Unix 的安全建立在一个朴素原则上:每个使用系统的人必须先证明自己是自己—— 这叫认证(authentication),就像进大楼出示工牌23。 做法是给每人一个唯一的用户名,配一个唯一的非负整数 UID(user ID, 用户标识),再加一个密码。登录时系统核对用户名/密码对,对不上就拒绝。 密码在系统文件里以加密形式存储24。

两个细节值得记住。其一,现代 Unix 里「用户」不一定是人:root、syslog、lp 这些系统服务账号也是用户——用户 = 能运行程序、能拥有文件的任何实体25。 其二,每个用户至少属于一个组(group),组也有名字和唯一整数 GID; 组是为了共享资源:文件可以关联到一个组,组内所有人对它有相同的访问权26。 id 命令能看你自己的 uid/gid 和所属的全部组27。

root:超级用户

用户名 root、UID 为 0 的用户叫超级用户(superuser),能做的事没有边界—— 改别人的用户名、改操作系统配置,都行。所以大多数 Unix 系统会记录每一次 root 登录尝试,好让管理员追查入侵28。UID 0 是硬编码的特殊值,这个数从 Unix 最初就是这样,到今天没变。

特权指令:CPU 层面的两把钥匙

「普通程序碰不了硬件」不能只是口头规矩,得由硬件兜底。Unix 要求处理器支持 两种运行模式:特权模式(也叫内核模式、supervisor mode)和非特权模式 (用户模式)。特权指令是能直接或间接改变系统资源的指令——申请更多内存、 修改系统时间、提高自己进程的优先级、读写磁盘、把自己切换进特权模式, 全是特权指令29。

只有内核能执行特权指令;普通用户的程序只能执行非特权指令。 操作系统的安全、可靠、完整,全部押在这道分离上30。

回到主线例子: 你敲 ./hello_world 后,printf → write() 那一跳, 本质上是 CPU 从用户模式切进特权模式、把控制权交给内核;内核写完终端, 再切回用户模式。系统调用的全部机制,就是「一次安全的模式切换」—— 第 2 章会把这个切换的具体过程讲透。还有一句原书放在这里的话,第 11 章要用: 内核不仅独占硬件,还独占「把程序加载进内存、运行、终止」的权力—— 进程连终止自己都做不到,只能请求内核终止它31。

判断(我们的,不是书里的): 「两级特权」是一切 Unix 安全叙事的根, 但它防的是「程序乱跑」,不防「程序乱信」。近年一大类内核漏洞,恰恰是从 用户模式钻进内核模式的那道门(系统调用处理、设备驱动)本身有缝。 这道门的存在是设计;门做得牢不牢,是另一场永远在打的仗。 如果错,会错在: 如果未来处理器普遍提供比「两级」更细的多级保护域, 「进出内核」就不再是唯一的门,这条叙述就要改写——但截至本书写作时(2025), 两级模型仍是 Linux 的事实。

7. 环境:每个新程序出生时就带着的一张表

什么是环境

程序在 Unix 里启动时,内核会塞给它一张表,叫环境列表(environment list, 简称环境)。表里每个条目都是一条 名字=值 的字符串,叫环境字符串; 名字部分叫环境变量。比如 LOGNAME=stewart,意思是变量 LOGNAME 的值是 stewart32。

三条规矩:名字里不许有 =;按惯例只用大写字母、数字、下划线,且不以数字开头; 值永远是字符串——哪怕存的是数字(比如 COLUMNS=80 表示终端窗口的列数), 也是以字符串形式存的33。

它从哪来、怎么传

你登录系统时,操作系统依据一堆配置文件建好这张表;从那以后,你运行的每个 程序都继承一份当时的拷贝。程序可以读它来决定自己的行为(比如按 COLUMNS 的宽度排版),也可以改自己的那份拷贝——但改的是自己的拷贝,影响不到别人, 也影响不到后来从 shell 启动的程序,除非你特意这么安排34。

看环境的命令行工具是 printenv(或 env),两者不加参数就把全部环境变量 列出来;只想看某几个,把名字当参数传给 printenv 即可35。 程序里则用 getenv() 函数取单个变量。原书给了一个示范程序 getenv_demo.c: 调 getenv("SHELL"),打印 /bin/bash36。你在任何语言的程序里写的 「读环境变量」,底层走的都是这张表——这是程序出生证明的一部分,第 10 章会讲 它在进程的内存图像里具体住在哪。

8. 文件与目录:Unix 组织一切的方式

Ritchie 和 Thompson 说过,操作系统最重要的角色是提供文件系统37; Kernighan 和 Pike 则记录了:Ritchie 和 Thompson 设计 Unix 时第一个讨论的 就是文件系统的结构,因为它决定了其他一切怎么工作——「UNIX 系统里万物皆文件」 就是他们的结论38。这一节是全书的承重墙之一,第 4–7 章全部建立在这节的概念上。

文件:对内核来说只是一串字节

别的操作系统认识「Word 文档」「图片」「表格」各种文件类型,Unix 不认识。 在内核眼里,普通文件就是一个线性的字节序列,不含任何结构;结构是创建它的 程序自己赋予的39。有的文件是人看得懂的文本(行与行之间用换行符隔开), 有的装的是可执行代码这类非文本字节,叫二进制文件——内核对两者一视同仁。

内核只额外定义了一小撮文件类型40:

类型 一句话 书里哪章展开
普通文件 就是上面说的字节序列 第 4、5 章
目录 一种特殊文件,装的是「名字 → 文件」的表 本节下面、第 7 章
设备文件 代表硬件设备的特殊文件 第 6、18 章
管道 进程间通信用的字节流 第 13 章
套接字(socket) 主要用于网络通信 本书不讲(原书明说)
符号链接 内容只有另一个文件名的文件 本节下面

设备文件、管道、套接字合称特殊文件——它们存在的全部意义,就是让 I/O 可以 「设备无关」地进行(这是主线例子里 write() 能通吃终端和磁盘的原因, 第 4 章展开)41。

文件状态、inode,以及一个反直觉的事实

每个文件有一组属性:最后修改时间、所有者 UID、大小(字节数)、谁被允许 做什么操作……这些属性合称文件状态(file status),也常叫元数据 (metadata)——「谁被允许做什么」那部分属性,还有个专门名字:文件模式 (file mode),也就是权限42。

两个要刻进脑子的区分:

  1. 内容 ≠ 状态。内容是文件的数据;状态存在一个叫 inode 的数据结构里, 内容可能散布在存储设备的许多块(block)上,两者分开存放43。
  2. 文件名不是文件的属性。 一个非目录文件可以有多个名字,名字不归文件所有, 归包含它的目录所有44。书里给了一个贴切的比方:同一个人揣着好几本护照, 每本写的名字不同、在 different 国家使用,但都是同一个人45。

这两个区分后面都会变成具体的系统调用:读状态是 stat()(第 6 章), 换名字、加名字、减名字是 link()/unlink()/rename()(第 7 章)。

目录:一张「inode 号,文件名」的对照表

目录是一种文件,内容是一张由链接(link)组成的表。 每个链接 = 一个文件名 + 对该文件 inode 的引用46。所以「目录里装着文件」 是错觉——目录和文件的关系,更像书的目录和章节的关系:目录只是指给你看, 章节不「在」目录里。

每条规矩都能从这张表推出来:

  • 目录永远不会空:每个目录都有两条固定链接 .(指向自己)和 .. (指向父目录);唯一的例外是根目录 /,它的 . 和 .. 指向同一个 inode——/.. 还是 /47。
  • 普通程序不能随便写目录:目录只能由特定系统调用创建和修改 (mkdir、link 之类),不像普通文件能用 open() 创建——因为内核要 保证这张表的格式永远合法48。
  • cat 目录名 通常看不出名堂(有的系统报错 Is a directory, 有的吐一堆乱码字节),因为目录不是文本文件,格式是内核私有的49。

文件名:护照,不是人

前面说了,一个非目录文件可以在不同目录有多个名字,这些名字在各自的链接里 指向同一个 inode——「像一个人带了好几本护照」45。文件独立于它出现的 目录而存在。

名字本身的规矩50:

  • 最长通常 255 个字符(系统常量 NAME_MAX);
  • 除 / 和空字符 \0 外几乎什么字符都允许——但最好别用:空格、换行使 得名字当命令参数时要加引号;$ & * 等对 shell 有特殊含义,得加反斜杠转义。 惯例是只用字母数字、下划线和连字符;
  • 大小写敏感:source 和 Source 是两个文件;
  • Unix 不按扩展名认文件类型(.c 只是给编译器看的后缀约定,内核不认)。

符号链接:一个内容为「别人名字」的文件

普通链接(硬链接)是目录表里的一条记录;符号链接则是一个真正的文件, 它的内容只有另一个文件的名字——指向的那个文件叫链接的目标(target)。 它的 inode 把自己标成「符号链接」类型。功能上很像 Windows 的快捷方式; 为了区分,普通链接叫硬链接,符号链接也叫软链接51。

大多数命令、程序和内核本身拿到符号链接时,默认操作的是它的目标而不是它自己 (这个「跟过去看」的动作叫解引用,dereference)。麻烦在于:符号链接可以 指向目录,于是程序顺着链接走时可能回到已经访问过的目录、兜进循环—— 处理目录遍历的程序必须防这一手,第 6、7 章会专门处理52。

目录层次:一棵树,又不完全是树

Unix 把所有文件组织成一棵以 /(根目录)为底的树状目录层次——注意 术语:很多人管这叫「文件系统」,更准确的说法是目录层次;「文件系统」 指的是磁盘设备上那套用来管理文件的数据结构53。

树的每条边是「父目录 → 它包含的文件」。但严格说它不是树而是有向无环图 (directed acyclic graph, DAG):硬链接让一个非目录文件可以有多个父节点, 这是树不允许的;而目录不允许有多个名字(除了 root 能用特权硬链目录的少数实现), 所以不会成环54。

单一目录层次是 Unix 的标志性特征。 Windows 给每个设备盘符一套独立的树 (C:\、D:);Unix 不管你插多少块盘,所有文件都在同一棵树下——一块新盘上的 层次通过一个叫挂载(mounting)的手续接到树上的某个目录,接好之后它的文件 就像树上土生土长的文件一样访问55。

路径名:怎么从树根走到任何一个文件

路径名(pathname)是标识一个文件的字符串,两种:

  • 绝对路径名以 / 开头,从根目录开始:如 /data/jammy/kernel/sched/sched.h。 除最后一段外,每段都必须是目录(或指向目录的符号链接);结尾多写几个 / 无害(/usr/local///share/man 就是 /usr/local/share/man)56。
  • 相对路径名不以 / 开头,从当前工作目录(current working directory) 开始解析。比如当前目录是 /home/stewart/unix_book 时, chapters/chapter_01 指的就是 /home/stewart/unix_book/chapters/chapter_0157。

「当前工作目录」是 shell 为每个会话维护的「你此刻站在哪」;cd 换它, pwd 打印它(值存在环境变量 PWD 里)58。路径名长度有限制:POSIX 规定 常量 PATH_MAX 是路径名的最大字节数(含结尾空字符),许多 Linux 上是 4096 字节59。

根目录下的标准布局和「可共享/可变性」

一棵典型的 Unix 目录层次,根下有一批固定的顶层目录60:

目录 装什么
/bin 最基本的可执行程序(单用户模式也得能用)
/boot 引导加载器的静态文件
/dev 设备文件
/etc 几乎所有本机配置文件(地位类似 Windows 注册表)
/home 各用户的主目录
/lib 基本共享库与内核模块
/media、/mnt 可移动介质 / 临时挂载点
/opt 附加应用软件包
/sbin 基本系统管理命令
/srv 本系统对外服务的数据
/tmp 临时文件
/usr 非基本程序的层次(bin、lib、local、share…)
/var 内容会变的文件(日志、邮件、…)

POSIX.1-2024 只强制要求 /dev 和 /tmp 两个61。

这一层怎么分,背后是一条分类学:所有文件都可以按两个独立的二元性质归类—— 可共享性(能不能存一台机器、给别的机器用?你 home 下的文件能,引导器文件 不能)和可变性(内容会不会变?可执行程序不会,日志会)。分类的结果是 /etc 不可共享而静态、/var 可变、/usr 可共享且静态——这么分,备份和 跨机器共享才好做62。

9. 进程与线程:跑起来的程序,和程序里的多条路

进程:程序的一次运行实例

程序是写下来的指令序列;写成的高级语言源代码要翻译成可执行代码 才能跑(脚本语言例外,由解释器直接读源码执行)63。你可能在三个终端同时跑 同一个程序,或一个程序被很多人同时跑——每个运行中的实例都是一个进程 (process)64,它们执行的是同一个可执行文件。

教科书定义「进程 = 运行中的程序的实例」,原书说这话等于没说——就像把棒球赛 定义为「1845 年 Cartwright 规则集的一个实例」。更具体的说法是:进程 = 分配给这个运行实例的全部资源,加上刻画它的全部属性和设置——内存、磁盘空间、 打开的文件、CPU 寄存器的当前值……它是操作系统必须打理的一个非常具体的东西65。

Unix 给每个进程一个唯一的非负整数 PID(process identifier,进程标识)。 ps 命令列出当前进程及其 PID;程序里用 getpid() 系统调用拿自己的 PID—— 这是全书第一个系统调用程序66:

#include <unistd.h>   /* 系统调用的声明几乎都在这个头文件 */
printf("I am the process with process-id %d\n", getpid());

跑一次打印一个 PID,再跑一次又是另一个——每次运行都新建一个进程, 这个事实证明给你看67。

线程:进程里的多条执行线

线程(thread)= 单个进程内的一条控制流(一条指令接一个指令的执行序列), 外加支持这条控制流所需的资源;这是 POSIX.1-2024 的正式定义68。 传统 Unix 进程只有一条控制流;多线程进程里,所有线程共享一批资源 (全局变量、打开的文件……),另有一批资源是每个线程私有的 (各自的栈、各自的寄存器值……)。第 15 章会给出完整的「谁共享、谁私有」清单。

Linux 处理线程的方式值得专门记住:内核把线程也当普通进程对待,不给线程 任何特殊的数据结构——线程和进程在内核里统一叫任务(task),用同一个数据结构 task_struct 表示。区别只在:线程之间可以共享地址空间等资源,进程之间 什么都不共享69。

线程也有自己的标识 TID(thread identifier)。POSIX 只要求 TID 在单个 进程内唯一;Linux 的做法是:单线程进程里 TID 等于 PID;多线程进程里所有线程 共享同一个 PID,但各有唯一的 TID,gettid() 系统调用可以取70。 (那个 _GNU_SOURCE 宏是特性测试宏,第 2 章讲它是干什么的。)

10. 联机文档:man 页是你的终身工具

这一章最后一个概念不是内核概念,是学习工具——原书的方法论高度依赖它。

man 页:八个小节装下整个系统

1971 年,第一版 UNIX 发布不久,Ritchie 和 Thompson 就写下了第一本 UNIX 程序员手册;此后手册按主题分成八个编号的小节(section), 至今不变71:

节 内容 例子
1 用户命令 ls、gcc
2 系统调用 read、getpid
3 库函数 printf、getenv
4 特殊文件 /dev 下的设备
5 文件格式与约定 /etc/passwd 的格式
6 游戏 —
7 杂项(约定、概览) pipe(7) 管道概览
8 系统管理命令 通常只有 root 能用

图说:查任何主题的文档,先问「它是哪一类的」——是命令(1)、系统调用(2) 还是库函数(3)? 查函数页会告诉你需要 include 哪些头文件;POSIX 还要求系统 必须带有所有这些头文件本身的 man 页(可能没装,但存在)72。

man 页详尽但不适合当教材。为什么不用谷歌代替?原书给了四条理由,都是硬理由: 你系统上的 man 页版本和你装的软件版本严格一致;man 页是写软件的人写的, 可信;交叉引用都在本地,断网也能读73。

一页 man 页的内部结构

常用小节:NAME(名字+一行简介)、SYNOPSIS(接口摘要)、DESCRIPTION、 OPTIONS(命令的选项)、ENVIRONMENT VARIABLES、EXIT STATUS / RETURN VALUE、ERRORS(函数可能放进 errno 的错误码)、FILES、 EXAMPLES、SEE ALSO、BUGS74。第一次读一页,先 NAME → SYNOPSIS → DESCRIPTION → SEE ALSO;是命令再看 OPTIONS;是函数,ERRORS 和 RETURN VALUE 必看——原书强调的正是 ERRORS:函数会报告什么错误、 返回什么值、你该怎么处理,全在这一节75。

SYNOPSIS 有一套速记语法要会读:方括号 [ ] = 可选;竖线 | = 多选一; 尖括号 < > = 占位符;省略号 ... = 可重复;[-aHvW] 表示这些选项字母可以 一起给,而 [-a | -H | -v | -W] 表示只能挑一个76。

搜索与翻页

看 man 页用到的翻页工具(pager)默认是 less:空格下一屏、b 上一屏、 q 退出、/关键词 向前搜、? 向后搜、G 到页尾77。可以用 PAGER 环境 变量换掉它78。

不知道怎么查的时候:man -k 关键词(等价 apropos)在所有 man 页的 短描述(NAME 节那一行)里搜;man -K 搜全文(慢); apropos -s 2,3 限定只在 2、3 节搜;apropos -e 精确匹配整个词; -a 要求匹配所有关键词79。这套检索能力是全书的方法论支点: 原书的每个项目都是从「读 man 页搞清命令用什么系统资源」开始的 (第 3 章会完整演示一遍这个流程)。

Info、--help 与其他文档

man 页有缺陷(不同页之间几乎没有导航),GNU 项目基于 Texinfo 做了 Info 文档系统;有的主题 Info 页比 man 页详尽得多,有的 man 页会 直接让你「详见 Info」80。命令的 --help 选项给出速览;help 是 bash 的内建命令帮助81。/usr/share/doc 里还躺着各软件自带的文档。 最该常备的一本:GNU C 库参考手册(GNU C Library Reference Manual)82。

11. Unix 的历史与标准:为什么「方言」这么多还要守规矩

为什么要学历史

原书给的理由很实际:Unix 复杂而混乱的历史,正是它没有单一标准、 读文档时必须反复核对「这功能在我系统上有吗」的原因;知道历史, 你就知道某个特性来自哪一支、各支之间哪里不兼容83。

1969–1993:三条线索

出生。 1969 年 Ken Thompson 在 AT&T 贝尔实验室用汇编写出第一版 UNIX; Dennis Ritchie 从 1971 年起和他合作,创造了 C 语言,1973 年内核几乎全部 用 C 重写——操作系统第一次变得可移植,只要有 C 编译器的机器就能跑 Unix84。早期版本叫「第 N 版」(V1、V2……),对应同时发布的 UNIX 程序员手册的版号。

分叉。 加州大学伯克利分校拿到 V4 后开始加功能,自成一支,叫 BSD (Berkeley Software Distributions);AT&T 1982 年拆分后可以卖 Unix 了, 1983 年发布商业版 System V。于是 Unix 世界里两大阵营:BSD 系和 AT&T 系, 同一个功能两边常常不兼容85。

GNU 与 Linux。 1983 年 Richard Stallman 发表 GNU 宣言、创立自由软件 基金会,要从零再造一个自由的 Unix;工具写了一大堆,内核(Hurd) 却始终没成气候86。1991 年,赫尔辛基大学学生 Linus Torvalds 写了一个 新内核 Linux——它只借鉴思想、不含 AT&T 一行代码——再和 GNU 的工具打包, 成了今天这套系统87。之后商标几经转手(AT&T → Novell → X/Open), Unix 家族彻底「方言丛生」88。

POSIX:方言时代的基础设施

POSIX(Portable Operating System Interface,可移植操作系统接口) 是 IEEE 1003 标准家族,也被 ISO 以 ISO/IEC 9945 发布。本书写作时的最新版是 POSIX.1-2024,同时就是 Open Group Base Specifications Issue 8, 合并了此前包括 Single UNIX Specification 第 4 版在内的主要标准89。

它的精神,原书直接引了规范自己的话:定义一个标准操作系统接口与环境(含 shell 和常用工具),在源代码级别保证应用的可移植性——给应用开发者用, 也给系统实现者用90。标准分四部分:基本定义、系统接口、shell 与工具、 原理(说明每条特性为何取舍)91。

三件实用的事:

  1. 判据:POSIX 不覆盖图形、数据库接口、记录式 I/O、二进制可移植性、 系统配置——在这些地方求「可移植」是白求92。
  2. UNIX 商标:归 Open Group 所有,一个系统要自称 UNIX, 得实现 Single UNIX Specification 的全部接口(2018 版含 1,833 个接口); Linux、FreeBSD 只是部分兼容93。
  3. man 页的 CONFORMING TO 小节会写明该页内容符合哪些标准; man standards 列出标准名全集94。

C 标准:系统编程的另一半坐标系

POSIX 规定了 C 标准库的各部分必须干什么,等于给 C 语言定义了一套扩展; 所以一个 POSIX 兼容系统必然带上整套 C 标准库——Linux 上这份实现就是 glibc(GNU C 库)95。C 语言本身从 Ritchie 发明以来多次修订, 写作时最新是 C23;GNU 编译器用 -std=c90、-std=c11 这类选项 按不同标准编译96。

为什么要抠 C 标准?分清「这是 C 语言的功能」还是「这是 Unix 的功能」 ——前者到哪都 portability 良好,后者换个系统就得查 man 页。写 Unix 程序 需要两套坐标同时在手,这一章把它们都摆上了桌。

12. 把概念落到键盘上:四个命令行场景

概念讲完,原书用一连串真实的终端会话把它们焊死。我们把其中四个走一遍—— 每一步都对应前面某一节的概念,这是把名词变成直觉的最快办法。

场景一:ls 与 cd ——「当前工作目录」到底在哪

$ ls
chapters/    fonts/     images/    main.tex       main.bib

ls 不带参数,列的是当前工作目录的内容(§8 的概念:每个 shell 会话 有唯一一个「你正站在哪」的目录)97。

$ cd chapters
$ ls
appendix_a.tex chapter_02.tex chapter_05.tex preface.tex
back_matter.tex chapter_03.tex front_matter.tex
chapter_01.tex chapter_04.tex intro.tex

cd chapters 换了工作目录,于是同一个 ls 列的是新地方。 再 cd .. 顺着「父目录链接」退回去,ls 列出的又是第一屏的内容—— .,.. 不是语法糖,就是目录表里两条真实的链接记录(§8)98。

场景二:id —— 你是谁、属于哪些组

$ id
uid=500(stewart) gid=500(stewart)
groups=500(stewart),4(adm),24(cdrom),27(sudo)

一行输出集齐了 §6 的全部概念:UID(500)、主组 GID(500)、所属组列表 (stewart、adm、cdrom、sudo)。换成 id syslog 看的系统账号,输出是 uid=102(syslog) gid=106(syslog) groups=106(syslog),4(adm),5(tty)—— 连打印机守护进程都有自己的身份,这正是「用户 = 能运行程序、能拥有文件的 任何实体」的意思99。

场景三:ps —— 进程就在你身边

$ ps
  PID TTY          TIME CMD
10278 pts/0    00:00:00 bash
11087 pts/0    00:00:00 ps

你身旁此刻就坐着一堆进程:PID 10278 是 shell 自己,PID 11087 是 ps 命令 本身——连「列进程」这个命令自己也是一个进程,TIME 全是 0 说明它们几乎 没耗 CPU100。再对照 getpid_demo.c 的输出 I am the process with process-id 18805,「每次运行都是新进程」从定义变成了 你亲手打印出来的数字101。

场景四:查 man 页 —— 从「想知道」到「知道」

想知道 read 系统调用的返回类型(原书本章习题之一),完整路径是:

$ apropos -s2 read          ← 先在第 2 节的短描述里找
$ man 2 read                ← 打开第 2 节的 read 页
    SYNOPSIS  →  看函数原型和要 include 的头文件
    RETURN VALUE  →  看返回值:成功读了多少字节 / 失败是 -1
    ERRORS  →  看 errno 可能是什么

图说:这套「apropos 定位 → 开页 → SYNOPSIS → RETURN VALUE/ERRORS」的 四步走,是全书的默认动作。 第 3 章会把这条路径完整走一遍给你看。

13. Unix 历史速查年表

原书的历史叙述散在一个长节里,压成一张表方便复述(每条都标了因果):

年份 事件 它解释了今天的什么
1969 Thompson 在贝尔实验室用汇编写出 UNIX Unix 为什么和 C 绑得这么死
1971–1973 Ritchie 创造 C;1973 年内核几乎全部用 C 重写 操作系统第一次可移植;内核 API 全是 C 风格
1974 在 ACM 论文发表;开始向大学授权(不许卖) BSD 分支的种子
1970s 伯克利在 V4 上加功能 → BSD 系列 「BSD 系 vs AT&T 系」两阵营的起源
1982–1983 AT&T 拆分后可卖 Unix;发布 System V;Stallman 发 GNU 宣言 商业 Unix;自由软件运动
1985–1991 GNU 工具成堆,内核 Hurd 难产 为什么今天叫「GNU/Linux」
1991 Linus Torvalds 写 Linux 内核 借 Minix 的思想,不含 AT&T 代码
1993 AT&T 把 Unix 卖给 Novell,次年商标给 X/Open 商标与代码彻底分家
1988–2024 IEEE 1003(POSIX)诞生并演进,2024 版合并 SUSv4 「可移植」终于有了可引用的定义

图说:左边是事件,右边是今天在替你付账的后果。 读 man 页看到 CONFORMING TO 小节时,这张表告诉你那些标准名各自从哪来。

14. 作者的判断与证据

这一章里,作者的断言和给定的证据分得比较开,照实列:

有原文或权威出处撑着的:

  • 「内核不该做的事,用户程序一件也做不了」——由特权指令机制保证, 这是硬件层面的事实陈述,不是观点102。
  • 「man 页比网页更该读」——给了四条可检验的理由(版本一致性、作者可信、 引用自足、离线可用)73。
  • 「目录层次是有向无环图」——由「目录不能有多个硬链接」推出, 推理链在原文里完整给出54。
  • Unix 历史各年份——引用了 Ritchie 本人的历史叙述、Kernighan 的回忆录等 一手材料103。

作者的判断(带着个人口味,证据是教学经验):

  • 「学系统编程最有效的路子是跟着例子写程序、把 man 页当地图」——这是作者 教了约 40 年的方法论主张,不是能被证明的命题104。
  • 「读历史是为了今天读文档时不被方言坑」——一个经验之谈,理由成立但属观点83。
  • 「本书采用操作系统的窄定义(内核而已)」——明说这是选择, 宽定义同样合法9。

15. 边界与局限

  • 套接字(network socket)整章不碰。 原书明说 socket「复杂到能单独写一本书」, 本书不讲41。这意味着网络编程(TCP/UDP)在这本书之外。
  • 图形界面不讲。 GUI 不在 POSIX 范围,也不在这本书的范围105。
  • 「Unix 历史」一节是浓缩导览不是学术史;它自己就把 Salus、Kernighan、 Ritchie 的完整版本列为延伸阅读103。
  • 本章所有数字(NAME_MAX 255、PATH_MAX 4096、UID 0)都是「典型 Linux 系统」 的值,不是 POSIX 强制值——man 页和你机器上的 <limits.h> 才是最终答案。
  • 标准一节写到 POSIX.1-2024 为止;C 一节写到 C23 为止。这些是写作时点(2025) 的快照,以后会有新版本,但「以标准名为锚」的方法不会过期。

16. 可带走的

主线例子,一行复述: 敲 ./hello_world → shell 找到程序(§5)→ 内核 建进程(§9)→ printf 在 C 库里、再调 write() 进内核(§3)→ 内核用特权 指令把字节交给终端(§4、§6)→ 字符上屏,而隔壁用户的程序毫发无损(§6)。

  1. 系统程序 = 直接通过 API 向内核请求服务的程序;系统调用就是那个 API 的函数。
  2. 内核是唯一被允许碰硬件的代码,CPU 的两级特权是这条规矩的硬件根基。
  3. shell 是可替换的普通程序——它不是操作系统的一部分;bash 只是最常见的一个。
  4. 用户有 UID、组有 GID,root 是 UID 0;连系统服务也是「用户」。
  5. 环境是 名字=值 的字符串表,值永远是字符串,新程序继承一份拷贝。
  6. 内核眼里文件只是字节序列;文件名不是文件的属性,是目录表里的一条链接。
  7. 硬链接共享 inode,符号链接是「装着别人名字的文件」,能指向目录所以会成环。
  8. Unix 只有一棵目录层次,新盘靠挂载接入;路径分绝对(/…)和相对。
  9. 进程 = 程序的一次运行实例,有 PID;Linux 把线程也当进程(task_struct)管。
  10. man 页八节:命令 1、系统调用 2、库函数 3;查函数必看 RETURN VALUE 和 ERRORS。

17. 原文地图

主题 原书章 原文位置
Hello world、scanf/printf、C 库与 read/write Chapter 1: Core Concepts text/07-ch01-chapter-1-core-concepts.txt:33(搜「Hello, world」) · :132(搜「Role of the C Library」)
资源与系统资源 同上 :183(搜「Resources are objects」) · :189(搜「system resources」)
API 与系统调用定义 同上 :207(搜「application programming interface」) · :213(搜「system calls」)
系统程序定义、Stallman 引文 同上 :226(搜「system program」) · :236(搜「compilers, assemblers」)
内核定义、窄口径 同上 :288(搜「unambiguously used as another name」) · :294(搜「narrow definition」)
内核职责、服务清单 同上 :320(搜「system space」) · :371(搜「Process scheduling」) · :360(搜「terminate itself」)
命令与选项语法 同上 :412(搜「instruction that you enter」) · :438(搜「single dash」) · :452(搜「nonrequired」)
shell、bash、内建命令 同上 :501(搜「programming language interpreter」) · :507(搜「builtins」) · :523(搜「bash」)
认证、UID/GID、root 同上 :531(搜「Authentication」) · :554(搜「need not be an actual person」) · :587(搜「superuser」)
特权指令与两种模式 同上 :597(搜「privileged instructions」) · :605(搜「depend upon this separation of」)
环境列表、getenv 同上 :611(搜「environment list」) · :629(搜「stored as a string」) · :664(搜「getenv」)
文件即字节序列、文件类型 同上 :701(搜「linear sequence of bytes」) · :715(搜「Directories」)
文件状态、inode、文件名不属于状态 同上 :736(搜「file mode」) · :750(搜「inode」) · :753(搜「not part of the status」)
目录=链接表、. 与 .. 同上 :772(搜「links」) · :778(搜「dot-dot」)
硬链接与护照比方 同上 :861(搜「passports」)
目录层次、DAG、挂载 同上 :896(搜「directory hierarchy」) · :914(搜「acyclic graph」) · :925(搜「mounting」)
顶层目录、可共享/可变 同上 :941(搜「top-level directories」) · :967(搜「shareability」)
符号链接与循环危险 同上 :992(搜「symbolic link」) · :1003(搜「circular references」)
路径名、PATH_MAX 同上 :1011(搜「absolute pathname starts at the root」) · :1025(搜「relative pathname」) · :1041(搜「4096」)
进程、PID、getpid 同上 :1078(搜「instance of a running program」) · :1093(搜「collection of all resources」) · :1114(搜「getpid」)
线程、task_struct、TID 同上 :1155(搜「flow of control」) · :1171(搜「task_struct」) · :1179(搜「two-pronged」)
man 页八节、为什么读本地 man 同上 :1227(搜「Manual Sections」) · :1253(搜「man pages on your system」)
man 页结构、SYNOPSIS 语法 同上 :1528(搜「NAME」) · :1568(搜「Square brackets」) · :1596(搜「RETURN VALUE sections」)
翻页、搜索、apropos 同上 :1464(搜「SPACEBAR」) · :1638(搜「-k and -K」)
Info、--help、GNU C 库手册 同上 :1334(搜「Info」) · :1398(搜「help」) · :1444(搜「GNU C Library Reference Manual」)
Unix 历史里程碑 同上 :1764(搜「assembly language」) · :1770(搜「rewritten in C」) · :1797(搜「System V」) · :1833(搜「Linus Torvalds」)
POSIX 与标准家族 同上 :1872(搜「1003.1-2024」) · :1882(搜「source code level」) · :1915(搜「outside of its scope」) · :1940(搜「man standards」)
C 标准与 glibc 同上 :1947(搜「C23」) · :1959(搜「glibc」)

18. 深挖一层:把 API 想成类的接口

原书在讲 API 时插了一条 NOTE,值得单独留一段106:如果你写过面向对象程序, 操作系统的 API 就像一个类的接口——数据(系统资源)被保护着, 只能通过一组事先定好的访问点(系统调用)碰。这个类比不是装饰,它解释了 一个后面会反复出现的现象:为什么系统调用的参数和返回值设计得那么「抠门」 (比如返回 -1 表示错误、错误细节放到 errno)——因为接口越窄, 保护越容易做。第 2 章讲错误处理时会回到这里。

19. 深挖一层:内核的「多入口点」究竟什么意思

「内核是一组有许多入口点的程序」这句话,值得拿主线例子再走一遍107。 你的 hello_world 只有一个入口 main(),从第一行跑到 return, 从头到尾一条线。内核不是这样:它驻留在内存里,睡着,直到有人按门铃—— 而门铃有几百个,每个通向不同的服务窗口:

  你的程序                        内核
      │  write(1, "hello, world\n", 13)   ← 按下「写文件」的门铃
      │ ───────────────────────────────►
      │                                │  内核检查:fd 1 是谁?这个进程
      │                                │  有权写吗?(§6 的身份在起作用)
      │                                │  把字节交给终端设备驱动
      │  返回 13(写了 13 个字节)      │
      │ ◄───────────────────────────────
      ▼
  继续跑你的下一条语句

图说:系统调用是「门铃 + 服务窗口 + 回执」三件套。 返回值就是回执: 成功时回执是「办了 13 字节」,失败时回执是 -1,细节写在 errno 里。 这张图的每一个零件,第 2 章(错误处理)、第 4 章(read/write)都会拆开讲。

20. 深挖一层:文件名与目录表的完整走查

把 §8 的目录表概念走成一个具体文件。假设存在这样一个目录 jammy, 它有三个孩子:kernel、drivers、README。它在磁盘上的真实形态不是「装着三个 文件」,而是这样一张表108:

  jammy 目录的磁盘内容(示意):

    引用(指向 inode)      名字
         53                .      ← 指向 jammy 自己
          2                ..      ← 指向 jammy 的父目录
         12              kernel
        185             drivers
        282              README

图说:目录的「内容」就是这张两列表。左边是 inode 号,右边是名字。 注意表里没有「文件大小」「修改时间」——那些都在 inode 里(§8 的「状态与 内容分开存」)。注意数字是为演示编的,inode 号的真实值依文件系统而定。

现在,当另一个目录(比如 webstuff)里有一条 image1.png → 同一个 inode 的链接时,这个文件就有了两个名字——cat webstuff/image1.png 和 cat mirror/image1_bck.png 读的是同一份数据,删掉其中一个名字, 文件还活着;把两个名字都删了,inode 的引用计数归零,空间才回收 (这条回收链,第 7 章讲 unlink() 时完整走)109。

21. 深挖一层:符号链接的「解引用」走到哪一步

符号链接多出来的风险,值得再用一张图钉一下。假设:

  /home/stewart/book  ->  /mnt/usb/book     ← 符号链接
  /mnt/usb/book/fig   ->  /home/stewart/book/fig   ← 链回去,成环!

一个遍历目录树的程序(第 7 章要写的 nftw 遍历)如果无脑跟着符号链接走: 进 /home/stewart/book → 解引用到 /mnt/usb/book → 又遇到 fig 链回起点 → 死循环。原书在这里只给了警告,处理办法(记录已访问的 inode、检测环) 要到第 7 章才兑现——你现在记住的是:符号链接是「文件系统里的 GOTO」, 功能强,责任也大110。

22. 深挖一层:man 页的「节」为什么让新手栽跟头

同一名字的条目可能出现在多个节里:printf 既是命令(第 1 节)也是 库函数(第 3 节);read 出现在系统调用(2)和 shell 内建(1,bash 的 help)。 man read 默认按预设顺序返回第一个找到的页——你很可能拿到的是 bash 内建版本的 read,而不是你要的系统调用111。 解法:man 2 read 显式指定节号。 这是新手最常踩的坑之一, 而它的根子在 §5:shell 内建命令和普通命令共享同一个名字空间, 文档系统只好用「节」把它们隔开。

23. 深挖一层:三个示范程序逐行走一遍

原书本章带了三个小程序,每个演示一个概念。逐行走一遍,比看定义记得牢。

hello.c:第一个「有输入」的程序

#include <stdio.h>
void main() {
    char username[256];          /* 能装 255 个字符 + 1 个结尾空字节 */
    printf("Enter your name: ");
    scanf("%255s", username);    /* 从键盘读,最多 255 个字符 */
    printf("hello, %s\n", username);
}

图说:三个新概念各就各位。 ① char username[256] 声明的数组能存 255 个 字符加一个 NULL 字节——代码为 0 的不可打印字符,写作 \0,C 的字符串 靠它结尾112。② scanf() 是 C 库的格式化输入函数:格式串 "%255s" 的 意思是「当字符串读(s),最多 255 个字符宽」,读到空白(空格、制表、换行)为止, 或者读满 255 个为止113。③ 参数 username 是数组名,在 C 里数组名可以 当常量指针用,所以能把数组起点交给 scanf 去填114。

链接是怎么回事:为什么头文件里没有实现

scanf 和 printf 的代码在哪?不在头文件里——这是初学者最常见的误会115。 机制是这样:编译器处理源文件时,遇到不在本文件里定义的函数调用,就把它标成 未解析符号(unresolved symbol)——没有地址可填。把地址填上的动作叫 链接(linking),干这活的工具叫链接器,它是编译器套装(如 gcc)的 一个组件116。C 语言本身不带输入输出、内存管理、字符串处理这些功能, 它们全在标准库里;链接器会自动去库里找到 printf 的实现,不用你操心117。

而库里 scanf 的下一层,是 read()——read 的实现不在库里,在操作系统 里;printf 的下一层是 write()。把这三层叠起来,就是 §2 那张四层图118:

  你的程序            C 库                操作系统
  ┌────────┐      ┌──────────┐      ┌────────────┐
  │ main() │───►  │ scanf()  │───►  │  read()    │───► 键盘
  │        │      │ (库里)   │      │ (内核里)   │
  └────────┘      └──────────┘      └────────────┘

图说:同样的四层结构,以后每个 I/O 主题都会再见到一次——读文件(第 4 章)、 读管道(第 13 章)、读终端(第 18 章),顶层是你,底层永远是内核。

getpid_demo.c 与 gettid_demo.c:第一次亲手调系统调用

getpid_demo.c 的全部本事就一行 getpid(),但它有历史意义:这是全书第一个 调用系统调用的程序。两个细节:① 它 include 了 <unistd.h>—— 系统调用的声明几乎都集中在这个头文件,这个名字以后会像条件反射一样出现; ② 返回值是 pid_t 类型的整数,printf 里用 %d 打出来119。

gettid_demo.c 多出来的东西只有一行:

#define _GNU_SOURCE      /* 必须出现在所有 #include 之前 */

这个 _GNU_SOURCE 是特性测试宏(feature test macro)。gettid() 是 Linux 特有的、不在 POSIX 里的函数;不定义这个宏,头文件就不给你看它的 声明,编译直接报错「找不到 gettid」。为什么要有这道闸?第 2 章讲可移植性时 会完整解释——现在记住:它是一扇门,推开才能用 Linux 私有功能,而推门这个 动作本身就是「我知道这程序可能跑不到别的 Unix 上」的签名120。

24. 深挖一层:易失与不易失、登录的词源

两个原书特意停下来讲的小知识点,各用一小段。

易失与不易失。 文件「住」在非易失存储(nonvolatile storage)上: 断电不丢数据的设备——磁带、磁盘、光盘、U 盘;主内存是易失存储(volatile), 断电即失。非易失设备也叫辅助存储/外存——名字是历史遗留,哪怕它看起来就插在 机器「里面」121。这条区分在第 6 章会变成具体机制:文件系统怎么把那套 inode/块结构写到磁盘上。

登录为什么叫 login。 log 这个动词在计算机出现之前就有一个词典义: 往航海日志里记录。login 的本义是「这次进入被记在日志里了」——Unix 确实 把每次登录记在一个充当日志的文件里(那个文件和它的接口,就是第 5 章的 「登录记账」)122。名词 login 之所以连写成一个词,纯粹是因为全世界 几百万个登录屏都这么写,约定俗成了。

25. 标准家族:一张表认全名字

原书后半章撒了一堆标准名,集中认一遍,免得出门撞见不认得:

名字 是什么 谁定的
POSIX.1-2024 / IEEE 1003.1 操作系统接口标准(= Open Group Base Specifications Issue 8) IEEE / Open Group
ISO/IEC 9945 POSIX 的 ISO 身份(同一份文件) ISO
Single UNIX Specification v4(SUSv4, 2018) 「能叫 UNIX」的门槛;除 POSIX 外还多一个 ncurses 标准 Open Group
X/Open、OSF 1996 年合并成 Open Group 的两个行业联盟 业界
Spec 1170 1994 年 X/Open 的可移植性指南,恰好 1,170 个接口,是 SUS 的前身 X/Open
C17 / C23(ISO/IEC 9899:2018) C 语言标准;POSIX.1-2024 基于 C17 ISO
glibc Linux 上 C 标准库的实现,带 GNU 扩展(不全是标准) GNU
man 页标准 Linux man-pages Project 维护,与 POSIX 并行 kernel.org 社区

图说:这张表左边一列是你在 man 页 CONFORMING TO 小节会见到的名字; 右边一列告诉你它管多宽。 记法很简单:POSIX/SUS 管「系统」,ISO 9899 管 「语言」,glibc 是具体实现123。


  1. 出处:「Chapter 1: Core Concepts」第 33 段(text/07-ch01-chapter-1-core-concepts.txt:33,搜「Hello, world」)。原文说这个程序最早出现在 1978 年 Kernighan 与 Ritchie 的 The C Programming Language 中,自此成为学一门新语言的第一个程序。 ↩

  2. 出处:「Chapter 1: Core Concepts」第 79 段(text/07-ch01-chapter-1-core-concepts.txt:79,搜「voilá」)。原文:运行程序,字符串就出现了;「It clearly isn't magic though, and a lot must be going on behind the scenes to make the characters appear on the screen.」 ↩

  3. 出处:「Chapter 1: Core Concepts」第 183 段(text/07-ch01-chapter-1-core-concepts.txt:183,搜「Resources are objects」)。 ↩

  4. 出处:「Chapter 1: Core Concepts」第 189 段(text/07-ch01-chapter-1-core-concepts.txt:189,搜「system resources」)。原文:System resources include hardware… They also include objects that aren't hardware, such as system data structures and files。 ↩

  5. 出处:「Chapter 1: Core Concepts」第 333 段(text/07-ch01-chapter-1-core-concepts.txt:333,搜「acquire all disk space」)。 ↩

  6. 出处:「Chapter 1: Core Concepts」第 207 段与第 207 段(text/07-ch01-chapter-1-core-concepts.txt:207,搜「application programming interface」;text/07-ch01-chapter-1-core-concepts.txt:213,搜「system calls」)。原文:操作系统提供的接口叫 API,「The functions in the API are called system calls」。 ↩

  7. 出处:「Chapter 1: Core Concepts」第 221–226 段(text/07-ch01-chapter-1-core-concepts.txt:226,搜「system program」)。原文:A program that makes direct requests for the services exposed in an operating system's API is called a system program。 ↩

  8. 出处:「Chapter 1: Core Concepts」第 236–240 段(text/07-ch01-chapter-1-core-concepts.txt:236,搜「compilers, assemblers」)。Stallman 的引文是「The kernel is an essential part of an operating system, but useless by itself」。 ↩↩

  9. 出处:「Chapter 1: Core Concepts」第 287–295 段(text/07-ch01-chapter-1-core-concepts.txt:294,搜「narrow definition」)。原文:In this book, I use the narrow definition of an operating system, namely that it is the kernel and nothing more。 ↩↩

  10. 出处:「Chapter 1: Core Concepts」第 297–313 段(text/07-ch01-chapter-1-core-concepts.txt:299,搜「entry point」)。 ↩

  11. 出处:「Chapter 1: Core Concepts」第 320 段(text/07-ch01-chapter-1-core-concepts.txt:320,搜「system space」)。原文:用户程序不允许访问系统空间,「If they try, the kernel terminates them」。 ↩

  12. 出处:「Chapter 1: Core Concepts」第 366–379 段(text/07-ch01-chapter-1-core-concepts.txt:371,搜「Process scheduling」)。 ↩

  13. 出处:「Chapter 1: Core Concepts」第 358–360 段(text/07-ch01-chapter-1-core-concepts.txt:360,搜「terminate itself」)。原文:A running program cannot even terminate itself; the best it can do is to ask the kernel to terminate it。 ↩

  14. 出处:「Chapter 1: Core Concepts」第 409 段(text/07-ch01-chapter-1-core-concepts.txt:409,搜「reads commands and carries them out」)。 ↩

  15. 出处:「Chapter 1: Core Concepts」第 436–444 段(text/07-ch01-chapter-1-core-concepts.txt:438,搜「single dash」)。 ↩

  16. 出处:「Chapter 1: Core Concepts」第 427 段(text/07-ch01-chapter-1-core-concepts.txt:427,搜「The command line is everything」)。 ↩

  17. 出处:「Chapter 1: Core Concepts」第 450–464 段(text/07-ch01-chapter-1-core-concepts.txt:452,搜「nonrequired」)。 ↩

  18. 出处:「Chapter 1: Core Concepts」第 489 段(text/07-ch01-chapter-1-core-concepts.txt:489,搜「presents a prompt」)。 ↩

  19. 出处:「Chapter 1: Core Concepts」第 501–506 段(text/07-ch01-chapter-1-core-concepts.txt:501,搜「programming language interpreter」)。 ↩

  20. 出处:「Chapter 1: Core Concepts」第 507 段(text/07-ch01-chapter-1-core-concepts.txt:507,搜「builtins」)。原文:内建命令更快,因为「调用函数比启动一个独立程序花费的时间少得多,后者需要内核介入」。 ↩

  21. 出处:「Chapter 1: Core Concepts」第 512–519 段(text/07-ch01-chapter-1-core-concepts.txt:514,搜「Stephen Bourne」)。 ↩

  22. 出处:「Chapter 1: Core Concepts」第 522–526 段(text/07-ch01-chapter-1-core-concepts.txt:523,搜「bash」)。 ↩

  23. 出处:「Chapter 1: Core Concepts」第 531 段(text/07-ch01-chapter-1-core-concepts.txt:531,搜「Authentication」)。 ↩

  24. 出处:「Chapter 1: Core Concepts」第 533–539 段(text/07-ch01-chapter-1-core-concepts.txt:535,搜「UID for short」)。 ↩

  25. 出处:「Chapter 1: Core Concepts」第 553–557 段(text/07-ch01-chapter-1-core-concepts.txt:554,搜「need not be an actual person」)。 ↩

  26. 出处:「Chapter 1: Core Concepts」第 558–565 段(text/07-ch01-chapter-1-core-concepts.txt:560,搜「GID for short」)。 ↩

  27. 出处:「Chapter 1: Core Concepts」第 566–572 段(text/07-ch01-chapter-1-core-concepts.txt:567,搜「id command」)。 ↩

  28. 出处:「Chapter 1: Core Concepts」第 586–591 段(text/07-ch01-chapter-1-core-concepts.txt:587,搜「superuser」)。 ↩

  29. 出处:「Chapter 1: Core Concepts」第 593–605 段(text/07-ch01-chapter-1-core-concepts.txt:597,搜「privileged instructions」)。 ↩

  30. 出处:「Chapter 1: Core Concepts」第 605 段(text/07-ch01-chapter-1-core-concepts.txt:605,搜「depend upon this separation of」)。原文:「The security, reliability, and integrity of the operating system depend upon this separation of powers.」 ↩

  31. 出处:「Chapter 1: Core Concepts」第 356–362 段(text/07-ch01-chapter-1-core-concepts.txt:360,搜「terminate itself」)。 ↩

  32. 出处:「Chapter 1: Core Concepts」第 608–619 段(text/07-ch01-chapter-1-core-concepts.txt:611,搜「environment list」)。 ↩

  33. 出处:「Chapter 1: Core Concepts」第 617–631 段(text/07-ch01-chapter-1-core-concepts.txt:629,搜「stored as a string」)。 ↩

  34. 出处:「Chapter 1: Core Concepts」第 632–644 段(text/07-ch01-chapter-1-core-concepts.txt:636,搜「inherits」)。 ↩

  35. 出处:「Chapter 1: Core Concepts」第 645–654 段(text/07-ch01-chapter-1-core-concepts.txt:646,搜「printenv」)。 ↩

  36. 出处:「Chapter 1: Core Concepts」第 655–678 段(text/07-ch01-chapter-1-core-concepts.txt:664,搜「getenv」)。 ↩

  37. 出处:「Chapter 1: Core Concepts」第 681–682 段(text/07-ch01-chapter-1-core-concepts.txt:682,搜「filesystem」)。 ↩

  38. 出处:「Chapter 1: Core Concepts」第 683–686 段(text/07-ch01-chapter-1-core-concepts.txt:686,搜「everything in the UNIX system is a file」)。 ↩

  39. 出处:「Chapter 1: Core Concepts」第 700–704 段(text/07-ch01-chapter-1-core-concepts.txt:701,搜「linear sequence of bytes」)。 ↩

  40. 出处:「Chapter 1: Core Concepts」第 712–728 段(text/07-ch01-chapter-1-core-concepts.txt:715,搜「Directories」)。 ↩

  41. 出处:「Chapter 1: Core Concepts」第 720–728 段(text/07-ch01-chapter-1-core-concepts.txt:721,搜「special files」)。套接字本书不讲见第 727 段(text/07-ch01-chapter-1-core-concepts.txt:727,搜「don't cover」)。 ↩↩

  42. 出处:「Chapter 1: Core Concepts」第 730–737 段(text/07-ch01-chapter-1-core-concepts.txt:736,搜「file mode」)。 ↩

  43. 出处:「Chapter 1: Core Concepts」第 738–752 段(text/07-ch01-chapter-1-core-concepts.txt:750,搜「inode」)。 ↩

  44. 出处:「Chapter 1: Core Concepts」第 753–756 段(text/07-ch01-chapter-1-core-concepts.txt:753,搜「not part of the status」)。 ↩

  45. 出处:「Chapter 1: Core Concepts」第 860–863 段(text/07-ch01-chapter-1-core-concepts.txt:861,搜「passports」)。 ↩↩

  46. 出处:「Chapter 1: Core Concepts」第 770–776 段(text/07-ch01-chapter-1-core-concepts.txt:772,搜「links」)。 ↩

  47. 出处:「Chapter 1: Core Concepts」第 777–781 段(text/07-ch01-chapter-1-core-concepts.txt:778,搜「dot-dot」)。 ↩

  48. 出处:「Chapter 7: The Directory Hierarchy」第 32 段(text/13-ch07-chapter-7-the-directory-hierarchy.txt:32,搜「created and modified only by」)。这句话原书在第 7 章展开;第 1 章只给了目录表的定义。 ↩

  49. 出处:「Chapter 7: The Directory Hierarchy」第 37 段(text/13-ch07-chapter-7-the-directory-hierarchy.txt:37,搜「cat and od commands」)与第 44 段(text/13-ch07-chapter-7-the-directory-hierarchy.txt:44,搜「Is a directory」)。 ↩

  50. 出处:「Chapter 1: Core Concepts」第 876–892 段(text/07-ch01-chapter-1-core-concepts.txt:876,搜「NAME_MAX」)。 ↩

  51. 出处:「Chapter 1: Core Concepts」第 990–996 段(text/07-ch01-chapter-1-core-concepts.txt:992,搜「symbolic link」)。 ↩

  52. 出处:「Chapter 1: Core Concepts」第 1002–1007 段(text/07-ch01-chapter-1-core-concepts.txt:1003,搜「circular references」)。 ↩

  53. 出处:「Chapter 1: Core Concepts」第 894–899 段(text/07-ch01-chapter-1-core-concepts.txt:896,搜「directory hierarchy」)。 ↩

  54. 出处:「Chapter 1: Core Concepts」第 913–920 段(text/07-ch01-chapter-1-core-concepts.txt:914,搜「acyclic graph」)。 ↩↩

  55. 出处:「Chapter 1: Core Concepts」第 921–927 段(text/07-ch01-chapter-1-core-concepts.txt:925,搜「mounting」)。 ↩

  56. 出处:「Chapter 1: Core Concepts」第 1011 段(text/07-ch01-chapter-1-core-concepts.txt:1011,搜「An absolute pathname starts at the root」)。 ↩

  57. 出处:「Chapter 1: Core Concepts」第 1025 段(text/07-ch01-chapter-1-core-concepts.txt:1025,搜「A relative pathname starts in the current」)。 ↩

  58. 出处:「Chapter 1: Core Concepts」第 1030–1036 段(text/07-ch01-chapter-1-core-concepts.txt:1031,搜「PWD」)。 ↩

  59. 出处:「Chapter 1: Core Concepts」第 1038–1041 段(text/07-ch01-chapter-1-core-concepts.txt:1041,搜「4096」)。 ↩

  60. 出处:「Chapter 1: Core Concepts」第 928–964 段(text/07-ch01-chapter-1-core-concepts.txt:941,搜「top-level directories」)。 ↩

  61. 出处:「Chapter 1: Core Concepts」第 941–943 段(text/07-ch01-chapter-1-core-concepts.txt:942,搜「required by POSIX」)。 ↩

  62. 出处:「Chapter 1: Core Concepts」第 967 段(text/07-ch01-chapter-1-core-concepts.txt:967,搜「shareability」)。 ↩

  63. 出处:「Chapter 1: Core Concepts」第 1043–1055 段(text/07-ch01-chapter-1-core-concepts.txt:1052,搜「executable」)。 ↩

  64. 出处:「Chapter 1: Core Concepts」第 1074–1080 段(text/07-ch01-chapter-1-core-concepts.txt:1078,搜「instance of a running program」)。 ↩

  65. 出处:「Chapter 1: Core Concepts」第 1081–1097 段(text/07-ch01-chapter-1-core-concepts.txt:1093,搜「collection of all resources」)。 ↩

  66. 出处:「Chapter 1: Core Concepts」第 1098–1129 段(text/07-ch01-chapter-1-core-concepts.txt:1114,搜「getpid」)。 ↩

  67. 出处:「Chapter 1: Core Concepts」第 1140–1141 段(text/07-ch01-chapter-1-core-concepts.txt:1140,搜「different PID」)。 ↩

  68. 出处:「Chapter 1: Core Concepts」第 1143–1157 段(text/07-ch01-chapter-1-core-concepts.txt:1155,搜「flow of control」)。 ↩

  69. 出处:「Chapter 1: Core Concepts」第 1166–1175 段(text/07-ch01-chapter-1-core-concepts.txt:1171,搜「task_struct」)。 ↩

  70. 出处:「Chapter 1: Core Concepts」第 1176–1207 段(text/07-ch01-chapter-1-core-concepts.txt:1179,搜「two-pronged」)。 ↩

  71. 出处:「Chapter 1: Core Concepts」第 1214–1239 段(text/07-ch01-chapter-1-core-concepts.txt:1227,搜「Manual Sections」)。 ↩

  72. 出处:「Chapter 1: Core Concepts」第 1282 段(text/07-ch01-chapter-1-core-concepts.txt:1282,搜「required to contain man pages」)。 ↩

  73. 出处:「Chapter 1: Core Concepts」第 1247–1261 段(text/07-ch01-chapter-1-core-concepts.txt:1253,搜「man pages on your system」)。 ↩↩

  74. 出处:「Chapter 1: Core Concepts」第 1525–1559 段(text/07-ch01-chapter-1-core-concepts.txt:1528,搜「NAME」)。 ↩

  75. 出处:「Chapter 1: Core Concepts」第 1596 段(text/07-ch01-chapter-1-core-concepts.txt:1596,搜「RETURN VALUE sections」)。 ↩

  76. 出处:「Chapter 1: Core Concepts」第 1568 段(text/07-ch01-chapter-1-core-concepts.txt:1568,搜「Square brackets」)。 ↩

  77. 出处:「Chapter 1: Core Concepts」第 1463–1475 段(text/07-ch01-chapter-1-core-concepts.txt:1464,搜「SPACEBAR」)。 ↩

  78. 出处:「Chapter 1: Core Concepts」第 1457 段(text/07-ch01-chapter-1-core-concepts.txt:1457,搜「usually a symbolic」)。pager 默认链到 less 亦见第 1457 段。 ↩

  79. 出处:「Chapter 1: Core Concepts」第 1600–1741 段(text/07-ch01-chapter-1-core-concepts.txt:1638,搜「-k and -K」)。 ↩

  80. 出处:「Chapter 1: Core Concepts」第 1332–1357 段(text/07-ch01-chapter-1-core-concepts.txt:1334,搜「Info」)。 ↩

  81. 出处:「Chapter 1: Core Concepts」第 1396–1439 段(text/07-ch01-chapter-1-core-concepts.txt:1398,搜「help」)。 ↩

  82. 出处:「Chapter 1: Core Concepts」第 1441–1445 段(text/07-ch01-chapter-1-core-concepts.txt:1444,搜「GNU C Library Reference Manual」)。 ↩

  83. 出处:「Chapter 1: Core Concepts」第 1750 段(text/07-ch01-chapter-1-core-concepts.txt:1750,搜「haphazard history」)。 ↩↩

  84. 出处:「Chapter 1: Core Concepts」第 1763–1776 段(text/07-ch01-chapter-1-core-concepts.txt:1770,搜「rewritten in C」)。 ↩

  85. 出处:「Chapter 1: Core Concepts」第 1784–1810 段(text/07-ch01-chapter-1-core-concepts.txt:1797,搜「System V」)。 ↩

  86. 出处:「Chapter 1: Core Concepts」第 1812–1827 段(text/07-ch01-chapter-1-core-concepts.txt:1820,搜「recursive acronym」)。 ↩

  87. 出处:「Chapter 1: Core Concepts」第 1828–1837 段(text/07-ch01-chapter-1-core-concepts.txt:1833,搜「Linus Torvalds」)。 ↩

  88. 出处:「Chapter 1: Core Concepts」第 1839–1851 段(text/07-ch01-chapter-1-core-concepts.txt:1840,搜「Novell」)。 ↩

  89. 出处:「Chapter 1: Core Concepts」第 1864–1876 段(text/07-ch01-chapter-1-core-concepts.txt:1872,搜「1003.1-2024」)。 ↩

  90. 出处:「Chapter 1: Core Concepts」第 1877–1883 段(text/07-ch01-chapter-1-core-concepts.txt:1882,搜「source code level」)。 ↩

  91. 出处:「Chapter 1: Core Concepts」第 1902–1914 段(text/07-ch01-chapter-1-core-concepts.txt:1903,搜「Base definitions」)。 ↩

  92. 出处:「Chapter 1: Core Concepts」第 1915–1920 段(text/07-ch01-chapter-1-core-concepts.txt:1915,搜「outside of its scope」)。 ↩

  93. 出处:「Chapter 1: Core Concepts」第 1890–1896 段(text/07-ch01-chapter-1-core-concepts.txt:1895,搜「1,833」)与第 1930–1933 段(text/07-ch01-chapter-1-core-concepts.txt:1932,搜「partly compliant」)。 ↩

  94. 出处:「Chapter 1: Core Concepts」第 1940 段(text/07-ch01-chapter-1-core-concepts.txt:1940,搜「man standards」)。 ↩

  95. 出处:「Chapter 1: Core Concepts」第 1952–1962 段(text/07-ch01-chapter-1-core-concepts.txt:1959,搜「glibc」)。 ↩

  96. 出处:「Chapter 1: Core Concepts」第 1944–1992 段(text/07-ch01-chapter-1-core-concepts.txt:1947,搜「C23」)。 ↩

  97. 出处:「Chapter 1: Core Concepts」第 811–816 段(text/07-ch01-chapter-1-core-concepts.txt:812,搜「display the contents」)。 ↩

  98. 出处:「Chapter 1: Core Concepts」第 833–850 段(text/07-ch01-chapter-1-core-concepts.txt:848,搜「once again the parent」)。原文:cd .. 之后列出的文件名与进 chapters 之前相同,证明回到了父目录。 ↩

  99. 出处:「Chapter 1: Core Concepts」第 570–584 段(text/07-ch01-chapter-1-core-concepts.txt:576,搜「id syslog」)。 ↩

  100. 出处:「Chapter 1: Core Concepts」第 1105–1113 段(text/07-ch01-chapter-1-core-concepts.txt:1106,搜「PID TTY」)。 ↩

  101. 出处:「Chapter 1: Core Concepts」第 1136–1141 段(text/07-ch01-chapter-1-core-concepts.txt:1138,搜「18805」)。 ↩

  102. 出处:「Chapter 1: Core Concepts」第 593–606 段(text/07-ch01-chapter-1-core-concepts.txt:605,搜「depend upon this separation of」)。 ↩

  103. 出处:「Chapter 1: Core Concepts」第 1852–1862 段(text/07-ch01-chapter-1-core-concepts.txt:1854,搜「A Quarter Century of UNIX」)。 ↩↩

  104. 出处:「Chapter 1: Core Concepts」第 1247–1252 段(text/07-ch01-chapter-1-core-concepts.txt:1247,搜「taught Unix system programming」)。作者自述教 Unix 系统编程多年,学生常说不需要学 man 页——他以此为靶子展开反驳。 ↩

  105. 出处:「Chapter 1: Core Concepts」第 406–408 段(text/07-ch01-chapter-1-core-concepts.txt:408,搜「graphical user interface」)。原文明确 GUI 不在本书讨论范围。 ↩

  106. 出处:「Chapter 1: Core Concepts」第 215–217 段(text/07-ch01-chapter-1-core-concepts.txt:216,搜「class interface」)。 ↩

  107. 出处:「Chapter 1: Core Concepts」第 296–313 段(text/07-ch01-chapter-1-core-concepts.txt:296,搜「collection of interacting programs」)。 ↩

  108. 出处:「Chapter 1: Core Concepts」第 782–800 段(text/07-ch01-chapter-1-core-concepts.txt:783,搜「Reference」)。书中 Figure 1-5 给出的 jammy 目录表,数字 53、2、12、185、282 取自原图。 ↩

  109. 出处:「Chapter 1: Core Concepts」第 852–875 段(text/07-ch01-chapter-1-core-concepts.txt:874,搜「three different names」)。引用计数归零才回收这一点是 Unix 通用知识,原书在本章只讲到「多个名字指向同一 inode」,第 7 章有完整机制。 ↩

  110. 出处:「Chapter 1: Core Concepts」第 1002–1007 段(text/07-ch01-chapter-1-core-concepts.txt:1003,搜「circular references」)。 ↩

  111. 出处:「Chapter 1: Core Concepts」第 1621–1624 段(text/07-ch01-chapter-1-core-concepts.txt:1623,搜「pre-defined order」)。原文:默认按预定顺序搜所有节,「show only the first page found, even if page exists in several sections」。 ↩

  112. 出处:「Chapter 1: Core Concepts」第 98–102 段(text/07-ch01-chapter-1-core-concepts.txt:100,搜「NULL byte」)。 ↩

  113. 出处:「Chapter 1: Core Concepts」第 116 段(text/07-ch01-chapter-1-core-concepts.txt:116,搜「format specification」)。 ↩

  114. 出处:「Chapter 1: Core Concepts」第 119–122 段(text/07-ch01-chapter-1-core-concepts.txt:121,搜「constant pointer」)。 ↩

  115. 出处:「Chapter 1: Core Concepts」第 124–130 段(text/07-ch01-chapter-1-core-concepts.txt:126,搜「mistakenly」)。 ↩

  116. 出处:「Chapter 1: Core Concepts」第 136 段(text/07-ch01-chapter-1-core-concepts.txt:136,搜「unresolved symbol」)。 ↩

  117. 出处:「Chapter 1: Core Concepts」第 142–149 段(text/07-ch01-chapter-1-core-concepts.txt:144,搜「standard library」)。 ↩

  118. 出处:「Chapter 1: Core Concepts」第 160 段(text/07-ch01-chapter-1-core-concepts.txt:160,搜「Figure 1-1」)。三层结构图是原书 Figure 1-1 的重绘。 ↩

  119. 出处:「Chapter 1: Core Concepts」第 1128 段(text/07-ch01-chapter-1-core-concepts.txt:1128,搜「system call declarations」)。 ↩

  120. 出处:「Chapter 1: Core Concepts」第 1195–1199 段(text/07-ch01-chapter-1-core-concepts.txt:1198,搜「feature test macro」)。 ↩

  121. 出处:「Chapter 1: Core Concepts」第 689 段(text/07-ch01-chapter-1-core-concepts.txt:689,搜「nonvolatile」)。 ↩

  122. 出处:「Chapter 1: Core Concepts」第 546 段(text/07-ch01-chapter-1-core-concepts.txt:546,搜「logbook」)。 ↩

  123. 出处:「Chapter 1: Core Concepts」第 1864–1997 段(text/07-ch01-chapter-1-core-concepts.txt:1868,搜「IEEE 1003」) · :1887(搜「Spec 1170」) · :1924(搜「9899:2018」) · :1959(搜「glibc」)。 ↩