人们在UNIX编程环境和C程序设计语言的标准化方面已经做了很多工作。虽然UNIX应用程序在不同的UNIX操作系统版本之间进行移植相当容易,但是20世纪80年代UNIX版本种类的剧增以及它们之间差别的扩大,导致很多大用户呼吁对其进行标准化。
本章首先回顾过去近25年人们在UNIX标准化方面做出的种种努力,然后讨论这些UNIX编程标准对本章所列举的各种UNIX操作系统实现的影响。所有标准化工作的一个重要部分是对每种实现必须定义的各种限制进行说明,所以我们讲说明这些限制以及确定它们值的各种方法。
(1)ISO C
1989年下半年,C程序设计语言的ANSI标准X3.159-1989得到批准,此标准被采纳为国际标准ISO/IEC 9899:1990。ANSI是美国国家标准学会的缩写,它是国际标准化组织中代表美国的成员。IEC是国际电子技术委员会的缩写。
ISO C标准现在由ISO/IEC的C程序设计语言国际标准工作组维护和开发,该工作组称为ISO/IEC JTCI/SC22/WG14,简称WG14。ISO C标准的意图是提供C程序的可移植性,使其能适合于大量不同的操作系统,而不只是适合UNIX系统。此标准不仅定义了C程序设计语言的语法和语义,还定义了其标准库。因为所有现今的UNIX系统都提供C标准中定义的库函数,所以该标准库非常重要。
1999年,ISO C标准被更新,并被批准为ISO/IEC 9899:1999,它显著改善了对进行数值处理的应用软件的支持。除了对某些函数原型增加了关键字restrict外,这种改变并不影响POSIX接口。restrict关键字告诉编译器,那些指针引用是可以优化的,其方法是指出指针引用的对象在函数中只能通过该指针进行访问。
1999年以来,以及公布了3个技术勘误来修正ISO C标准中的错误,分别在2001年,2004年和2007年公布。如同大多数标准一样,在批准标准和修改软件使其符合标准两者之间有一段时间延迟。随着供应商编译系统的不断演化,对最新ISO C标准的支持也就越来越多。
按照该标准定义的各个头文件可将ISO C库分成24个区。POSIX.1标准包括这些头文件以及另外一些头文件。从图2-1中可以看出,所有这些头文件在4中UNIX实现中都支持。本章后面将对这4中UNIX实现进行说明。
头文件
<assert.h>:验证程序断言
<complex.h>:复数算数运算支持
<ctype.h>:字符分类和映射支持
<errno.h>:出错码
<float.h>:浮点常量及特性
<inttypes.h>:整型格式变换
<iso646.h>:赋值,关系及一元操作符宏
<limits.h>:实现常量
<locale.h>:本地化类别及相关定义
<math.h>:数学函数,类型声明及常量
<setjmp.h>:非局部goto
<signal.h>:信号
<stdarg.h>:可变长度参数表
<stdbool.h>:布尔类型和值
<stddef.h>:标准定义
<stdint.h>:整型
<stdio.g>:标准IO库
<stdlib.h>:使用函数
<string.h>:字符串操作
<tgmath.h>:通用类型数学宏
<time.h>:时间和日期
<wchar.h>:扩充的多字节和宽字符支持
<wctype.h>:宽字符分类和映射支持
(2)IEEE POSIX
POSIX是一个最初由IEEE制定的标准族。POSIX指的是可以指操作系统接口,它原来指的只是IEEE标准1003.1-1988,后来则扩展成包括很多标记为1003的标准及标准草案,如shell和使用程序(1003.2)。
1003.1标准的目的是提升应用程序在各种UNIX系统环境之间的可移植性。它定义了"符合POSIX的"操作系统必须提供的各种服务。该标准已被很多计算机制造商采用。
由于1003.1标准说明了一个接口而不是一种实现,所以并不区分系统调用和库函数。所有在标准中的例程都被称为函数。
2001年的1003.1版本与以前各大版本有较大的差别,它组合了多个1003.1的修正,1003.2标准以及single UNIX Specification第2版的若干部分。
2004年POSIX.1随着技术勘误得到更新,2008年作了更多综合的改动,ISO在2009年进行发布。
POSIX.1没有包括超级用户的概念,代之规定某些操作要求"适当的优先权"。
(3)Single UNIX Specification
Single UNIX Specification是POSIX标准的一个超集,它定义了一些附加接口扩展POSIX.1规范提供的功能。
POSIX.1中的X/open系统接口选项描述了可选的接口,也定义了遵循XSI的实现必须支持POSIX.1的那些可选部分。这些必须支持的部分包括:文件同步,线程栈地址和长度属性,线程进程共享同步以及_XOPEN_UNIX符号常量。只有遵循XSI的实现才能称为UNIX系统。
(4)FIPS
FIPS代表的是联邦信息处理标准,这一标准是由美国政府发布的,并由美国政府用于计算机系统的采购。
略…
略…
UNIX系统实现定义了很多幻数和常量,其中有很多以及被硬编码到程序中,或用特定的技术确定。由于大量标准化工作的努力,已有若干种可移植的方法用以确定这些幻数和具体实现定义的限制。这非常有助于改善UNIX环境下软件的可移植性。
以下两种类型的限制是必需的:
①编译时限制(例如,短整型的最大值是什么?)
②运行时限制(例如,文件名有多少个字符)
编译时限制可在头文件中定义。程序在编译时可以包含这些头文件。但是,运行时限制则要求进程调用一个函数获得限制值。
另外某些限制在一个给定的实现中可能是固有的(因此可以静态地在一个文件头中定义),而在另一个实现中则可能是变动的(需要一个运行时函数调用)。这种类型限制的一个例子是文件名的最大字符数。SVR4之前的系统V由于历史原因只允许文件名最多包含14个字符,而源于BSD的系统则将此增加为255。目前,大多数UNIX支持多文件系统类型,而每一种类型有它自己的限制。文件名的最大长度依赖于该文件处于何种文件系统,例如,根文件系统中的文件名长度限制可能是14个字符,而在另一个文件系统中文件名长度限制可能是255个字符,这是运行时限制的一个例子。
为了解决这类问题,提供了以下3种限制:
①编译时限制(头文件)
②与文件或目录无关的运行时限制(sysconf)
③与文件或目录相关的运行时限制(pathconf和fpathconf函数)。
使事情变得更加复杂的是,如果一个特定的运行时限制在一个给定的系统上并不改变,则将其静态地定义在一个头文件中,但是,如果没有将其定义在头文件中,应用程序就必须调用3个conf函数中的一个,以确定其运行时的值。
(1)ISO C限制
ISO C定义的所有编译时限制都列在头文件
示例:2-6
我们将会遇到一个区别是系统是否提供带符号或无符号的字符值。从2-6示例中的第4列可以看出,该特定系统使用带符号字符。从图中可以看出CHAR_MIN等于SCHAR_MIN,SCHAR_MAX等于SCHAR_MAX。如果系统使用无符号字符,则CHAR_MIN等于0,CHAR_MAX等于UCHAR_MAX。
在头文件
我们会遇到应一个ISO C常量是FOPEN_MAX,这是具体实现保证可同时可开的标准IO流的最小个数,该值在头文件
ISO C还在
虽然ISO C定义了常量FILENAME_MAX,但我们应该便面使用该常量,因为POSIX.1提供了更好的替代常量(NAME_MAX和PATH_MAX),我们很快就会介绍该常量。
(2)POSIX限制
POSIX.1定义了很多设计操作系统实现限制的常量,遗憾的是,这是POSIX.1种最令人疑惑不解的部分之一。虽然POSIX.1定义了大量限制和常量,我们只关心与基本POSIX.1接口有关的部分。这些限制和常量分成下列7类。
①数值限制:LONG_BIT,SSIZE_MAX和WORD_BIT。
②最小值:图2-8种的25个常量。
③最大值:_POSIX_ClOCKRES_MIN。
④运行时可以增加的值:CHARCLASS_NAME_MAX,COLL_WEIGHTS_MAX,LINE_MAX,NGROUPS_MAX和RE_DUP_MAX。
⑤运行时不变值:图2-9的17个常量
⑥其他不变值:NL_ARGMAX,NL_MSGMAX,NL_SETMAX,NL_TEXTMAX
⑦路径名可编制:FILESIZEBITS,LINK_MAX,MAX_CANON,MAX_INPUT,NAME_MAX,PATH_MAX,PIPE_BUF,SYMLINK_MAX
在这些限制和常量中,某些可能定义在
示例2-8:
遗憾的是,这些不变最小值中的某一些在实际应用中太小了。例如目前在大多数UNIX系统中,每个进程可同时打开的文件数远远超过20。另外,_POSIX_PATH_MAX的最小限制是255,这太小了,路径名可能会超过这一限制。这意味着在编译时不能使用_POSIX_OPEN_MAX和_POSIX_PATH这两个常量作为数组长度。
图2-8中的25个不变最小值的每一个都有一个相关的实现值,其名字是将图中2-8中的名字删除前缀_POSIX后构成的。没有_POSIX_前缀的名字用于给定具体实现支持的该不变最小值的实际数值。问题是是不能确保所有这25个实现值都在
例如,某个特定值可能不在此头文件中定义,其理由是,一个给定进程的实际值可能依赖于系统的存储总量。如果没有在头文件中定义它们,则不能在编译时使用它们作为数组边界。所以,POSIX.1提供了3个运行时函数以供调用,它们是:sysconfig,pathconf以及fpathconf。使用这3个函数可以运行时得到实际的实现值。但是还有一个问题,其中某些值时"可能不确定的"(逻辑上无限)。
图2-9:运行时不变值
(3)XSI限制
XSI定义了代表实现限制的几个常量。
①最小值:图2-10中列出了5个常量
②运行时不变值(可能不确定):IOV_MAX和PAGE_SIZE
图2-10列出了最小值。最后两个常量值说明了POSIX.1最小值太小的情况,根据推测着可能考虑到了嵌入式POSIX.1实现。为此,Single UNIXSpecification为符合XSI的系统增加了具有较大最小值的符号。
图2-10:
(4)函数sysconf,pathconf,fpathconf
我们已列出了实现必须支持的各种最小值,但是怎样才能找到一个特定系统支持的限制值呢?正如前面提到的,某些限制值在编译时是可用的,而另外一些则必须在运行时确定。我们也曾提及某些限制值在一个给定的系统中可能是不会更改的,而其他限制值可能会更改,因为它们与文件和目录相关联。运行时限制可调用下面3个函数之一获得。
#include
long sysconf(int name);
long pathconf(const char * pathname,int name);
long fpathconf(int fd,int name);
后面两个函数的差别是:一个用路径名作为其参数,另一个则取文件描述符作为参数。
图2-11列出了sysconf函数所使用的name参数,它用于表示系统限制。以_SC_开始的常量用作表示运行时限制的sysconf参数。
我们需要更详细的讨论一下这3个函数不同的返回值。
①如果name参数并不是一个合适的常量,这3个函数都返回-1,并把errno置为EINVAL。如2-11和图2-12的第三列给出了我们在整本书中将要涉及的限制常量。
②有些name会返回一个变量值或者提示该值是不确定的。不确定的值通过返回-1来体现,而不改变errno的值。
图2-11
③_SC_CLK_TCK的返回值是每秒的时钟滴答数,用于times函数的返回值。对于pathconf的参数pathname和fpathconf的参数fd有很多限制。如果不满足其中任何一个限制,则结果是未定义的。
图2-12:
图2-12列出了pathconf和fpathconf函数为标识系统限制所使用的name参数。以_PC开始的常量用作标识运行时限制的pathconf和fpathconf参数。
①_PC_MAX_CANON和_PC_MAX_INPUT引用的文件必须是终端文件。
②_PC_LINK_MAX和_PC_TIMESTAMP_RESOLUTION引用的文件可以是文件或目录。如果是目录,则返回值用于目录本身,而不用与目录内的文件名项。
③_PC_FILESIZEBITS和_PC_NAME_MAX引用的文件名必须是目录,返回值用于该目录中的文件名。
④_PC_PATH_MAX引用的文件必须是目录。当所指定的目录是工作目录时,返回值是相对路径名的最大长度。
⑤_PC_PIPE_BUF引用的文件必须是管道,FIFO或目录。在管道或者FIFO情况下,返回值对所引用的管道或FIFO的限制值。对于目录,返回是对在该目录中创建的任一FIFO的限制值。
⑥_PC_SYMLINK_MAX引用的文件必须是目录。返回值是该目录中符号链接可包含字符串的最大长度。
实例2-13:打印个pathconf和sysconf的值
#include "apue.h"
#include
#include
static void pr_sysconf(char * int);
static void pr_pathconf(char *,char * ,int);
int
main(int argc,char * argv[])
{
if(argc!=2)
{
err_quit("usage:a.out " );
}
#ifdef ARG_MAX
printf("ARG_MAX defined to be %ld\n",(long)ARG_MAX+0);
#else
printf("no sysmbol for ARG_MAX");
#endif
#ifdef _SC_ARG_MAX
pr_sysconf("ARG_MAX=",_SC_ARG_MAX);
#elseif
printf("no symbol for _SC_ARG_MAX\n");
#endif
#ifdef MAX_CANON
printf("MAX_CANON defined tobe %ld\n",(long) MAX_CANON+0);
#else
printf("no symbol for MAX_CANON\n");
#endif
#ifdef _PC_MAX_CANON
pr_pathconf("MAX_XANON=",argv[1],_PC_MAX_CANON);
#else
printf("no symbol for _PC_MAX_CANON\n");
#endif
exit(0);
}
static void
pr_sysconf(char * mesg,int name)
{
long val;
fputs(mesg,stdout);
errno=0;
if((val=sysconf(name))<0){
if(errno!=0){
if(errno==EINVAL)
fputs(" not supported)\n",stdout);
else
err_sys("sysconf error");
}
else {
printf("%ld\n",val);
}
}
}
static void
pr_pathconf(char *mesg,char * path,int name)
{
long val;
fputs(mesg,stdout);
errno=0;
if((val=pathconf(path,name))<0){
if(errno!=0){
if(errno==EINVAL)
fputs(" (not supported)\n",stdout);
else
err_sys("pathconf error,path=%s",path);
}
else {
fputs(" (no limit)\n",stdout);
}
}
else {
printf("%ld\n",val);
}
}
(5)不确定的运行时限制
①.路径名
很多程序需要为路径名分配存储区,一般来说,在编译时就为其分配了存储区,而且不同的程序使用各种不同的幻数作为数组长度,如256,512,1024或标准IO常量BUFSIZE。头文件
POSIX.1试图用PATH_MAX值来帮助我们,但是如果此值是不确定的,那么仍是毫无帮助的,2-16程序是为路径名动态分配存储区的函数。
#include "apur.h"
#include
#include
#ifdef PATH_MAX
static long pathmax=PATH_MAX;
#else
static long pathmax=0;
#endif
static long posix_version=0;
static long xsi_version=0;
#define PATH_MAX_GUESS 1024
char *
path_alloc(size_t *sizep)
{
char * ptr;
size_t size;
if(posix_version==0)
posix_version=sysconf(_SC_VERSION);
if(xsi_version==0)
xsi_version=sysconf(_SC_XOPEN_VERSION);
if(pathmax==0){
errno=0;
if((pathmax=pathconf("/",_PC_PATH_MAX))<0){
if(errno==0)
pathmax=PATH_MAX_GUESS;
else
err_sys("pathconf error for _PC_PATH_MAX");
}
else{
pathmax++;
}
}
if((posix_version<200112L)&&(xsi_version<4))
size=pathmax+1;
else
size=pathmax;
if((ptr=malloc(size))==NULL)
err_sys("malloc error for pathname");
if(sizep!=NULL)
*sizep=size;
return(ptr);
}
如果
对于PATH_MAX是否考虑到在路径名末尾有一个null字节这一点,2001年以前的POSIX.1版本表述得并不清楚。处于安全方面的考虑,如果操作系统的实现符合某个先前版本的标准,但并符合Single UNIX Specification的任何版本(SUS明确要求在结尾处加一个终止null字节),
则需要在为路径名分配的存储量上加1。
处理不确定结果情况的正确方法与如何使用分配的存储空间有关。例如,如果我们为getcwd调用分配存储空间,但分配空间太小,则会返回一个错误,并将errno设置为ERANGE。然后可调用realloc来增加分配的空间。
②.最大文件打开数
支持Single UNIX Specification中XSI扩展的系统提供了getrlimit函数。它返回一个进程可以同时打开的描述符的最多个数。
(6)POSIX.1定义了3种处理选项的方法。
①编译时选项定义在
②与文件或目录无关的运行时选项用sysconf函数来判断。
③与文件或目录有关的运行时选项通过调用pathconf和fpathconf函数来判断。
如果符号常量未定义,则必须使用sysconf,pathconf或fpathconf来判断是否支持该选项。这种情况下,这些函数的name参数前缀_POSIX必须替换位_SC或_PC。对于以_XOPEN为前缀的常量,在构成name参数时必须在其前放置_SC或_PC。例如,若常量_POSIC_RAW_THREADS是未定义的,那么就可以将name参数设置为SC_RAW_THREADS,并以此调用sysconf来判断该平台是否支持POSIX线程选项。