作者: andylin02
学习章节: APUE全书总结
关键词:UNIX系统编程;文件I/O;进程控制;线程;IPC;网络编程;守护进程;终端I/O

一、写在前面

历时数月,终于完成了《UNIX环境高级编程》(第3版)全书21章的学习。这本被誉为UNIX系统编程“圣经”的巨著,由W. Richard Stevens和Stephen A. Rago合著,涵盖了UNIX/Linux环境下系统级编程的方方面面。

本文是对全书知识体系的系统性总结,按照内容逻辑分为六大模块,提炼核心概念、关键函数、设计模式和实践技巧,并附有代表性的代码片段和架构图,希望能为同样在学习APUE的读者提供一个清晰的导航。

💡 学习感悟:APUE不仅仅是一本API手册,更是一本讲解“UNIX设计哲学”的经典。书中每个章节都贯穿着“一切皆文件”、“模块化设计”、“简洁高效”的思想。掌握这些思想,比单纯记忆函数名更重要。

二、全书知识体系总览

APUE全书结构

基础知识

第1章 UNIX基础

第2章 标准化

第3章 文件I/O

第4章 文件和目录

第5章 标准I/O库

第6章 系统数据文件

进程控制

第7章 进程环境

第8章 进程控制

第9章 进程关系

第10章 信号

多线程

第11章 线程

第12章 线程控制

高级I/O与IPC

第13章 守护进程

第14章 高级I/O

第15章 进程间通信

第16章 网络IPC:套接字

第17章 高级IPC

终端与伪终端

第18章 终端I/O

第19章 伪终端

实践案例

第20章 数据库函数库

第21章 网络打印机通信

三、模块一:基础知识(第1-6章)

3.1 UNIX基础与标准化

核心概念

  • 操作系统为程序提供服务:执行新程序、打开文件、分配内存、获取时间等
  • 文件描述符是核心抽象,所有I/O操作都通过文件描述符完成
  • 进程、线程、用户ID、组ID的基本概念

关键函数

#include <unistd.h>

/* 获取进程标识 */
pid_t getpid(void);      /* 进程ID */
pid_t getppid(void);     /* 父进程ID */
uid_t getuid(void);      /* 用户ID */
gid_t getgid(void);      /* 组ID */

3.2 文件I/O(第3章)

不带缓冲的I/O函数(POSIX标准):

函数 功能 关键点
open / openat 打开文件 标志:O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_TRUNC, O_APPEND
read 读取数据 返回实际读取的字节数,0表示文件尾
write 写入数据 可能只写入部分数据,需循环调用
lseek 移动文件偏移量 SEEK_SET, SEEK_CUR, SEEK_END
close 关闭文件 自动释放文件描述符
dup / dup2 复制文件描述符 用于重定向标准I/O

文件共享内核数据结构

内核

进程表项

文件描述符表
fd0, fd1, fd2, ...

文件表项
文件状态标志
当前偏移量
v节点指针

v节点表项
i节点信息
文件大小

3.3 文件和目录(第4章)

核心函数

  • stat / fstat / lstat:获取文件元数据
  • access:按实际用户ID测试访问权限
  • umask:设置文件模式创建屏蔽字
  • chmod / fchmod:修改文件权限
  • link / unlink:创建/删除硬链接
  • symlink / readlink:处理符号链接
  • mkdir / rmdir:创建/删除目录
  • chdir / getcwd:改变/获取当前工作目录

文件类型宏

S_ISREG()   /* 普通文件 */
S_ISDIR()   /* 目录 */
S_ISCHR()   /* 字符设备 */
S_ISBLK()   /* 块设备 */
S_ISFIFO()  /* 管道或FIFO */
S_ISLNK()   /* 符号链接 */
S_ISSOCK()  /* 套接字 */

3.4 标准I/O库(第5章)

标准I/O库对文件I/O进行了缓冲封装,提供了三种缓冲类型:

缓冲类型 说明 典型场景
全缓冲 填满缓冲区后才进行实际I/O 磁盘文件
行缓冲 遇到换行符时执行I/O 终端输入输出
无缓冲 立即执行I/O stderr

关键函数

/* 打开/关闭流 */
FILE *fopen(const char *path, const char *mode);
int fclose(FILE *fp);

/* 读写 */
int fgetc(FILE *fp); int fputc(int c, FILE *fp);
char *fgets(char *buf, int n, FILE *fp); int fputs(const char *str, FILE *fp);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *fp);

/* 格式化I/O */
int printf(const char *format, ...);
int scanf(const char *format, ...);
int fprintf(FILE *fp, const char *format, ...);
int fscanf(FILE *fp, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);

