Chinaunix首页 | 论坛 | 博客
  • 博客访问: 26855
  • 博文数量: 10
  • 博客积分: 25
  • 博客等级: 民兵
  • 技术积分: 60
  • 用 户 组: 普通用户
  • 注册时间: 2012-04-06 18:51
文章分类
文章存档

2012年(10)

我的朋友

分类:

2012-08-13 20:42:41

各种实现定义了很多魔数和常量。其中有许多使用临时特定的技术(ad hoc technique)被硬编码在程序里。随着各种标准化的努力,有更多的可移植的方法可以用来决定这些魔数和由实现定义的限量,极大地提高了我们软件的可移植性。


总共需要两种类型的限量:


1、编译期限量(比如short型的最大值);


2、运行期限量(比如文件名的最大字符数);


编译期限量可以被定义在头文件里,任何程序都可以在编译期引入,但是运行期限量需要进程调用函数来获得限量的值。


此外,有一些限量在一些实现上是固定的,它们能静态地定义在一个头文件里,然而在其它实现上是变化的,从而需要一个运行期的函数调用。这种类型的限量的例 子有文件名的最大字符数。在SVR4前,历史上系统V只允许一个文件名里有14个字符,而BSD的后裔把这个数值增大到了255。大多数UHIX系统实现 最近都支持多个文件系统类型,而且每种类型都有它自己的限量。这种情况下,一个文件名的运行期限量取决于它的文件系统。比如在root文件系统可以有14 个字符的限量,而在另一个系统可以有255个字符的限量。


为了解决这样的问题,有三种类型的限量被提供:


1、编译期限量(头文件);


2、与文件和目录无关的运行期限量(sysconf函数);


3、与文件和目录有关的运行期限量(pathconf和fpathconf函数)。


如果一个特定的运行期限量在一个系统实现上是不变的,则它可以在头文件里静态定义。如果不定义在头文件里,则应用程序必须调用三个conf函数中的一个来得到运行期的值。


ISO C限量

所有在ISO C里定义的限量都是编译期限量。下表给出定义在的C标准里的限量。这些常量一直都定义在这个头文件里,而不会在系统实现 上有变化。第三列显示了ISO C标准最小的可接受的值,考虑到一个使用反码计算(one's-complement arithmetic)的16位整型的系统。第四列显示了使用补码计算(two's-complement arithmetic)的32位Linux系统上的值。注意没有一个无符号数据类型有最小值,因为对于无符号数据类型来说这些值必须为0。在64位系统 上,long型值的最大值与long long型的最大值相同。


里定义的整型值的大小
名称
描述
可接受的最小值
典型值
CHAR_BIT char里的位数 8 8
CHAR_MAX char的最大值 稍后讨论 127
CHAR_MIN char的最小值 稍后讨论 -128
SCHAR_MAX signed char的最大值 127 127
SCHAR_MIN signed char的最小值 -127 -128
UCHAR_MAX unsigned char的最大值 255 255
INT_MAX int的最大值 32,767 2,147,483,647
INT_MIN int的最小值 -32,767 -2,147,483,648
UINT_MAX unsigned int的最大值 65,535 4,294,967,295
SHRT_MIN short的最小值 -32,767 -32,768
SHRT_MAX short的最大值 32,767 32,767
USHRT_MAX unsigned short的最大值 65,535 65,535
LONG_MAX long的最大值 2,147,438,647 2,147,483,647
LOGN_MIN long的最小值 -2,147,483,647 -2,147,483,648
ULONG_MAX unsigned long的最大值 4,294,967,295 4,294,967,295
LLONG_MAX long long的最大值 9,223,372,036,854,775,807 9,223,372,036,854,775,807
LLONG_MIN long long的最小值 -9,223,372,036,854,775,807 -9,223,372,036,854,775,808
ULLONG_MAX unsigned long long的最大值 18,446,744,073,709,551,615 18,446,744,073,709,551,615
MB_LEN_MAX 多字节字符常量的最大字节数 1 16

