UNIX环境高级编程 学习笔记 第二章 UNIX标准及实现

C语言的ANSI标准在1989年得到批准,此标准也被采纳为国际标准ISO/IEC 9899:1990。ANSI是美国国家标准学会(American National Standards Institute),它是国际标准化组织ISO(Intenational Organization for Standardization)中代表美国的成员。IEC是国际电子技术委员会(International Electrotechnical Commission)。

1999年,ISO C标准被更新。它由ISO/IEC的工作组WG14维护和开发,该标准意图提供C程序的可移植性。

ISO C库可分为24个区:
在这里插入图片描述
POSIX可移植操作系统接口(Portable Operating System Interface)是电气和电子工程师学会IEEE(Institute of Electrical and Electronics Engineers)制定的标准组,原指操作系统接口标准,后扩展到一些shell和实用程序的标准。它的目的是提升应用程序在各种UNIX系统环境间的可移植性。POSIX不限于UNIX和UNIX类操作系统,有些专用操作系统也声称其符合POSIX标准。

SUS单一UNIX规范(Single UNIX Specification)是POSIX.1标准的一个超集,它扩展了POSIX.1规范提供的功能。

POSIX.1中的X/Open系统接口XSI(X/Open System Interface)选项描述了可选的接口,也定义了遵循XSI的实现必须支持POSIX.1的哪些可选部分。只有遵循XSI的实现才能称为UNIX系统。

Single UNIX Specification第一版由工业社团X/Open发布。

FIPS是联邦信息处理标准(Federal Information Processing Standard),它是美国政府发布的,用于其计算机系统的采购。

UNIX的各种版本和变体起源于在PDP-11系统上运行的UNIX分时系统第六版和第七版(常称为v6和v7),演进出了以下分支:
1.AT&T(American Telephone & Telegraph,美国电话电报公司)分支,从此引出了系统III和系统V(被称为UNIX商用版本)。
2.加州大学伯克利分校分支,从此引出4.xBSD实现。
3.由AT&T贝尔实验室的计算科学研究中心不断开发的UNIX版本,从此引出UNIX分时系统第八版、第九版和终止于1990年的第十版。

SVR4(UNIX System V Release 4)是AT&T的UNIX系统实验室(UNIX System Laboratories,USL)的产品。

BSD是加州伯克利分校的计算机系统研究组(CSRG)研究开发和分发的。4.4BSD-Lite是CSRG计划开发的最后一个发行版。

FreeBSD是基于4.4BSD-Lite的操作系统。在加州大学伯克利分校CSRG决定终止其在UNIX操作系统的BSD版本的研发工作,在386BSD项目被长期忽视后,为了坚持BSD系列,形成了FreeBSD项目,由该项目产生的所有软件,包括其二进制代码和源代码,都是免费使用的。

Linux是一种提供类似于UNIX的丰富编程环境的操作系统,在GNU公用许可证的指导下,是免费使用的。

Mac OS X核心操作系统称为Darwin,Mac OS X 10.5的Intel部分已被验证为是UNIX系统。

Solaris是Sun公司(现为Oracle)开发的UNIX系统版本。

以上四种系统都提供UNIX编程环境,因为它们都在不同程度上符合UNIX系统。

幻数是直接使用的常数,应避免使用,因为要修改时,需要修改很多地方,建议使用枚举或全局变量代替。

UNIX系统中明确对某些值的限制可以提高UNIX环境下软件的可移植性,提供三种限制:
1.编译时限制(头文件)。
2.与文件或目录无关的运行时限制(sysconf函数)。
3.与文件或目录有关的运行时限制(pathconf和fpathconf函数)。

如果一个特定的运行时限制在一个给定系统上不改变,则可将其静态地定义在一个头文件中。

某些限制可能在一个系统上是固定的(可将其静态地定义在头文件中),但在某些系统上是变动(需要运行时调用函数获取)的。

ISO C定义的所有编译时限制都在头文件<limits.h>中,它们在一个给定的系统中不会改变:
在这里插入图片描述
如系统使用带符号char,则CHAR_MIN等于SCHAR_MIN,CHAR_MAX等于SCHAR_MAX;如使用无符号char,则CHAR_MIN等于0,CHAR_MAX等于UCHAR_MAX。

