Unix/Linux编程:系统限制和系统选项

引入原因

但凡Unix实现,无不对各种系统特性和资源加以限制,并提供(也可能不提供)由标准所定义的选项,比如:

  • 一个进程能同时拥有多少打开的文件?
  • 系统是否支持实时信号
  • int类型变量可存储的最大值是多少?
  • 一个程序的参数列表能多大?
  • 路径名的最大长度是多少?

通常,不建议将对系统限制和选项的假设值硬性写入应用程序代码,因为这些值既可能随系统的不同而发生变化,也可能在同一个系统实现中因不同的运行期间或文件系统而不同,但这将破坏程序的可移植性。

因为系统限制和选项会影响应用程序的行为,所以可移植应用程序需要获取限制值,弄清系统对选项的支持情况。C语言标准和SUSv3对此提供了两种重要路径:

  • 在编译程序时能够获得一些限制和选项。比如,int的最大值取决于硬件结构和编译器的设计选择。此类限制可以在头文件中记录
  • 另一些限制和选项在程序运行时可能会变化。对此,SUSv3定义了三个选项sysconf()、pathconf()和fpathconf(),供应用程序调用以检查系统实现的限制和选项。

SuSv3规定由一系列限制,要求符号规范的实现必须支持,同时还规定了一套选项,特定系统可以有选择的对其中各个选项给予支持。

系统限制

SUSv3要求,针对其所规范的每个限制,所有实现都必须支持一个最小值。在大多数情况下,会将这些最小值定义为<limits.h>文件中的常量,其命名常形如_POSIX_XXX_MAX

  • SUSv3 将其所定义的各类限制描述为最小值,但命名却使用了字符串_MAX,这可能颇令人疑惑。换一种思路,将此类常量中的每一个都视为对某类资源或特性的上限,且标准要求这些上限都必须拥有一个确定的最小值,这种命名的用意也就不言自明了。
  • 在某些情况下,会为某个限制提供最大值,并且在对这些值的命名中包含字符串_MIN。对于这些常量,道理正好反过来;它们代表了对某些资源的下限,按照标准规定,在符合标准的实现中,该下限不能高于某个值。例如,限制 FLT_MIN(1E-37)为某个实现中所能表征的最小浮点数定义了最大值。所有满足标准的实现至少能够表征如此之小的浮点数

如果应用程序将本身限制在SUSv3对每个限制所要求的最小值之内,那么该程序对符号标准的所有实现都具有可移植性。然而,这一做法阻碍了应用程序去利用特定实现可提供的更高限制。因此,在特定系统上获得限制,通常更可取的方法是使用<limits.h>文件、sysconf()、pathconf()

