2.1 引言
UNIX环境标准化的工作今天已经十分完善,今天软件在类UNIX系统版本之间移植已经十分容易,都要归功于过去30年里UNIX系统标准化的工作。2.2 UNIX标准化
UNIX系统提供了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关键字告诉编译器,哪些指针引用是可以优化的,其方法是指出 指针引用的对象在函数中只通过该指针进行访问。
虽然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版。
如果熟悉c语言大家因该会很熟悉这些函数
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 标准说明了一个接口(interface)而不是一种实现 (implementation),所以并不区分系统调用和库函数。所有在标准中的 例程都被称为函数。
标准是不断演进的,1003.1标准也不例外。该标准的1988版,即 IEEE标准1003.1-1988经修改后递交给 ISO,它没有增加新的接口或功 能,但修订了文本。最终的文档作为 IEEE 标准1003.1-1990 正式出版 [IEEE 1990],这也就是国际标准 ISO/IEC 9945-1:1990。该标准通常称为 POSIX.1,本书将使用此术语来表示不同版本的标准。
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,包含勘误表。
POSIX.1 没有包括超级用户(superuser)这样的概念,代之以规定 某些操作要求“适当的优先权”,POSIX.1将此术语的含义留由具体实 现进行解释。某些符合美国国防部安全性指南要求的UNIX系统具有很 多不同的安全级。本书仍使用传统的UNIX术语,并指明要求超级用户 特权的操作。
经过20多年的工作,相关标准已经成熟稳定。POSIX.1标准现在由 Austin Group开放工作组(http://www.opengroup.org/austin)维护。为了
保证它们仍然有价值,仍需经常对这些标准进行更新或再确认。
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商标的许可证。
所以只有通过SUS认证才能称之为UNIX系统,当然,SUS认证是需要授权收费的,大家熟知的Solaris、MacOS就是经SUS认证的UNIX系统,而GNU/Linux虽然兼容POSIX,却没有付费给SUS认证,所以称为类UNIX系统
2.3 UNIX系统实现
在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 版。
BSD
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版本)。
FreeBSD
FreeBSD基于4.4BSD-Lite 操作系统。在加州大学伯克利分校的 CSRG决定终止其在UNIX操作系统的BSD版本的研发工作,而且386BSD 项目被忽视很长时间之后,为了继续坚持BSD系列,形成了FreeBSD项 目。
由FreeBSD项目产生的所有软件,包括其二进制代码和源代码,都 是免费使用的。
Linux
Linux是一种提供类似于UNIX的丰富编程环境的操作系统,在GNU 公用许可证的指导下, Linux是免费使用的。Linux的普及是计算机产业 中的一道亮丽风景线。Linux经常是支持较新硬件的第一个操作系统, 这一点使其引人注目。
Linux是由Linus Torvalds在1991年为替代MINIX而研发的。一位当时 名不见经传人物的努力掀起了澎湃巨浪,吸引了遍布全世界的很多软件 开发者,在使用和不断增强Linux方面自愿贡献出了他们大量的时间。
Ubuntu 12.04 的 Linux 分发版本是用以测试本书实例的操作系统之 一。该系统使用了 Linux操作系统3.2.0版内核。
MacOS
与其以前的版本相比,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)是用以测试本书实例的操作系统之 一。
Solaris
Solaris是由Sun Microsystems(现为Oracle)开发的UNIX系统版本。 它基于SVR4,在超过15 年的时间里,Sun Microsystems 的工程师对其功
能不断增强。它是唯一在商业上取得成功的SVR4后裔,并被正式验证 为UNIX系统。 2005年,Sun Microsystems把Solaris操作系统的大部分源代码开放给 公众,作为OpenSolaris开放源代码操作系统的一部分,试图建立围绕 Solaris的外部开发人员社区。
其他UNIX操作系统
已经通过验证的其他UNIX版本包括:
•AIX,IBM版的UNIX系统;
•HP-UX,HP版的UNIX系统;
•IRIX,Silicon Graphics版的UNIX系统;
•UnixWare,SVR4派生的UNIX系统,现由SCO销售
这些UINX发行版公众都比较少见。国内主要是金融、军事等大型机使用。
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标准的系统中是可选 的功能。
2.6 限制
UNIX 系统实现定义了很多幻数和常量,其中有很多已被硬编码到 程序中,或用特定的技术确定。由于大量标准化工作的努力,已有若干 种可移植的方法用以确定这些幻数和具体实现定义的限制。这非常有助 于改善UNIX环境下软件的可移植性。
以下两种类型的限制是必需的。
(1)编译时限制(例如,短整型的最大值是什么?)
(2)运行时限制(例如,文件名有多少个字符?)
编译时限制可在头文件中定义。程序在编译时可以包含这些头文 件。但是,运行时限制则要求进程调用一个函数获得限制值
另外,某些限制在一个给定的实现中可能是固定的(因此可以静态 地在一个头文件中定义),而在另一个实现中则可能是变动的(需要有 一个运行时函数调用)。这种类型限制的一个例子是文件名的最大字符 数。SVR4之前的系统V由于历史原因只允许文件名最多包含14个字符, 而源于BSD的系统则将此增加为255。目前,大多数UNIX系统支持多文 件系统类型,而每一种类型有它自己的限制。文件名的最大长度依赖于 该文件处于何种文件系统,例如,根文件系统中的文件名长度限制可能 是14个字符,而在另一个文件系统中文件名长度限制可能是255个字 符,这是运行时限制的一个例子。
为了解决这类问题,提供了以下3种限制。
(1)编译时限制(头文件)。
(2)与文件或目录无关的运行时限制(sysconf函数)。
(3)与文件或目录有关的运行时限制(pathconf和fpathconf函数)。
使事情变得更加复杂的是,如果一个特定的运行时限制在一个给定 的系统上并不改变,则可将其静态地定义在一个头文件中,但是,如果 没有将其定义在头文件中,应用程序就必须调用 3个conf函数中的一个 (我们很快就会对它们进行说明),以确定其运行时的值。
限制其实也就是统一,将不同系统实现约定一个规则
ISO 限制
ISO C定义的所有编译时限制都列在头文件
所以我们常见的65535 、2147483647就是定义在
在头文件
虽然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流的最小个数,该值在头文件
ISO C还在
虽然ISO C定义了常量FILENAME_MAX,但我们应避免使用该常 量,因为POSIX.1提供了更好的替代常量(NAME_MAX和 PATH_MAX),我们很快就会介绍该常量。
在图2-7中,我们列出了本书所讨论4种平台上的 FILENAME_MAX、FOPEN_MAX和TMP_MAX值。
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。
在这些限制和常量中,某些可能定义在
遗憾的是,这些不变最小值中的某一些在实际应用中太小了。例 如,目前在大多数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个实现值都在
例如,某个特定值可能不在此头文件中定义,其理由是:一个给定 进程的实际值可能依赖于系统的存储总量。如果没有在头文件中定义它 们,则不能在编译时使用它们作为数组边界。所以,POSIX.1提供了3个 运行时函数以供调用,它们是:sysconf、pathconf以及fpathconf。使用这 3个函数可以在运行时得到实际的实现值。
。例如,在Solaris中,进程结束时注册可运行 atexit的函数个数仅受系统存储总量的限制。
函数sysconf、pathconf、fpathconf
我们已列出了实现必须支持的各种最小值,但是怎样才能找到一个 特定系统实际支持的限制值呢?正如前面提到的,某些限制值在编译时 是可用的,而另外一些则必须在运行时确定。我们也曾提及某些限制值 在一个给定的系统中可能是不会更改的,而其他限制值可能会更改,因 为它们与文件和目录相关联。运行时限制可调用下面3个函数之一获 得。
#include
long sysconf(int name);
long pathconf(const char *pathname, int name);
log fpathconf(int fd, int name);
后面两个函数的差别是:一个用路径名作为其参数,另一个则取文 件描述符作为参数。 图2-11中列出了sysconf函数所使用的name参数,它用于标识系统限 制。以SC开始的常量用作标识运行时限制的sysconf参数。图2-12列出 了pathconf和fpathconf函数为标识系统限制所使用的name参数。以PC 开始的常量用作标识运行时限制的pathconf或fpathconf参数。
我们可以写一个简单的程序看看这些值在tlinux下的值
#include
#include
int main() {
std::cout << "ARG_MAX : " << sysconf(_SC_ARG_MAX) << std::endl;
std::cout << "ATEXIT_MAX: " << sysconf(_SC_ATEXIT_MAX) << std::endl;
std::cout << "CHILD_MAX: " << sysconf(_SC_CHILD_MAX) << std::endl;
std::cout << "LOGIN_NAME_MAX: " << sysconf(_SC_LOGIN_NAME_MAX) << std::endl;
std::cout << "OPEN_MAX: " << sysconf(_SC_OPEN_MAX) << std::endl;
std::cout << "PAGESIZE: " << sysconf(_SC_PAGESIZE) << std::endl;
return 0;
}
各个系统配置的实例
不确定的运行时值
前面已提及某些限制值可能是不确定的。我们遇到的问题是,如果 这些限制值没有在头文件
1.路径名
很多程序需要为路径名分配存储区,一般来说,在编译时就为其分 配了存储区,而且不同的程序使用各种不同的幻数(其中很少是正确 的)作为数组长度,如256、512、1 024或标准I/O常量BUFSIZ。4.3BSD 头文件
2.最大打开文件数
守护进程(daemon process,在后台运行且不与终端相连接的一种 进程)中一个常见的代码序列是关闭所有打开文件。某些程序中有下列 形式的代码序列,这段程序假定在
#include
for (i = 0; i < NOFILE; i++)
close(i);
另外一些程序则使用某些
我们希望用POSIX.1的OPEN_MAX确定此值以提高可移植性,但是 如果此值是不确定的,则仍然有问题,如果我们编写下列代码:
#include
for (i = 0; i < sysconf(_SC_OPEN_MAX); i++)
close(i);
如果OPEN_MAX是不确定的,那么for循环根本不会执行,因为 sysconf将返回-1。在这种情况下,最好的选择就是关闭所有描述符直至 某个限制值(如256)。如同上面的路径名实例一样,虽然并不能保证在所有情况下都能正确工作,但这却是我们所能选择的最好方法。图217的程序中使用了这种技术。
我们可以使用Bourne-again shell的内建命令ulimit来更改进程 可同时打开文件的最多个数。如果要将此限制值设置为在效果上是无限 制的,那么通常要求具有特权(超级用户)。但是,一旦将其值设置为 无穷大,sysconf就会将LONG_MAX作为OPEN_MAX的限制值报告。程 序若将此值作为要关闭的文件描述符数的上限
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。
功能测试宏 (feature test macro)。
所有功能测试宏都以下划线开始。当要使用它们 时,通常在cc命令行中以下列方式定义:
cc -D_POSIX_C_SOURCE=200809L file.c
2.8基本系统数据类型
历史上,某些UNIX系统变量已与某些C数据类型联系在一起,例 如,历史上主、次设备号存放在一个16位的短整型中,8位表示主设备 号,另外8位表示次设备号。但是,很多较大的系统需要用多于256个值 来表示其设备号,于是,就需要一种不同的技术。(实际上,Solaris用 32位表示设备号:14位用于主设备号,18位用于次设备号。)
头文件
用这种方式定义了这些数据类型后,就不再需要考虑因系统不同而 变化的程序实现细节
2.9 标准之间的冲突
就整体而言,这些不同的标准之间配合得相当好。因为 SUS 基本说 明和 POSIX.1 是同一个东西,所以我们不对它们进行特别的说明,我们 主要关注ISO C标准和POSIX.1之间的差别。它们之间的冲突并非有意, 但如果出现冲突,POSIX.1服从ISO C标准。然而它们之间还是存在着一 些差别的。
ISO C定义了clock函数,它返回进程使用的CPU时间,返回值是 clock_t类型值,但ISO C 标准没有规定它的单位。为了将此值变换成以 秒为单位,需要将其除以在头文件中定义的 CLOCKS_PER_SEC