头文件float.h中对浮点数大小限制也有定义。

头文件stdio.h中定义的FOPEN_MAX保证可同时打开的标准I/O的最小个数,最小值为8。POSIX.1中的STREAM_MAX应与FOPEN_MAX具有相同值。

头文件stdio.h中定义的常量TMP_MAX是由函数tmpnam(用于产生一个唯一文件名)产生的唯一文件名的最大个数。

ISO C还定义了FILENAME_MAX,但最好使用更好的POSIX提供的NAME_MAX和PATH_MAX。
在这里插入图片描述
limits.h中定义了POSIX.1各参数最大值的最小可接受值,下表中的第二列翻译有误,应改为最大值的最小可接受值:
在这里插入图片描述
但以上限制相对于具体实现的限制大小都太小了,不应该使用以上限制的最小值作为限制,因为实际运行时可能会超出该限制。以上25个最大值都有一个相关实现值,它的名字是去掉_POSIX_,但不确保它们都定义在limits.h中,因为一个给定进程的实际值可能依赖于系统的存储总量。POSIX.1提供了3个运行时函数sysconf、pathconf、fpathconf,它们在运行时得到实际值,但有些值由POSIX.1定义为可能不确定的(逻辑上无上限的),如Solaris中可运行的atexit函数个数仅受系统存储总量的限制。

limits.h中定义的POSIX.1运行时不变值:
在这里插入图片描述
某些限制值在编译时可用,另外一些要运行时才能确定,运行时限制可调用以下函数获得,其中第三个函数返回类型为long,书上的log是印刷错误:
在这里插入图片描述
后两个函数一个用路径名作参数,一个用文件描述符作参数。

sysconf函数所用的name参数如下:
在这里插入图片描述
如上,以_SC_开始的常量用作标识运行时限制的sysconf的name参数。

函数pathconf和fpathconf的name参数:
在这里插入图片描述
如上,以_PC_开始的常量用作标识运行时限制的pathconf和fpathconf的参数。

这三个函数的返回值:

  1. 如输入的name不是合适的常量,这三个函数返回-1,并把errno置为EINVAL。
  2. 输入的某些name会提示该值是不确定的,不确定的值通过返回-1来体现,而不改变errno的值。
  3. _SC_CLK_TCK返回值是每秒时钟滴答数,可用于times函数的返回值(进程运行时间)。

函数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引用的文件必须是目录,当指定目录是工作目录时,返回值是相对路径名的最大字节数(包括空字符)。
5._PC_PIPE_BUF引用的文件必须是管道、FIFO或目录。如是前两种情况,则返回值是对所引用管道或FIFO的限制值;如是目录,则返回值是在该目录中创建的任一FIFO的限制。
6._PC_SYMLINK_MAX引用文件必须是目录,返回值是该目录中符号链接中可包含字符串的最大长度。

打印这些限制:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.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) {
        printf("usage: a.out <dirname>");
        exit(1);
    }

    #ifdef ARG_MAX    // 这些宏定义在limits.h中
        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("ARG_MAX =", 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);    // fputs来自头文件stdio.h,将mesg放入stdout
                            // stdout来自头文件stdio.h,它是其中定义的宏,表示标准输出对应的文件
    errno = 0;
    if ((val = sysconf(name)) < 0) {    
        if (errno == EINVAL) {
            fputs(" (not supported)\n", stdout);
        } else {
            printf("sysconf error");    // 返回不确定的值
        }
    } 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 {
                printf("pathconf error, path = %s", path);    // 输入不支持的name之外的错误
            }
        } else {
            fputs(" (no limit)\n", stdout);    // 当pathconf返回值为-1且errno为0,返回不确定的值
        }
    } else {
        printf(" %ld\n", val);
    }
}

以上代码输出:
在这里插入图片描述
以上代码仅输出了ARG_MAX(exec函数的参数最大长度)和MAX_CANON(终端规范输入队列的最大字节数)情况,更多情况写起来比较繁琐且重复量大,可以使用awk来完成。

Linux中的pathconf和fpathconf都是C库函数,这些函数的返回值依赖于底层文件系统类型,因此如果文件系统不被C库熟知的话,函数返回的是一个猜测值。

