《UNIXLinux程序设计教程》一1.8 系统能力限制

简介: 本节书摘来自华章出版社《UNIXLinux程序设计教程》一 书中的第1章,第1.8节,作者:赵克佳 沈志宇,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

1.8 系统能力限制

任何计算机系统,不论能力如何强大,由于硬件本身和系统软件实现策略的局限,在某些方面总会有一些限制。当我们编写应用程序时,往往不可避免地要涉及这些能力限制。例如,我们常常需要知道:浮点双精度数的字长是多少位?口令的有效长度是多少个字符?一个进程最多能同时打开多少个文件?文件名的长度是否有限制,如果有的话,限制值是多少?机器的软、硬时钟频率各为多少?等等。
这些限制值随着系统的变化而变化。以文件名为例,系统V UNIX在历史上曾经限制文件名长度至多为14个字符,BSD兼容的系统则允许255个字符,而SVR4则更为灵活地允许对所创建的每一个文件系统指明它是系统V文件系统还是BSD文件系统,并随文件系统不同而有不同的限制。
另一方面,在同一个系统中这些限制值有一些可以静态确定,而另一些则是动态变化的,因此必须在运行时调用专门的函数来确定它们。SVR4中的文件名长度就需动态确定,因为文件名的长度取决于文件所在的文件系统。例如,根文件系统中的文件名可能有14个字符的长度限制,而在另外的文件系统中的文件名则可能允许255个字符。
在UNIX标准形成之前,应用程序涉及这些限制值时通常以固定的数值来引用它们。当将程序从一个系统移植到另一个系统时,往往不得不根据系统限制值的变化修改它们并重新编译程序。不过,随着UNIX各种标准的出现,现在我们可以采用可移植性更好的方法来确定这些限制值。
那么,在UNIX系统中究竟有哪些能力限制值,我们如何才能以可移植方式来获取它们呢?这就是本节要讨论的内容。

1.8.1 一般能力限制值

UNIX系统中每一个限制值均有一个宏名,POSIX将它们分为如下7类:
1)运行时的不变值,如exec()的参数argv和environ一起允许的最大总字节长度(ARG_MAX)、可由atexit()注册的函数最大个数(ATEXIT_MAX)、口令的最大有效字符个数(PASS_MAX)、进程同时能够打开的最大文件个数(OPEN_MAX)等。
2)运行时可增加值,只有一个,即进程能够同时拥有的附加组(4.3节)的最大个数(NGROUPS_MAX)。
3)路径名可变值,如文件名的最大字节数(NAME_MAX)、路径名的最大字节数(PATH_MAX)、终端加工输入队列(9.2.4节)的最大字节数(MAX_CANON)、终端输入队列(9.2.4节)有效空间的最大字节数(MAX_INPUT)、写管道(11.1节)时原子写入的最大字节数(PIPE_BUF)等。
4)数值限制值,主要是与数的表示有关的限制值,如int类型的最大和最小值(INT_MAX、 INT_MIN)、字长(WORD_BIT)、float类型的精度(FLT_DI)、double类型的最大和最小值(DBL_MAX、DBL_MIN)等。
5)其他不变值,如最大消息数(NL_MSGMAX)、默认的进程优先数(NZERO)、由tmpnam()生成的唯一路径名的最大个数(TMP_MAX)等。
6)最小值,如每个实际用户ID最多同时拥有的进程个数(POSIX_CHILD_MAX)、注册名的最大长度(_POSIX_LOGIN_NAME_MAX)、终端加工输入队列的最大字节数(_POSIX_ MAX_CANON)、文件名的最大字节数(POSIX_NAME_MAX)、路径名的最大字节数(POSIX_ PATH_MAX)、原子写入管道的最大字节数(POSIX_PIPE_BUF)等。
7)最大值,主要是与shell命令、线程以及实时控制有关的限制。
在这7类限制值中,前五类是系统的实际限制值,后两类是UNIX标准要求具体实现对这些限制值必须满足的最大和最小值。
最大、最小值保证了不超过这两个限制值的程序可以安全地运行于任何系统。例如,当我们为一个文件名分配存储空间时,如果限制文件名不超过_POSIX_NAME_MAX给定的值(14个字符),则这个程序可以运行于任何系统而不会出问题,因为所有的系统都至少必须允许14个字符长度的文件名,虽然其中有一些允许更长的文件名。
一般而言,可移植应用涉及的限制值最好不要大于标准的最小值,但不幸的是,这些最小值中有一些对于实际应用太小。例如,当前大多数UNIX系统允许一个进程同时打开的文件数远远大于16。同样,对_POSIX_PATH_MAX的限制也略嫌小,路径名可能超过此限制。这就意味着我们不能静态地使用这两个常数_POSIX_OPEN_MAX和_POSIX_PATH_MAX作为数组大小,在这种情况下便只有使用系统提供的实际限制值。
对于一给定的操作系统,实际限制值中有一些是固定的常数,有一些是在运行时或在重建内核时由系统管理员来配置的可变值。此外,有一些限制值可能是不确定的,它们的定义取决于某些条件。因此,从应用的角度可以将这些限制值分为如下3类:
静态限制值(第4和第5类)。
与文件或目录无关的运行时的限制值(第1和第2类)。
与文件或目录有关的运行时的限制值(第3类)。
静态限制值是编译时的限制,它们总是定义在头文件中。例如,关于数据类型的限制值多数是C标准的限制,相对于任何一个特定的系统,它们都是固定不变的且可以作为 #if预处理指导命令中的常数表达式。
对于运行时的限制值,它们的宏名仅当系统有固定不变的限制值时才定义在中,如果在中没有定义,则必须在运行时调用系统提供的sysconf()、pathconf()或fpathconf()三个函数之一来确定它们的运行值。对于那些可能是不确定的限制值,当没有定义时,这三个函数会返回–1。

