这章主要讲了 Unix 相关标准,以及一些限制。
2.2 Unix Standardization
这里涉及的几个重要规范是:
- ISO C
- IEEE POSIX
- The Single UNIX Specification
2.2.1 ISO C
C 语言的规范是 ANSI 在 1989 年发布的,所以 C89 也被称作 ANSI C。一年后 ISO 收纳了这份 C 标准,有了 C90 跟 ISO C 的说法。C89 跟 C90 指的是同一份规范。这之后还有 C95, C99, C11,Wikipedia 上的 ANSI C 有简要描述。
现阶段最新的 gcc 6.1 默认使用的 C 语言规范是 gnu11
,即 GNU dialect of ISO C11。默认的 C++ 规范是 gnu++14
,即 GNU dialect of -std=c++14
。(来源)
2.2.2 IEEE POSIX
Portable Operating System Interface (POSIX) 定义了操作系统的接口需要实现什么功能。由 IEEE 维护,一开始制订时基于 Unix 系统,但是不限制在 Unix 上。
POSIX 一开始是指 IEEE Standard 1003.1-1988。这个规范又被称作 POSIX.1。这个规范也是这本书内容所涉及的规范。同时这个规范包含了 C 语言的规范。
满足这个规范的操作系统实现,被称作 POSIX compliant。貌似大部分主流操作系统是 POSIX compliant 的(但是不重要,反正你在 Unix/Linux 平台编程时,程序是相对容易在各个操作系统间移植的;而为 Windows 写的程序往往用途不一样,没有必要与 Linux 程序在代码上兼容)。
POSIX 的后续发展可以参考 Wikipedia 页面。
2.2.3 The Single UNIX Specification
The Single Unix Specification (SUS) 是 POSIX 的超集,定义了一些额外的功能(如文件同步、进程线程间同步等),由 The Open Group 维护。
SUS 定义了 X/Open System Interfaces (XSI),实现了 XSI 的操作系统叫作 XSI conforming。The Open Group 持有 UNIX 的商标,只有遵守 SUS 规范(即也实现了 XSI)的操作系统,才可以被称作 UNIX 操作系统。它的发展历史,参考 Wikipedia 页面。
2.3 Unix System Implementations
这部分很复杂,而且不重要,在 Wikipedia 上找了张图:
2.5 Limits
规范定义了一些限制。主要分为编译期知道的,比如一个 short 整形的最小取值;和运行期知道的,如文件名最长可以是多少字节(跟文件系统 (ext2, ext4, etc) 相关)。为了解决这个问题,规范定义了三种类型的限制:
- 编译期知道的限制(通过头文件)
- 跟文件/目录无关的,运行期知道的限制(通过
sysconf
函数) - 跟文件/目录相关的,运行期知道的限制(通过
pathconf
函数)
简单的说:ISO C 定义了数值类型相关的限制,POSIX.1 定义了操作系统相关的限制。XSI 的限制,不是太受关心。
2.5.1 ISO C Limits
ISO C 定义的编译期限制都放在 <limits.h>
头文件中。定义这些限制的目的,应该是让不同的系统(主要跟 CPU 相关,有不同的整型长度)可以用上补码运算(one's complement)。
留意其中比较奇怪的,是 CHAR_MIN
跟 CHAR_MAX
的值。对于一个提供 signed char 的系统(如 Linux),CHAR_MIN
跟 SCHAR_MIN
一样,CHAR_MAX
跟 SCHAR_MAX
一样;而对于一个提供 unsigned char 的系统(如 IBM AIX 6.1),CHAR_MIN
则跟 UCHAR_MIN
一样,CHAR_MAX
同理。
浮点数的限制定义在 <float.h>
。
POSIX.1 对 C 规范做了扩展,要求:
- INT_MAX >= 2,147,483,647
- INT_MIN <= -2,147,483,647
- UINT_MAX >= 4,294,967,295
- CHAR_BIT == 8,SCHAR_MIN == -128,SCHAR_MAX == 127,UCHAR_MAX == 255
ISO 还定义了几个限制。FOPEN_MAX
定义了一个进程同时可以打开的文件数,TMP_MAX
定义了 tmpnam
创建的临时文件的文件名长度限制,FILENAME_MAX
定义了文件名的长度限制,但是没什么用,因为 POSIX.1 定义了更好的 NAME_MAX
和 PATH_MAX
。一般不需要关注他们,因为你不会遇到这些问题。
2.5.2 POSIX Limits
POSIX.1 定义了一些限制。
但是这些限制存在的问题是,它有些值太小了不适合使用(比如 _POSIX_OPEN_MAX
才 20 个),而且大多数的 UNIX 系统都提供了更大的限制值。因此这表中 25 个值中,都存在一个跟本地系统相关的限制值。其中大部分(并不保证全部都有本地限制)定义在 <limits.h>
中,名称是其 POSIX 对应名去掉 _POSIX_
前缀,如 _POSIX_OPEN_MAX
的 local limit 定义为了 OPEN_MAX
。
2.5.3 XSI Limits
We don't care.
2.5.4 sysconf
, pathconf
, and fpathconf
Functions
#include <unistd.h>
long sysconf(int name);
long pathconf(const char *pathname, int name);
long fpathconf(int fd, int name);
All three return: corresponding value if OK, −1 on error (see later)
sysconf
的参数基本上是相关的 POSIX.1 限制名(2.5.2)加上 _SC_
前缀,如 _SC_ARG_MAX
, _SC_OPEN_MAX
;pathconf
和 fpathconf
是相应限制名加上 _PC_
前缀。
关于返回值:
- 如果
name
不是合适的值,这些函数返回-1
并设errno
为EINVAL
- 有一些
name
会返回该值不可知(indeterminate),体现在返回-1
但是不改变errno
的值 _SC_CLK_TCK
的返回值被用于与times
系列函数一起使用(8.17 节详述)
对于 pathconf
和 fpathconf
有一些限制如果不被满足,那么返回结果是未定义的;比如 _PC_MAX_CANON
和 _PC_MAX_INPUT
的文件对象必需是个 terminal。这些使用时查阅即可。
总而言之,获取一个限制值的办法是:
- 定义在
limits.h
中,去掉POSIX_
前缀的常量,比如OPEN_MAX
- 如果
limits.h
中无定义相应的常量或者其值为 -1,使用sysconf
或者pathconf
拿到的值,如sysconf(_SC_OPEN_MAX)
;如果limits.h
中的常量的值为 0,必须用sysconf
pathconf
判断这个常量是否受到支持 - 如果上一步的函数调用拿不到值,给一个猜测的值
POSIX_
前缀的相应常量值,是对这个限制的最高要求,实际场景一般不使用
2.5.5 Indeterminate Runtime Limits
有一些限制可能在运行时仍不可知。书里举了两个例子,PATH_MAX
和 OPEN_MAX
。
如果你需要为 getpwd
这类函数分配内存空间时,你需要关注路径的最大长度是多少,即 PATH_MAX
变量。PATH_MAX
存在的问题是:
PATH_MAX
有可能不存在在<limits.h>
中,这时我们需要调用pathconf
pathconf
获取PATH_MAX
时返回的是相对路径的最大长度,因此我们需要传入根路径,再对返回结果加 1- 如果
pathconf
获取不到值,我们只能给一个猜想的值 - POSIX.1 2001 版本前,并没有规定
pathconf
返回的值中是否包含最后的空字符;Single UNIX Specification 有指明需要
作者写了一段程序用来获取绝对路径的最长长度,值得参考。
OPEN_MAX
的使用场景,出现在一个后台进程需要关闭全部文件时。有些人假设 <sys/param.h>
中存在 NOFILE
(not portable),写了这样的程序:
#include <sys/param.h>
for (i = 0; i < NOFILE; i++)
close(i);
还有人用 <stdio.h>
中定义的 _NFILE
。这些都是不可移植的。正确的做法类似上一例子,优先用定义了的宏(OPEN_MAX
),如果不存在用 pathconf
,如果 pathconf
无法确定,那就用我们猜的值。
在 OPEN_MAX
不受限制的情况下(比如你用 ulimit
修改了它),Linux 会返回 LONG_MAX
。作者觉得这是个不好的实践,因为会导致上面的程序尝试关闭 2,147,483,647 个文件描述符,浪费一堆 CPU。
支持 getrlimit
的系统(XSI conforming),可以使用这个函数获取一个进程可打开的最大文件数,效果同作者 figure 2.17 里写的程序类似。
2.6 Options
这节主要讲了一些选项(Option),他们的值用来表示系统是否支持这种功能。比如 _PC_NO_TRUNC
为 -1 时表示系统不支持这个功能,大于 0 时表示文件名大于 NAME_MAX
时会产生错误。有一些选项(如 _POSIX_READER_WRITER_LOCKS
)的值是 200809L,表示是 POSIX1.2008 规范加进来的功能。
_SC_VERSION
_SC_XOPEN_VERSION
分别可以看系统支持的 POSIX 规范版本、XSI 功能对应的 SUS 规范的版本。
2.7 Feature Test Macros
一些系统实现可能会定义除了 POSIX、XSI 以外,自己的常量定义。如果想让程序不依赖这些常量定义,可以在编译时指定变量 _POSIX_C_SOURCE
,比如 cc -D_POSIX_C_SOURCE=200809L file.c
表示 file.c
遵守 POSIX.1 规范。同样的 XSI 规范对应的变量是 _XOPEN_SOURCE
。
2.8 Primitive System Data Types
POSIX 定义了一些数据类型,用来表示某种特殊的含义。比如 size_t
可以用来表示字符串长度。这样做的目的是为了让写程序的不需要去关心具体的数据大小,比如你要存一个字符串长度,你不需要去考虑用 unsigned short
还是 unsigned int
,你只需要直接用 size_t
就好了。你也不需要去知道 size_t
表示多大的数据范围。同时 size_t
可能在不同的系统上有不同的数据范围。
2.9 Differences Between Standards
ISO C 与 POSIX 规范有冲突时,POSIX 规范会尊重 ISO C。绝大多数是没有冲突的,但是也有一些小情况有冲突。比如对于 clock_t
,ISO C 函数 clock
返回一个进程所使用的 CPU 时间,你需要拿他除以 CLOCKS_PER_SEC
来表示使用了多少秒;但是对于 POSIX 的 times
系列函数,返回的值需要除以 sysconf
获取的 clock ticks per second 值来表示使用了多少秒。这里不同的 per second 值可能导致 clock_t
的单位不一样。