运行时限制没有定义在limits.h中,不能编译时确定,此时,它们的值可能是不确定的,也可能是未定义的:
1.如路径名,有的程序编译时就分配了路径名存储区,不同的程序使用各种不同的幻数(字面值)或标准IO常量BUFSIZE用作数组长度。4.3BSD中,头文件<sys/param.h>中的常量MAXPATHLEN才是正确的值,但很多4.3BSD应用程序并未使用它。POSIX.1使用PATH_MAX,它定义在头文件limits.h中,如PATH_MAX未定义,可使用pathconf("/", _PC_PATH_MAX)获取基于根目录的相对路径名的最大长度,最后结果加根目录长度(即1)就得出路径最大长度了。
2.最大打开文件数,守护进程常见功能是关闭所有打开文件:

#include <sys/param.h>

for (i = 0; i < NOFILE; ++i) {
    close(i);
}

NOFILE定义在头文件sys/param.h中,而有些程序使用的是头文件stdio.h中提供的作为上限的常量_NFILE,而某些程序直接将其上限值硬编码为20,这些方法都不可移植。还可能这样编写:

#include <unistd.h>

for (i = 0; i < sysconf(_SC_OPEN_MAX); ++i) {
    close(i);
}

但sysconf在参数未定义时会返回-1,导致循环不运行,最好的选择是关闭所有文件描述符至某个上限(如256)。我们可以一直调用close,直到得到一个出错返回值,但从close(EBADF)出错返回并不区分无效描述符和没有打开的描述符,如使用此方法,如描述符9未打开,而10打开了,则会关闭到9,而10不会关闭。获得文件描述符个数:

#include <errno.h>
#include <limits.h>

#ifdef OPEN_MAX
static long openmax = OPEN_MAX;
#else
static long openmax = 0;
#endif

#define OPEN_MAX_GUESS 256    // 猜测值

long open_max() {
    if (openmax == 0) {
        errno = 0;    // 先格式化为0
        if ((openmax = sysconf(_SC_OPEN_MAX)) < 0) {    // 如输入常量不合适(会将errno置为EINVAL)或该值不确定
            if (errno == 0) {    // 该值不确定时
                openmax = OPEN_MAX_GUESS;
            } else {
                printf("sysconf error for _SC_OPEN_MAX");
                exit(1);
            }
        }
    }

    return openmax;
}

获取打开的描述符最大值的函数的某些实现直接返回LONG_MAX作为限制值,这与不限制其值效果是相同的,Linux的ATEXIT_MAX(atexit函数可登记的最大函数数量)限制就是这样,使得函数运行的返回结果十分糟糕。

我们可以在Bourne-again shell中使用内建命令ulimit改变进程可同时打开的文件数,若想设置为无限制,需要root特权,设为无穷大之后sysconf会将LONG_MAX作为OPEN_MAX的限制值报告,如将此值作为要关闭的文件描述符上限,那么为了试图关闭2147483647个文件描述符,会浪费大量时间,其中绝大多数文件描述符并未得到使用。

支持Single UNIX Specification的XSI扩展的系统上提供了getrlimit函数,它可以传入参数RLIMIT_NOFILE来返回一个进程可同时打开的描述符个数,就可以避免以上找能打开的最大文件描述符数量时遇到的问题。

OPEN_MAX被POSIX系统称为运行时不变值,意味着在一个进程生命周期中不应发生变化。但在支持XSI扩展的系统上,可以调用setrlimit函数更改一个运行进程的OPEN_MAX值,在这种系统上,以上返回可打开的文件描述符的最大数量的函数中,每次调用时都要调用一次sysconf更新保存最大打开数量的static值,而不是像以上函数中那样只会在第一次调用函数时调用sysconf。

某些程序会用到系统的某些选项组,可移植的程序需要判断当前系统的选项组是否支持其实现,POSIX.1提供了处理选项的方法:
1.编译时选项定义在unistd.h中。
2.与文件或目录无关的运行时选项用sysconf函数判断。
3.与文件或目录有关的运行时选项用pathconf和fpathconf函数判断。