1.8.2 系统和文件特征选项

除了上一小节介绍的限制值之外,UNIX系统还有另外一些关于系统能力的选项,它们表示的不是资源的限制值而是系统是否具有某种能力或提供某种功能,这种能力是UNIX标准允许的可选特征。
UNIX标准涉及的特征选项有许多,对于每一种选项相应地有一个选项宏名字。我们这里仅介绍它们之中的几个,其他的选项可查阅<unsdt.h>头文件或sysconf()、pathconf()和fpathconf()函数的联机说明。

  1. 系统选项

这些宏仅当包含了时才有定义。如果它们定义在中,则系统支持该选项,否则,系统可能支持也可能不支持该选项,此时要用sysconf()来查询。
_POSIX_JOB_CONTROL:如果定义了此符号,系统支持作业控制(6.10节),否则,一个会晤期内(6.8节)的所有进程均属于单个进程组(6.7节)。
_POSIX_THREADS:如果定义了此符号,系统支持POSIX线程。
_POSIX_SAVED_IDS:如果定义了此符号,表示每一个进程有一个保留的调整用户ID和保留的调整组ID(5.9节)。它指出系统在执行设置了调整用户ID或调整组ID位的可执行程序文件之前保存进程的有效用户ID和有效组ID(5.9节),并且允许显式地改变有效用户ID或有效组ID回到这些保存值。若未定义此宏,如果非特权进程改变它的有效用户ID或有效组ID至实际用户ID或实际组ID,则它不能再改变回去。
_POSIX_VERSION:此常数给出实现遵循的POSIX标准版本号。它的定义是一个形如L的整数值,其中'**'是所采纳的POSIX标准被批准时的年月,前四位代表年,后两位代表月。例如,对于遵循POSIX.1-2008标准的实现,该值为整数200809L。在任何POSIX系统,_POSIX_VERSION总是定义的,它定义在unistd.h中。
_XOPEN _VERSION:指出XPG版本。如果系统与老的X/OPEN兼容,其值为4;如果与统一规范版本3(UNIX04)一致,其值为600;如果与统一规范版本4(UNIX V7)一致,其值为700。

  1. 文件选项

