每个UNIX实现对各种系统特性和资源设置了 限制(limits),并提供(或选择不提供)由各种标准定义的选项。包含如下例子:
- 进程同时可以打开多少文件?
- 系统是否支持实时的信号?
- int类型的变量中可以存储的最大值是什么?
- 程序中的参数列表最大可以有多少?
- 路径名的长度最大是多少?
虽然我们可以在应用中对假定的limit和选项进行硬编码(hard-code),但这降低了可移植性,因为限制和选项是多样的:
- 在UNIX实现之间
【Across UNIX implementations,跨UNIX实现】:尽管在每个单独的实现中limit和选项可能是固定的,在不同的UNIX实现中,这些值可能会有所不同。limit的一个例子是int可存储的最大值。 - 特定实现中的运行时(at run time on a particular implementation):例如:内核可能已经通过重新配置来改变了limit。又或者应用可能在某个系统上进行了编译,但是运行在使用了不同的limit和选项的其他系统上。
- 从一个文件系统到另一个文件系统:例如,传统的System V 文件系统的文件名最多可以有14个字节,而传统的BSD文件系统以及大部分“原生”
【native,本地】的Linux系统的文件名最多可以有255个字节。
因为系统limits和选项会影响应用的行为,所以可移植的应用需要有一些方法来确定limit值以及是否支持某些选项。C编程语言标准和SUSv3为应用获取这些信息提供了两种主要途径:
- 某些limits和选项可以在编译时确定。例如,int的最大值是由硬件架构和编译器设计选择决定的。这些limits可在头文件中记录。
- 其他的limits和选项在运行时可能会有不同。在这种情况下,SUSv3定义了三个函数——sysconf()、pathconf()和fpathconf(),应用可以通过调用这三个函数检查limits和选项。
SUSv3中规定了一系列limits,符合标准的实现需要遵从这些规则。并且还规定了一套选项,特定的系统可能支持也可能不支持其中的选项。在本章中将阐述一些limits和选项,而其余的在后续章节的相关知识点中讲述。
11.1 System Limits
在SUSv3中定义的每个limit,SUSv3都要求所有(系统)实现都要定义一个limit的 最小值(minimum value)。大部分情况下,最小值是作为 <limit.h> 中的常量来定义的,名称的前缀是 _POSIX_,并且通常包含字符串 _MAX;这样,名称的形式就是 _POSIX_XXX_MAX 。
如果应用中将limit的值设置为SUSv3所要求的每个limit的最小值,那么该应用可以移植到所有标准实现中。这样对应用使用更高的limit产生了限制。出于这个原因,通常是更好的做法是:在特定系统上使用<limits.h>、sysconf()或者pathconf()来 获取【determine ,决定】 limit。
SUSv3中规定的limit名称中字符串_MAX会令人费解,明明定义的是最小值,却使用了_MAX。其实我们这样想就清楚明了了:每个limit常量用于定义资源或特性的上限值,而标准中要求这个limit应当有确定的最小值。
在一些情况下,为limit提供了 最大值,而这些值的名称中包含了字符串_MIN。对于这些常量,可以反过来说:这些limit常量表示某些资源的 下限,而标准中说的是为了系统之间的兼容性,这个下限不能大于某个特定的值。
每个limit都有具体的名称,对应于上述的最小值名称,但去掉_POSIX_前缀。系统实现可以在<limits.h>中以此名称来定义一个常量,以表示对该limit的实现。
如果定义了这个limit,那么这个值至少是上述的最小值。(即 XXX_MAX >= _POSIX_XXX_MAX )
SUSv3将它规定的limits分成三类:运行时恒定值(runtime invariant values) 、路径名变量值(pathname variable values) 和 运行时可增值(runtime increasable values)。
Runtime invariant values (possibly indeterminate)
运行时恒定值:如果在<limit.h>中定义了,那么对系统实现来说这个limit的值就是固定的。然而,这个值可能是不确定的(可能取决于内存空间的可用性),因此在<limits.h>中忽略对其的定义。在这种情况下(即使该limit在<limits.h>中定义了),应用在运行时,可以使用 sysconf() 来获取这个值。
MQ_PRIO_MAX这个limit是运行时恒定值的一个例子。在52.5.1节将看到,这是POSIX消息队列的消息优先级中的一个limit。SUSv3将常量_POSIX_MQ_PRIO_MAX的值定义为32,所有兼容性的系统实现都要为这个limit提供这个最小值。这意味着所有具有兼容性的系统实现的消息优先级都至少有从0~31。在UNIX实现中,可以通过在<limits.h>中定义常量MQ_RPIO_MAX来将这个值设置得更高。例如,在Linux中,MQ_PRIO_MAX的值定义为32768。该值可以在运行时使用以下调用来获得:
lim = sysconf(_SC_MQ_PRIO_MAX);
Pathname variable values
路径名变量值(pathname)是与路径名(文件、目录、终端等等)有关的limits。每个limit可能是实现中的常量,或者随文件系统的不同而不同 【 may vary from one file system to another,文件系统之间存在不同】 。在应用中,可以使用 pathconf() 或者 fpathconf() 来获取与路径名相关的limits。
NAME_MAX 这个limit是路径变量名的一个例子。这个limit定义了特定文件系统中文件名的最大长度。SUSv3定义了常量 _POSIX_NAME_MAX ,它的值是 14(老的System V文件系统limit),这是系统实现中该limit所要支持的最小值。如果想要更大的值,可以定义 NAME_MAX 常量。使用如下调用获取特定文件系统的相关信息:
lim = pathconf(directory_path, _PC_NAME_MAX);
directory_path是文件系统上目录的路径名。
Runtime increasable values
运行时可增值(a runtime increasable value) 是一个limit, 对于特定的系统实现具有固定的最小值。然而特定系统在运行时可能会增加这个limit,应用可以使用sysconf()找到系统所支持的实际值。
运行时可增值的一个例子是 NGROUPS_MAX ,它定义了进程同时拥有的组IDs的最大个数。SUSv3中定义了相应的最小值:_POSIX_GRTOUPS_MAX,值是 8 。运行时,应用可以调用 sysconf(_SC_NGROUPS_MAX) 获取这个值。
Summary of selected SUSv3 limits
Table 11-1 列出了一些SUSv3中定义的limits:
limit的名称 (<limits.h>) | 最小值 | sysconf()/pathconf()名称(<unistd.h>) | 描述 |
---|---|---|---|
ARG_MAX | 4096 | _SC_ARG_MAX | 提供给exec()的参数(argv)加上环境(environ)的最大字节(6.7节和27.2.3节) |
无 | 无 | _SC_CLK_TCK | times()的度量单位 |
LOGIN_NAME_MAX | 9 | _SC_LOGIN_NAME_MAX | 登录名的最大长度 (包含终止null字节) |
OPEN_MAX | 20 | _SC_OPEN_MAX | 进程可同时打开的文件描述符的最大个数,比最大可用数多1(36.2节) |
NGROUPS_MAX | 8 | _SC_NGROUP_MAX | 进程辅助组的个数 |
无 | 1 | _SC_PAGESIZE | 虚拟内存页的大小(与_SC_PAGE_SIZE同义) |
PTSIG_MAX | 8 | _SC_RTSIG_MAX | 单一实时信号的最大个数(22.8节) |
SIGQUEUE_MAX | 32 | _SC_SIGQUEUE_MAX | 排队实时信号的最大个数(22.8节) |
STREAM_MAX | 8 | _SC_STREAM_MAX | 同时可打开的stdio流的最大个数 |
NAME_MAX | 14 | _PC_NAME_MAX | 文件名的最大字节数,不包括终止null字节 |
PATH_MAX | 256 | _PC_PATH_MAX | 路径名的最大字节数,包括终止null字节 |
PIPE_BUF | 512 | _PC_PIPE_BUF | 一次性原子操作写入管道或FIFO中的最大字节数(44.1节) |
Table 11-1的第一列给出了limit的名称,它可能是<limits.h>中定义的常量,表示特定实现的某个limit。第二列是SUSv3中定义的limit的最小值(也在<limits.h>中定义)。在大部分情况下,最小值的常量定义的前缀都是 _POSIX_ 。第三列给出了在运行时调用sysconf()或pathconf()时需要给出的常量名称,用于获取实现中的limit。以 _SC_ 开头的常量是给 sysconf() 使用的。以 _PC_ 开头的常量是给 pathconf() 和 fpathconf() 使用的。
下面是Table 11-1的补充信息:
- getdtablesize()函数用于获取进程文件描述符的limit(OPEN_MAX),现已废弃。这个函数在SUSv2中定义(标记为LEGACY),在SUSv3中移除了这个规定。
- getpagesize()函数用于获取系统的page size(_SC_PAGESIZE),现已废弃。这个函数在SUSv2中定义(标记为LEGACY),在SUSv3中移除了这个规定。
- 定义在<stdio.h>中的FOPEN_MAX常量与STREAM_MAX同义。
- NAME_MAX不包含表示终止的null字节。而PATH_MAX包含null字节。
Determining limits and options from the shell:getconf
使用shell时,可以通过 getconf 命令来获取特定UNIX实现的limits和选项。这个命令的通常形式如下:
$ getconf variable-name [pathname]
variable-name 表示一个SUSv3标准limit名称,例如ARG_MAX或NAME_MAX。当limit与路径名有关时,必须将路径名作为命令的第二个参数。如下:
11.2 Retrieving System Limits (and Options) at Run Time
sysconf()函数允许应用在运行时获取系统limits的值。
#include <unistd.h>
// 成功时返回由name指定的limit的值,遇到错误或无法确定值时返回-1
long sysconf(int name);
name 参数是<unistd.h>中定义的 _SC_* 常量,其中的一些在Table 11-1中列出。limit的值作为函数的结果返回。
若无法确定一个limit,sysconf()返回-1。遇到错误时也返回-1。(唯一指定的错误是EINVAL,表示name是无效的。)为了区别不确定的limit和发生错误这两种情况,都在调用之前必须将errno设置为0,如果调用后,函数返回-1并且errno被设置,那么是发生了一个错误。
sysconf()(以及pathconf()和fpathconf())返回的limit值经常是(long)整型。SUSv3指出(note that)曾经一度考虑将string也作为返回值,但是由于实现和使用的复杂性,放弃了这种构想。
Listing 11-1演示了sysconf()的用法,用于展示各种系统limits。在Linux 2.6.31/x86-32系统中运行该程序产生如下结果:
// Listing 11-1: Using sysconf()
// syslim/t_sysconf.c
#include "tlpi_hdr.h"
/* Print 'msg' plus sysconf() value for 'name' */
static void sysconfPrint(const char *msg, int name)
{
long lim;
errno = 0;
lim = sysconf(name);
if (lim != -1) { /* Call succeeded, limit determinate */
printf("%s %ld\n", msg, lim);
} else {
if (errno == 0) /* Call succeeded, limit indeterminate */
printf("%s (indeterminate)\n", msg);
else /* Call failed */
errExit("sysconf %s", msg);
}
}
int main(int argc, char *argv[])
{
sysconfPrint("_SC_ARG_MAX: ", _SC_ARG_MAX);
sysconfPrint("_SC_LOGIN_NAME_MAX: ", _SC_LOGIN_NAME_MAX);
sysconfPrint("_SC_OPEN_MAX: ", _SC_OPEN_MAX);
sysconfPrint("_SC_NGROUPS_MAX: ", _SC_NGROUPS_MAX);
sysconfPrint("_SC_PAGESIZE: ", _SC_PAGESIZE);
sysconfPrint("_SC_PTSIG_MAX: ", _SC_RTSIG_MAX);
exit(EXIT_SUCCESS);
}
SUSv3要求sysconf()返回的特定的limit值在进程的生命周期中是一个常量。例如,我们可以假定返回的_SC_PAGESIZE的值在进程的运行过程中不会改变。
11.3 Retrieving File-Related Limits (and Options) at Run Time
pathconf() 和 fpathconf() 函数允许应用在运行时获取文件相关limits的值。
#include <unistd.h>
// 成功时返回由name指定的limit的值,遇到错误或无法确定值时返回-1
long pathconf(const char *pathname, int name);
long fpathconf(int fd, int name);
pathconf()和fpathconf()的唯一区别是对于文件或目录的指定方式。对于pathconf(),指定的是路径名,而对于fpathconf()指定的是(前面打开)文件描述符。
name参数是<unistd.h>中定义的 _PC_* 常量。这些常量中的部分在Table 11-1中列出。Table 11-2为Table 11-1中的一些常量进行了更详细的说明。
limit的值作为函数结果返回。可以采用与sysconf()相同的方式来区分返回的值是不确定的还是遇到了错误。
不像sysconf()那样, SUSv3并不要求pathconf()和fpathconf()返回的值在进程的生命周期中是常量。这是因为,例如在进程运行期间,可能会卸载一个文件系统,然后再以不同特性重新装载该文件系统。
Table 11-2: pathconf()中 PC* name的详情
Constant | Notes |
---|---|
_PC_NAME_MAX | 针对目录,返回该目录下文件命名的最大长度,对于其他文件类型,则未做规定。 |
_PC_PATH_MAX | 对于目录,返回该目录中相对路径名的最大长度,对于其他文件类型,则未做规定。 |
_PC_PIPE_BUF | 对于FIFO或者管道,返回一个应用于引用文件的值。对于目录,返回的值应用于在该目录下 创建的一个FIFO。对于其他文件类型,则未做规定。 |
Listing 11-2演示了fpathconf()获取文件各种limits的用法:
// Listing 11-2: Using fpathconf()
// syslim/t_fpathconf.c
#include "tlpi_hdr.h"
/* Print 'msg' plus value of fpathconf(fd, name) */
static void fpathconfPrint(const char *msg, int fd, int name)
{
long lim;
errno = 0;
lim = fpathconf(fd, name);
if (lim != -1) { /* Call succeeded, limit determinate */
printf("%s %ld\n", msg, lim);
} else {
if (errno == 0) /* Call succeeded, limit indeterminate */
printf("%s (indeterminate)\n", msg);
else /* Call failed */
errExit("fpathconf %s", msg);
}
}
int main(int argc, char *argv[])
{
fpathconfPrint("_PC_NAME_MAX: ", STDIN_FILENO, _PC_NAME_MAX);
fpathconfPrint("_PC_PATH_MAX: ", STDIN_FILENO, _PC_PATH_MAX);
fpathconfPrint("_PC_PIPE_BUF: ", STDIN_FILENO, _PC_PIPE_BUF);
exit(EXIT_SUCCESS);
}
11.4 Indeterminate Limits
有时,我们可能发现一些系统limit没有定义成limit常量(例如PATH_MAX),sysconf()或pathcconf()将通知我们limit是 未确定的(indeterminate)。这种情况下,我们可以采用以下策略:
- 当编写一个多UNIX系统之间可移植的应用时,可以选择SUSv3中指定的最小limit。也就是名称形式为_POSIX_*_MAX的常量。有时,这种方法是不可行的,因为limit的值实在是太小,例如_POSIX_PATH_MAX 和 _POSIX_OPEN_MAX。
- 在某些情况下,实际的解决方案可能是:忽略对limit的检查,使用相关的系统调用或者库函数来替代。如果调用执行失败,并且errno表明出错的原因是超出了一些系统的limit,那么我们可以再次尝试,必要时修改应用的行为。例如,大部分UNIX实现引入了实时信号数的limit,实时信号在进程中以队列的形式排列。一旦达到了limit这个值,(使用sigqueue())进一步发送信号会失败,发生EAGAIN这个错误。在这种情况下,发送进程可能会每隔一小段时间后重试。类似地,尝试打开一个文件,如果文件名称太长会产生ENAMETOOLONG的错误,那么应用可能会使用更短的名称再次尝试。
- 我们也可以自己编写程序或函数对limit的值进行推断和评估。这时,就会使用相关的sysconf()或pathconf()调用,如果limit的值是未确定的,那么函数返回一个合理的猜想值。当然这种方案不完美,但在实践中经常是可行的。
- 可以采用类似GNU Autoconf这样的工具,该工具能够确定各种系统特性及limit存在与否、如果设置。
11.5 System Options
除了规定各种系统资源的limits,SUSv3还规定了UNIX实现所需支持的各种选项(options)。包括对于实时信号、POSIX共享内存、工作(job)控制和POSIX线程等特色的支持。除了少数选项例外,实现没有要求支持这些选项。
系统实现可以在编译时通过在<unistd.h>中定义相应常量来支持特定的SUSv3选项。每个常量的前缀表示它起源于哪种的标准(_POSIX_ 或 _XOPEN_ )
每个已定义的选项,其值必属于下列之一:
- 值为 -1 表示不支持该选项。在这种情况下,相关选项的头文件、数据类型和函数接口就不需要在系统实现中定义。我们可以使用#if预处理器指令,通过条件编译的方式来处理这种情况。
- 值为 0 意味着 可能 支持该选项。应用需要在运行时检查是否支持这个选项。
- 值 大于0 意味着支持该选项。 所有与选项相关的头文件、数据类型和函数接口都需要定义,其行为也要符合规范要求。在很多情况下,SUSv3要求这个整数是200112L,这是一个常量,表示SUSv3将该选项作为标准的年月。(SUSv4中的200809L也是类似的)
当定义的常量值是0时,应用可以在运行时使用sysconf()和pathconf()(或fpathconf())函数来检查该选项是否被支持。传入函数的name参数通常与编译时常量具有一致的形式,只是前缀有_SC_或_PC_替代。系统实现必须提供至少头文件、常量和函数接口来执行运行时检查。
Table 11-3列出了SUSv3中指定的一些选项。表中的第一列给出了该选项(定义在<unistd.h>中)相关编译时常量的名称,以及sysconf()(SC)和pathconf()(PC)函数的入参name值。对于特定选项需要注意以下几点:
- 某些选项在SUSv3中被定义了。也就是,编译时计算出的值经常大于0。很久以来,这些选项一直是可选的,但是现在不是了。这些选项在备注(Note)这一列使用字符 + 标记。(在SUSv4中,一些在SUSv3中可选的选项已经变成强制要求的了)
- 对于某些选项,编译时的常量必须有值,而不是-1。这些选项在备注(Note)这一列使用字符 * 标记。