首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

2.5 限制

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

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

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

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

编译时限制可在头文件中定义,程序在编译时可以包含这些头文件。但是,运行时限制则

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

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

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

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

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

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

2.5.1 ISO C限制     

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

我们将会遇到的一个不同之处是系统是否提供带符号或不带符号的字符值。从表2-6的第4 列可以看出,该特定系统使用了带符号字符。从表中可以看到CHAR_MIN等于SCHAR_MIN,       

CHAR_MAX等于SCHAR_MAX。如果系统使用不带符号的字符,则CHAR_MIN等于0,CHAR_MAX等于UCHAR_MAX。在头文件<float.h>中,对浮点数据类型也有类似的一组定义。如果读者在工作中涉及大量浮点数据类型,则应仔细查看该文件。我们会遇到的另一个ISO C常量是FOPEN_MAX,这是具体实现保证可同时打开的标准I/O流的最小数。该值在头文件<stdio.h>中定义,其最小值是8。POSIX.1中的STREAM_MAX(若定义的话)必须具有与FOPEN_MAX相同的值。     

ISO C还定义了常量FILENAME_MAX,因为某些操作系统实现在历史上将它定义得太小,以至于不能满足应用的需求,所以我们应避免使用该常量。

2.5.2 POSIX限制

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

(1) 不变的最小值:表2-8中的19个常量。

(2) 不变值:SSIZE_MAX。

(3) 运行时可以增加的值:CHARCLASS_NAME_MAX、COLL_WEIGHTS_MAX、LINE_ MAX、NGROUPS_MAX以及RE_DUP_MAX。

(4) 运行时不变的值(可能不确定):ARG_MAX、CHILD_MAX、HOST_NAME_MAX、LOGIN_NAME_MAX、OPEN_MAX、PAGESIZE、RE_DUP_MAX、STREAM_MAXS、SYMLOOP_MAX、TTY_NAME_MAX以及TZNAME_MAX。

(5) 路径名可变值(可能不确定):FILESIZEBITS、LINK_MAX、MAX_CANON、MAX_INPUT、NAME_MAX、PATH_MAX、PIPE_BUF以及SYMLINK_MAX。

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

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

一个严格遵循(strictly conforming)POSIX的应用程序有别于仅遵循POSIX(merely POSIX Confirming)的应用程序。一个遵循POSIX的应用程序只使用在IEEE标准1003.1-2001中定义的接口。一个严格遵循的应用程序也是遵循POSIX的,但除此之外它还应不依赖于POSIX未定义的行为,不使用任何已废弃的接口,以及不要求所使用的常量值大于表2-8中所列出的最小值。

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

表2-8中的每一个不变最小值都有一个相关的实现值,其名字是将表2-8中的名字删除前缀_POSIX_后构成的。不带有前导_POSIX_的名称打算作为给定实现支持的实际值(这19个实现值是本节开始部分所列出的2~5项:不变值、运行时可增加的值、运行时不变的值、以及路径名可变值)。问题是并不能确保所有这19个实现值都在<limits.h>头文件中定义。

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

2.5.3 XSI限制

XSI还定义了处理实现限制的下面几个常量:

(1) 不变最小值:表2-9中列出的10个常量。

(2) 数值限制:LONG_BIT和WORD_BIT。

(3) 运行时不变值(可能不确定):ATEXIT_MAX、IOV_MAX以及PAGE_SIZE。不变最小值列示于表2-9中,在这些值中,很多与消息类有关。最后两个常量值例证了POSIX.1 最小值太小的情况,根据推测这可能是考虑到了嵌入式POSIX.1实现。为此,Single UNIX Specification为遵循XSI的系统增加了具有较大最小值的符号。

2.5.4 sysconf、pathconf和fpathconf函数

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

 

我们需要更详细地说明这三个函数的不同返回值。

(1) 如果name不是表2-10和表2-11 的第3列中的一个合适的常量,则所有这三个函数都会返回-1,并将errno设置为EINVAL。在本书后续部分都将使用这些限制常量。

(2) 有些name可以返回变量的值(返回值≥0),或者返回-1,这表示该值是不确定的,此时并不改变errno的值。

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

对于pathconf的参数pathname以及fpathconf的参数filedes有一些限制。如果不满足其中任何一个限制,则结果是未定义的。

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

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

(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-1中所示的awk(1)程序构建了一个C程序,它打印各pathconf和sysconf符号的值。

 

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

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

程序清单2-2中的程序由awk产生,它会打印所有这些限制,并处理未定义限制的情况。

 

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

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

2.5.5 不确定的运行时限制

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

1. 路径名

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

POSIX.1试图用PATH_MAX值来帮助我们,但是如果此值是不确定的,那么仍是毫无帮助的。程序清单2-3显示了一个全书中用来为路径名动态分配存储区的函数。

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

对于PATH_MAX 是否在路径名末尾包括一个null字符这一点,SUS v3之前的标准表述得不清楚。出于安全方面的考虑,如果操作系统实现遵循先前的标准版本,则需要在为路径名分配的存储数量上加1。

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

  

2. 最大打开文件数

守护进程(daemon process,是指在后台运行且不与终端相连接的一种进程,也常被称为精灵进程或后台进程)中一个常见的代码序列是关闭所有打开的文件。某些程序中有下列形式的代码序列:

 

这段程序假定在<sys/param.h>头文件中定义了常量NOFILE。另外一些程序则使用某些<stdio.h>版本提供作为上限的常量_NFILE。某些程序则将其上限值硬编码为20。我们希望用POSIX.1的OPEN_MAX来确定此值以提高可移植性,但是,如果此值是不确定的,则仍然有问题。如果我们编写了下列代码:

 

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

 

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

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

例如,我们可以使用内建在Bourne-again shell中的ulimit命令,来更改进程可同时打开的最大文件数。如果要将此限制值设置为在效果上是无限制的,那么通常要求具有特权(超级用户)。但是,一旦将其值设置为无穷大,sysconf就会将LONG_MAX报告为OPEN_MAX的限制值。程序若将此值作为要关闭的文件描述符数的上限(如程序清单2-4所示),那么为了试图关闭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和Korn shell的ulimit命令更改这个值)。如果系统支持这种功能,则可以更改程序清单2-4中的函数,使得每次调用此函数时都会调用sysconf,而不只是发生在第一次调用此函数时。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论