POSIX对与文件操作有关的系统调用定义了一些特殊选项,有些系统支持这些选项,而另一些系统则不支持。下面列出的宏可用来确定系统是否支持某个特定的选项。如果在中定义了下面列出的宏名,则它的值指出是否支持对应的选项,值为–1表示不支持,其他值则表示支持。如果在中没有定义相应的宏名,则意味着选项对有些文件有效,对另一些文件无效。为了查询一具体的文件是否有这些选项,要调用函数pathconf()或fpathconf()。不管表中列出的宏名是否定义在中,都可利用函数pathconf()或fpathconf()查询与特定文件的关系。
_POSIX_CHOWN_RESTRICTED:如果定义该选项,函数chown()的使用仅限于具有适当特权的进程,并且只能改变文件的组ID到有效组ID,或改变到它的任一个附加组ID(4.3节)。
_POSIX_NO_TRUNC:如果定义该选项,路径名分量(2.1节)比NAME_MAX长将产生ENAMETOOLONG错误;否则过长的路径名分量被截断而不报错。
_POSIX_VDISABLE:此宏名仅对终端设备文件名才有意义。如果该选项有定义,则可以单独去掉对特殊控制字符(9.4.5节)的处理。

1.8.3 sysconf()、pathconf()和fpathconf()函数

对于运行时的限制值和选项,我们已经知道可以用sysconf()、pathconf()和fpathconf()三个函数之一来确定它们的值。具体地,sysconf()用于获得与文件或目录无关的限制值,以及系统特征选项;pathconf()和fpathconf()用于获得与文件或目录有关的限制值。这三个函数的原型为:

#include <unistd.h>

long int sysconf(int parameter);
long int pathconf(const char *pathname,int parameter);
long int fpathconf(int filedes,int parameter);

sysconf()用于确定当前的系统变量之值(即限制值和特征选项),其中参数parameter指出要询问的是哪个系统变量,它应当是头文件中定义的以'_SC_'开头的符号常数之一,表1-3列出了其中的一部分。
screenshot

该函数调用成功将返回所询问参数之值,这个值在调用进程的生存期内不会改变。如果parameter参数不合法,sysconf()返回–1并设置errno指出错误。如果系统不支持所询问的参数或者所询问的限制值不确定,sysconf ()返回–1但不设置errno。
因为–1既是正常返回值也是异常返回值,因此,希望检查错误情形的应用在调用它们之前应当先设置errno为0。如果返回值为–1,则通过检查errno是否为非0来判别错误。
另外,建议首先用特征测试宏检查你感兴趣的宏名是否有定义并且仅当它没有定义时才调用sysconf(),这样做可使程序更有效率。例如,下面的例子说明了如何测试系统是否支持作业控制。

int have_job_control(void)
{
#ifdef _POSIX_JOB_CONTROL
   return 1;
#else
   int value;
   errno = 0;
   value = sysconf(_SC_JOB_CONTROL);
   if (value < 0)
      if (errno == 0)
         value = 0;
      else
         fatal(strerror(errno));
   return value;
#endif
}

函数pathconf()和fpathconf()作用相同,它们都用于查询与文件系统限制和选项有关的值,不同的只是pathconf()作用于文件名pathname,而fpathconf()作用于已打开的文件描述字 filedes。参数parameter必须是定义在头文件中以'_PC_'开头的符号常数之一,表1-4列出了其中的一部分。
screenshot