有一个我们将会碰到的不同之处,是系统是提供有符号字符还是无符号字符。从第4列可以看到这个特定的系统使用有符号字符。我们看到CHAR_MIN与 SCHAR_MIN相等,而CHAR_MAX与SHAR_MAX相等。如果系统使用无符号字符,我们就会有CHAR_MIN等于0而CHAR_MAX与 UCHAR_MAX相同。

浮点数据类型定义在,它也有着一组类似的定义。任何人要执行重要的浮点工作时都要检查这个文件。


另一个我们将碰到的ISO C常量是FOPEN_MAX,它表示一个系统实现可以保证的同时打开的标准I/O流的最小数量。这个值定义在头文件里,而 且它的值为8。POSIX.1的STREAM_MAX值,如果它定义了的话,必须与FOPEN_MAX的值一样。


ISO C在里还定义了一个常量TMP_MAX。它是tmpnam函数可以产生的独一无二的文件名的最大数量。


下表我们给出本文讨论的四个系统上FOPEN_MAX和TMP_MAX的值:

限量 FreeBSD 5.2.1 Linux 2.4.22 Mac OS X 10.3 Solaris 9
FOPEN_MAX 20 16 20 20
TMP_MAX 308,915,776 238,328 308,915,776 17,576

ISO C还定义了常量FILENAME_MAX,但是我们应避免使用它,因为由于历史原因有些操作系统把它设得太小而不适合使用。


POSIX限量

POSIX.1定义了许多处理操作系统实现的限量的常量。不幸的是,这是一个更令人困惑特性。尽量POSIX.1定义了许多限量和常量,但我们仅仅关心影响基本POSIX.1接口的那些。这些限量和常量被分为五个各类:


1、不变的最小值:下表中的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_MAX、SYMLOOP_MAX、TTY_NAME_MAX和TZNAME_MAX;


5、路径名变量的值,可能不定:FILESIZEBITS、LINK_MAX、MAX_CANON、MAX_INPUT、NAME_MAX、PATH_MAX、PIPE_BUF和SYMLINK_MAX。


里定义的POSIX.1的不变最小值
名称 描述:...的可接受的最小值
_POSIX_ARG_MAX exec函数参数的长度 4,096
_POSIX_CHILD_MAX 每个真实用户ID的子进程数 25
_POSIX_HOST_NAME_MAX gethostname返回的主机名的最大长度 255
_POSIX_LINK_MAX 一个文件的链接数 8
_POSIX_LOGIN_NAME_MAX 登录名的最大长度 9
_POSIX_MAX_CANON 终端最简洁的(canonical)输入队列的字节数 255
_POSIX_MAX_INPUT 终端输入队列的可用空格 255
_POSIX_NAME_MAX 文件名的字节数,不包括终止字符null 14
_POSIX_NGROUPS_MAX 一个进程的同步的补充组ID的数量 8
_POSIX_OPEN_MAX 一个进程打开的文件数 20
_POSIX_PATH_MAX 路径名的字节数,包括终止符null 256
_POSIX_PIP_BUF 可被原子写入管道的字节数 512
_POSIX_RE_DUP_MAX 当使用间隔标记“\{m,n\}”时,被regexec和regcomp函数认可的基本正则表达式的重复出现次数 255
_POSIX_SSIZE_MAX 可以存入ssize_t对象的值 32,767
_POSIX_STREAM_MAX 一个进程能同时打开的标准I/O流的数量 8
_POSIX_SYMLINK_MAX 符号链接的字节数 255
_POSIX_SYMLOOP_MAX 路径名解析时可以转换的符号链接数 8
_POSIX_TTY_NAME_MAX 终端设备名的长度,包括终止符null 9
_POSIX_TZNAME_MAX 时区名的字节数 6

在这44个限量和常量中,有些可能定义在里,而其它的可能有也可能没有定义,取决于特定的条件。当我们讨论sysconf、pathconf和fpathconf函数时会再讨论这些可能有也可能没有定义的限量和常量。