每个限制都有一个名称,与上面最小值的名称相对应,但缺少了_POSIX_前缀。某个实现可以在<limits.h>文件中以该名称定义一个常量,用以表示该实现的相应限制。若已然定义,则该限制值总是至少等同于前述最大值(即XXX_MAX >= _POSIX_XXX_MAX

SUSv3 将其规定的限制归为 3 类:运行时恒定值、路径名变量值和运行时可增加值。

运行时恒定值(可能不确定)

所谓运行时恒定值是指某一限制,若已然在<limits.h>文件中定义,则对于实现而言固定不变。然而该值可能是不确定的(因为该值可能依赖于可用的内存空间),因而在<limits.h>文件中会忽略对其定义。在这种情况下(即使在<limits.h>文件中已然定义了该限制),应用程序可以使用 sysconf()来获取运行时的值

MQ_PRIO_MAX 限制就是运行时恒定值的例子之一。针对 POSIX 消息队列中的消息,存在着优先级方面的限制。SUSv3 定义了值为 32 的常量_POSIX_MQ_ PRIO_MAX,将其作为符合规范的实现为该限制所必须提供的最小值。这意味着,所有符合规范的实现,其对消息优先级的支持至少应为从 0~31。一个 UNIX 实现可以为此限制设定更高值,并将该值在<limits.h>文件中以常量 MQ_PRIO_MAX加以定义。例如,Linux 就将
MQ_PRIO_MAX 的值定义为 32768。也可以通过下列调用在运行时获取该值:

    int lim  = sysconf(_SC_MQ_PRIO_MAX);

路径名变量值

所谓路径名变量值是指与路径名(文件、目录、终端等)相关的限制,每个限制可能是相对于某个系统实现的常量,也可能随文件系统的不同而不同。在限制可能因路径名而发生变化的情况下,应用程序可以使用pathconf()或者fpathconf()来换取该值

NAME_MAX 限制是路径名变量值的例子之一。此限制定义了在一个特定文件系统中件名的最大长度SUSv3 定义了值为 14 (老版本的 System V 文件系统限制)的常量_POSIX_NAME_MAX,作为系统实现必须支持的最小限制值。系统实现可以定义一个高于此值的 NAME_MAX 限制,并/或向应用开放如下形式的调用,以获取特定文件系统的相关信息:

int lim = pathconf("directory_path", _PC_NAME_MAX);

运行时可增加值

运行时可增加值是指某一限制,相对于特定实现其值固定,且运行此实现的所有系统至少都应支持这一最小值。然而,特定系统在运行时可能会增加该值,应用程序可以使用 sysconf()来获得系统所支持的实际值。

运行时可增加值的例子之一是 NGROUPS_MAX,该限制定义了一进程可同时从属的辅助组 ID的最大数量。SUSv3 定义了相应的最小值_POSIX_NGROUPS_MAX,其值为8。应用可在运行时通过调用sysconf(_SC_NGROUPS_MAX)来获取此限制值。

对选定 SUSv3 限制的总结

下表列举了由 SUSv3 所定义的部分限制

  • 第一列给出了限制的名称,可将其作为常量定义于<limits.h>文件中,用于表示特定实现下的限制
  • 第二列是 SUSv3 为这些限制所定义的最小值(也定义于<limits.h>文件中)。
    • 在大多数情况下,会将每个限制的最小值定义为冠以字符串_POSIX_的常量。
    • 例如,常量_POSIX_RTSIG_MAX(其值为 8)为 SUSv3 实现对相应 RTSIG_MAX 常量的最低要求
  • 第三列列出了为在运行期间获取实现的限制,调用 sysconf()或 pathconf()时应输入入参的常量名
    • 冠以_SC_的常量用于 sysconf(),
    • 冠以_PC_的常量用于 pathconf()和 fpathconf()
限制名称(<limits.h>)最小值(sysconf() / pathconf()入参 name 名)描 述
ARG_MAX4096_SC_ARG_MAX提供给 exec()的参数(argv)与环境变量(environ)所占存储空间之和的最大字节数
nonenone_SC_CLK_TCK为 times()提供的度量单位
LOGIN_NAME_ MAX9_SC_LOGIN_NAME_MAX登录名的最大长度(含终止空字符)
OPEN_MAX20_SC_OPEN_MAX进程同时可打开的文件描述符的最大数量,比可用文件描述符的最大数量多 1 个
NGROUPS_MAX8_SC_NGROUPS_MAX进程所属辅助组 ID 数量的最大值
none1_SC_PAGESIZE一个虚拟内存页的大小 (_SC_PAGE_SIZE 与其同义)
RTSIG_MAX8_SC_RTSIG_MAX单一实时信号的最大数量
SIGQUEUE_MAX32_SC_SIGQUEUE_MAX排队实时信号的最大数量
STREAM_MAX8_SC_STREAM_MAX同时可打开的 stdio 流的最大数量
NAME_MAX14_PC_NAME_MAX排除终止空字符外,文件名称可达的最大字节长度
PATH_MAX256_PC_PATH_MAX路径名称可达的最大字节长度,含尾部空字符
PIPE_BUF512_PC_PIPE_BUF一次性(原子操作)写入管道或 FIFO中的最大字节数
  • getdtablesize()函数是确定进程文件描述符(OPEN_MAX)限制的备选方法,已遭弃用,该函数曾一度为 SUSv2 所定义(标记为 LEGACY),但 SUSv3 将其剔除
  • getpagesize()函数是确定系统页大小(_SC_PAGESIZE)的备选方法,已然废弃。该函数一度曾为 SUSv2 所定义(标记为 LEGACY),但 SUSv3 将其剔除。
  • 定义于<stdio.h>文件中的常量 FOPEN_MAX,等同于常量 STREAM_MAX
  • NAME_MAX 不包含终止空字符,而 PATH_MAX 则包括。POSIX.1 标准在定义PATH_MAX 时,对于是否包含终止空字符始终含糊不清,而上述差异则恰好弥补了这一缺陷。定义 PATH_MAX 中包含终止符也意味着,为路径名称分配了 PATH_MAX个字节的应用程序依然符合标准。

查询

从 shell 中获取限制和选项:getconf

在 shell 中,可以使用 getconf 命令获取特定 UNIX 系统中已然实现的限制和选项。该命令的格式一般如下

% getconf variable-name [pathname]

看个例子:

$ getconf NAME_MAX /boot
255
$ getconf ARG_MAX
2097152

在运行时获取系统限制(和选项)

NAME
       sysconf - 在运行时获取配置信息 

SYNOPSIS
       #include <unistd.h>

       long sysconf(int name);

DESCRIPTION
	    ...... 

若无法确定某一限制,则 sysconf()返回−1。若调用 sysconf()函数时发生错误,也会返回−1。(唯一指定的错误是 EINVAL,表示 name 无效。)为区别上述两种情况,必须在调用函数前将 errno 设置为 0,如果调用返回−1,且调用后 errno 值不为 0,那么调用 sysconf()函数时发生了错误

#include <stdio.h>
#include <stdlib.h>
#include <zconf.h>
#include <bits/errno.h>
#include <errno.h>


static void             /* Print 'msg' plus sysconf() value for 'name' */
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 */
            perror("sysconf");
            exit(EXIT_FAILURE);
        }
    }
}
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_RTSIG_MAX:      ", _SC_RTSIG_MAX);
    exit(EXIT_SUCCESS);
}

