《UNIX环境高级编程(第三版)》第二章 重难点详解
第二章核心围绕"UNIX标准"与"系统实现细节"展开,核心目标是解决"程序可移植性"问题------让代码在不同UNIX变体(FreeBSD、Linux、Mac OS X、Solaris)上正常运行。以下按"核心概念→代码实践→难点突破"的逻辑,分步详解重难点。
一、核心重难点框架
本章重难点集中在5个维度:
- 三大核心标准(ISO C、POSIX、SUS)的关系与应用场景
- 系统限制(编译时/运行时)与查询函数(sysconf/pathconf/fpathconf)
- 选项(Options)与功能测试宏(Feature Test Macro)
- 基本系统数据类型(primitive system data type)的作用
- 标准之间的冲突与兼容处理
二、重难点1:三大核心标准的关系(基础中的基础)
1. 核心概念拆解
- ISO C标准 :定义C语言语法、语义及标准库(如
<stdio.h>、<string.h>),不依赖操作系统,是跨平台基础。 - POSIX标准 :全称"可移植操作系统接口",以UNIX为基础,定义了系统调用、库函数、shell等接口,包含ISO C标准库,是UNIX类系统的核心规范(如POSIX.1-2008)。
- Single UNIX Specification(SUS) :POSIX的超集,增加了XSI(X/Open System Interface)扩展,只有符合XSI的系统才能称为"UNIX系统"(如Solaris、Mac OS X)。
2. 关键区别(一句话总结)
- ISO C是"通用C语言标准",POSIX是"UNIX系统接口标准",SUS是"UNIX系统认证标准"。
- 示例:
printf(ISO C)、read(POSIX)、shmat(XSI扩展,SUS特有)。
3. 代码关联
所有UNIX系统编程需同时遵循"ISO C语法"和"POSIX接口",示例代码的头文件包含逻辑:
c
#include <stdio.h> // ISO C标准库
#include <unistd.h> // POSIX标准库(包含read/write等系统调用原型)
#include "apue.h" // 书中自定义头文件(封装了POSIX接口和错误处理)
三、重难点2:系统限制(可移植性的核心痛点)
1. 核心概念拆解
UNIX系统的"限制"是导致程序不可移植的主要原因(如文件名最大长度、进程最大打开文件数),分为3类:
| 限制类型 | 说明 | 获取方式 |
|---|---|---|
| 编译时限制 | 固定不变,定义在头文件中 | 包含<limits.h>直接使用 |
| 运行时无关文件限制 | 与文件无关(如每秒时钟滴答数) | 调用sysconf函数 |
| 运行时有关文件限制 | 与文件/目录相关(如文件名最大长度) | 调用pathconf/fpathconf函数 |
2. 编译时限制(简单场景)
代码示例:查看ISO C定义的整型最大值
c
#include <stdio.h>
#include <limits.h> // 编译时限制头文件
int main(void) {
printf("int最大值: %d\n", INT_MAX); // ISO C定义(32位系统通常为2147483647)
printf("long最大值: %ld\n", LONG_MAX); // POSIX扩展(64位系统通常为9223372036854775807)
printf("文件名最大长度: %d\n", NAME_MAX); // POSIX定义(通常为255)
return 0;
}
运行结果(Linux 3.2.0):
int最大值: 2147483647
long最大值: 9223372036854775807
文件名最大长度: 255
关键说明:
- 编译时限制是"系统固定值",但不同系统可能不同(如早期System V的NAME_MAX=14)。
- 必须使用头文件中的常量(如
NAME_MAX),而非硬编码(如直接写255)。
3. 运行时限制(复杂场景,重点难点)
核心问题:有些限制是"动态的"(如不同文件系统的NAME_MAX可能不同),需运行时查询。
3.1 sysconf函数(查询与文件无关的运行时限制)
- 原型:
long sysconf(int name); - 参数
name:以_SC_开头的常量(如_SC_OPEN_MAX表示进程最大打开文件数)。 - 返回值:成功返回限制值;失败返回-1;若限制"不确定"(如无上限)也返回-1(需通过
errno判断)。
代码示例:查询进程最大打开文件数(OPEN_MAX)
c
#include "apue.h"
#include <unistd.h>
int main(void) {
long open_max;
errno = 0; // 重置errno,区分"出错"和"不确定"
open_max = sysconf(_SC_OPEN_MAX);
if (open_max == -1) {
if (errno == 0) {
printf("OPEN_MAX: 无确定限制\n");
} else {
err_sys("sysconf _SC_OPEN_MAX error"); // 书中错误处理函数
}
} else {
printf("进程最大打开文件数: %ld\n", open_max);
}
return 0;
}
运行结果(Linux 3.2.0):
进程最大打开文件数: 1024
关键说明:
sysconf(_SC_CLK_TCK)可查询"每秒时钟滴答数"(用于计算进程CPU时间)。- 若返回-1且
errno=0,表示限制"不确定"(如某些系统的ATEXIT_MAX无上限)。
3.2 pathconf函数(查询与文件相关的运行时限制)
- 原型:
long pathconf(const char *pathname, int name); - 作用:查询
pathname对应的文件/目录的限制(如_PC_NAME_MAX表示该目录下的文件名最大长度)。
代码示例:查询/tmp目录的文件名最大长度
c
#include "apue.h"
#include <unistd.h>
int main(void) {
long name_max;
errno = 0;
// 查询/tmp目录的文件名最大长度
name_max = pathconf("/tmp", _PC_NAME_MAX);
if (name_max == -1) {
if (errno == 0) {
printf("/tmp目录的NAME_MAX: 无确定限制\n");
} else {
err_sys("pathconf /tmp _PC_NAME_MAX error");
}
} else {
printf("/tmp目录的文件名最大长度: %ld\n", name_max);
}
return 0;
}
运行结果(Mac OS X 10.6.8):
/tmp目录的文件名最大长度: 255
关键说明:
fpathconf与pathconf功能一致,只是第一个参数是"文件描述符"(需先打开文件)。- 若查询
_PC_PATH_MAX,需传入目录路径(如/),返回相对路径的最大长度。
4. 难点突破:处理"不确定的限制"
有些限制(如ATEXIT_MAX)在某些系统中无上限,代码需兼容:
c
#include "apue.h"
#include <unistd.h>
#define OPEN_MAX_GUESS 256 // 默认猜测值
long open_max(void) {
long openmax;
errno = 0;
openmax = sysconf(_SC_OPEN_MAX);
if (openmax == -1) {
if (errno == 0) {
openmax = OPEN_MAX_GUESS; // 无确定限制,用猜测值
} else {
err_sys("sysconf _SC_OPEN_MAX error");
}
}
return openmax;
}
int main(void) {
printf("兼容后的OPEN_MAX: %ld\n", open_max());
return 0;
}
四、重难点3:选项(Options)与功能测试宏
1. 核心概念拆解
- 选项 :POSIX/SUS定义的"可选功能"(如线程、异步I/O),系统可能支持或不支持(如
_POSIX_THREADS表示是否支持线程)。 - 功能测试宏 :编译时定义的宏,用于"启用特定标准的接口"(如
_POSIX_C_SOURCE=200809L启用POSIX.1-2008标准)。
2. 功能测试宏的使用(重点)
作用:避免编译时冲突,仅启用需要的标准接口。
使用方式:
-
编译时定义:
gcc -D_POSIX_C_SOURCE=200809L test.c -o test -
源码中定义(需在包含头文件前):
c#define _POSIX_C_SOURCE 200809L // 启用POSIX.1-2008标准 #include <unistd.h>
常用宏:
| 宏 | 作用 |
|---|---|
_POSIX_C_SOURCE=200809L |
启用POSIX.1-2008核心接口 |
_XOPEN_SOURCE=700 |
启用SUSv4(XSI扩展)接口 |
_BSD_SOURCE |
启用BSD特有接口(如gettimeofday) |
3. 查询选项是否被支持(代码示例)
用sysconf查询_POSIX_THREADS(是否支持线程):
c
#include "apue.h"
#include <unistd.h>
int main(void) {
long threads_support;
errno = 0;
threads_support = sysconf(_SC_THREADS); // 查询_POSIX_THREADS选项
if (threads_support == -1) {
if (errno == 0) {
printf("不支持POSIX线程\n");
} else {
err_sys("sysconf _SC_THREADS error");
}
} else {
printf("支持POSIX线程(标准版本: %ld)\n", threads_support);
}
return 0;
}
运行结果(Linux 3.2.0):
支持POSIX线程(标准版本: 200809)
关键说明:
- 选项返回值为"标准版本号"(如200809表示POSIX.1-2008),非0即支持。
- 与文件相关的选项用
pathconf查询(如_PC_CHOWN_RESTRICTED表示是否限制更改文件所有者)。
五、重难点4:基本系统数据类型
1. 核心概念拆解
- 定义:
typedef声明的跨平台数据类型(如pid_t、uid_t),目的是"屏蔽不同系统的硬件差异"(如32位/64位系统的int长度不同)。 - 特点:均以
_t结尾,定义在<sys/types.h>等头文件中。
2. 常用数据类型及用途
| 类型 | 用途 | 示例 |
|---|---|---|
pid_t |
进程ID/进程组ID | getpid()返回值 |
uid_t |
用户ID | getuid()返回值 |
gid_t |
组ID | getgid()返回值 |
off_t |
文件偏移量 | lseek()的偏移参数 |
size_t |
对象长度(无符号) | strlen()返回值 |
ssize_t |
字节计数(带符号) | read()/write()返回值 |
3. 代码示例:正确使用pid_t(避免移植性问题)
c
#include "apue.h"
int main(void) {
pid_t pid; // 用pid_t存储进程ID,而非int
pid = getpid();
// 必须强制转换为long打印(保证32/64位系统兼容)
printf("当前进程ID: %ld\n", (long)pid);
return 0;
}
关键说明:
- 不能用
int存储pid_t(64位系统中pid_t可能是64位,int仅32位)。 - 打印时强制转换为
long(printf的%ld格式符匹配),是UNIX编程的"可移植性约定"。
六、重难点5:标准之间的冲突与兼容
1. 核心冲突场景
- ISO C与POSIX的冲突 :ISO C定义的函数(如
signal)与POSIX定义的语义不同(如Solaris的signal与sigaction行为差异)。 - 时间单位冲突 :ISO C的
clock()返回"CPU时间(时钟滴答数)",POSIX的times()返回"时钟滴答数",但两者的"每秒滴答数"不同(需用sysconf(_SC_CLK_TCK)查询)。
2. 冲突解决示例:正确计算进程CPU时间
c
#include "apue.h"
#include <sys/times.h>
#include <unistd.h>
int main(void) {
struct tms tmsbuf;
clock_t start, end;
long clktck;
clktck = sysconf(_SC_CLK_TCK); // 获取每秒时钟滴答数
if (clktck == -1) {
err_sys("sysconf _SC_CLK_TCK error");
}
start = times(&tmsbuf); // 记录开始时间(POSIX函数)
if (start == -1) {
err_sys("times error");
}
// 模拟耗时操作(循环1000000次)
for (int i = 0; i < 1000000; i++);
end = times(&tmsbuf); // 记录结束时间
if (end == -1) {
err_sys("times error");
}
// 计算用户CPU时间(tms_utime为用户态滴答数)
printf("用户CPU时间: %.2f秒\n", (double)(tmsbuf.tms_utime) / clktck);
return 0;
}
运行结果(FreeBSD 8.0):
用户CPU时间: 0.01秒
关键说明:
- 用POSIX的
times()函数而非ISO C的clock(),避免语义冲突。 - 必须通过
sysconf(_SC_CLK_TCK)转换滴答数为秒,不能硬编码(如假设每秒100滴答)。
七、章节核心总结
第二章的本质是"教你编写可移植的UNIX程序",核心方法论:
- 遵循"ISO C语法 + POSIX接口",必要时启用SUS的XSI扩展。
- 用"系统限制查询函数"(sysconf/pathconf)替代硬编码。
- 用"基本系统数据类型"(pid_t/uid_t)替代原生类型(int/long)。
- 用"功能测试宏"控制编译时启用的接口,避免冲突。
八、实践建议(必做代码练习)
- 编写程序,查询当前系统的
NAME_MAX(文件名最大长度)、OPEN_MAX(最大打开文件数)、_SC_THREADS(是否支持线程)。 - 验证功能测试宏的作用:分别用
-D_POSIX_C_SOURCE=200809L和不定义宏编译,观察struct timespec是否可用(POSIX.1-2008才支持)。 - 用
pathconf查询不同文件系统(如/和/tmp)的_PC_NAME_MAX,观察是否相同。
中等难度多选题
题目1:关于UNIX三大核心标准(ISO C、POSIX、SUS)的关系与特性,下列说法正确的有( )
A. ISO C标准定义C语言语法与标准库,不依赖操作系统,是跨平台编程的基础
B. POSIX标准是ISO C的超集,包含系统调用、库函数等UNIX系统接口,仅适用于UNIX类系统
C. SUS(Single UNIX Specification)是POSIX的超集,支持XSI扩展的系统才能被认证为"UNIX系统"
D. printf属于ISO C标准接口,read属于POSIX标准接口,shmat属于SUS的XSI扩展接口
E. POSIX.1-2008完全弃用了STREAMS相关接口,所有UNIX实现均不再支持该接口
题目2:关于UNIX系统限制的分类与查询函数,下列说法正确的有( )
A. 编译时限制(如INT_MAX)定义在<limits.h>中,固定不变,可直接包含头文件使用
B. 运行时与文件无关的限制(如OPEN_MAX)需通过sysconf函数查询,参数以_SC_开头
C. 运行时与文件相关的限制(如NAME_MAX)需通过pathconf函数查询,路径参数必须是绝对路径
D. 若sysconf返回-1且errno=0,表示该限制"不确定"(无实际上限),需用默认值兼容
E. PATH_MAX是编译时限制,其值在所有文件系统中均为256字节,可直接硬编码使用
题目3:关于POSIX选项与功能测试宏,下列说法正确的有( )
A. 功能测试宏(如_POSIX_C_SOURCE)需在包含头文件前定义,用于启用特定版本的标准接口
B. 查询系统是否支持POSIX线程(_POSIX_THREADS),需用pathconf函数,参数为_PC_THREADS
C. 若_XOPEN_UNIX常量定义且值大于0,说明系统支持SUS的XSI扩展,属于UNIX认证系统
D. 选项_POSIX_CHOWN_RESTRICTED控制文件所有者更改权限,可通过sysconf查询其是否生效
E. 定义_XOPEN_SOURCE=700等价于启用SUSv4标准,包含POSIX.1-2008核心接口与XSI扩展
题目4:关于UNIX基本系统数据类型(primitive system data type),下列说法正确的有( )
A. 所有系统数据类型均以_t结尾,定义在<sys/types.h>中,目的是屏蔽硬件平台差异
B. pid_t用于存储进程ID,是带符号类型,打印时需强制转换为long以保证32/64位系统兼容
C. size_t是无符号类型,用于表示对象长度(如strlen返回值),ssize_t是带符号类型,用于表示字节计数(如read返回值)
D. off_t用于存储文件偏移量,在32位系统中固定为32位,64位系统中自动扩展为64位,无需额外配置
E. uid_t和gid_t分别存储用户ID和组ID,早期为16位,现均为32位,确保多用户场景下的唯一性
题目5:关于UNIX标准之间的冲突与兼容处理,下列说法正确的有( )
A. ISO C的clock函数与POSIX的times函数均返回CPU时间,但clock的单位由CLOCKS_PER_SEC定义,times需用sysconf(_SC_CLK_TCK)转换单位
B. POSIX标准与ISO C冲突时,以ISO C为准,例如signal函数的语义优先遵循ISO C定义
C. 不同文件系统的NAME_MAX可能不同(如UFS与PCFS),需用pathconf查询具体目录的限制,避免硬编码导致移植性问题
D. 为兼容老系统,POSIX.1允许系统保留非标准接口(如System V的O_NDELAY),但新程序应优先使用标准接口(如O_NONBLOCK)
E. 编译可移植程序时,应避免使用FILENAME_MAX(ISO C定义),优先使用NAME_MAX(POSIX定义),因为后者支持运行时动态查询
题目详解
题目1 正确答案:ACD
- A正确:第二章2.2.1节明确ISO C标准专注于C语言本身,不依赖操作系统,定义了语法、语义及标准库,是跨平台编程的基础。
- B错误:POSIX标准包含ISO C标准库(是ISO C的超集),但并非仅适用于UNIX类系统,部分非UNIX系统也可实现POSIX接口以保证兼容性。
- C正确:第二章2.2.3节说明SUS是POSIX的超集,其中XSI(X/Open System Interface)扩展是UNIX系统认证的核心要求,仅支持XSI的系统才能称为"UNIX系统"。
- D正确 :
printf是ISO C标准库函数,read是POSIX定义的系统调用接口,shmat(共享内存附着)是SUS特有的XSI扩展接口,符合第二章对三类标准接口的划分。 - E错误:POSIX.1-2008弃用STREAMS接口,但部分老系统(如Solaris 10)仍保留该接口以兼容 legacy 程序,并非所有UNIX实现均不再支持。
题目2 正确答案:ABD
- A正确 :第二章2.5.1节指出,ISO C定义的编译时限制(如整型最大值、字符位数)固定不变,统一定义在
<limits.h>中,直接包含头文件即可使用。 - B正确 :第二章2.5.4节说明,
sysconf函数用于查询与文件无关的运行时限制,参数以_SC_开头(如_SC_OPEN_MAX对应进程最大打开文件数)。 - C错误 :
pathconf函数的路径参数可是相对路径(如当前目录"."),若路径为相对路径,将基于当前工作目录解析;仅当查询_PC_PATH_MAX时,路径需为目录。 - D正确 :第二章2.5.4节明确,
sysconf返回-1时,若errno=0表示限制"不确定"(无实际上限),需通过默认猜测值(如OPEN_MAX_GUESS=256)保证程序兼容。 - E错误 :
PATH_MAX是运行时与文件相关的限制,不同文件系统可能不同(如部分嵌入式系统可能小于256字节),且需用pathconf查询,不能硬编码。
题目3 正确答案:ACE
- A正确 :第二章2.7节说明,功能测试宏需在包含任何头文件前定义(如编译时
-D_POSIX_C_SOURCE=200809L),目的是启用特定版本的标准接口,排除实现专有定义。 - B错误 :查询
_POSIX_THREADS(是否支持线程)需用sysconf函数,参数为_SC_THREADS;pathconf用于查询与文件相关的选项。 - C正确 :第二章2.2.3节指出,
_XOPEN_UNIX是SUS的核心常量,定义且值大于0说明系统支持XSI扩展,符合UNIX系统认证要求。 - D错误 :
_POSIX_CHOWN_RESTRICTED是与文件相关的选项,需通过pathconf查询(参数_PC_CHOWN_RESTRICTED),sysconf不用于查询文件相关选项。 - E正确 :第二章2.7节说明,
_XOPEN_SOURCE=700对应SUSv4标准,既包含POSIX.1-2008核心接口,也启用XSI扩展接口,是编写兼容UNIX系统程序的常用配置。
题目4 正确答案:BC
- A错误 :多数系统数据类型以
_t结尾且定义在<sys/types.h>中,但部分类型(如clock_t)可能定义在<time.h>中,并非全部集中在一个头文件。 - B正确 :第二章2.8节指出,
pid_t是带符号类型,其长度随平台变化(32/64位),打印时强制转换为long可保证兼容性(避免溢出或格式错误)。 - C正确 :第二章2.8节明确,
size_t为无符号类型(适配对象长度非负特性),ssize_t为带符号类型(需表示"出错返回-1"),分别对应strlen和read的返回值类型。 - D错误 :
off_t的长度需通过_FILE_OFFSET_BITS宏配置(如定义为64则为64位),32位系统默认可能为32位,需显式配置才能支持大文件偏移量。 - E错误 :
uid_t和gid_t的长度随系统变化(部分嵌入式系统仍为16位),并非均为32位,其核心作用是标识用户/组,而非保证"唯一性"(由系统管理确保ID唯一)。
题目5 正确答案:ACDE
- A正确 :第二章2.9节说明,ISO C的
clock函数单位由CLOCKS_PER_SEC定义(如Solaris中为100万),POSIX的times函数单位需通过sysconf(_SC_CLK_TCK)查询(如Linux中为100),两者单位不同需分别处理。 - B错误 :POSIX标准明确规定,当与ISO C冲突时,以POSIX为准(例如
signal函数的语义优先遵循POSIX定义,而非ISO C),避免接口行为不一致。 - C正确 :第二章2.5.2节指出,
NAME_MAX(文件名最大长度)依赖文件系统类型(如UFS支持255字节,PCFS可能仅支持8.3格式),需用pathconf查询具体目录的限制,确保移植性。 - D正确 :第二章2.4节说明,POSIX.1为兼容老系统,允许保留非标准接口(如
O_NDELAY),但新程序应优先使用标准接口(O_NONBLOCK),避免依赖非标准特性。 - E正确 :第二章2.5.1节指出,ISO C的
FILENAME_MAX兼容性较差,POSIX的NAME_MAX支持运行时动态查询(pathconf),更适合编写可移植程序,应优先使用。