19个不变最小值定义在上表中。这些值不会因为系统不同而有所区别。它们为那些特性定义了最具有约束性的值。遵守POSIX.1的系统实现必须提供至少那 么大的值,这也是为什么它们被称为最小值的原因,尽管它们名字里都含有“MAX”。还有,为了确保可移植性,一个严格遵守标准的应用程序一定不能要求一个 更大的值。我们在本文会讨论所有这些常量。


一个严格遵守POSIX标准(srictly-conforming POSIX)的应用程序与一个仅遵守POSIX标准(merely POSIX conforming)的应用程序不同。一个严格遵守的应用程序只使用IEEE标准1003.1-2001的接口。一个严格遵守的应用程序是一个 POSIX程序,而且不信赖于任何无定义的行为、不使用任何逐步废弃的接口、以及不使用比上表中的最小值更大的值。


不幸的是,不变最小值里有一些太小了,而不适合实际使用。比如,多数UNIX系统为每个进程打开远大于20个的文件。同 样,_POSIX_PATH_MAX的255的限制也太小,路径名会超过这个限制。这意味着我们不能使用_POSIX_OPEN_MAX和 _POSIX_PATH_MAX作为编译期的数组大小。


上表中的19个不变最小值中,每一个都有一个相应实现,名字为去掉_POSIX_前缀的常量名。没有_POSIX前绷的名字说明这是个真实系统实现上支持 的值。这19个实现值是早先我们列出的第2~5项:不变值、运行期可增长的值、运行期不变值和路径名变量值。问题是这19个实现值并不保证一定定义 在头文件里。


比如,一个特定的值可能没有包含在这个头文件里,如果它的进程的真实值依赖于系统里的内存总量。如果值没有在头文件里定义,我们不能在编译期里把它作为数 组边界。如以, POSIX.1决定为我们提供三个运行期函数:sysconf、pathconf和fpathconf,用来得到在运行时的真实实现值。尽管如此,仍然有 个问题,POSIX.1定义的值里面有些被定义为“可能不确定的”,即理论上是“无限”的。这意味着这个值没有实际的上限。比如,在Linux上,可以被 readv和writev使用的iovec结构的数量只限于系统的内存总量。为此,IOV_MAX在Linux被认为是“不确定”的,我们在讲述后面的运 行期限量时再来讨论这个问题。


XSI 限量

XSI同样定义了处理实现限量的常数,包括:


1、不变最小量:下表中的10个常量;


2、数字限量:LONG_BIT和WORD_BIT;


3、运行期不变值,可能不确定:ATEXIT_MAX、IOV_MAX和PAGE_SIZE。


里定义的XSI不变最小值
名称
描述
最小可接受值
典型值
NL_ARGMAX printf和scanf调用数字的最大值 9 9
NL_LANGMAX LANG环境变量的最大字节数 14 14
NL_MSGMAX 最大消息数量 32,767 32,767
NL_NMAX 多对一映射字符的最大字节数 未定义 1
NL_SETMAX 最大集合数量 255 255
NL_TEXTMAX 消息字符串的最大字节数 _POSIX2_LINE_MAX 2,048
NZERO 默认的进程优先级 20 20
_XOPEN_IOV_MAX readv和writev使用的iovec结构的最大数量 16 16
_XOPEN_NAME_MAX 文件名的最大字节数 255 255
_XOPEN_PATH_MAX 路径名的最大字节数 1,024 1,024

上表中有许多值是用来处理消息类别。最后两个值证明了POSIX.1的最小值太小了,很可能只允许嵌入式的POSIX.1系统,所以单一UNIX规范在XSI标准里加入更大的最小值。

函数synconf、pathconf和fpathconf

我们已经列出一个系统实现必须支持的各种最小值,但是我们怎么知道一个特定系统真正支持了那些限量呢?如我们早先所述,这些限量中有些可以在编译期可用, 而一些只有在运行期得到。我们也提到过有些限量在某个系统上是固定的,而其它可能是变化的,因为它们与文件或目录相关。运行期限量可以通过调用以下三个函 数来得到:


#include
long sysconf(int name);
long pathconf(const char* pathname, int name);
long fpathconf(int filedes, int name);

这三个函数成功都返回一个相应的值,而失败则返回-1。