USv3 要求,针对特定限制,调用 sysconf()所获取的值在调用进程的生命周期内应保持不变。例如,就可以这样认定:针对_SC_PAGESIZE 限制的返回值在进程运行期间不会改变。

  • 有一些例外:进程能够使用 setrlimit())修改进程的各种资源限制,这会波及由 sysconf()所报告的限制值:
    • RLIMIT_NOFILE,该限制确定进程能够打开的文件数量(_SC_OPEN_MAX);
    • RLIMIT_NPROC(实际并未纳入SUSv3 中 ) ,即允许进程基于每用户所创建的子进程限额( _SC_CHILD_MAX );
    • RLIMIT_STACK,始于 Linux 2.6.23 版本,该限制确定了进程的命令行参数和环境变量所占存储空间的限额(_SC_ARG_MAX,具体参见 execve(2)手册页)。

运行时获取与文件相关的限制(和选项)

pathconf()和 fpathconf()函数允许应用程序在运行时获取文件相关的限制值

NAME
       fpathconf, pathconf - 运行时获取与文件相关的限制

SYNOPSIS
       #include <unistd.h>

       long fpathconf(int fd, int name);
       long pathconf(char *path, int name);

pathconf()和 fpathconf()之间唯一的区别在于对文件或目录的指定方式。pathconf()采用路径名方式来指定,而 fpathconf()则使用(之前已经打开的)文件描述符

有别于 sysconf()函数,SUSv3 并不要求 pathconf()和 fpathconf()的返回值在进程的生命周期内保持恒定。这是因为,例如,在进程运行期间,可能会卸载一个文件系统,然后再以不同特性重新装载该文件系统

:pathconf()函数中,选定_PC_系列命名的详细说明

常量说明
_PC_NAME_MAX针对目录,返回该目录下文件命名的最大长度,对于其他文件类型,则未作规定
_PC_PATH_MAX对于目录,返回该目录中相对路径名的最大长度,对于其他文件类型,则未作规定
_PC_PIPE_BUF对于 FIFO 或者管道,返回一个应用于引用文件的值。对于目录,返回的值应用于在该目录下创建的一 FIFO。对于其他文件类型,则未作规定
#include <stdio.h>
#include <stdlib.h>
#include <zconf.h>
#include <bits/errno.h>
#include <errno.h>


static void             /* Print 'msg' plus value of fpathconf(fd, name) */
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 {
            fprintf(stderr, "fpathconf %s", msg);
            exit(EXIT_FAILURE);
        }

    }
}
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);
}

在这里插入图片描述

不确定的限制

有时,系统实现并未将一些系统限制定义为限制常量(比如:PATH_MAX),并且 sysconf()或 pathconf()在返回相应限制(比如_PC_PATH_MAX)时会将其归为不确定。对此,可采用如下策略之一

  • 当编写一个可在多个 UNIX 实现间移植的应用程序时,可选择使用 SUSv3 所规定的最低限制值。此类以_POSIX_*_MAX 形式命名的常量。此方法有时并不可行,因为该限制之低已经超乎实际情况,正如_POSIX_PATH_MAX 和_POSIX_OPEN_MAX 的情况
  • 在某些情况下,切实可行的解决方法是省去对限制的检查,取而代之以执行相关的系统调用或库函数。如果调用失败,且 errno 表明出错是由于超出了系统限制时,那么可以根据需要调整应用的行为,并再次尝试调用。例如,对于可发送给进程的实时信号队列长度,大多数 UNIX实现都进行了强制限制。一旦达到限额,试图进一步发送信号(使用 sigqueue()函数)将以失败告终,且会将错误号 errorno 置为 EAGAIN。这时,发送进程只需简单重试即可,或许是在等待片刻之后。与之相类似,试图打开一个文件时,若文件命名过长,将会产生 ENAMETOOLONG 错误,之后应用程序可以一个更加简短的命名进行重试。
  • 自行编写程序或函数,以推断或估算限制值。无论在哪一种情况下,都会调用相关的sysconf()或pathconf(),若限制不确定,则函数将返回一合理估值。虽然有欠完美,但这种解决方案往往在实践中是可行的
  • 也可以利用诸如 GNU Autoconf 之类的扩展工具,该工具能够确定各种系统特性及限制存在与否、如何设置。Autoconf 程序可基于其收集到的信息而生成头文件,并能在C 程序中将其包含在内