这两个函数调用成功返回所查询参数之值。如果系统不施加限制或者当有错时,返回值为–1。在前一种情况下不设置errno,在后一种情况下设置errno指明错误原因。因此,同sysconf()一样,可靠调用这两个函数的方法是在调用它们之前将errno清零。
对于不同的查询参数,这两个函数对参数pathname和filedes有不同的要求,例如,对于_PC_MAX_CANON、_PC_ MAX_INPUT和_PC_ VDISABLE,要求所引用的文件必须是一个终端文件;对于_PC_PATH_MAX,要求所引用的文件必须是一个目录;对于_PC_PIPE_BUF,要求所引用的文件必须是管道、FIFO或者目录,并且当pathname指向一个FIFO文件,或filedes指向一个管道或FIFO文件时,返回值适用于这个文件本身,当pathname或filedes指向一个目录时,返回值适用于此目录内已存在的或能创建的FIFO文件,具体的细节可查阅联机说明。
至此,我们已经讲述了UNIX系统的常用限制值和有关的选项,并且知道了它们要么静态地定义在头文件中,要么必须通过这一节介绍的函数询问它们的值。如果它们在头文件中有定义,通过包含这个头文件并引用有关的系统变量,可以直接引用它们;如果它们没有定义在头文件中,就必须调用函数来确定它们。但问题并不完全这样简单,它们还可能是不确定的,不确定通常意味着没有限制。因此,在编写可移植程序时,应当充分注意到这种情况。
例1-3 有许多程序需要为路径名分配存储空间,典型的做法是在编译时通过指定一个固定的常数来确定空间大小,比如256、512甚至1024。然而,一个固定的常数往往不能适合所有的系统,为了程序的可移植性,需要利用限制值PATH_MAX。程序1-3就是这样一个例子。其中函数get_current_dir()的功能是获得进程当前工作目录的绝对路径名。为了分配存放路径名的缓冲区,我们利用PATH_MAX来确定它的大小。如果PATH_MAX是定义在中的常数,或者调用pathconf()能够得到PATH_MAX的值,则缓冲区的大小就是PATH_MAX+1,因为PATH_MAX不包括结尾的空字符,实际缓冲区的大小要多一个字节。如果pathconf()指出PATH_MAX是不确定的,我们则必须试探性地分配足够的空间来存放路径名。对于这种限制值不确定的情形,正确的做法取决于所分配存储空间的用法。在这个例子中,分配的缓冲区用于调用getcwd()(4.9.1节),这个函数返回当前工作目录的绝对路径名于指定的缓冲区中并返回缓冲区指针。如果指定的缓冲区太小,getcwd()将返回NULL指针并置errno为ERANGE。这时我们扩大缓冲区一倍后重复这一过程直至调用getcwd()成功。
程序1-3同时也示例了一种既具有可移植性又不失高效的编程方法。如果系统在中定义了PATH_MAX,这个程序将在编译时直接使用其值,从而省去了调用pathconf()和反复进行存储分配的开销。反之,如果不考虑系统可能给出PATH_MAX之值的情形而简单地采用最后一种手段,尽管它具有可移植性,但对大多数系统而言,该程序是低效的,因为多数系统都定义了PATH_MAX。

程序1-3 get_current_dir()之例
#include "ch01.h"
char *get_current_dir()
{
   char *buffer;
   char *value;
   int size = 0;
   /*确定当前工作目录路径名的最大长度于size,当PATH_MAX 不确定时,size为-1*/
#ifdef PATH_MAX
   size = PATH_MAX;
#else
   errno = 0;
   if((size = pathconf(“./”,_PC_PATH_MAX)) < 0)
      if(errno != 0){
         printf("pathconf error for _PC_PATH_MAX\n");
         exit(-1);
     }
#endif
   if(size > 0){ /* PATH_MAX有定义,可以保证分配的空间足以存放路径名*/
      buffer = (char *)malloc(size+1);
      value = getcwd(buffer, size);
   }else{  /* PATH_MAX没有定义,必须试探性地分配足够的空间来存放路径名*/
      size = _POSIX_PATH_MAX;
      buffer = (char *)malloc(size);
      while (1) {
         value = getcwd(buffer, size);
         if(value == 0 && errno ==ERANGE){ /* buffer太小,重新申请更大的空间 */
            size *= 2;
            free (buffer);
            buffer = (char *)malloc(size);
         }
      } 
   }
   return buffer;
}
相关文章
|
2月前
|
存储 Shell Linux
【Shell 命令集合 网络通讯 】Linux 显示Unix-to-Unix Copy (UUCP) 系统的状态信息 uustat命令 使用指南
【Shell 命令集合 网络通讯 】Linux 显示Unix-to-Unix Copy (UUCP) 系统的状态信息 uustat命令 使用指南
28 0
|
5天前
|
Oracle 关系型数据库 Unix
SAP系统拷贝 UNIX + Oracle
SAP系统拷贝 UNIX + Oracle
|
5月前
|
Unix Linux Shell
在Unix/Linux系统中,文件和目录的权限管理
在Unix/Linux系统中,文件和目录的权限管理
36 3
|
Unix Linux Android开发
1.4 类UNIX系统是什么鬼?
上节《UNIX和linux的区别》中讲到了 UNIX 系统的历史,UNIX 是操作系统的开山鼻祖,是操作系统的发源地,后来的 Windows 和 Linux 都参考了 UNIX。
741 0
1.4 类UNIX系统是什么鬼?