第二章_UNIX标准及实现_《UNIX环境高级编程(第三版)》_笔记

《UNIX环境高级编程(第三版)》第二章 重难点详解

第二章核心围绕"UNIX标准"与"系统实现细节"展开,核心目标是解决"程序可移植性"问题------让代码在不同UNIX变体(FreeBSD、Linux、Mac OS X、Solaris)上正常运行。以下按"核心概念→代码实践→难点突破"的逻辑,分步详解重难点。

一、核心重难点框架

本章重难点集中在5个维度:

  1. 三大核心标准(ISO C、POSIX、SUS)的关系与应用场景
  2. 系统限制(编译时/运行时)与查询函数(sysconf/pathconf/fpathconf)
  3. 选项(Options)与功能测试宏(Feature Test Macro)
  4. 基本系统数据类型(primitive system data type)的作用
  5. 标准之间的冲突与兼容处理

二、重难点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
关键说明:
  • fpathconfpathconf功能一致,只是第一个参数是"文件描述符"(需先打开文件)。
  • 若查询_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_tuid_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位)。
  • 打印时强制转换为longprintf%ld格式符匹配),是UNIX编程的"可移植性约定"。

六、重难点5:标准之间的冲突与兼容

1. 核心冲突场景

  • ISO C与POSIX的冲突 :ISO C定义的函数(如signal)与POSIX定义的语义不同(如Solaris的signalsigaction行为差异)。
  • 时间单位冲突 :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程序",核心方法论:

  1. 遵循"ISO C语法 + POSIX接口",必要时启用SUS的XSI扩展。
  2. 用"系统限制查询函数"(sysconf/pathconf)替代硬编码。
  3. 用"基本系统数据类型"(pid_t/uid_t)替代原生类型(int/long)。
  4. 用"功能测试宏"控制编译时启用的接口,避免冲突。

八、实践建议(必做代码练习)

  1. 编写程序,查询当前系统的NAME_MAX(文件名最大长度)、OPEN_MAX(最大打开文件数)、_SC_THREADS(是否支持线程)。
  2. 验证功能测试宏的作用:分别用-D_POSIX_C_SOURCE=200809L和不定义宏编译,观察struct timespec是否可用(POSIX.1-2008才支持)。
  3. 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_tgid_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_THREADSpathconf用于查询与文件相关的选项。
  • 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"),分别对应strlenread的返回值类型。
  • D错误off_t的长度需通过_FILE_OFFSET_BITS宏配置(如定义为64则为64位),32位系统默认可能为32位,需显式配置才能支持大文件偏移量。
  • E错误uid_tgid_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),更适合编写可移植程序,应优先使用。
相关推荐
郭涤生4 小时前
第一章_UINX基础知识_《UNIX 环境高级编程(第三版)》_笔记
网络·unix
我在人间贩卖青春4 天前
Unix和Linux简史及标准化
linux·unix
齐鲁大虾6 天前
UOS(统信操作系统)如何更新CUPS(通用Unix打印系统)
linux·服务器·chrome·unix
wregjru6 天前
【操作系统】2.用户和权限
linux·服务器·unix
race condition11 天前
UNIX网络编程笔记 信号处理
笔记·unix·信号处理
李斯维13 天前
第18章 过滤器:统计和格式化
linux·bash·unix
2401_8582861113 天前
OS39.5.【Linux】分析ar命令生成的归档文件的格式
linux·ar·unix
Jack___Xue16 天前
LangChain实战快速入门笔记(六)--LangChain使用之Agent
笔记·langchain·unix
CloudJourney16 天前
从Unix到OpenEuler及其关键协议解析
服务器·unix