最后两个函数的区别在于一个接受路径名作为参数,而另一个接受文件描述符作为参数。


下表列出了sysconf得到系统限量所用的名字参数,都以_SC_开头:

sysconf的名字参数以及相应的限量
限量名
描述
名字参数
AGR_MAX exec函数参数的最大长度,以字节为单位 _SC_ARG_MAX
ATEXIT_MAX 可以被atexit函数注册的函数的最大数量 _SC_ATEXIT_MAX
CHILD_MAX 一个真实用户ID可以拥有的最大进程数 _SC_CHILD_MAX
clock ticks/second 每秒钟的时钟周期 _SC_CLK_TCK
COLL_WEIGHTS_MAX 在本地定义文件里可以赋值给LC_COLLATE命令关键字的重量的最大数量 _SC_SOLL_WEIGHTS_MAX
HOST_NAME_MAX gethostname返回的主机名的最大长度 _SC_HOST_NAME_MAX
IOV_MAX readv和writev使用的iovec结构的最大数量 _SC_IOV_MAX
LINE_MAX 实用工具的输入行的最大长度 _SC_LINE_MAX
LOGIN_NAME_MAX 登录名的最大长度 _SC_LOGIN_NAME_MAX
NGROUPS_MAX 每个进程的同步补充组ID数 _SC_NGROUPS_MAX
OPEN_MAX 一个进程打开文件的最大数量 _SC_OPEN_MAX
PAGESIZE 系统内存页的字节数 _SC_PAGESIZE
PAGE_SIZE 系统内存页的字节数 _SC_PAGE_SIZE
RE_DUP_MAX regexec和regcomp函数使用间隔符\{m,n\}可以识别的基本表达式的重复次复 _SC_RE_DUP_MAX
STREAM_MAX 在任何时候一个进程的标准流的最大数量;如果有定义,这个值必须与FOPEN_MAX一样 _SC_STREAM_MAX
SYMLOOP_MAX 路径名解析时可以处理的符号链接数 _SC_SYMLOOP_MAX
TTY_NAME_MAX 终端设备名的长度,包括终止符null _SC_TTY_NAME_MAX
TZNAME_MAX 时区名的最大字节数 _SC_TZNAME_MAX

下表列出了pathconf和fpathconf函数使用的名字参数,都以_PC_开头:


pathconf和fpathconf的名字参数以及相应限量
限量名 描述 名字参数
FILESIZEBITS 为了表示在指定目录下允许的普通文件大小的最大值,所使用的有符号整型值所需的最小比特位数 _PC_FILESIZEBITS
LINK_MAX 一个文件链接数的最大值 _PC_LINK_MAX
MAX_CONON 一个终端最简洁的输入队列的最大字节数 _PC_MAX_CONON
MAX_INPUT 一个终端的输入队列可用空间的字节数 _PC_MAX_INPUT
NAME_MAX 文件名的最大字节数,不包括终止符null _PC_NAME_MAX
PATH_MAX 相对路径名的最大字节数,包括终止符null _PC_PATH_MAX
PIPE_BUF 可以原子写入管道的最大字节数 _PC_PIP_BUF
SYMLINK_MAX 一个符号链接的字节数 _PC_SYMLINK_MAX

下面再深入讨论下这三个函数的不同的返回值:


1、如果名字参数不正确,这三个函数都会返回-1并设置errno值为EINVAL。


2、一些名字参数可以返回一个变量的值,或者表明这个值是不确定的。通过返回-1但不改变erron的值来表示一个不确定的值;


3、_SC_CLK_TC的返回值是每秒钟的时钟周期,用来与times函数的返回值共同使用。


在传递路径名给pathconf和域参数给fpathconf时有些约束。如果这些约束中任意一个没有满足,则结果无定义:

1、_PC_MAX_CONON和_PC_MAX_INPUT的对应的文件并须是一个终端文件;


2、_PC_LINK_MAX的对应的文件既可以是文件也可以是目录。如果相应文件是一个目录,则返回值对应于目录本身,而不是目录下的文件;


3、_PC_FILESIZEBITS和_PC_NAME_MAX的相应文件必须是目录。返回值对应于这个目录下的文件;


