核心概念 — 系统编程与 Unix 的地基
核心概念 — 系统编程与 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。
两个要刻进脑子的区分:
- 内容 ≠ 状态。内容是文件的数据;状态存在一个叫 inode 的数据结构里, 内容可能散布在存储设备的许多块(block)上,两者分开存放43。
- 文件名不是文件的属性。 一个非目录文件可以有多个名字,名字不归文件所有, 归包含它的目录所有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。
三件实用的事:
- 判据:POSIX 不覆盖图形、数据库接口、记录式 I/O、二进制可移植性、 系统配置——在这些地方求「可移植」是白求92。
- UNIX 商标:归 Open Group 所有,一个系统要自称 UNIX, 得实现 Single UNIX Specification 的全部接口(2018 版含 1,833 个接口); Linux、FreeBSD 只是部分兼容93。
- 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.bibls 不带参数,列的是当前工作目录的内容(§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.texcd 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)。
- 系统程序 = 直接通过 API 向内核请求服务的程序;系统调用就是那个 API 的函数。
- 内核是唯一被允许碰硬件的代码,CPU 的两级特权是这条规矩的硬件根基。
- shell 是可替换的普通程序——它不是操作系统的一部分;bash 只是最常见的一个。
- 用户有 UID、组有 GID,root 是 UID 0;连系统服务也是「用户」。
- 环境是
名字=值的字符串表,值永远是字符串,新程序继承一份拷贝。 - 内核眼里文件只是字节序列;文件名不是文件的属性,是目录表里的一条链接。
- 硬链接共享 inode,符号链接是「装着别人名字的文件」,能指向目录所以会成环。
- Unix 只有一棵目录层次,新盘靠挂载接入;路径分绝对(
/…)和相对。 - 进程 = 程序的一次运行实例,有 PID;Linux 把线程也当进程(task_struct)管。
- 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。
Footnotes
-
出处:「Chapter 1: Core Concepts」第 33 段(
text/07-ch01-chapter-1-core-concepts.txt:33,搜「Hello, world」)。原文说这个程序最早出现在 1978 年 Kernighan 与 Ritchie 的 The C Programming Language 中,自此成为学一门新语言的第一个程序。 ↩ -
出处:「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.」 ↩ -
出处:「Chapter 1: Core Concepts」第 183 段(
text/07-ch01-chapter-1-core-concepts.txt:183,搜「Resources are objects」)。 ↩ -
出处:「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。 ↩ -
出处:「Chapter 1: Core Concepts」第 333 段(
text/07-ch01-chapter-1-core-concepts.txt:333,搜「acquire all disk space」)。 ↩ -
出处:「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」。 ↩ -
出处:「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。 ↩ -
出处:「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」。 ↩ ↩2 -
出处:「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。 ↩ ↩2 -
出处:「Chapter 1: Core Concepts」第 297–313 段(
text/07-ch01-chapter-1-core-concepts.txt:299,搜「entry point」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 320 段(
text/07-ch01-chapter-1-core-concepts.txt:320,搜「system space」)。原文:用户程序不允许访问系统空间,「If they try, the kernel terminates them」。 ↩ -
出处:「Chapter 1: Core Concepts」第 366–379 段(
text/07-ch01-chapter-1-core-concepts.txt:371,搜「Process scheduling」)。 ↩ -
出处:「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。 ↩ -
出处:「Chapter 1: Core Concepts」第 409 段(
text/07-ch01-chapter-1-core-concepts.txt:409,搜「reads commands and carries them out」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 436–444 段(
text/07-ch01-chapter-1-core-concepts.txt:438,搜「single dash」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 427 段(
text/07-ch01-chapter-1-core-concepts.txt:427,搜「The command line is everything」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 450–464 段(
text/07-ch01-chapter-1-core-concepts.txt:452,搜「nonrequired」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 489 段(
text/07-ch01-chapter-1-core-concepts.txt:489,搜「presents a prompt」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 501–506 段(
text/07-ch01-chapter-1-core-concepts.txt:501,搜「programming language interpreter」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 507 段(
text/07-ch01-chapter-1-core-concepts.txt:507,搜「builtins」)。原文:内建命令更快,因为「调用函数比启动一个独立程序花费的时间少得多,后者需要内核介入」。 ↩ -
出处:「Chapter 1: Core Concepts」第 512–519 段(
text/07-ch01-chapter-1-core-concepts.txt:514,搜「Stephen Bourne」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 522–526 段(
text/07-ch01-chapter-1-core-concepts.txt:523,搜「bash」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 531 段(
text/07-ch01-chapter-1-core-concepts.txt:531,搜「Authentication」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 533–539 段(
text/07-ch01-chapter-1-core-concepts.txt:535,搜「UID for short」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 553–557 段(
text/07-ch01-chapter-1-core-concepts.txt:554,搜「need not be an actual person」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 558–565 段(
text/07-ch01-chapter-1-core-concepts.txt:560,搜「GID for short」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 566–572 段(
text/07-ch01-chapter-1-core-concepts.txt:567,搜「id command」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 586–591 段(
text/07-ch01-chapter-1-core-concepts.txt:587,搜「superuser」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 593–605 段(
text/07-ch01-chapter-1-core-concepts.txt:597,搜「privileged instructions」)。 ↩ -
出处:「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.」 ↩ -
出处:「Chapter 1: Core Concepts」第 356–362 段(
text/07-ch01-chapter-1-core-concepts.txt:360,搜「terminate itself」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 608–619 段(
text/07-ch01-chapter-1-core-concepts.txt:611,搜「environment list」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 617–631 段(
text/07-ch01-chapter-1-core-concepts.txt:629,搜「stored as a string」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 632–644 段(
text/07-ch01-chapter-1-core-concepts.txt:636,搜「inherits」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 645–654 段(
text/07-ch01-chapter-1-core-concepts.txt:646,搜「printenv」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 655–678 段(
text/07-ch01-chapter-1-core-concepts.txt:664,搜「getenv」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 681–682 段(
text/07-ch01-chapter-1-core-concepts.txt:682,搜「filesystem」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 683–686 段(
text/07-ch01-chapter-1-core-concepts.txt:686,搜「everything in the UNIX system is a file」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 700–704 段(
text/07-ch01-chapter-1-core-concepts.txt:701,搜「linear sequence of bytes」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 712–728 段(
text/07-ch01-chapter-1-core-concepts.txt:715,搜「Directories」)。 ↩ -
出处:「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」)。 ↩ ↩2 -
出处:「Chapter 1: Core Concepts」第 730–737 段(
text/07-ch01-chapter-1-core-concepts.txt:736,搜「file mode」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 738–752 段(
text/07-ch01-chapter-1-core-concepts.txt:750,搜「inode」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 753–756 段(
text/07-ch01-chapter-1-core-concepts.txt:753,搜「not part of the status」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 860–863 段(
text/07-ch01-chapter-1-core-concepts.txt:861,搜「passports」)。 ↩ ↩2 -
出处:「Chapter 1: Core Concepts」第 770–776 段(
text/07-ch01-chapter-1-core-concepts.txt:772,搜「links」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 777–781 段(
text/07-ch01-chapter-1-core-concepts.txt:778,搜「dot-dot」)。 ↩ -
出处:「Chapter 7: The Directory Hierarchy」第 32 段(
text/13-ch07-chapter-7-the-directory-hierarchy.txt:32,搜「created and modified only by」)。这句话原书在第 7 章展开;第 1 章只给了目录表的定义。 ↩ -
出处:「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」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 876–892 段(
text/07-ch01-chapter-1-core-concepts.txt:876,搜「NAME_MAX」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 990–996 段(
text/07-ch01-chapter-1-core-concepts.txt:992,搜「symbolic link」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1002–1007 段(
text/07-ch01-chapter-1-core-concepts.txt:1003,搜「circular references」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 894–899 段(
text/07-ch01-chapter-1-core-concepts.txt:896,搜「directory hierarchy」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 913–920 段(
text/07-ch01-chapter-1-core-concepts.txt:914,搜「acyclic graph」)。 ↩ ↩2 -
出处:「Chapter 1: Core Concepts」第 921–927 段(
text/07-ch01-chapter-1-core-concepts.txt:925,搜「mounting」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1011 段(
text/07-ch01-chapter-1-core-concepts.txt:1011,搜「An absolute pathname starts at the root」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1025 段(
text/07-ch01-chapter-1-core-concepts.txt:1025,搜「A relative pathname starts in the current」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1030–1036 段(
text/07-ch01-chapter-1-core-concepts.txt:1031,搜「PWD」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1038–1041 段(
text/07-ch01-chapter-1-core-concepts.txt:1041,搜「4096」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 928–964 段(
text/07-ch01-chapter-1-core-concepts.txt:941,搜「top-level directories」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 941–943 段(
text/07-ch01-chapter-1-core-concepts.txt:942,搜「required by POSIX」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 967 段(
text/07-ch01-chapter-1-core-concepts.txt:967,搜「shareability」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1043–1055 段(
text/07-ch01-chapter-1-core-concepts.txt:1052,搜「executable」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1074–1080 段(
text/07-ch01-chapter-1-core-concepts.txt:1078,搜「instance of a running program」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1081–1097 段(
text/07-ch01-chapter-1-core-concepts.txt:1093,搜「collection of all resources」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1098–1129 段(
text/07-ch01-chapter-1-core-concepts.txt:1114,搜「getpid」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1140–1141 段(
text/07-ch01-chapter-1-core-concepts.txt:1140,搜「different PID」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1143–1157 段(
text/07-ch01-chapter-1-core-concepts.txt:1155,搜「flow of control」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1166–1175 段(
text/07-ch01-chapter-1-core-concepts.txt:1171,搜「task_struct」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1176–1207 段(
text/07-ch01-chapter-1-core-concepts.txt:1179,搜「two-pronged」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1214–1239 段(
text/07-ch01-chapter-1-core-concepts.txt:1227,搜「Manual Sections」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1282 段(
text/07-ch01-chapter-1-core-concepts.txt:1282,搜「required to contain man pages」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1247–1261 段(
text/07-ch01-chapter-1-core-concepts.txt:1253,搜「man pages on your system」)。 ↩ ↩2 -
出处:「Chapter 1: Core Concepts」第 1525–1559 段(
text/07-ch01-chapter-1-core-concepts.txt:1528,搜「NAME」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1596 段(
text/07-ch01-chapter-1-core-concepts.txt:1596,搜「RETURN VALUE sections」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1568 段(
text/07-ch01-chapter-1-core-concepts.txt:1568,搜「Square brackets」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1463–1475 段(
text/07-ch01-chapter-1-core-concepts.txt:1464,搜「SPACEBAR」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1457 段(
text/07-ch01-chapter-1-core-concepts.txt:1457,搜「usually a symbolic」)。pager 默认链到 less 亦见第 1457 段。 ↩ -
出处:「Chapter 1: Core Concepts」第 1600–1741 段(
text/07-ch01-chapter-1-core-concepts.txt:1638,搜「-k and -K」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1332–1357 段(
text/07-ch01-chapter-1-core-concepts.txt:1334,搜「Info」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1396–1439 段(
text/07-ch01-chapter-1-core-concepts.txt:1398,搜「help」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1441–1445 段(
text/07-ch01-chapter-1-core-concepts.txt:1444,搜「GNU C Library Reference Manual」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1750 段(
text/07-ch01-chapter-1-core-concepts.txt:1750,搜「haphazard history」)。 ↩ ↩2 -
出处:「Chapter 1: Core Concepts」第 1763–1776 段(
text/07-ch01-chapter-1-core-concepts.txt:1770,搜「rewritten in C」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1784–1810 段(
text/07-ch01-chapter-1-core-concepts.txt:1797,搜「System V」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1812–1827 段(
text/07-ch01-chapter-1-core-concepts.txt:1820,搜「recursive acronym」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1828–1837 段(
text/07-ch01-chapter-1-core-concepts.txt:1833,搜「Linus Torvalds」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1839–1851 段(
text/07-ch01-chapter-1-core-concepts.txt:1840,搜「Novell」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1864–1876 段(
text/07-ch01-chapter-1-core-concepts.txt:1872,搜「1003.1-2024」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1877–1883 段(
text/07-ch01-chapter-1-core-concepts.txt:1882,搜「source code level」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1902–1914 段(
text/07-ch01-chapter-1-core-concepts.txt:1903,搜「Base definitions」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1915–1920 段(
text/07-ch01-chapter-1-core-concepts.txt:1915,搜「outside of its scope」)。 ↩ -
出处:「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」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1940 段(
text/07-ch01-chapter-1-core-concepts.txt:1940,搜「man standards」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1952–1962 段(
text/07-ch01-chapter-1-core-concepts.txt:1959,搜「glibc」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1944–1992 段(
text/07-ch01-chapter-1-core-concepts.txt:1947,搜「C23」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 811–816 段(
text/07-ch01-chapter-1-core-concepts.txt:812,搜「display the contents」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 833–850 段(
text/07-ch01-chapter-1-core-concepts.txt:848,搜「once again the parent」)。原文:cd .. 之后列出的文件名与进 chapters 之前相同,证明回到了父目录。 ↩ -
出处:「Chapter 1: Core Concepts」第 570–584 段(
text/07-ch01-chapter-1-core-concepts.txt:576,搜「id syslog」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1105–1113 段(
text/07-ch01-chapter-1-core-concepts.txt:1106,搜「PID TTY」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1136–1141 段(
text/07-ch01-chapter-1-core-concepts.txt:1138,搜「18805」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 593–606 段(
text/07-ch01-chapter-1-core-concepts.txt:605,搜「depend upon this separation of」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1852–1862 段(
text/07-ch01-chapter-1-core-concepts.txt:1854,搜「A Quarter Century of UNIX」)。 ↩ ↩2 -
出处:「Chapter 1: Core Concepts」第 1247–1252 段(
text/07-ch01-chapter-1-core-concepts.txt:1247,搜「taught Unix system programming」)。作者自述教 Unix 系统编程多年,学生常说不需要学 man 页——他以此为靶子展开反驳。 ↩ -
出处:「Chapter 1: Core Concepts」第 406–408 段(
text/07-ch01-chapter-1-core-concepts.txt:408,搜「graphical user interface」)。原文明确 GUI 不在本书讨论范围。 ↩ -
出处:「Chapter 1: Core Concepts」第 215–217 段(
text/07-ch01-chapter-1-core-concepts.txt:216,搜「class interface」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 296–313 段(
text/07-ch01-chapter-1-core-concepts.txt:296,搜「collection of interacting programs」)。 ↩ -
出处:「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 取自原图。 ↩ -
出处:「Chapter 1: Core Concepts」第 852–875 段(
text/07-ch01-chapter-1-core-concepts.txt:874,搜「three different names」)。引用计数归零才回收这一点是 Unix 通用知识,原书在本章只讲到「多个名字指向同一 inode」,第 7 章有完整机制。 ↩ -
出处:「Chapter 1: Core Concepts」第 1002–1007 段(
text/07-ch01-chapter-1-core-concepts.txt:1003,搜「circular references」)。 ↩ -
出处:「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」。 ↩ -
出处:「Chapter 1: Core Concepts」第 98–102 段(
text/07-ch01-chapter-1-core-concepts.txt:100,搜「NULL byte」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 116 段(
text/07-ch01-chapter-1-core-concepts.txt:116,搜「format specification」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 119–122 段(
text/07-ch01-chapter-1-core-concepts.txt:121,搜「constant pointer」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 124–130 段(
text/07-ch01-chapter-1-core-concepts.txt:126,搜「mistakenly」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 136 段(
text/07-ch01-chapter-1-core-concepts.txt:136,搜「unresolved symbol」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 142–149 段(
text/07-ch01-chapter-1-core-concepts.txt:144,搜「standard library」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 160 段(
text/07-ch01-chapter-1-core-concepts.txt:160,搜「Figure 1-1」)。三层结构图是原书 Figure 1-1 的重绘。 ↩ -
出处:「Chapter 1: Core Concepts」第 1128 段(
text/07-ch01-chapter-1-core-concepts.txt:1128,搜「system call declarations」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 1195–1199 段(
text/07-ch01-chapter-1-core-concepts.txt:1198,搜「feature test macro」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 689 段(
text/07-ch01-chapter-1-core-concepts.txt:689,搜「nonvolatile」)。 ↩ -
出处:「Chapter 1: Core Concepts」第 546 段(
text/07-ch01-chapter-1-core-concepts.txt:546,搜「logbook」)。 ↩ -
出处:「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」)。 ↩