/* 定位 */
int fseek(FILE *fp, long offset, int whence);
long ftell(FILE *fp);
void rewind(FILE *fp);

/* 缓冲控制 */
int fflush(FILE *fp);
int setvbuf(FILE *fp, char *buf, int mode, size_t size);

3.5 系统数据文件(第6章)

  • 口令文件:/etc/passwd,函数:getpwnam, getpwuid, getpwent, setpwent, endpwent
  • 阴影口令:/etc/shadow,函数:getspnam
  • 组文件:/etc/group,函数:getgrnam, getgrgid, getgrent
  • 时间与日期:time, localtime, gmtime, strftime

四、模块二:进程控制(第7-10章)

4.1 进程环境(第7章)

终止方式

return from main

exit/_exit

pthread_exit

abort/signal

启动

C运行时初始化

main函数

进程终止

内存布局

  • 正文段(text):CPU执行的机器指令,只读共享
  • 初始化数据段(data):已初始化的全局变量
  • 未初始化数据段(bss):未初始化的全局变量,程序执行前由内核清零
  • 堆(heap):动态分配内存,malloc/free
  • 栈(stack):自动变量、函数调用信息

环境变量getenv, setenv, putenv, unsetenv

4.2 进程控制(第8章)

核心函数

#include <unistd.h>
#include <sys/wait.h>

pid_t fork(void);           /* 创建子进程,子进程返回0,父进程返回子进程PID */
void _exit(int status);     /* 终止进程,不执行清理 */
pid_t wait(int *status);    /* 阻塞等待任一子进程终止 */
pid_t waitpid(pid_t pid, int *status, int options); /* 指定等待的进程 */
int execve(const char *path, char *const argv[], char *const envp[]);
/* exec系列:execl, execv, execlp, execvp, fexecve */

fork的特性

  • 子进程获得父进程数据空间、堆、栈的副本(写时复制)
  • 子进程不继承父进程的锁(记录锁不继承)
  • 子进程拥有独立的PID和PPID

孤儿进程与僵尸进程

  • 孤儿进程:父进程先终止,子进程被init进程收养
  • 僵尸进程:子进程终止,父进程未调用wait/waitpid,进程表中保留终止状态

4.3 进程关系(第9章)

关键概念

  • 进程组:一个或多个进程的集合,用于信号分发
  • 会话:一个或多个进程组的集合,通常由登录shell创建
  • 控制终端:会话中的每个进程可以有一个控制终端
pid_t getpgrp(void);               /* 获取进程组ID */
int setpgid(pid_t pid, pid_t pgid); /* 设置进程组ID */
pid_t setsid(void);                 /* 创建新会话 */
pid_t getsid(pid_t pid);            /* 获取会话首进程的进程组ID */

4.4 信号(第10章)

信号是软件中断,用于进程间通信和异常处理。

信号生命周期:产生 → 递送 → 处理

信号处理函数

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

int kill(pid_t pid, int signo);      /* 向进程发送信号 */
int raise(int signo);                /* 向自身发送信号 */
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); /* 设置信号屏蔽字 */
int sigpending(sigset_t *set);       /* 获取未决信号集 */
int sigsuspend(const sigset_t *mask);/* 原子操作:设置屏蔽字并等待信号 */

可重入函数:在信号处理函数中只能调用可重入函数(异步信号安全函数)。

五、模块三:多线程(第11-12章)

5.1 线程概念与创建

线程是进程内的执行单元,同一进程的线程共享地址空间、文件描述符等资源。

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);
int pthread_join(pthread_t thread, void **retval);
void pthread_exit(void *retval);
int pthread_detach(pthread_t thread);    /* 分离线程,自动回收资源 */
pthread_t pthread_self(void);            /* 获取自身线程ID */

5.2 线程同步

互斥量(Mutex)

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);

条件变量(Condition Variable)

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

读写锁(RWLock)

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

线程同步模式示例(生产者-消费者)

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE 10

int buffer[BUFFER_SIZE];
int count = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_produce = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_consume = PTHREAD_COND_INITIALIZER;