4、_PC_PATH_MAX相应的文件必须是目录。当这个目录是工作目录时,返回值是相对路径名的最大长度。(不幸的是,这不是我们想知道的绝对路径的真实长度,稍后再来讨论这个问题。)


5、_PC_PIPE_BUF的相应文件必须是个管道,FIFO或者目录。前两种情况下返回值为相应管道或FIFO的限量。最后一种情况的返回值为指定目录下创建的任一FIFO的限量。


6、_PC_SYMLINK_MAX的相应文件必须是一个目录。返回值为目录里的符号链接可以包含的字符串的最大长度。


不确定的运行期限量(Indeterminate Runtime Limits)

我们之前提到了有运行期变量可以是不确定的。问题在于如果这些限量没有在里定义,那我们就不能在编译期使用它们。而且如 果它们的值是不确定的,那么可能在运行期也没有定义!我们来看下两个特殊的情况:为路径名分配内存,以及决定文件描述符的数量。


路径名(Pathname)

许多程序需要为路径名分配内存。典型地,内存在编译期分配,而一些魔数(没有一个是正确的值)被用来作为数组尺寸:256、512、1024或都标准 I/O常量BUFSIZ。定义在里的4.3BSD常量MAXPATHLEN才是正确的值,但许多程序都不用它。


POSIX.1试图用PATH_MAX来解决这个问题,但如果这个值是不确定的,那我们仍然不走运。本文使用下面这个函数来动态地为路径名分配内存:


  1. #include <errno.h>
  2. #include <limits.h>
  3. #include <unistd.h>

  4. #ifdef PATH_MAX
  5. static int pathmax = PATH_MAX;
  6. #else
  7. static int pathmax = 0;
  8. #endif

  9. #define SUSV3 200112L

  10. static long posix_version = 0;

  11. /* If MATH_MAX is indeterminate, no guarantee this is adquate */
  12. #define PATH_MAX_GUESS 1024

  13. void err_sys(const char* msg);

  14. char *
  15. path_alloc(int *sizep) /* also return allocated size, if nonnull */
  16. {
  17.     char *ptr;
  18.     int size;
  19.     
  20.     if (posix_version == 0)
  21.         posix_version = sysconf(_SC_VERSION);

  22.     if (pathmax == 0) { /*first time through */
  23.         errno = 0;
  24.         if ((pathmax = pathconf("/", _PC_PATH_MAX)) < 0) {
  25.             if (errno == 0)
  26.                 pathmax = PATH_MAX_GUESS; /* it's indeterminate */
  27.             else
  28.                 err_sys("pathconf error for _PC_PATH_MAX");
  29.         } else {
  30.             pathmax++; /* add one since it's relative to root */
  31.         }
  32.     }
  33.     if (posix_version < SUSV3)
  34.         size = pathmax + 1;
  35.     else
  36.         size = pathmax;

  37.     if ((ptr = malloc(size)) == NULL)
  38.         printf("malloc error for pathname");

  39.     if (sizep != NULL)
  40.         *sizep = size;
  41.     return(ptr);
  42. }

  43. void err_sys(const char* msg) {
  44.     printf("%s\n", msg);
  45.     exit(1);
  46. }

如果PATH_MAX在里定义了,那就使用它,不然就调用pathconf。如果第一个参数是工作路径,那么 pathconf的返回值是一个相对路径名的最大尺寸,所以我们把根目录作为第一个参数,而后在结果上加1。如果pathconf指出PATH_MAX是 不确定的,那我们只有猜测一个值了。

SUSv3之前的标准并没有清楚说明PATH_MAX最末尾是否包括一个null的字节。如果操作系统实现信赖于这些早期的标准,则我们需要加一个字节的内存,这样会更安全些。


处理不确定结果情况的正确方法取决于分配的空间怎样被使用。如果分配的内存是为了函数调用getcwd--比如:返回当前工作目录的绝对路径名--而且如 果分配的空间太小的话,那errno会返回一个错误ERANGE。我们可以接着调用realloc来增加这块分配的空间并再次尝试。我们可以持续这样做, 直到getcwd成功为止。


