第2章 UNIX标准及实现

2.1 引言

人们在UNIX编程环境和C程序设计语言的标准化方面已经做了很多工作。虽然UNIX应用程序在不同的UNIX操作系统版本之间进行移植相当容易,但是20世纪80年代UNIX版本种类的剧增以及它们之间差别的扩大,导致很多大用户(如美国政府)呼吁对其进行标准化。

本章首先回顾过去近 25 年人们在 UNIX 标准化方面做出的种种努力,然后讨论这些 UNIX编程标准对本书所列举的各种UNIX操作系统实现的影响。所有标准化工作的一个重要部分是对每种实现必须定义的各种限制进行说明,所以我们将说明这些限制以及确定它们值的各种方法。

2.2 UNIX标准化

2.2.1 ISO C

1989年下半年,C程序设计语言的ANSI标准X3.159-1989得到批准。此标准被也采纳为国际标准ISO/IEC 9899:1990。ANSI是美国国家标准学会(American National Standards Institute)的缩写,它是国际标准化组织(International Organization for Standardization,ISO)中代表美国的成员。IEC是国际电子技术委员会(International Electrotechnical Commission)的缩写。

ISO C标准现在由ISO/IEC的C程序设计语言国际标准工作组维护和开发,该工作组称为ISO/IEC JTC1/SC22/WG14,简称 WG14。ISO C标准的意图是提供C程序的可移植性,使其能适合于大量不同的操作系统,而不只是适合UNIX系统。此标准不仅定义了C程序设计语言的语法和语义,还定义了其标准库(参见ISO 1999第7章;Plauger[1992];Kernighan和Ritchie[1988]中的附录B)。因为所有现今的UNIX系统(如本书介绍的几个UNIX系统)都提供C标准中定义的库函数,所以该标准库非常重要。

1999年,ISO C标准被更新,并被批准为ISO/IEC 9899:1999,它显著改善了对进行数值处理的应用软件的支持。除了对某些函数原型增加了关键字restrict外,这种改变并不影响本书中描述的POSIX接口。restrict关键字告诉编译器,哪些指针引用是可以优化的,其方法是指出指针引用的对象在函数中只通过该指针进行访问。

1999年以来,已经公布了3个技术勘误来修正ISO C标准中的错误,分别在2001年、2004年和 2007 年公布。如同大多数标准一样,在批准标准和修改软件使其符合标准两者之间有一段时间延迟。随着供应商编译系统的不断演化,对最新ISO C标准的支持也就越来越多。

gcc对ISO C标准1999版本符合程度的总结可参见http://www.gnu.org/c99status. html,虽然C标准已经在2011年更新,但由于其他标准还没有进行相应的更新,因此在本书中我们还是沿用1999年的版本。

按照该标准定义的各个头文件(见图2-1)可将ISO C库分成24个区。POSIX.1标准包括这些头文件以及另外一些头文件。从图2-1中可以看出,所有这些头文件在4种UNIX实现(FreeBSD 8.0、Linux 3.2.0、Mac OS X 10.6.8和Solaris 10)中都支持。本章后面将对这4种UNIX实现进行说明。

ISO C头文件依赖于操作系统所配置的C编译器的版本。FreeBSD 8.0配置了gcc 4.2.1版, Solaris 10配置了gcc 3.4.3版(以及Sun Studio自带的C编译器),Ubuntu 12.04(Linux 3.2.0)配置了gcc 4.6.3版,Mac OS X 10.6.8配置了gcc 4.0.1和4.2.1版。

图2-1 ISO C标准定义的头文件

2.2.2 IEEE POSIX

POSIX是一个最初由IEEE(Institute of Electrical and Electronics Engineers,电气和电子工程师学会)制订的标准族。POSIX指的是可移植操作系统接口(Portable Operating System Interface)。它原来指的只是IEEE标准1003.1-1988(操作系统接口),后来则扩展成包括很多标记为1003的标准及标准草案,如shell和实用程序(1003.2)。

与本书相关的是1003.1操作系统接口标准,该标准的目的是提升应用程序在各种UNIX系统环境之间的可移植性。它定义了“符合POSIX 的”(POSIX compliant)操作系统必须提供的各种服务。该标准已被很多计算机制造商采用。虽然1003.1标准是以UNIX操作系统为基础的,但是它并不限于UNIX和UNIX类的系统。确实,有些提供专有操作系统的制造商也声称他们的系统符合POSIX(同时还保留所有专有功能)。

由于 1003.1 标准说明了一个接口(interface)而不是一种实现(implementation),所以并不区分系统调用和库函数。所有在标准中的例程都被称为函数。

标准是不断演进的,1003.1标准也不例外。该标准的1988版,即IEEE标准1003.1-1988经修改后递交给 ISO,它没有增加新的接口或功能,但修订了文本。最终的文档作为 IEEE 标准1003.1-1990 正式出版[IEEE 1990],这也就是国际标准 ISO/IEC 9945-1:1990。该标准通常称为POSIX.1,本书将使用此术语来表示不同版本的标准。

IEEE 1003.1工作组此后继续对这一标准做了更多修改。1996年,该标准的修订版发布,它包括了1003.1-1990、1003.1b-1993实时扩展标准以及被称为pthreads的多线程编程接口(POSIX线程),这就是国际标准ISO/IEC 9945-1:1996。1999年出版了IEEE标准1003.1d-1999,其中增加了更多实时接口。一年后,出版了IEEE标准1003.1j-2000和1003.1q-2000,前者包含了更多实时接口,后者增加了标准在事件跟踪方面的扩展。

2001年的1003.1版本与以前各版本有较大的差别,它组合了多个1003.1的修正、1003.2标准以及Single UNIX Specificaiton(SUS)第2版的若干部分(对于SUS,后面将进行更多说明),这形成了IEEE标准1003.1-2001,它包括下列几个标准。

•ISO/IEC 9945-1(IEEE标准1003.1-1996),包括

♦IEEE标准1003.1-1990

♦IEEE标准1003.1b-1993(实时扩展)

♦IEEE标准1003.1c-1995(pthreads)

♦IEEE标准1003.1i-1995(实时技术勘误表)

•IEEE P1003.1a草案(系统接口修正)

•IEEE标准1003.1d-1999(高级实时扩展)

•IEEE标准1003.1j-2000(更多高级实时扩展)

•IEEE标准1003.1q-2000(跟踪)

•部分IEEE标准1003.1g-2000(协议无关接口)

•ISO/IEC 9945-2(IEEE标准1003.2-1993)

•IEEE P1003.2b草案(shell及实用程序的修正)

•IEEE标准1003.2d-1994(批处理扩展)

