本节书摘来自异步社区《UNIX环境高级编程(第3版)》一书中的第2章,第2.5节,作者:【美】W. Richard Stevens , Stephen A.Rago著,更多章节内容可以访问云栖社区“异步社区”公众号查看
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定义的所有编译时限制都列在头文件中(见图2-6)。这些限制常量在一个给定系统中并不会改变。表中第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。
在头文件中,对浮点数据类型也有类似的一组定义。如若读者在工作中涉及大量浮点数据类型,则应仔细查看该文件。
虽然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流的最小个数,该值在头文件中定义,其最小值是8。POSIX.1中的STREAM_MAX(若定义的话)则应与FOPEN_MAX具有相同的值。
ISO C还在中定义了常量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.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。
在这些限制和常量中,某些可能定义在中,其余的则按具体条件可定义、可不定义。在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中所列出的最小值。
遗憾的是,这些不变最小值中的某一些在实际应用中太小了。例如,目前在大多数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个函数可以在运行时得到实际的实现值。但是,还有一个问题,其中某些值由POSIX.1定义为“可能不确定的”(逻辑上无限的),这就意味着该值没有实际上限。例如,在Solaris中,进程结束时注册可运行atexit的函数个数仅受系统存储总量的限制。所以在Solaris中,ATEXIT_MAX被认为是不确定的。2.5.5节还将讨论运行时限制不确定的问题。
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.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会返回一个变量值(返回值 geqslant 0)或者提示该值是不确定的。不确定的值通过返回−1来体现,而不改变errno的值。
(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符号的值。
#!/usr/bin/awk -f
BEGIN {
printf("#include \"apue.h\"\n")
printf("#include <errno.h>\n")
printf("#include <limits.h>\n")
printf("\n")
printf("static void pr_sysconf(char *, int);\n")
printf("static void pr_pathconf(char *, char *, int);\n")
printf("\n")
printf("int\n")
printf("main(int argc, char *argv[])\n")
printf("{\n"}
printf("\tif (argc != 2)\n")
printf("\t\terr_quit(\"usage: a.out <dirname>\");\n\n")
FS="\t+"
while (getline <"sysconf.sym" > 0) {
printf("#ifdef %s\n", $1)
printf("\tprintf(\"%s defined to be %%ld\\n\", (long)%s+0);\n", $1, $1)
printf("#else\n")
printf("\tprintf(\"no symbol for %s\\n\");\n", $1)
printf("#endif\n")
printf("#ifdef %s\n", $2)
printf("\tpr_sysconf(\"%s =\", %s);\n", $1, $2)
printf("#else\n")
printf("\tprintf(\"no symbol for %s\\n\");\n", $2)
printf("#endif\n")
}
close("sysconf.sym")
while (getline <"pathconf.sym" > 0) {
printf("#ifdef %s\n", $1)
printf("\tprintf(\"%s defined to be %%ld\\n\", (long)%s+0);\n", $1, $1)
printf("#else\n")
printf("\tprintf(\"no symbol for %s\\n\");\n", $1)
printf("#endif\n")
printf("#ifdef %s\n", $2)
printf("\tpr_pathconf(\"%s =\", argv[1], %s);\n", $1, $2)
printf("#else\n")
printf("\tprintf(\"no symbol for %s\\n\");\n", $2)
printf("#endif\n")
}
close("pathconf.sym")
exit
}
END {
printf("\texit(0);\n")
printf("}\n\n")
printf("static void\n")
printf("pr_sysconf(char *mesg, int name)\n")
printf("{\n"}
printf("\tlong val;\n\n")
printf("\tfputs(mesg, stdout);\n")
printf("\terrno = 0;\n")
printf("\tif ((val = sysconf(name)) < 0) {\n"}
printf("\t\tif (errno != 0) {\n"}
printf("\t\t\tif (errno == EINVAL)\n")
printf("\t\t\t\tfputs(\" (not supported)\\n\", stdout);\n")
printf("\t\t\telse\n")
printf("\t\t\t\terr_sys(\"sysconf error\");\n")
printf("\t\t) else {\n"}
printf("\t\t\tfputs(\" (no limit)\\n\", stdout);\n")
printf("\t\t)\n")
printf("\t} else {\n"}
printf("\t\tprintf(\" %%ld\\n\", val);\n")
printf("\t)\n")
printf("}\n\n")
printf("static void\n")
printf("pr_pathconf(char *mesg, char *path, int name)\n")
printf("{\n"}
printf("\tlong val;\n")
printf("\n")
printf("\tfputs(mesg, stdout);\n")
printf("\terrno = 0;\n")
printf("\tif ((val = pathconf(path, name)) < 0) {\n"}
printf("\t\tif (errno != 0) {\n"}
printf("\t\t\tif (errno == EINVAL)\n")
printf("\t\t\t\tfputs(\" (not supported)\\n\", stdout);\n")
printf("\t\t\telse\n")
printf("\t\t\t\terr_sys(\"pathconf error, path = %%s\", path);\n")
printf("\t\t) else {\n"}
printf("\t\t\tfputs(\" (no limit)\\n\", stdout);\n")
printf("\t\t)\n")
printf("\t) else {\n"}
printf("\t\tprintf(\" %%ld\\n\", val);\n")
printf("\t}\n")
printf("}\n")
}
图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所示,它会打印所有这些限制,并处理未定义限制的情况。
#include "apue.h"
#include <errno.h>
#include <limits.h>
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 <dirname>");
#ifdef ARG_MAX
printf("ARG_MAX defined to be %ld\n", (long)ARG_MAX+0);
#else
printf("no symbol for ARG_MAX\n");
#endif
#ifdef _SC_ARG_MAX
pr_sysconf("ARG_MAX =", _SC_ARG_MAX);
#else
printf("no symbol for _SC_ARG_MAX\n");
#endif
/* similar processing for all the rest of the sysconf symbols... */
#ifdef MAX_CANON
printf("MAX_CANON defined to be %ld\n", (long)MAX_CANON+0);
#else
printf("no symbol for MAX_CANON\n");
#endif
#ifdef _PC_MAX_CANON
pr_pathconf("MAX_CANON =", argv[1], _PC_MAX_CANON);
#else
printf("no symbol for _PC_MAX_CANON\n");
#endif
/* similar processing for all the rest of the pathconf symbols... */
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 {
fputs(" (no limit)\n", stdout);
}
} 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);
}
}
图2-14 打印所有可能的sysconf和pathconf值
图2-15总结了在本书讨论的4种系统上图2-14所示程序的输出结果。“无符号”项表示该系统没有提供相应_SC或_PC符号以查询相关常量值。因此,该限制是未定义的。与此对比,“不支持”项表示该符号由系统定义,但是未被sysconf和pathcon函数识别。“无限制”项表示该系统将相关常量定义为无限制,但并不表示该限制值可以是无限的,它只表示该限制值不确定。
注意,有些限制报告地并不正确。例如,在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 不确定的运行时限制
前面已提及某些限制值可能是不确定的。我们遇到的问题是,如果这些限制值没有在头文件中定义,那么在编译时也就不能使用它们。但是,如果它们的值是不确定的,那么在运行时它们可能也是未定义的。让我们来观察两个特殊的情况,为一个路径名分配存储区,以及确定文件描述符的数目。
1.路径名
很多程序需要为路径名分配存储区,一般来说,在编译时就为其分配了存储区,而且不同的程序使用各种不同的幻数(其中很少是正确的)作为数组长度,如256、512、1 024或标准I/O常量BUFSIZ。4.3BSD头文件中的常量MAXPATHLEN才是正确的值,但是很多4.3BSD应用程序并未使用它。
POSIX.1试图用PATH_MAX值来帮助我们,但是如果此值是不确定的,那么仍是毫无帮助的。图2-16程序是本书用来为路径名动态分配存储区的函数。
#include "apue.h"
#include <errno.h>
#include <limits.h>
#ifdef PATH_MAX
static long pathmax = PATH_MAX;
#else
static long pathmax = 0;
#endif
static long posix_version = 0;
static long xsi_version = 0;
/* If PATH_MAX is indeterminate, no guarantee this is adequate */
#define PATH_MAX_GUESS 1024
char *
path_alloc(size_t *sizep) /* also return allocated size, if nonnull */
{
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) { /* first time through */
errno = 0;
if ((pathmax = pathconf("/", _PC_PATH_MAX)) < 0) {
if (errno == 0)
pathmax = PATH_MAX_GUESS; /* it's indeterminate */
else
err_sys("pathconf error for _PC_PATH_MAX");
} else {
pathmax++; /* add one since it's relative to root */
}
}
/*
* Before POSIX.1-2001, we aren't guaranteed that PATH_MAX includes
* the terminating null byte. Same goes for XPG3.
*/
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);
}
图2-16 为路径名动态地分配空间
如果中定义了常量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,在后台运行且不与终端相连接的一种进程)中一个常见的代码序列是关闭所有打开文件。某些程序中有下列形式的代码序列,这段程序假定在头文件中定义了常量NOFILE。
#include <sys/param.h>;
for (i = 0; i < NOFILE; i++)
close(i);
另外一些程序则使用某些版本提供的作为上限的常量_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时确实会返回一个特定的出错值,但是用复制一个描述符两、三百次的方法来确定此值是一种非常极端的方法。
#include "apue.h"
#include <errno.h>
#include <limits.h>
#ifdef OPEN_MAX
static long openmax = OPEN_MAX;
#else
static long openmax = 0;
#endif
/*
* If OPEN_MAX is indeterminate, this might be inadequate.
*/
#define OPEN_MAX_GUESS 256
long
open_max(void)
{
if (openmax == 0) { /* first time through */
errno = 0;
if ((openmax = sysconf(_SC_OPEN_MAX)) < 0) {
if (errno == 0)
openmax = OPEN_MAX_GUESS; /* it's indeterminate */
else
err_sys("sysconf error for _SC_OPEN_MAX");
}
}
return(openmax);
}
图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。