void *producer(void *arg) {
    int item = 0;
    while (1) {
        pthread_mutex_lock(&mutex);
        while (count == BUFFER_SIZE)
            pthread_cond_wait(&cond_produce, &mutex);
        buffer[count++] = item++;
        printf("Produced: %d, count=%d\n", item-1, count);
        pthread_cond_signal(&cond_consume);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

void *consumer(void *arg) {
    int item;
    while (1) {
        pthread_mutex_lock(&mutex);
        while (count == 0)
            pthread_cond_wait(&cond_consume, &mutex);
        item = buffer[--count];
        printf("Consumed: %d, count=%d\n", item, count);
        pthread_cond_signal(&cond_produce);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

5.3 线程属性与线程私有数据

  • 线程属性:pthread_attr_t,可设置分离状态、栈大小等
  • 线程私有数据:pthread_key_create, pthread_setspecific, pthread_getspecific

六、模块四:高级I/O与IPC(第13-17章)

6.1 守护进程(第13章)

守护进程是后台运行的进程,通常无控制终端。

编写守护进程的标准步骤

  1. umask(0) — 清除文件创建屏蔽字
  2. fork() → 父进程exit — 保证子进程不是进程组组长
  3. setsid() — 创建新会话,脱离控制终端
  4. 可选:再次fork — 确保不是会话首进程
  5. chdir("/") — 改变工作目录到根
  6. 关闭所有文件描述符
  7. 重定向标准I/O到/dev/null

日志记录syslog(3)函数族

#include <syslog.h>
openlog("daemon", LOG_PID, LOG_DAEMON);
syslog(LOG_INFO, "daemon started");
closelog();

6.2 高级I/O(第14章)

技术 核心函数 用途
非阻塞I/O fcntl(fd, F_SETFL, O_NONBLOCK) 避免进程阻塞在低速设备上
记录锁 fcntl(fd, F_SETLK, &flock) 进程间对文件区域互斥访问
I/O多路转接 select, poll, epoll 单进程监控多个文件描述符
散布读/聚集写 readv, writev 一次读写多个不连续缓冲区
存储映射I/O mmap, munmap 将文件映射到内存,直接访问

select/poll/epoll对比

epoll

事件驱动

注册时拷贝一次

O(1)返回就绪fd

poll

pollfd数组 无固定上限

每次拷贝全部fd

O(n)遍历

select

fd_set位图 限制1024

每次拷贝全部fd

O(n)遍历

6.3 进程间通信(第15章)

IPC方式 特点 典型函数
管道 半双工,亲缘进程 pipe, popen
FIFO 半双工,不相关进程 mkfifo, open
XSI消息队列 消息类型,系统持久 msgget, msgsnd, msgrcv
XSI信号量 信号量集,同步 semget, semop, semctl
XSI共享内存 最快IPC shmget, shmat, shmdt
POSIX消息队列 命名,优先级 mq_open, mq_send, mq_receive
POSIX信号量 单个信号量,命名/未命名 sem_open, sem_wait, sem_post
POSIX共享内存 基于mmap shm_open, mmap

6.4 网络IPC:套接字(第16章)

TCP客户端-服务器模型

Server Client Server Client socket() bind() listen() socket() connect() accept() send() recv() send() recv() close() close()

核心函数

int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

地址转换

#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);           /* 旧版,不推荐 */
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

6.5 高级IPC(第17章)

UNIX域套接字:用于同一主机进程间通信,比因特网域套接字更高效(无协议栈开销)。

#include <sys/un.h>
struct sockaddr_un {
    sa_family_t sun_family;  /* AF_UNIX */
    char sun_path[108];      /* 路径名 */
};

int socketpair(int domain, int type, int protocol, int sv[2]);

文件描述符传递:通过UNIX域套接字的辅助数据(SCM_RIGHTS)传递文件描述符。

七、模块五:终端与伪终端(第18-19章)

7.1 终端I/O(第18章)

终端I/O的核心是termios结构:

#include <termios.h>

struct termios {
    tcflag_t c_iflag;   /* 输入标志 */
    tcflag_t c_oflag;   /* 输出标志 */
    tcflag_t c_cflag;   /* 控制标志 */
    tcflag_t c_lflag;   /* 本地标志 */
    cc_t c_cc[NCCS];    /* 控制字符数组 */
};

两种工作模式

  • 规范模式ICANON):行缓冲,按行输入
  • 非规范模式~ICANON):通过VMINVTIME控制读行为

关键函数

int tcgetattr(int fd, struct termios *termptr);
int tcsetattr(int fd, int opt, const struct termios *termptr);
int cfgetispeed(const struct termios *termptr);
int cfsetispeed(struct termios *termptr, speed_t speed);
int tcdrain(int fd);
int tcflow(int fd, int action);
int tcflush(int fd, int queue);

7.2 伪终端(第19章)

伪终端由主设备(master)和从设备(slave)组成,用于模拟终端。

write

内核传递

行规程

输出

终端模拟器
xterm/sshd

主设备

从设备

Shell进程

标准POSIX接口

int posix_openpt(int oflag);
int grantpt(int fd);
int unlockpt(int fd);
char *ptsname(int fd);

/* 简化函数(多数系统支持)*/
#include <pty.h>
int openpty(int *amaster, int *aslave, char *name,
            const struct termios *termp, const struct winsize *winp);
pid_t forkpty(int *amaster, char *name,
              const struct termios *termp, const struct winsize *winp);

八、模块六:实践案例(第20-21章)

8.1 数据库函数库(第20章)

实现了一个简单的多进程数据库,展示了:

  • 索引文件和数据文件分离
  • 固定大小哈希表 + 链地址法解决冲突
  • 记录锁实现并发控制

文件结构

  • pathname.idx:索引文件,包含哈希表(固定长度)和索引记录
  • pathname.dat:数据文件,包含可变长度数据

核心API

DBHANDLE db_open(const char *pathname, int oflag, ...);
void db_close(DBHANDLE db);
const char *db_fetch(DBHANDLE db, const char *key);
int db_store(DBHANDLE db, const char *key, const char *data, int flag);
int db_delete(DBHANDLE db, const char *key);
void db_rewind(DBHANDLE db);
char *db_nextrec(DBHANDLE db, char *keybuf);

8.2 与网络打印机通信(第21章)

实现了一个网络打印系统,包含:

  • print客户端:发送打印请求
  • printd守护进程:接收请求,提交到打印假脱机

技术栈综合应用

  • 守护进程(第13章)
  • 套接字通信(第16章)
  • 线程池与互斥量(第11-12章)
  • 信号处理(第10章)
  • 记录锁(第14章)

九、全书核心思维导图

APUE核心

文件I/O

不带缓冲: open/read/write/lseek

标准I/O: fopen/fread/fwrite

高级: mmap/readv/writev

进程管理

fork/exec/wait

信号: signal/sigaction

守护进程: setsid/umask/syslog

线程

pthread_create/join

同步: mutex/cond/rwlock

私有数据: pthread_key

进程间通信

管道/FIFO

XSI: 消息队列/信号量/共享内存

POSIX: mq/sem/shm

网络IPC

socket/bind/listen/accept/connect

send/recv

UNIX域套接字

终端

termios结构

规范/非规范模式

伪终端: posix_openpt/forkpty

十、推荐开发环境与调试工具

工具 用途
gcc / clang 编译器,需要链接 -lpthread-lrt 等库
gdb 调试器,支持多线程和信号
valgrind 内存检测(memcheck)、线程竞争(helgrind
strace 跟踪系统调用
ltrace 跟踪库函数调用
ipcs / ipcrm 查看/删除XSI IPC对象
lsof 查看进程打开的文件描述符
netstat / ss 查看网络套接字状态
stty 查看/修改终端设置

十一、延伸阅读

恭喜完成APUE的学习!接下来建议深入学习:

  1. 《UNIX网络编程》(UNP):Stevens的另一本经典,深入讲解套接字编程、协议实现、高级网络服务
  2. 《Linux系统编程》(Robert Love):更贴近现代Linux内核的实践指南
  3. Linux内核源码分析:从用户空间进入内核空间,理解系统调用的底层实现
  4. 《深入理解计算机系统》(CSAPP):从硬件到操作系统再到应用的完整计算机系统知识

后续学习建议:APUE是系统编程的基石,但现代系统开发还需要掌握:epoll/kqueue高性能I/O、协程(如libco)、容器技术(Docker底层的namespace/cgroup)、eBPF动态跟踪等。建议将APUE的知识作为“内功”,再结合具体项目方向(网络服务、存储系统、嵌入式等)深入实践。

十二、结语

APUE全书21章,涵盖了UNIX系统编程的几乎所有重要主题。通过系统学习,我们掌握了:

  • 文件I/O的核心抽象和操作方法
  • 进程控制的基本原语(fork、exec、wait)和信号机制
  • 多线程编程的同步与通信技巧
  • 进程间通信的多种机制及适用场景
  • 网络编程的基础(TCP/UDP套接字)
  • 守护进程、终端、伪终端等系统服务编写方法

最后两章的实践案例,将这些知识融会贯通,构建了真实可用的应用程序。

希望这份总结能帮助你回顾和巩固APUE的知识体系。路漫漫其修远兮,UNIX/Linux系统编程的大门已经打开,剩下的就是在实践中不断锤炼。


本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

Logo

脑启社区是一个专注类脑智能领域的开发者社区。欢迎加入社区,共建类脑智能生态。社区为开发者提供了丰富的开源类脑工具软件、类脑算法模型及数据集、类脑知识库、类脑技术培训课程以及类脑应用案例等资源。

更多推荐