•Single UNIX Specification第2版基本说明,包括

♦系统接口定义,第5发行版

♦命令和实用程序,第5发行版

♦系统接口和头文件,第5发行版

•开放组技术标准,网络服务,5.2发行版

•ISO/IEC 9899-1999,C程序设计语言

2004年,POSIX.1说明随着技术勘误得到更新,2008年做了更多综合的改动并作为基本说明的第 7 发行版发布,ISO 在 2008 年底批准了这个版本并在 2009 年进行发布,即国际标准ISO/IEC9945:2009。该标准基于其他几个标准。

•IEEE标准1003.1,2004年版。

•开放组织技术标准,2006,扩展API集,第1~4部分。

•ISO/IEC 9899:1999,包含勘误表。

图2-2、图2-3以及图2-4总结了POSIX.1指定的必需的和可选的头文件。因为POSIX.1包含了ISO C标准库函数,所以它还需要图2-1中列出的各个头文件。这4张图中的表也总结了本书所讨论的4种UNIX系统实现所包含的头文件。

图2-2 POSIX标准定义的必需的头文件

本书中描述了POSIX.1 2008年版,其接口分成两部分:必需部分和可选部分。可选接口部分按功能又进一步分成40个功能分区。图2-5按各自的选项码总结了包含未弃用的编程接口。选项码是能够表述标准的 2~3 个字母的缩写,用以标识属于各个功能分区的接口,其中的接口依赖于特定选项的支持。很多选项处理实时扩展。

图2-3 POSIX标准定义的XSI可选头文件

图2-4 POSIX标准定义的可选头文件

图2-5 POSIX.1可选接口组和选项码

POSIX.1 没有包括超级用户(superuser)这样的概念,代之以规定某些操作要求“适当的优先权”,POSIX.1将此术语的含义留由具体实现进行解释。某些符合美国国防部安全性指南要求的UNIX系统具有很多不同的安全级。本书仍使用传统的UNIX术语,并指明要求超级用户特权的操作。