选项包含了下图2-5和图2-19、2-18中的符号:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果上图中的符号常量未定义,就要使用sysconf、pathconf、fpathconf函数判断是否支持该选项,使用函数判断时,需要将上图中的name参数列中的_POSIX前缀替换为_SC(用于sysconf函数)或_PC(用于pathconf和fpathconf函数)。而对于以_XOPEN为前缀的常量,需要在其前放置_SC或_PC。

关于sysconf、pathconf、fpathconf函数如何处理选项,有以下内容值得注意:
1._SC_VERSION的返回值表示标准发布的年(4位数表示)、月(2位数表示)。该值可能是198808L等值。与SUSv3(POSIX.1 2001年版)相关联的值是200112L,与SUSv4(POSIX.1 2008年版)相关联的值为200809L。
2._SC_XOPEN_VERSION的返回值表示系统支持的XSI版本,与SUSv3相关联的值是600,与SUSv4相关联的值是700。
3._SC_JOB_CONTROL、_SC_SAVED_IDS、_PC_VDISABLE的值不再表示可选功能,从SUSv3起,这些符号仍被保留以向后兼容。
4.如果对指定的pathname或fd不再支持此功能,则_PC_CHOWN_RESTRICTED(使用chown是否是受限的)和_PC_NO_TRUNC(路径名大于NAME_MAX时是否出错)返回-1,而errno不变。在所有符合POSIX的系统中,返回值将大于0,表示此选项被支持。
5._PC_CHOWN_RESTRICT引用的文件必须是一个文件或目录,如果是目录,则返回值指明该选项是否可用于目录中的各个文件。
6._PC_NO_TRUNC和_PC_2_SYMLINKS(目录中是否支持符号链接)引用的文件必须是一个目录。
7._PC_NO_TRUNC的返回值可用于目录中的各个文件名。
8._PC_VDISABLE(如定义,可用此值禁用终端特殊字符)引用的文件必须是一个终端文件。
9._PC_ASYNC_IO(相关联的文件是否可使用异步IO)、_PC_PRIO_IO(相关联的文件是都可以使用优先的IO)、_PC_SYNC_IO(相关联的文件是否可使用同步IO)引用的文件一定不能是一个目录。

常量_POSIX_C_SOURCE及_XOPEN_SOURCE,被称为功能测试宏。很多头文件定义了POSIX.1和XSI符号,但大多数实现在这些头文件中也加入了它们自己的一些定义,如编译一个程序时,希望它只与POSIX的定义有关,而不与任何实现定义的常量冲突,就需要常量_POSIX_C_SOURCE,在POSIX.1的2001版之前,使用的是_POSIX_SOURCE。

使用功能测试宏:

cc -D _POSIX_C_SOURCE=200809L file.c

也可在源文件第一行添加:

#define _POSIX_C_SOURCE 200809L

为使SUSv4的XSI选项可由应用程序使用,需将常量_XOPEN_SOURCE定义为700,这除了让XSI的选项可用以外,就POSIX.1的功能而言,其定义等价于将_POSIX_C_SOURCE值设为200809L。

使用-std=c99选项在gcc的C编译器中启用1999 ISO C扩展:

gcc -D _XOPEN_SOURCE=700 -std=c99 file.c -o file

某些UNIX系统变量已与某些C数据类型联系在一起。头文件sys/types.h中定义了某些与实现有关的数据类型,它们被称为基本系统数据类型,它们都是用C的typedef定义的,大多以_t结尾,用这些数据类型后,就不用考虑因系统不同而变化的程序实现细节:
在这里插入图片描述
如果POSIX.1和ISO C标准发生冲突,前者服从后者,但它们还有差别。

ISO C定义了clock函数,它返回使用的CPU时间,返回值类型为clock_t,但ISO C并没有规定它的返回值的单位,为将其单位变为秒,需要除time.h中的CLOCKS_PER_SEC(每秒时钟周期数),而POSIX.1定义了times函数,它返回其调用者和其调用者的所有终止子进程的CPU时间和时钟时间,类型为clock_t,我们可以用sysconf获得每秒时钟滴答数,然后将clock_t转换成秒等单位来表示times函数的返回值。

ISO C和POSIX.1两种标准用同一种类型clock_t保存对时间的测量,但各自定义了不同的单位。比如Solaris中clock函数返回进程使用的微秒数(CLOCK_PRE_SEC是100万),但sysconf为每秒滴答数返回的是100。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值