系统选项

除了对各种系统资源的限制加以规范外,SUSv3 还规定了 UNIX 实现可支持的各种选项。这包括对诸如实时信号、POSIX 共享内存、任务控制以及 POSIX 线程之类功能的支持。除少数特例外,并未要求实现支持这些选项。相反,对于实现在编译及运行时是否支持某一特定特性,SUSv3 允许实现自行给出建议。

通过在< unistd.h>文件中定义相应常量,实现能够在编译时通告其对特定 SUSv3 选项的支持。此类常量的命名均会冠以前缀(比如_POSIX_ 或者_XOPEN_),以标识其源于何种标准。

各个选项常量,一经定义,其值必为下列之一

  • 值为−1,表示实现不支持该选项。此时,系统实现无需定义与该选项有关的头文件、数据类型和函数接口。可以使用#if 预处理程序指令,通过条件编译来处理这种情况
  • 值为 0,表示实现可能支持该选项。应用程序必须在运行时检查该选项是否获得支持
  • 值大于 0,则表示实现支持该选项。实现定义了与该选项有关的所有头文件、数据类型和函数接口,且其行为也符合规范要求。

当定义常量为 0 时,应用程序可使用 sysconf()和 pathconf()(或 f pathconf())在运行时检查选项是否获得实现的支持。传递给这些函数的入参 name,其命名通常与编译时常量形式相同,只是前缀为_SC__PC_所取代。系统实现必须至少提供头文件、常量以及实施运行时检查所必要的函数接口

下表列举了了 SUSv3 所规定的一些选项。

  • 某些选项一度确实曾是可选项,现在是必选项(编译时其常量值总应大于 0)。“备注”栏会以字符“+”标识此类选项
  • 对于某些选项,其编译时常量必须为-1 以外的值。换言之,要么必须支持该选项,要么必须有方法可以检查出系统在运行时是否支持该选项。这些选项的“备注”栏以字符“*”标识这些选项。
(sysconf() / pathconf()入参 name 名)描 述
_POSIX_ASYNCHRONOUS_IO (_SC_ASYNCHRONOUS_IO)异步 I/O
_POSIX_CHOWN_RESTRICTED (_PC_CHOWN_RESTRICTED)仅有特权级进程能够使用 chown() 和 fchown()函数将文件的用户 ID 和组 ID 修改为任意值
_POSIX_JOB_CONTROL (_SC_JOB_CONTROL)作业控制
_POSIX_MESSAGE_PASSING (_SC_MESSAGE_PASSING)POSIX 消息队列
_POSIX_PRIORITY_SCHEDULING (_SC_PRIORITY_SCHEDULING)进程调度
_POSIX_REALTIME_SIGNALS (_SC_REALTIME_SIGNALS)实时信号扩展
_POSIX_SAVED_IDS(none)进程拥有的保存 (saved)set-user-ID 和保存(saved)set-group-ID
_POSIX_SEMAPHORES (_SC_SEMAPHORES)POSIX 信号
_POSIX_SHARED_MEMORY_OBJECTS (_SC_SHARED_MEMORY_OBJECTS)POSIX 共享内存对象
_POSIX_THREADS (_SC_THREADS)POSIX 线程
_XOPEN_UNIX (_SC_XOPEN_UNIX)支持 XSI 扩展功能

总结

  • 对于大多数限制,SUSv3 规定了所有实现所必须支持的最小值。
  • 此外,每个实现还能在编译时(通过定义于<limits.h>或<unistd.h>文件中的常量)和/或运行时(通过调用 sysconf()、pathconf()或 fpathconf()函数) 发布其特有的限制和选项。

此类技术同样可应用于找出实现所支持的SUSv3 选项。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值