《UNIX环境高级编程》读书笔记23: 全书完整总结
作者: andylin02
学习章节: APUE全书总结
关键词:UNIX系统编程;文件I/O;进程控制;线程;IPC;网络编程;守护进程;终端I/O
一、写在前面
历时数月,终于完成了《UNIX环境高级编程》(第3版)全书21章的学习。这本被誉为UNIX系统编程“圣经”的巨著,由W. Richard Stevens和Stephen A. Rago合著,涵盖了UNIX/Linux环境下系统级编程的方方面面。
本文是对全书知识体系的系统性总结,按照内容逻辑分为六大模块,提炼核心概念、关键函数、设计模式和实践技巧,并附有代表性的代码片段和架构图,希望能为同样在学习APUE的读者提供一个清晰的导航。
💡 学习感悟:APUE不仅仅是一本API手册,更是一本讲解“UNIX设计哲学”的经典。书中每个章节都贯穿着“一切皆文件”、“模块化设计”、“简洁高效”的思想。掌握这些思想,比单纯记忆函数名更重要。
二、全书知识体系总览
三、模块一:基础知识(第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 |
文件共享内核数据结构:
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章)
内存布局:
- 正文段(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章)
守护进程是后台运行的进程,通常无控制终端。
编写守护进程的标准步骤:
umask(0)— 清除文件创建屏蔽字fork()→ 父进程exit— 保证子进程不是进程组组长setsid()— 创建新会话,脱离控制终端- 可选:再次
fork— 确保不是会话首进程 chdir("/")— 改变工作目录到根- 关闭所有文件描述符
- 重定向标准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对比:
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客户端-服务器模型:
核心函数:
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):通过VMIN和VTIME控制读行为
关键函数:
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)组成,用于模拟终端。
标准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章)
九、全书核心思维导图
十、推荐开发环境与调试工具
| 工具 | 用途 |
|---|---|
gcc / clang |
编译器,需要链接 -lpthread、-lrt 等库 |
gdb |
调试器,支持多线程和信号 |
valgrind |
内存检测(memcheck)、线程竞争(helgrind) |
strace |
跟踪系统调用 |
ltrace |
跟踪库函数调用 |
ipcs / ipcrm |
查看/删除XSI IPC对象 |
lsof |
查看进程打开的文件描述符 |
netstat / ss |
查看网络套接字状态 |
stty |
查看/修改终端设置 |
十一、延伸阅读
恭喜完成APUE的学习!接下来建议深入学习:
- 《UNIX网络编程》(UNP):Stevens的另一本经典,深入讲解套接字编程、协议实现、高级网络服务
- 《Linux系统编程》(Robert Love):更贴近现代Linux内核的实践指南
- Linux内核源码分析:从用户空间进入内核空间,理解系统调用的底层实现
- 《深入理解计算机系统》(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系统编程的大门已经打开,剩下的就是在实践中不断锤炼。
本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!
更多推荐

所有评论(0)