第 2 章 基本概念 (Fundamental Concepts)

      +

      核心结论

      • 内核职责:Linux 内核(典型路径 /boot/vmlinuz)负责进程调度、内存管理、提供文件系统、创建与终止进程、设备访问、网络收发、系统调用 API;用户进程对硬件的访问一律通过内核。

      • 用户态与内核态:CPU 至少有两种执行模式;用户态只能访问用户空间,内核态可访问全部内存;只有内核态能执行 halt、访问 MMU、发起设备 I/O——这是「为什么用户进程必须通过系统调用访问硬件」的根本原因。

      • 通用 I/O 模型:UNIX 把所有文件(普通文件、设备、管道、套接字)抽象为「字节流」,统一通过 open/read/write/close 访问——这是「一次编写,所有文件通用」的哲学基础。

      • 进程模型:进程 = 正在执行的程序实例;包含 text/data/bss/heap/stack 段;用 PID 标识;通过 fork+execve 创建;进程通过 UID/GID/补充组 ID 获得权限。

      • 文件系统:单根目录树(/ 为根);文件类型包括普通文件、目录、设备、管道、套接字、符号链接;每个文件有 owner/group + 三组 9 个权限位。

      • IPC 与信号:信号是软中断;进程间通信包括管道/FIFO、套接字、消息队列、信号量、共享内存、文件锁——这些机制源自 BSD(sockets)和 SysV(IPC)两大传统。

      • 线程与会话:多线程共享进程的虚拟内存;进程组、作业控制、会话、控制终端、伪终端构成「shell 工作环境」的基础。

      本章主旨

      本章是全书的总览,铺陈 Linux 系统编程涉及的全部核心抽象:内核与用户态、文件系统与权限、进程与内存、信号与 IPC、线程与会话。它不深入 API 细节,而是给读者一张「地图」——后面章节(文件 I/O 第 4-5 章、内存第 7 章、凭证第 9 章、信号第 20 章等)会逐项展开。本章的价值在于建立「从进程视角看 vs 从内核视角看」的双重视角。

      一、核心概念

      本章围绕 7 个核心概念展开:先理解「什么是操作系统内核」,再理解「用户态如何通过系统调用进入内核」,最后理解「进程、文件、IPC、信号」这些抽象。

      概念 定义 + 重要性 实现提示

      内核与核心任务

      内核 = 管理系统资源的软件层;任务包括进程调度、虚拟内存管理、文件系统、进程创建/终止、设备访问、网络收发、提供系统调用 API。

      §2.1;Linux 内核镜像在 /boot/vmlinuz;用户进程无法绕过内核访问硬件。

      用户态 vs 内核态

      CPU 在用户态只能访问用户空间;在内核态可访问全部内存。系统调用/中断/异常触发模式切换。

      §2.1.2;某些指令(halt、MMU 操作、设备 I/O)必须在内核态执行。

      通用 I/O 模型 (Universality of I/O)

      同一组系统调用 open/read/write/close 用于所有类型文件(普通文件、设备、管道、套接字等);内核把 I/O 请求翻译为对应文件系统的操作。

      §2.5;这一抽象使同一份代码可以处理磁盘文件与终端。

      进程与内存布局

      进程 = 正在执行的程序实例;虚拟内存划分为 text、data、bss、heap、stack 段;用 PID 标识,通过 fork 创建,通过 exec 加载新程序。

      §2.7;fork 后子进程继承父进程的内存副本;exec 销毁旧段并加载新段。

      文件与权限

      单根目录树;文件有 owner + group;权限分为 owner/group/other 三类,每类 rwx 三位,共 9 位;目录的 rwx 含义与文件略不同。

      §2.4;目录的 x 权限是「搜索」权限(允许 cd 和访问目录内文件)。

      进程凭证 (Credentials)

      每个进程有 real/effective/saved set/file-system UID/GID,加上 supplementary group IDs;权限检查基于 effective IDs。

      §2.7;effective UID 0 = 超级用户(特权进程),可绕过权限检查。

      IPC 与信号

      信号是软中断;IPC 包括管道/FIFO、套接字、消息队列、信号量、共享内存、文件锁;信号可在进程间异步传递事件。

      §2.10-§2.11;信号机制与 IPC 机制并列但用途不同:信号适合「通知事件」,IPC 适合「交换数据」。

      二、详细笔记

      2.1 内核与用户态

      What:内核是管理 CPU、内存、设备等硬件资源的核心软件;用户进程必须通过系统调用(受控入口)请求内核服务。

      Why:理解内核与用户态的边界,才能理解「为什么系统调用有性能开销」「为什么特权操作必须经过内核」——这是后续所有章节的基础。

      How:内核的核心任务(§2.1):

      任务 描述

      进程调度

      抢占式多任务;调度器决定哪个进程在何时占用 CPU 多久

      内存管理

      虚拟内存;进程隔离;按需分页

      文件系统

      提供创建/读取/写入/删除文件的接口

      进程管理

      加载程序;创建/终止进程;回收资源

      设备访问

      抽象化设备;进程通过统一接口访问

      网络

      收发数据包;路由

      系统调用 API

      进程向内核请求服务的入口

      CPU 至少有两种执行模式:

      • 用户态 (user mode):只能访问用户空间;试图访问内核空间触发硬件异常。

      • 内核态 (kernel mode):可访问全部内存;可执行特权指令(halt、MMU 操作、设备 I/O)。

      When:所有用户态程序默认运行在用户态;只有发生系统调用、中断或异常时才进入内核态;处理完毕后返回用户态。

      Example:从用户态切换到内核态的典型场景:

      // 摘自《The Linux Programming Interface》第 2 章
      // 用户态程序执行 read() —— 触发系统调用进入内核态
      char buf[1024];
      ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));
      // CPU 切换到内核态 → 内核读键盘 → 复制数据到 buf → 返回用户态

      2.2 进程 vs 内核视角

      What:同一事件,从「进程视角」和「内核视角」看完全不同——这是理解系统编程的关键思维转换。

      Why:很多看似「进程做了某事」(如「进程创建了另一个进程」「进程写了文件」)其实是「进程请求内核做某事」。保持这种视角转换能避免理解上的混淆。

      How:对比表:

      事件 进程视角 内核视角

      时间

      不知道何时被调度;何时获得 CPU

      决定何时调度哪个进程;维护进程状态

      内存

      不知道自己在 RAM 中的物理位置

      维护虚拟内存→物理内存+swap 的映射

      文件

      通过名字引用文件,不知道磁盘位置

      维护文件名→磁盘物理位置的映射

      进程

      不能直接与其他进程通信

      提供所有 IPC 机制

      I/O

      不能直接访问设备

      设备驱动执行所有 I/O

      When:当你写「进程 A 创建了进程 B」「进程 A 写了文件 F」这类陈述时,实际意思是「进程 A 请求内核创建进程 B」「进程 A 请求内核把数据写入文件 F」。

      Example:以下说法都是「进程视角」的简写——实际是内核执行:

      • 「进程调用 fork() 创建子进程」→ 内核复制父进程的地址空间结构

      • 「进程通过管道发送数据」→ 内核维护管道缓冲区

      • 「进程收到 SIGTERM」→ 内核投递信号

      2.3 通用 I/O 模型

      What:UNIX 把所有 I/O 抽象为「字节流上的顺序 I/O」;同一组系统调用适用于所有文件类型。

      Why:这一抽象是 UNIX「小工具组合」哲学的基础——cp src dst 可以在普通文件、设备、管道之间自由组合。

      How:四大系统调用:

      // 摘自《The Linux Programming Interface》第 2 章
      // 通用 I/O 模型 —— 同一接口适用于所有文件类型
      int fd = open(pathname, flags, mode);  // 返回文件描述符;flags=O_RDONLY/O_WRONLY/O_RDWR
      ssize_t n = read(fd, buf, count);      // 读入最多 count 字节;返回实际读到的字节数
      ssize_t m = write(fd, buf, count);     // 写出最多 count 字节;返回实际写入的字节数
      int s = close(fd);                     // 释放文件描述符与内核资源

      When:处理任何类型的文件时——磁盘文件、终端、管道、套接字、FIFO、设备——都用这一套调用。

      Example:下面是 4 个等价的 cp 用法(§4.2):

      $ ./copy test test.old           # 普通文件→普通文件
      $ ./copy a.txt /dev/tty          # 普通文件→终端
      $ ./copy /dev/tty b.txt          # 终端→普通文件
      $ ./copy /dev/pts/16 /dev/tty    # 一个终端→另一个终端

      2.4 单根目录树与文件

      What:Linux 把所有文件组织成「单根目录树」,根目录为 /;每个文件有类型(普通文件/目录/设备/管道/套接字/符号链接)。

      Why:单根树让所有文件路径可预测;多种文件类型让「一切皆文件」成为可能。

      How

      • 路径名:以 / 开头为绝对路径,否则为相对路径(相对于进程的 CWD);路径名中除 /\0 外其他字符都可出现,但建议只用 [-._a-zA-Z0-9]

      • 目录:特殊文件,内容是「文件名→i-node 引用」的映射表;含 .(自身)和 ..(父目录)两个特殊条目。

      • 符号链接:内容是另一个文件的路径名;自动解引用(除非 O_NOFOLLOW);可形成环路(内核有限制)。

      • 文件名长度:ext2/3/4 等大多数 Linux 文件系统允许 255 字符;传统 SysV 文件系统仅 14 字符。

      When

      • 使用相对路径——快速但依赖 CWD。

      • 使用绝对路径——可移植但冗长。

      • 使用符号链接——解引用透明;如果链路断开则是「悬空链接」。

      Examplecd ../etc; cat passwd —— .. 解析为「上一级目录」。

      2.5 文件所有权与权限

      What:每个文件关联一个 user ID 和 group ID;权限分 owner/group/other 三类,每类 rwx 三位;共 9 位。

      Why:理解权限是「为什么我的程序读不到这个文件」「为什么需要 set-UID」的前提——第 9 章会展开凭证。

      How:权限位语义:

      类别 权限位 对文件的含义 / 对目录的含义

      r

      读文件内容 / 列出目录内文件名

      w

      修改文件内容 / 添加/删除/重命名目录内文件

      x

      执行

      执行文件(程序或脚本) / 「搜索」目录(cd 或访问其中文件)

      每个文件还有一个 owner UID + group GID;ls -l 显示为 rwxr-xr-x mtk users …​

      When

      • 进程访问文件时,内核按顺序匹配 owner → group → other。

      • 若进程 UID = 文件 owner UID → 用 owner 权限位。

      • 否则若进程的任一 GID = 文件 group GID → 用 group 权限位。

      • 否则用 other 权限位。

      Examplechmod 644 file —— owner 读写,group/other 只读;chmod u+s prog —— 打开 set-user-ID 位。

      2.6 进程内存布局与生命周期

      What:进程的虚拟内存划分为几个段:text(代码)、data(初始化数据)、bss(未初始化数据)、heap(堆)、stack(栈)。

      Why:理解内存布局是理解 malloc(heap)、局部变量(stack)、全局变量(data/bss)的关键;理解 fork/exec 的内存效果。

      How

      内容

      特点

      text

      机器指令

      只读;可被多个进程共享

      data

      显式初始化的全局/静态变量

      可读可写

      bss

      未初始化的全局/静态变量

      程序加载时清零;在可执行文件中不占空间

      heap

      动态分配的内存(malloc

      从低地址向高地址增长

      stack

      局部变量、函数调用信息

      从高地址向低地址增长

      进程生命周期(§2.7):

      • 创建:fork() 复制父进程;通常子进程立即调用 execve() 加载新程序。

      • 终止:调用 _exit() 或被信号杀死;向父进程返回终止状态。

      • 等待:父进程调用 wait() 回收子进程并获取终止状态。

      When

      • 写程序时——全局变量在 data/bss,局部变量在 stack,动态分配在 heap。

      • 调试时——段错误通常发生在非法访问(解引用未初始化指针、栈溢出等)。

      Example:C 变量映射到段(§6.3,Listing 6-1):

      // 摘自《The Linux Programming Interface》第 2 章
      char globBuf[65536];           /* Uninitialized data segment (bss) */
      int primes[] = { 2, 3, 5, 7 }; /* Initialized data segment */
      static int key = 9973;          /* Initialized data segment */
      char *p = malloc(1024);         /* p 在 stack,1024 字节在 heap */

      2.7 IPC、信号与线程

      What:Linux 提供丰富的进程间通信机制——信号、管道/FIFO、套接字、消息队列、信号量、共享内存、文件锁;多线程是「共享内存的多执行流」。

      Why:理解各种 IPC 的适用场景——单主机用管道/共享内存;跨主机用套接字;异步事件用信号——这是设计分布式/并发程序的基础。

      How:IPC 机制对照表(§2.10):

      机制 来源 典型用途

      信号 (signals)

      早期 UNIX

      异步事件通知(Ctrl-C、子进程终止)

      管道 / FIFO

      早期 UNIX / SysV

      父子进程或无关进程字节流通信

      套接字 (sockets)

      BSD (1983)

      同主机或跨主机通信

      文件锁

      早期 UNIX

      协调多进程对同一文件的访问

      消息队列

      SysV

      离散消息传递

      信号量

      SysV

      进程同步

      共享内存

      SysV

      高速数据共享

      线程(§2.12):同一进程内的多个执行流共享虚拟内存、全局变量、堆;各自有独立栈;通过条件变量 + 互斥锁同步。

      When

      • 单机小数据量通信——管道或 FIFO。

      • 跨主机——套接字(TCP/UDP)。

      • 高性能数据共享——共享内存 + 信号量。

      • 异步事件——信号。

      • 同一进程多任务——线程(共享内存,开销低)。

      Examplels -l | sort -k5n | less —— shell 用管道 + fork 创建 3 个进程,形成进程组;Ctrl-C 信号由 shell 转发到整个前台进程组。

      三、关键图表

      非可视化条目(概念对照表)
      概念 / 工具 描述

      内核 vs 用户态

      CPU 两种执行模式;系统调用是进入内核的受控入口

      通用 I/O 模型

      open/read/write/close 适用于所有文件类型

      单根目录树

      起点为 /;Linux 与 Windows(多根)的核心区别

      文件 7 类

      普通/目录/符号链接/字符设备/块设备/FIFO/套接字

      进程 5 段

      text、data、bss、heap、stack

      进程凭证 4 套

      real/effective/saved set/file-system UID/GID + supplementary

      IPC 7 大机制

      信号、管道/FIFO、套接字、文件锁、消息队列、信号量、共享内存

      会话/进程组

      控制终端、前台/后台作业

      /proc 文件系统

      内核数据结构的虚拟文件系统

      四、思维导图

      mindmap
        root((第 2 章 基本概念))
          内核
            进程调度
            内存管理
            文件系统
            设备访问
            系统调用 API
          用户态内核态
            特权指令
            系统调用切换
            中断异常
          通用 I O
            open read write close
            文件描述符
            字节流抽象
          文件系统
            单根目录树
            文件类型 7 类
            权限 9 位
          进程
            text data bss heap stack
            fork exec wait
            PID PPID
          凭证
            real effective saved FS
            UID GID 补充组
            超级用户
          IPC
            信号
            管道 FIFO
            套接字
            共享内存
            消息队列 信号量
          线程会话
            多线程共享内存
            进程组 会话
            控制终端
            伪终端
          扩展
            客户端服务器
            实时扩展 POSIX1b
            proc 文件系统

      五、重点与易错点

      1. 「内核」vs「操作系统」:「操作系统」可指整套软件(含 shell、工具),也可狭义指「内核」(仅资源管理核心);本书关心的是后者。

      2. Linux 内核文件名 vmlinuzvm = virtual memory;z = 压缩;早期 UNIX 的内核叫 unix

      3. 「通用 I/O」不等于「一样高效」:同一组系统调用适用于所有文件类型,但磁盘和终端的性能特征差别巨大(详见第 13 章)。

      4. 目录的「执行」权限 ≠ 普通文件的执行权限:目录的 x 是「搜索权限」,允许 cd 进目录或访问目录内文件(依赖具体文件本身的权限)。

      5. 「..」在根目录下指向自身/.. = /;这是路径解析的特例。

      6. Linux 文件名长度 255 ≠ SysV 文件系统 14 字符NAME_MAX 随文件系统而异;pathconf(_PC_NAME_MAX) 可查询(第 11 章)。

      7. 进程段 vs 硬件段:第 6 章讨论的 text/data/bss/heap/stack 是逻辑段,不是 x86 的硬件段;两者概念不同。

      8. 「PID 复用」:进程终止后其 PID 会被后续进程重用;不要把 PID 当作唯一标识用——需要稳定标识用 /proc/self 或进程启动时间等。

      9. 信号 vs IPC:信号适合「通知事件」;IPC 适合「交换数据」;不要用信号传递大量数据。

      10. 单根 vs 多根:Linux/UNIX 是单根(所有磁盘挂载到 / 树下);Windows 是多根(每个盘符 C: D:);这一差异影响路径处理。

      11. 「root 用户」vs「/ 目录」:root 是 UID 0 的超级用户;/ 是文件系统的根目录;两者是不同的概念。

      12. 「文件描述符」vs「FILE:前者是内核级整数(int);后者是 stdio 库级封装(FILE);两者通过 fdopen/fileno 互转。

      13. 跨章衔接:第 3 章深入系统调用机制与错误处理;第 4-5 章展开 open/read/write/close;第 6 章展开进程内存布局;第 9 章展开进程凭证;第 20 章展开信号。