经过20多年的工作,相关标准已经成熟稳定。POSIX.1标准现在由Austin Group开放工作组(http://www.opengroup.org/austin)维护。为了保证它们仍然有价值,仍需经常对这些标准进行更新或再确认。

2.2.3 Single UNIX Specification

Single UNIX Specification(SUS,单一UNIX规范)是POSIX.1标准的一个超集,它定义了一些附加接口扩展了POSIX.1规范提供的功能。POSIX.1相当于Single UNIX Specification中的基本规范部分。

POSIX.1中的X/Open系统接口(X/Open System Interface,XSI)选项描述了可选的接口,也定义了遵循XSI(XSI conforming)的实现必须支持POSIX.1的哪些可选部分。这些必须支持的部分包括:文件同步、线程栈地址和长度属性、线程进程共享同步以及_XOPEN_UNIX符号常量(在图2-5中它们被加上“SUS强制的”的标记)。只有遵循XSI的实现才能称为UNIX系统。

Open Group拥有UNIX商标,他们使用Single UNIX Specification定义了一系列接口。一个系统要想称为 UNIX 系统,其实现必须支持这些接口。UNIX 系统供应商必须以文件形式提供符合性声明,并通过验证符合性的测试,才能得到使用UNIX商标的许可证。

有些接口在遵循 XSI 的系统中是可选的,这些接口根据功能被分成若干选项组(option group),具体如下。

•加密:由符号常量_XOPEN_CRYPE标记。

•实时:由符号常量_XOPEN_REALTIME标记。

•高级实时。

•实时线程:由符号常量_XOPEN _REALTIME_THREADS标记。

•高级实时线程。

Single UNIX Specification 是 Open Group 的出版物,而 Open Group 是由两个工业社团X/Open和开放系统软件基金会(Open System Software Foundation,OSF)在1996年合并构成的。X/Open过去出版了X/Open Portability Guide(X/Open可移植性指南),它采用了若干特定标准,填补了其他标准缺失功能的空白。这些指南的目的是改善应用的可移植性,使其不仅仅符合已发布的标准。

X/Open在1994年发布了Single UNIX Specification第1版,因为它大约包含了1170个接口,因此也称为“Spec 1170”。它源自通用开放软件环境(Common Open Software Environment, COSE)的倡议,该倡议的目标是进一步改善应用程序在所有 UNIX 操作系统实现之间的可移植性。COSE的成员包括Sun、IBM、HP、Novell/USL以及OSF等,他们的UNIX都包含了通用商业化应用软件使用的接口,这较之仅仅赞同和支持标准前进了一大步。从这些应用软件的接口中选出的 1170 个接口被包括在下列标准中:X/Open 通用应用环境(Common Application Environment,CAE)第4发行版(也被称为XPG4,以表示它与其前身X/Open Portability Guide的历史关系)、系统V接口定义(System V Interface Definition,SVID)第3版Level 1接口、OSF应用环境规范(Application Environment Specification,AES)Full Use接口。

1997年,Open Group发布了Single UNIX Specification第2版。新版本增加了对线程、实时接口、64位处理、大文件以及增强的多字节字符处理等功能的支持。

Single UNIX Specification第3版(SUSv3)由Open Group在2001年发布。SUSv3的基本规范与IEEE标准1003.1-2001相同,分成4个部分:基本定义、系统接口、shell和实用程序以及基本理论。SUSv3还包括X/Open Curses第4发行版第2版,但该规范并不是POSIX.1的组成部分。

2002年,ISO将IEEE标准1003.1-2001批准为国际标准ISO/IEC 9945:2002。Open Group在2003 年再次更新了 1003.1 标准,包括了技术方面的更正。ISO 将其批准为国际标准 ISO/IEC 9945:2003。2004年4月,Open Group发布了Single UNIX Specification第3版2004年版,将更多技术上的更正合并到标准的正文中。

2008年,Single UNIX Specification再次更新,包括了更正和新的接口、移除弃用的接口以及将一些未来可能被删除的接口标记为弃用接口等。另外,有一些过去被认为可选的接口变成必选接口,其中包括异步I/O、屏障、时钟选择、存储映像文件、内存保护、读写锁、实时信号、POSIX信号量、旋转锁、线程安全函数、线程、超时机制以及时钟等。最终形成的标准就是基本规范的第7发行版,也即POSIX.1-2008。Open Group把这个版本和X/OPEN Curses规范的更新版打包,并于2010年作为Single UNIX Specification第4版发布。我们把这个规范称为SUSv4。

2.2.4 FIPS

FIPS代表的是联邦信息处理标准(Federal Information Processing Standard),这一标准是由美国政府发布的,并由美国政府用于计算机系统的采购。FIPS151-1(1989年4月)基于IEEE标准1003.1-1988及ANSI C标准草案。此后是FIPS 151-2(1993年5月),它基于IEEE标准1003.1-1990。在POSIX.1中列为可选的某些功能,在FIPS 151-2中是必需的。所有这些可选功能在POSIX.1-2001中已成为强制性要求。

POSIX.1 FIPS的作用是,它要求任何希望向美国政府销售符合POSIX.1标准的计算机系统的厂商都应支持POSIX.1的某些可选功能。因为POSIX.1 FIPS已经撤回,所以在本书中我们不再进一步考虑它。

2.3 UNIX系统实现

上一节说明了3个由各自独立的组织所制定的标准:ISO C、IEEE POSIX以及Single UNIX Specification。但是,标准只是接口的规范。这些标准是如何与现实世界相关连的呢?这些标准由厂商采用,然后转变成具体实现。本书中我们不仅对这些标准感兴趣,还对它们的具体实现感兴趣。

在McKusick等[1996]的1.1节中给出了UNIX系统家族树的详细历史。UNIX的各种版本和变体都起源于在PDP-11系统上运行的UNIX分时系统第6版(1976年)和第7版(1979年)(通常称为V6和V7)。这两个版本是在贝尔实验室以外首先得到广泛应用的UNIX系统。从这棵树上演进出以下3个分支。

(1)AT&T分支,从此引出了系统III和系统V(被称为UNIX的商用版本)。

(2)加州大学伯克利分校分支,从此引出4.xBSD实现。

(3)由AT&T贝尔实验室的计算科学研究中心不断开发的UNIX研究版本,从此引出UNIX分时系统第8版、第9版,终止于1990年的第10版。

2.3.1 SVR4

SVR4(UNIX System V Release 4)是AT&T的UNIX系统实验室(UNIX System Laboratories, USL,其前身是AT&T的UNIX Software Operation)的产品,它将下列系统的功能合并到了一个一致的操作系统中:AT&T UNIX系统V 3.2版(SVR3.2)、Sun Microsystems公司的SunOS操作系统、加州大学伯克利分校的4.3BSD以及微软的Xenix系统(Xenix是在V7的基础上开发的,后来又采纳了很多系统V的功能)。其源代码于1989年后期发布,在1990年开始向终端用户提供。SVR4符合POSIX 1003.1标准和X/Open XPG3标准。

AT&T也出版了系统V接口定义(SVID)[AT&T 1989]。SVID第3版说明了UNIX系统要达到SVR4质量要求必须提供的功能。如同POSIX.1一样,SVID定义了一个接口,而不是一种实现。SVID 并不区分系统调用和库函数。对于一个 SVR4 的具体实现,应查看其参考手册,以了解系统调用和库函数的不同之处[AT&T 1990e]。

2.3.2 4.4BSD

BSD(Berkeley Software Distribution)是由加州大学伯克利分校的计算机系统研究组(CSRG)研究开发和分发的。4.2BSD于1983年问世,4.3BSD则于1986年发布。这两个版本都在VAX小型机上运行。它们的下一个版本4.3BSD Tahoe于1988年发布,在一台称为Tahoe的小型机上运行(Leffler等[1989]说明了4.3BSD Tahoe版)。其后又有1990年的4.3BSD Reno版,它支持很多POSIX.1的功能。

最初的BSD系统包含了AT&T专有的源代码,它们需要AT&T许可证。为了获得BSD系统的源代码,首先需要持有 AT&T 的 UNIX 源代码许可证。这种情况正在改变,近几年,越来越多的AT&T源代码被替换成非AT&T源代码,很多添加到BSD系统上的新功能也来自于非AT&T方面。

1989年,伯克利将4.3BSD Tahoe中很多非AT&T源代码包装成BSD网络软件1.0版,并使其成为可公开获得的软件。1991年发布了BSD网络软件2.0版,它是从4.3BSD Reno版派生出来的,其目的是使大部分(如果不是全部的话)4.4BSD系统不再受AT&T许可证的限制,这样,大家都可以得到源代码。

4.4BSD-Lite是CSRG计划开发的最后一个发行版。由于与USL产生的法律纠纷,该版本曾一度延迟推出。在纠纷解决后,4.4BSD-Lite立即于1994年发布,并且不再需要具有UNIX源代码使用许可证就可以使用它。1995年CSRG发布了修复了bug的版本。4.4BSD-Lite第2发行版是CSRG的最后一个BSD版本(McKusick等[1996]描述了该BSD版本)。

在伯克利所进行的UNIX开发工作是从PDP-11开始的,然后转移到VAX小型机上,接着又转移到工作站上。20 世纪 90 年代早期,伯克利得到支持在广泛应用的 80386 个人计算机上开发BSD版本,结果产生了386BSD。这一工作是由Bill Jolitz完成的,其工作在1991年全年的Dr.Dobb’s期刊上以每月一篇文章连载发表。其中很多代码出现在BSD网络软件2.0版中。

2.3.3 FreeBSD

FreeBSD基于4.4BSD-Lite 操作系统。在加州大学伯克利分校的 CSRG决定终止其在UNIX操作系统的BSD版本的研发工作,而且386BSD项目被忽视很长时间之后,为了继续坚持BSD系列,形成了FreeBSD项目。

由FreeBSD项目产生的所有软件,包括其二进制代码和源代码,都是免费使用的。为了测试书中的实例,本书选取了4个操作系统,FreeBSD 8.0操作系统是其中之一。

有许多基于 BSD 的免费操作系统。NetBSD 项目(http://www.netbsd.org)类似于FreeBSD项目,但是更注重不同硬件平台之间的可移植性。OpenBSD项目(http://www.openbsd. org)也类似于FreeBSD项目,但更注重于安全性。

2.3.4 Linux

Linux是一种提供类似于UNIX的丰富编程环境的操作系统,在GNU公用许可证的指导下, Linux是免费使用的。Linux的普及是计算机产业中的一道亮丽风景线。Linux经常是支持较新硬件的第一个操作系统,这一点使其引人注目。

Linux是由Linus Torvalds在1991年为替代MINIX而研发的。一位当时名不见经传人物的努力掀起了澎湃巨浪,吸引了遍布全世界的很多软件开发者,在使用和不断增强Linux方面自愿贡献出了他们大量的时间。

Ubuntu 12.04 的 Linux 分发版本是用以测试本书实例的操作系统之一。该系统使用了 Linux操作系统3.2.0版内核。

2.3.5 Mac OS X

与其以前的版本相比,Mac OS X使用了完全不同的技术。其核心操作系统称为“Darwin”,它基于Mach内核(Accetta等[1986])、FreeBSD操作系统以及具有面向对象框架的驱动和其他内核扩展的结合。Mac OS X 10.5的Intel部分已经被验证为是一个UNIX系统。(关于UNIX验证的更多信息,请参见http://www.opengroup.org/certification/idx/UNIX.html)。

Mac OS X 10.6.8(Darwin 10.8.0)是用以测试本书实例的操作系统之一。

2.3.6 Solaris

Solaris是由Sun Microsystems(现为Oracle)开发的UNIX系统版本。它基于SVR4,在超过15 年的时间里,Sun Microsystems 的工程师对其功能不断增强。它是唯一在商业上取得成功的SVR4后裔,并被正式验证为UNIX系统。

2005年,Sun Microsystems把Solaris操作系统的大部分源代码开放给公众,作为OpenSolaris开放源代码操作系统的一部分,试图建立围绕Solaris的外部开发人员社区。

Solaris 10 UNIX操作系统也是用以测试本书实例的操作系统之一。

2.3.7 其他UNIX系统

已经通过验证的其他UNIX版本包括:

•AIX,IBM版的UNIX系统;

•HP-UX,HP版的UNIX系统;

•IRIX,Silicon Graphics版的UNIX系统;

•UnixWare,SVR4派生的UNIX系统,现由SCO销售。

2.4 标准和实现的关系

前面提到的各个标准定义了任一实际系统的子集。本书主要关注4种实际的UNIX系统:FreeBSD 8.0、Linux 3.2.0、Mac OS X 10.6.8和Solaris 10。在这4种系统中,虽然只有Mac OS X 和Solaris 10 能够称自己是一种UNIX系统,但是所有这4种系统都提供UNIX编程环境。因为所有这4种系统都在不同程度上符合POSIX标准,所以我们也将重点关注POSIX.1标准所要求的功能,并指出这4种系统具体实现与POSIX 之间的差别。仅仅一个特定实现所具有的功能和例程会被清楚地标记出来。我们还关注那些属于UNIX系统必需的,但却在符合POSIX标准的系统中是可选的功能。

应当看到,这些实现都提供了对它们早期版本(如SVR3.2和4.3BSD)功能的向后兼容性。例如,Solaris 对 POSIX.1 规范中的非阻塞 I/O(O_NONBLOCK)以及传统的系统 V 中的方法(O_NDELAY)都提供了支持。本书将只使用POSIX.1的功能,但是也会提及它所替换的是哪一种非标准功能。与此相类似,SVR3.2和4.3BSD以某种方法提供了可靠的信号机制,这种方法也有别于POSIX.1标准。第10章将只说明POSIX.1的信号机制。

2.5 限制

UNIX 系统实现定义了很多幻数和常量,其中有很多已被硬编码到程序中,或用特定的技术确定。由于大量标准化工作的努力,已有若干种可移植的方法用以确定这些幻数和具体实现定义的限制。这非常有助于改善UNIX环境下软件的可移植性。

以下两种类型的限制是必需的。

(1)编译时限制(例如,短整型的最大值是什么?)

(2)运行时限制(例如,文件名有多少个字符?)

编译时限制可在头文件中定义。程序在编译时可以包含这些头文件。但是,运行时限制则要求进程调用一个函数获得限制值。

另外,某些限制在一个给定的实现中可能是固定的(因此可以静态地在一个头文件中定义),而在另一个实现中则可能是变动的(需要有一个运行时函数调用)。这种类型限制的一个例子是文件名的最大字符数。SVR4之前的系统V由于历史原因只允许文件名最多包含14个字符,而源于BSD的系统则将此增加为255。目前,大多数UNIX系统支持多文件系统类型,而每一种类型有它自己的限制。文件名的最大长度依赖于该文件处于何种文件系统,例如,根文件系统中的文件名长度限制可能是14个字符,而在另一个文件系统中文件名长度限制可能是255个字符,这是运行时限制的一个例子。

为了解决这类问题,提供了以下3种限制。

(1)编译时限制(头文件)。

(2)与文件或目录无关的运行时限制(sysconf函数)。

(3)与文件或目录有关的运行时限制(pathconf和fpathconf函数)。

使事情变得更加复杂的是,如果一个特定的运行时限制在一个给定的系统上并不改变,则可将其静态地定义在一个头文件中,但是,如果没有将其定义在头文件中,应用程序就必须调用 3个conf函数中的一个(我们很快就会对它们进行说明),以确定其运行时的值。

2.5.1 ISO C限制

ISO C定义的所有编译时限制都列在头文件<limits.h>中(见图2-6)。这些限制常量在一个给定系统中并不会改变。表中第3列列出了ISO C标准可接受的最小值。这用于16位整型的系统,用1的补码表示。第4列列出了32位整型Linux系统的值,用2的补码表示。注意,我们没有列出无符号数据类型的最小值,这些值应该都为0。在64位系统中,其long整型的最大值与表中long long整型的最大值相匹配。

图2-6 <limits.h>中定义的整型值大小

我们将会遇到的一个区别是系统是否提供带符号或无符号的的字符值。从图2-6中的第4列可以看出,该特定系统使用带符号字符。从图中可以看到CHAR_MIN 等于SCHAR_MIN,CHAR_MAX等于SCHAR_MAX。如果系统使用无符号字符,则CHAR_MIN等于0,CHAR_MAX等于UCHAR_MAX。

在头文件<float.h>中,对浮点数据类型也有类似的一组定义。如若读者在工作中涉及大量浮点数据类型,则应仔细查看该文件。

虽然ISO C标准规定了整型数据类型可接受的最小值,但POSIX.1对C标准进行了扩充。为了符合POSIX.1标准,具体实现必须支持INT_MAX的最小值为2 147 483 647,INT_MIN为2 147 483 647, UINT_MAX为4 294 967 295。因为POSIX.1要求具体实现支持8位的char类型,所以CHAR_BIT必须是8,SCHAR_MIN必须是−128,SCHAR_MAX必须是127,UCHAR_MAX必须是255。

我们会遇到的另一个ISO C常量是FOPEN_MAX,这是具体实现保证可同时打开的标准I/O流的最小个数,该值在头文件<stdio.h>中定义,其最小值是8。POSIX.1中的STREAM_MAX(若定义的话)则应与FOPEN_MAX具有相同的值。

ISO C还在<stdio.h>中定义了常量TMP_MAX,这是由tmpnam函数产生的唯一文件名的最大个数。关于此常量我们将在5.13节中进行更多说明。

虽然ISO C定义了常量FILENAME_MAX,但我们应避免使用该常量,因为POSIX.1提供了更好的替代常量(NAME_MAX和PATH_MAX),我们很快就会介绍该常量。

在图2-7中,我们列出了本书所讨论4种平台上的FILENAME_MAX、FOPEN_MAX和TMP_MAX值。

图2-7 在各种平台上ISO的限制

2.5.2 POSIX限制

POSIX.1定义了很多涉及操作系统实现限制的常量,遗憾的是,这是POSIX.1中最令人迷惑不解的部分之一。虽然POSIX.1定义了大量限制和常量,我们只关心与基本POSIX.1接口有关的部分。这些限制和常量分成下列7类。

(1)数值限制:LONG_BIT、SSIZE_MAX和WORD_BIT。

(2)最小值:图2-8中的25个常量。

(3)最大值:_POSIX_CLOCKRES_MIN。

(4)运行时可以增加的值:CHARCLASS_NAME_MAX、COLL_WEIGHTS_MAX、LINE_MAX、NGROUPS_MAX和RE_DUP_MAX。

(5)运行时不变值(可能不确定):图2-9中的17个常量(加上12.2节中介绍的4个常量和14.5节中介绍的3个常量)。

(6)其他不变值:NL_ARGMAX、NL_MSGMAX、NL_SETMAX和NL_TEXTMAX。

(7)路径名可变值:FILESIZEBITS、LINK_MAX、MAX_CANON、MAX_INPUT、NAME_MAX、PATH_MAX、PIPE_BUF和SYMLINK_MAX。

在这些限制和常量中,某些可能定义在<limits.h>中,其余的则按具体条件可定义、可不定义。在2.5.4节中说明sysconf、pathconf和fpathconf函数时,我们将描述可定义或可不定义的限制和常量。在图2-8中,我们列出了25个最小值。

这些最小值是不变的—它们并不随系统而改变。它们指定了这些特征最具约束性的值。一个符合POSIX.1的实现应当提供至少这样大的值。这就是为什么将它们称为最小值,虽然它们的名字都包含了MAX。另外,为了保证可移植性,一个严格符合POSIX标准的应用程序不应要求更大的值。我们将在本书的适当章节说明每一个常量的含义。

一个严格符合(strictly conforming)POSIX的应用区别于一个刚刚符合POSIX(merely POSIX confirming)的应用。符合POSIX的应用只使用在IEEE 1003.1-2001中定义的接口。严格符合POSIX的应用满足更多的限制,例如,不依赖于POSIX未定义的行为、不使用其任何已弃用的接口以及不要求所使用的常量值大于图2-8中所列出的最小值。

图2-8 <limits.h>中的POSIX.1最小值

遗憾的是,这些不变最小值中的某一些在实际应用中太小了。例如,目前在大多数UNIX系统中,每个进程可同时打开的文件数远远超过20。另外,_POSIX_PATH_MAX的最小限制值为255,这太小了,路径名可能会超过这一限制。这意味着在编译时不能使用_POSIX_OPEN_MAX 和_POSIX_PATH_MAX这两个常量作为数组长度。

图2-8中的25个不变最小值的每一个都有一个相关的实现值,其名字是将图2-8中的名字删除前缀_POSIX_后构成的。没有_POSIX_前缀的名字用于给定具体实现支持的该不变最小值的实际值(这25个实现值是本节开始部分所列出的1、4、5、7类:2个是运行时可以增加的值、15个是运行时不变值、7个是路径名可变值,以及数值SSIZE_MAX)。问题是并不能确保所有这25个实现值都在<limit.h>头文件中定义。

例如,某个特定值可能不在此头文件中定义,其理由是:一个给定进程的实际值可能依赖于系统的存储总量。如果没有在头文件中定义它们,则不能在编译时使用它们作为数组边界。所以,POSIX.1提供了3个运行时函数以供调用,它们是:sysconf、pathconf以及fpathconf。使用这3个函数可以在运行时得到实际的实现值。但是,还有一个问题,其中某些值由 POSIX.1 定义为“可能不确定的”(逻辑上无限的),这就意味着该值没有实际上限。例如,在Solaris中,进程结束时注册可运行atexit的函数个数仅受系统存储总量的限制。所以在 Solaris 中,ATEXIT_MAX 被认为是不确定的。2.5.5 节还将讨论运行时限制不确定的问题。

图2-9 <limits.h>中的POSIX.1运行时不变值

2.5.3 XSI限制

XSI定义了代表实现限制的几个常量。

(1)最小值:图2-10中列出的5个常量。

(2)运行时不变值(可能不确定):IOV_MAX和PAGE_SIZE。

图 2-10 列出了最小值。最后两个常量值说明了 POSIX.1 最小值太小的情况,根据推测这可能是考虑到了嵌入式POSIX.1实现。为此,Single UNIX Specification为符合XSI的系统增加了具有较大最小值的符号。

图2-10 <limits.h>中的XSI最小值

2.5.4 函数sysconf、pathconf和fpathconf

我们已列出了实现必须支持的各种最小值,但是怎样才能找到一个特定系统实际支持的限制值呢?正如前面提到的,某些限制值在编译时是可用的,而另外一些则必须在运行时确定。我们也曾提及某些限制值在一个给定的系统中可能是不会更改的,而其他限制值可能会更改,因为它们与文件和目录相关联。运行时限制可调用下面3个函数之一获得。

#include <unistd.h>

long sysconf(int name);

long pathconf(const char *pathname, int name);

log fpathconf(int fd, int name);

所有函数返回值:若成功,返回相应值;若出错,返回-1(见后)

后面两个函数的差别是:一个用路径名作为其参数,另一个则取文件描述符作为参数。

图2-11中列出了sysconf函数所使用的name参数,它用于标识系统限制。以_SC_开始的常量用作标识运行时限制的sysconf参数。图2-12列出了pathconf和fpathconf函数为标识系统限制所使用的name参数。以_PC_开始的常量用作标识运行时限制的pathconf或fpathconf参数。

我们需要更详细地讨论一下这3个函数不同的返回值。

(1)如果name参数并不是一个合适的常量,这3个函数都返回−1,并把errno置为EINVAL。图2-11和图2-12的第3列给出了我们在整本书中将要涉及的限制常量。

(2)有些name会返回一个变量值(返回值≥0)或者提示该值是不确定的。不确定的值通过返回−1来体现,而不改变errno的值。

图2-11 对sysconf的限制及name参数

图2-12 对pathconf和fpathconf的限制及name参数

(3)_SC_CLK_TCK的返回值是每秒的时钟滴答数,用于times函数的返回值(8.17节)。

对于pathconf的参数pathname和fpathconf的参数fd有很多限制。如果不满足其中任何一个限制,则结果是未定义的。

(1)_PC_MAX_CANON和_PC_MAX_INPUT引用的文件必须是终端文件。

(2)_PC_LINK_MAX 和_PC_TIMESTAMP_RESOLUTION 引用的文件可以是文件或目录。如果是目录,则返回值用于目录本身,而不用于目录内的文件名项。

(3)_PC_FILESIZEBITS和_PC_NAME_MAX引用的文件必须是目录,返回值用于该目录中的文件名。

(4)_PC_PATH_MAX引用的文件必须是目录。当所指定的目录是工作目录时,返回值是相对路径名的最大长度(遗憾的是,这不是我们想要知道的一个绝对路径名的实际最大长度,我们将在2.5.5节中再次回到这一问题上来)。

(5)_PC_PIPE_BUF引用的文件必须是管道、FIFO或目录。在管道或FIFO情况下,返回值是对所引用的管道或FIFO的限制值。对于目录,返回值是对在该目录中创建的任一FIFO的限制值。

(6)_PC_SYMLINK_MAX引用的文件必须是目录。返回值是该目录中符号链接可包含字符串的最大长度。

实例

图2-13中所示的awk(1)程序构建了一个C程序,它打印各pathconf和sysconf符号的值。

图2-13 构建C程序以打印所有得到支持的系统配置限制

该awk程序读两个输入文件——pathconf.sym和sysconfig.sym,这两个文件中包含了用制表符分隔的限制名和符号列表。并非每种平台都定义所有符号,所以围绕每个pathconf和sysconf调用,awk程序都使用了必要的#ifdef语句。

例如,awk程序将输入文件中类似于下列形式的行:

NAME_MAX _PC_NAME_MAX

转换成下列C代码:

#ifdef NAME_MAX

printf("NAME_MAX is defined to be %d\n", NAME_MAX+0);

#else

printf("no symbol for NAME_MAX\n");

#endif

#ifdef _PC_NAME_MAX

pr_pathconf("NAME_MAX =", argv[1], _PC_NAME_MAX);

#else

printf("no symbol for _PC_NAME_MAX\n");

#endif

由awk产生的C程序如图2-14所示,它会打印所有这些限制,并处理未定义限制的情况。

图2-14 打印所有可能的sysconf和pathconf值

图2-15总结了在本书讨论的4种系统上图2-14所示程序的输出结果。“无符号”项表示该系统没有提供相应_SC或_PC符号以查询相关常量值。因此,该限制是未定义的。与此对比,“不支持”项表示该符号由系统定义,但是未被sysconf和pathcon函数识别。“无限制”项表示该系统将相关常量定义为无限制,但并不表示该限制值可以是无限的,它只表示该限制值不确定。

图2-15 配置限制的实例

注意,有些限制报告地并不正确。例如,在Linux中,SYMLOOP_MAX被报告成无限制,但是检查源代码后就会发现,实际上它在硬编码中有限制值,这一限制将循环缺失的情况下遍历连续符号链接的数目限制为40(参阅fs/namei.c中的follow_link函数)。

Linux中另一个潜在的不精确的来源是pathconf和fpathconf函数都是在C库函数中实现的,这些函数返回的配置限制依赖于底层的文件系统类型,因此如果你的文件系统不被C库熟知的话,函数返回的是一个猜测值。

我们将在4.14节中看到,UFS是Berkeley快速文件系统的SVR4实现,PCFS是Solaris的MS-DOS FAT文件系统的实现。

2.5.5 不确定的运行时限制

前面已提及某些限制值可能是不确定的。我们遇到的问题是,如果这些限制值没有在头文件<limits.h>中定义,那么在编译时也就不能使用它们。但是,如果它们的值是不确定的,那么在运行时它们可能也是未定义的。让我们来观察两个特殊的情况,为一个路径名分配存储区,以及确定文件描述符的数目。

1.路径名

很多程序需要为路径名分配存储区,一般来说,在编译时就为其分配了存储区,而且不同的程序使用各种不同的幻数(其中很少是正确的)作为数组长度,如256、512、1 024或标准I/O常量BUFSIZ。4.3BSD头文件<sys/param.h>中的常量MAXPATHLEN 才是正确的值,但是很多4.3BSD应用程序并未使用它。

POSIX.1试图用PATH_MAX值来帮助我们,但是如果此值是不确定的,那么仍是毫无帮助的。图2-16程序是本书用来为路径名动态分配存储区的函数。

图2-16 为路径名动态地分配空间

如果<limits.h>中定义了常量PATH_MAX,那么就没有任何问题;如果未定义,则需调用pathconf。因为pathconf的返回值是基于工作目录的相对路径名的最大长度,而工作目录是其第一个参数,所以,指定根目录为第一个参数,并将得到的返回值加 1 作为结果值。如果pathconf指明PATH_MAX是不确定的,那么我们就只能猜测某个值。

对于PATH_MAX 是否考虑到在路径名末尾有一个 null字节这一点,2001 年以前的POSIX.1版本表述得并不清楚。出于安全方面的考虑,如果操作系统的实现符合某个先前版本的标准,但并不符合Single UNIX Specification的任何版本(SUS明确要求在结尾处加一个终止null字节),则需要在为路径名分配的存储量上加1。

处理不确定结果情况的正确方法与如何使用分配的存储空间有关。例如,如果我们为getcwd调用分配存储空间(返回当前工作目录的绝对路径名,见 4.23 节),但分配到的空间太小,则会返回一个错误,并将errno设置为ERANGE。然后可调用realloc来增加分配的空间(见7.8节和习题4.16)并重试。不断重复此操作,直到getcwd调用成功执行。

2.最大打开文件数

守护进程(daemon process,在后台运行且不与终端相连接的一种进程)中一个常见的代码序列是关闭所有打开文件。某些程序中有下列形式的代码序列,这段程序假定在<sys/param.h>头文件中定义了常量NOFILE。

#include <sys/param.h>;

for (i = 0; i < NOFILE; i++)

close(i);

另外一些程序则使用某些<stdio.h>版本提供的作为上限的常量_NFILE。某些程序则直接将其上限值硬编码为20。但是,这些方法都不是可移植的。

我们希望用POSIX.1的OPEN_MAX确定此值以提高可移植性,但是如果此值是不确定的,则仍然有问题,如果我们编写下列代码:

#include <unistd.h>

for (i = 0; i < sysconf(_SC_OPEN_MAX); i++)

close(i);

如果OPEN_MAX是不确定的,那么for循环根本不会执行,因为sysconf将返回-1。在这种情况下,最好的选择就是关闭所有描述符直至某个限制值(如256)。如同上面的路径名实例一样,虽然并不能保证在所有情况下都能正确工作,但这却是我们所能选择的最好方法。图2-17的程序中使用了这种技术。

我们可以耐心地调用 close,直至得到一个出错返回,但是从 close(EBADF)出错返回并不区分无效描述符和没有打开的描述符。如果使用此技术,而且描述符 9 未打开,描述符 10打开了,那么将停止在9上,而不会关闭10。dup函数(见3.12节)在超过了OPEN_MAX时确实会返回一个特定的出错值,但是用复制一个描述符两、三百次的方法来确定此值是一种非常极端的方法。

图2-17 确定文件描述符个数

某些实现返回 LONG_MAX 作为限制值,但这与不限制其值在效果上是相同的。Linux 对ATEXIT_MAX所取的限制值就属于此种情况(见图2-15),这将使程序的运行行为变得非常糟糕,因此并不是一个好方法。

例如,我们可以使用Bourne-again shell的内建命令ulimit来更改进程可同时打开文件的最多个数。如果要将此限制值设置为在效果上是无限制的,那么通常要求具有特权(超级用户)。但是,一旦将其值设置为无穷大,sysconf就会将LONG_MAX作为OPEN_MAX的限制值报告。程序若将此值作为要关闭的文件描述符数的上限(如图2-17所示),那么为了试图关闭2 147 483 647个文件描述符,就会浪费大量时间,实际上其中绝大多数文件描述符并未得到使用。

支持Single UNIX Specification中XSI扩展的系统提供了getrlimit(2)函数(见7.11节)。它返回一个进程可以同时打开的描述符的最多个数。使用该函数,我们能够检测出对于进程能够打开的文件数实际上并没有设置上限,于是也就避开了这个问题。

OPEN_MAX被POSIX称为运行时不变值,这意味着在一个进程的生命周期中其值不应发生变化。但是在支持XSI扩展的系统上,可以调用setrlimit(2)函数(见7.11节)更改一个运行进程的OPEN_MAX值(也可用C shell的limit或Bourne shell、Bourne-again shell、Debian Almquist和Korn shell的ulimit命令更改这个值)。如果系统支持这种功能,则可以更改图2-17中的函数,使得每次调用此函数时都会调用 sysconf,而不只是在第一次调用此函数时调用sysconf。

2.6 选项

图2-5列出了POSIX.1的选项,并且2.2.3节讨论了XSI的选项组。如果我们要编写可移植的应用程序,而这些程序可能会依赖于这些可选的支持的功能,那么就需要一种可移植的方法来判断实现是否支持一个给定的选项。

如同对限制的处理(见2.5节)一样,POSIX.1定义了3种处理选项的方法。

(1)编译时选项定义在<unistd.h>中。

(2)与文件或目录无关的运行时选项用sysconf函数来判断。

(3)与文件或目录有关的运行时选项通过调用pathconf或fpathconf函数来判断。

选项包括了图2-5中第3列的符号以及图2-19和图2-18中的符号。如果符号常量未定义,则必须使用sysconf、pathconf或fpathconf来判断是否支持该选项。在这种情况下,这些函数的name参数前缀_POSIX必须替换为_SC或_PC。对于以_XOPEN为前缀的常量,在构成name参数时必须在其前放置_SC或_PC。例如,若常量_POSIX_RAW_THREADS是未定义的,那么就可以将name参数设置为SC_RAW_THREADS,并以此调用sysconf来判断该平台是否支持POSIX线程选项。如若常量_XOPEN_UNIX是未定义的,那么就可以将name参数设置为_SC_XOPEN_UNIX,并以此调用sysconf来判断该平台是否支持XSI扩展。

对于每一个选项,有以下3种可能的平台支持状态。

(1)如果符号常量没有定义或者定义值为−1,那么该平台在编译时并不支持相应选项。但是有一种可能,即在已支持该选项的新系统上运行老的应用时,即使该选项在应用编译时未被支持,但如今新系统运行时检查会显示该选项已被支持。

(2)如果符号常量的定义值大于0,那么该平台支持相应选项。

(3)如果符号常量的定义值为0,则必须调用sysconf、pathconf或fpathconf来判断相应选项是否受到支持。

图2-18总结了pathconf和fpathconf使用的符号常量。除了图2-5中列出的选项之外,图2-19总结了其他一些sysconf使用的未弃用的选项及它们的符号常量。注意,我们省略了与实用命令相关的选项。

图2-18 pathconf和fpathconf的选项及name参数

图2-19 sysconf的选项及name参数

如同系统限制一样,关于sysconf、pathconf和fpathconf如何处理选项,有如下几点值得注意。

(1)_SC_VERSION的返回值表示标准发布的年(以4位数表示)、月(以2位数表示)。该值可能是 198808L、199009L、199506L 或表示该标准后续版本的其他值。与 SUSv3 (POSIX.1 2001年版)相关连的值是200112L,与SUSv4(POSIX.1 2008年版)相关连的值是200809L。

(2)_SC_XOPEN_VERSION的返回值表示系统支持的XSI版本。与SUSv3相关联的值是600,与SUSv4相关的值是700。

(3)_SC_JOB_CONTROL、_SC_SAVED_IDS 以及_PC_VDISABLE 的值不再表示可选功能。虽然XPG4和SUS早期版本要求支持这些选项,但从SUSv3起,不再需要这些功能,但这些符号仍然被保留,以便向后兼容。

(4)符合POSIX.1-2008的平台还要求支持下列选项:

•_POSIX_ASYNCHRONOUS_IO

•_POSIX_BARRIERS

•_POSIX_CLOCK_SELECTION

•_POSIX_MAPPED_FILES

•_POSIX_MEMORY_PROTECTION

•_POSIX_READER_WRITER_LOCKS

•_POSIX_REALTIME_SIGNALS

•_POSIX_SEMAPHORES

•_POSIX_SPIN_LOCKS

• _POSIX_THREAD_SAFE_FUNCTIONS

• _POSIX_THREADS

•_POSIX_TIMEOUTS

•_POSIX_TIMERS

这些常量定义成具有值200809L。相应的_SC符号同样是为了向后兼容而被保留下来的。

(5)如果对指定的 pathname 或 fd 已不再支持此功能,那么_PC_CHOWN_RESTRICTED 和_PC_NO_TRUNC返回−1,而errno不变,在所有符合POSIX的系统中,返回值将大于0(表示该选项被支持);

(6)_PC_CHOWN_RESTRICT引用的文件必须是一个文件或者是一个目录。如果是一个目录,那么返回值指明该选项是否可应用于该目录中的各个文件。

(7)_PC_NO_TRUNC和_PC_2_SYMLINKS引用的文件必须是一个目录。

(8)_PC_NO_TRUNC的返回值可用于目录中的各个文件名。

(9)_PC_VDISABLE引用的文件必须是一个终端文件。

(10)_PC_ASYNC_IO、_PC_PRIO_IO和_PC_SYNC_IO引用的文件一定不能是一个目录。

图2-20列出了若干配置选项以及在本书所讨论的4个示例系统上的对应值。如果系统定义了某个符号常量但它的值为−1或0,但是相应的sysconf或pathconf调用返回的是−1,就表示该项未被支持。可以看到,有些系统实现还没有跟上Single UNIX Specification的最新版本。

图2-20 配置选项的实例

注意,当用于Solaris PCFS文件系统中的文件时,对于_PC_NO_TRUNC,pathconf返回−1。PCFS文件系统支持DOS格式(软盘格式),DOS文件名按DOS文件系统所要求8.3格式截断,在进行此种操作时并无任何提示。

2.7 功能测试宏

如前所述,头文件定义了很多POSIX.1和XSI符号。但是除了POSIX.1和XSI定义外,大多数实现在这些头文件中也加入了它们自己的定义。如果在编译一个程序时,希望它只与POSIX的定义相关,而不与任何实现定义的常量冲突,那么就需要定义常量_POSIX_C_SOURCE。一旦定义了_POSIX_C_SOURCE,所有POSIX.1头文件都使用此常量来排除任何实现专有的定义。

POSIX.1标准的早期版本定义了_POSIX_SOURCE常量。在POSIX.1的2001版中,它被替换为_POSIX_C_SOURCE。

常量_POSIX_C_SOURCE及_XOPEN_SOURCE被称为功能测试宏(feature test macro)。所有功能测试宏都以下划线开始。当要使用它们时,通常在cc命令行中以下列方式定义:

cc -D_POSIX_C_SOURCE=200809L file.c

这使得C程序在包括任何头文件之前,定义了功能测试宏。如果我们仅想使用POSIX.1定义,那么也可将源文件的第一行设置为:

#define_POSIX_C_SOURCE 200809L

为使SUSv4的XSI选项可由应用程序使用,需将常量_XOPEN_SOURCE定义为700。除了让XSI选项可用以外,就POSIX.1的功能而言,这与将_POSIX_C_SOURCE定义为200809L的作用相同。

SUS将c99实用程序定义为C编译环境的接口。随之,就可以用如下方式编译文件:

c99 -D_XOPEN_SOURCE=700 file.c –o file

可以使用-std=c99选项在gcc的C编译器中启用1999 ISO C扩展,如下所示:

gcc -D_XOPEN_SOURCE=700 -std=c99 file.c -o file

2.8 基本系统数据类型

历史上,某些UNIX系统变量已与某些C数据类型联系在一起,例如,历史上主、次设备号存放在一个16位的短整型中,8位表示主设备号,另外8位表示次设备号。但是,很多较大的系统需要用多于256个值来表示其设备号,于是,就需要一种不同的技术。(实际上,Solaris用32位表示设备号:14位用于主设备号,18位用于次设备号。)

头文件<sys/types.h>中定义了某些与实现有关的数据类型,它们被称为基本系统数据类型(primitive system data type)。还有很多这种数据类型定义在其他头文件中。在头文件中,这些数据类型都是用C的typedef来定义的。它们绝大多数都以_t结尾。图2-21列出了本书将使用的一些基本系统数据类型。

用这种方式定义了这些数据类型后,就不再需要考虑因系统不同而变化的程序实现细节。在本书中涉及这些数据类型时,我们会说明为什么要使用它们。

图2-21 一些常用的基本系统数据类型

2.9 标准之间的冲突

就整体而言,这些不同的标准之间配合得相当好。因为 SUS 基本说明和 POSIX.1 是同一个东西,所以我们不对它们进行特别的说明,我们主要关注ISO C标准和POSIX.1之间的差别。它们之间的冲突并非有意,但如果出现冲突,POSIX.1服从ISO C标准。然而它们之间还是存在着一些差别的。

ISO C定义了clock函数,它返回进程使用的CPU时间,返回值是clock_t类型值,但ISO C 标准没有规定它的单位。为了将此值变换成以秒为单位,需要将其除以在<time.h>头文件中定义的CLOCKS_PER_SEC。POSIX.1定义了times函数,它返回其调用者及其所有终止子进程的CPU 时间以及时钟时间,所有这些值都是clock_t 类型值。sysconf 函数用来获得每秒滴答数,用于表示times函数的返回值。ISO C和POSIX.1用同一种数据类型(clock_t)来保存对时间的测量,但定义了不同的单位。这种差别可以在Solaris中看到,其中clock返回微秒数(CLOCK_PER_SEC是100万),而sysconf为每秒滴答数返回的值是100。因此,我们在使用clock_t类型变量的时候,必须十分小心以免混淆不同的时间单位。

另一个可能产生冲突的地方是:在ISO C标准说明函数时,可能没有像POSIX.1那样严。在POSIX环境下,有些函数可能要求有一个与C环境下不同的实现,因为POSIX环境中有多个进程,而ISO C环境则很少考虑宿主操作系统。尽管如此,很多符合POSIX的系统为了兼容性也会实现ISO C函数。signal函数就是一个例子。如果在不了解的情况下使用了Solaris提供的signal函数(希望编写可在ISO C环境和较早UNIX系统中运行的可兼容程序),那么它提供了与POSIX.1 sigaction函数不同的语义。第10章将对signal函数做更多说明。

2.10 小结

在过去25年多的时间里,UNIX编程环境的标准化已经取得了很大进展。本章对3个主要标准——ISO C、POSIX和Single UNIX Specification进行了说明,也分析了这些标准对本书主要关注的4个实现,即FreeBSD、Linux、Mac OS X和Solaris所产生的影响。这些标准都试图定义一些可能随实现而更改的参数,但是我们已经看到这些限制并不完美。本书将涉及很多这些限制和幻常量。

在本书最后的参考书目中,说明了如何获得这些标准的方法。

习题

2.1 在2.8节中提到一些基本系统数据类型可以在多个头文件中定义。例如,在FreeBSD 8.0中, size_t在29个不同的头文件中都有定义。由于一个程序可能包含这29个不同的头文件,但是ISO C却不允许对同一个名字进行多次typedef,那么如何编写这些头文件呢?

2.2 检查系统的头文件,列出实现基本系统数据类型所用到的实际数据类型。

2.3 改写图2-17中的程序,使其在sysconf为OPEN_MAX限制返回LONG_MAX时,避免进行不必要的处理。