打开文件的最大数量(Maximum Number of Open Files)

一个后台进程(daemon process)--运行在后台,不与任何终端关联的进程--的一个普遍代码流程是关闭所有打开的文件。一些程序用以下代码,设想常量NOFILE定义在头文件里:

  1. #include <sys/param.h>
  2. for (i = 0; i < NOFILE; i++)
  3.     close(i);


其它一些程序使用一些版本的提供的常量_NFILE作为上限。有些把它硬编码为20.

我们本希望用POSIX.1的值OPEN_MAX来决定这个值,但如果这个值是不确定的,我们仍然会有问题。如果我们把代码写成下面的模样,而同时OPEN_MAX是不确定的,那会造成死循环,因为sysconf会返回-1:

  1. #include <unistd.h>
  2. for (i = 0; i < sysconf(_SC_OPEN_MAX); i++)
  3.     close(i);


我们最好的解决方案是仅仅关闭所有不超过一个任意上限(比如256)的描述符。和我们的pathname例子一样,这个并不保证可以在所有情况下都工作,但这是我们能做的最好的事情。看如下代码:


  1. #include <errno.h>
  2. #include <limits.h>
  3. #include <unistd.h>

  4. #ifdef OPEN_MAX
  5. static long openmax = OPEN_MAX;
  6. #else
  7. static long openmax = 0;
  8. #endif

  9. /*
  10.  * If OPEN_MAX is indeterminate, we're not
  11.  * guaranteed that this is adequate.
  12.  */
  13. #define OPEN_MAX_GUESS 256

  14. void err_sys(const char* msg);

  15. long
  16. open_max(void)
  17. {
  18.     if (openmax == 0) { /* first time through */
  19.         errno = 0;
  20.         if ((openmax = sysconf(_SC_OPEN_MAX)) < 0) {
  21.             if (errno == 0)
  22.                 openmax = OPEN_MAX_GUESS; /* it's indeterminate */
  23.             else
  24.                 err_sys("sysconf error for _SC_OPEN_MAX");
  25.         }
  26.     }
  27.     return(openmax);
  28. }

我们可能想尝试调用close函数,直到得到一个返回的错误,但是这个从close(EBADF)返回的错误并不能区分它是一个无效的文件描述符,还是这 个文件描述符并没有打开。如果我们尝试使用这种技术,但描述符9并没有打开而描述符10被打开,我们可以终止在9而不能关闭10。如果OPEN_MAX溢 出的话,dup函数确实返回一个特定的错误,但只能重复这个描述符两百多次才能得到这个值。

一些系统实现会返回LONG_MAX当作限值,来有效的表示没有限制。Linux的ATEXIT_MAX就是其一。这不是个好方法,因为它可能会导致程序很坏的行为。


比如,我们可以用Bourne-again shell的内置ulimit命令来改变我们进程可以同时打开的文件数的最大值。如果这个限制要设置成无限制的话,通常需要一个特殊(超级用户)的权限。 但一旦它被设置为无限的话,sysconf会返回LONG_MAX作为OPEN_MAX的值。程序信赖于这个值作为关闭的文件描述符的上限,而尝试关闭 2,147,483,647个文件描述符是非常浪费时间的,而它们之中大多数都没有被使用。


支持UNIX单一规范里的XSI扩展的系统会提供getrlimit函数。它能用来返回一个进程能打开的描述符的最大数量。利用这个函数,我们可以确保我们的进程没有打开超过预设的上限数的文件,从而避免上述问题。


OPEN_MAX被POSIX称为运行期不变量,意味着它的值不能在一个进程的生命周期内改变。但在支持XSI扩展的系统上我们可以调用 setrlimit函数在为一个运行着的进程改变这个值。(这个值也可以在C shell上用limit命令改变,或在Bourne、Bourne-again和Knon shell上用ulimit函数。)如果我们的系统支持这个功能,我们可以改变上述代码:每次调用open_max函数时,都调用sysconf,而不仅 仅在第一次调用时。


阅读(3799) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~