• UNIX环境高级编程-第二章


    1.引言

    人们在UNIX编程环境和C程序设计语言的标准化方面已经做了很多工作。虽然UNIX应用程序在不同的UNIX操作系统版本之间进行移植相当容易,但是20世纪80年代UNIX版本种类的剧增以及它们之间差别的扩大,导致很多大用户呼吁对其进行标准化。
    本章首先回顾过去近25年人们在UNIX标准化方面做出的种种努力,然后讨论这些UNIX编程标准对本章所列举的各种UNIX操作系统实现的影响。所有标准化工作的一个重要部分是对每种实现必须定义的各种限制进行说明,所以我们讲说明这些限制以及确定它们值的各种方法。

    2.UNIX标准化

    (1)ISO C
    1989年下半年,C程序设计语言的ANSI标准X3.159-1989得到批准,此标准被采纳为国际标准ISO/IEC 9899:1990。ANSI是美国国家标准学会的缩写,它是国际标准化组织中代表美国的成员。IEC是国际电子技术委员会的缩写。
    ISO C标准现在由ISO/IEC的C程序设计语言国际标准工作组维护和开发,该工作组称为ISO/IEC JTCI/SC22/WG14,简称WG14。ISO C标准的意图是提供C程序的可移植性,使其能适合于大量不同的操作系统,而不只是适合UNIX系统。此标准不仅定义了C程序设计语言的语法和语义,还定义了其标准库。因为所有现今的UNIX系统都提供C标准中定义的库函数,所以该标准库非常重要。
    1999年,ISO C标准被更新,并被批准为ISO/IEC 9899:1999,它显著改善了对进行数值处理的应用软件的支持。除了对某些函数原型增加了关键字restrict外,这种改变并不影响POSIX接口。restrict关键字告诉编译器,那些指针引用是可以优化的,其方法是指出指针引用的对象在函数中只能通过该指针进行访问。
    1999年以来,以及公布了3个技术勘误来修正ISO C标准中的错误,分别在2001年,2004年和2007年公布。如同大多数标准一样,在批准标准和修改软件使其符合标准两者之间有一段时间延迟。随着供应商编译系统的不断演化,对最新ISO C标准的支持也就越来越多。
    按照该标准定义的各个头文件可将ISO C库分成24个区。POSIX.1标准包括这些头文件以及另外一些头文件。从图2-1中可以看出,所有这些头文件在4中UNIX实现中都支持。本章后面将对这4中UNIX实现进行说明。
    头文件

    <assert.h>:验证程序断言
    <complex.h>:复数算数运算支持
    <ctype.h>:字符分类和映射支持
    <errno.h>:出错码
    <float.h>:浮点常量及特性
    <inttypes.h>:整型格式变换
    <iso646.h>:赋值,关系及一元操作符宏
    <limits.h>:实现常量
    <locale.h>:本地化类别及相关定义
    <math.h>:数学函数,类型声明及常量
    <setjmp.h>:非局部goto
    <signal.h>:信号
    <stdarg.h>:可变长度参数表
    <stdbool.h>:布尔类型和值
    <stddef.h>:标准定义
    <stdint.h>:整型
    <stdio.g>:标准IO库
    <stdlib.h>:使用函数
    <string.h>:字符串操作
    <tgmath.h>:通用类型数学宏
    <time.h>:时间和日期
    <wchar.h>:扩充的多字节和宽字符支持
    <wctype.h>:宽字符分类和映射支持
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    (2)IEEE POSIX
    POSIX是一个最初由IEEE制定的标准族。POSIX指的是可以指操作系统接口,它原来指的只是IEEE标准1003.1-1988,后来则扩展成包括很多标记为1003的标准及标准草案,如shell和使用程序(1003.2)。
    1003.1标准的目的是提升应用程序在各种UNIX系统环境之间的可移植性。它定义了"符合POSIX的"操作系统必须提供的各种服务。该标准已被很多计算机制造商采用。
    由于1003.1标准说明了一个接口而不是一种实现,所以并不区分系统调用和库函数。所有在标准中的例程都被称为函数。
    2001年的1003.1版本与以前各大版本有较大的差别,它组合了多个1003.1的修正,1003.2标准以及single UNIX Specification第2版的若干部分。
    2004年POSIX.1随着技术勘误得到更新,2008年作了更多综合的改动,ISO在2009年进行发布。
    POSIX.1没有包括超级用户的概念,代之规定某些操作要求"适当的优先权"。
    (3)Single UNIX Specification
    Single UNIX Specification是POSIX标准的一个超集,它定义了一些附加接口扩展POSIX.1规范提供的功能。
    POSIX.1中的X/open系统接口选项描述了可选的接口,也定义了遵循XSI的实现必须支持POSIX.1的那些可选部分。这些必须支持的部分包括:文件同步,线程栈地址和长度属性,线程进程共享同步以及_XOPEN_UNIX符号常量。只有遵循XSI的实现才能称为UNIX系统。
    (4)FIPS
    FIPS代表的是联邦信息处理标准,这一标准是由美国政府发布的,并由美国政府用于计算机系统的采购。

    3.UNIX系统的历史

    略…

    4.标准和实现的关系

    略…

    5.限制

    UNIX系统实现定义了很多幻数和常量,其中有很多以及被硬编码到程序中,或用特定的技术确定。由于大量标准化工作的努力,已有若干种可移植的方法用以确定这些幻数和具体实现定义的限制。这非常有助于改善UNIX环境下软件的可移植性。
    以下两种类型的限制是必需的:
    ①编译时限制(例如,短整型的最大值是什么?)
    ②运行时限制(例如,文件名有多少个字符)
    编译时限制可在头文件中定义。程序在编译时可以包含这些头文件。但是,运行时限制则要求进程调用一个函数获得限制值。
    另外某些限制在一个给定的实现中可能是固有的(因此可以静态地在一个文件头中定义),而在另一个实现中则可能是变动的(需要一个运行时函数调用)。这种类型限制的一个例子是文件名的最大字符数。SVR4之前的系统V由于历史原因只允许文件名最多包含14个字符,而源于BSD的系统则将此增加为255。目前,大多数UNIX支持多文件系统类型,而每一种类型有它自己的限制。文件名的最大长度依赖于该文件处于何种文件系统,例如,根文件系统中的文件名长度限制可能是14个字符,而在另一个文件系统中文件名长度限制可能是255个字符,这是运行时限制的一个例子。
    为了解决这类问题,提供了以下3种限制:
    ①编译时限制(头文件)
    ②与文件或目录无关的运行时限制(sysconf)
    ③与文件或目录相关的运行时限制(pathconf和fpathconf函数)。
    使事情变得更加复杂的是,如果一个特定的运行时限制在一个给定的系统上并不改变,则将其静态地定义在一个头文件中,但是,如果没有将其定义在头文件中,应用程序就必须调用3个conf函数中的一个,以确定其运行时的值。
    (1)ISO C限制
    ISO C定义的所有编译时限制都列在头文件中,这些限制常量在一个给定系统中并不会改变。第3列列出了ISO C标准可接受的最小值。这用于16为整型的系统,用1的不嘛表示。第4列列出了32为整型Linux系统的值,用2的补码表示。注意,我们没有列出无符号数据类型的最小值,这些值应该都为0。在64位系统中,其long整型的最大值与表中long long整型的最大值相匹配。
    示例:2-6
    在这里插入图片描述
    我们将会遇到一个区别是系统是否提供带符号或无符号的字符值。从2-6示例中的第4列可以看出,该特定系统使用带符号字符。从图中可以看出CHAR_MIN等于SCHAR_MIN,SCHAR_MAX等于SCHAR_MAX。如果系统使用无符号字符,则CHAR_MIN等于0,CHAR_MAX等于UCHAR_MAX。
    在头文件中,对浮点数据类型也有类似的一组定义。如若读者在工作中设计大量浮点数据类型,则应该仔细查看该文件。
    我们会遇到应一个ISO C常量是FOPEN_MAX,这是具体实现保证可同时可开的标准IO流的最小个数,该值在头文件中定义,其最小值是8。POSIX.1中的STREAM_MAX则应与FOPEN_MAX具有相同的值。
    ISO C还在中定义了常量TMP_MAX,这是由tmpnam函数产生的唯一文件名的最大个数。
    虽然ISO C定义了常量FILENAME_MAX,但我们应该便面使用该常量,因为POSIX.1提供了更好的替代常量(NAME_MAX和PATH_MAX),我们很快就会介绍该常量。
    (2)POSIX限制
    POSIX.1定义了很多设计操作系统实现限制的常量,遗憾的是,这是POSIX.1种最令人疑惑不解的部分之一。虽然POSIX.1定义了大量限制和常量,我们只关心与基本POSIX.1接口有关的部分。这些限制和常量分成下列7类。
    ①数值限制:LONG_BIT,SSIZE_MAX和WORD_BIT。
    ②最小值:图2-8种的25个常量。
    ③最大值:_POSIX_ClOCKRES_MIN。
    ④运行时可以增加的值:CHARCLASS_NAME_MAX,COLL_WEIGHTS_MAX,LINE_MAX,NGROUPS_MAX和RE_DUP_MAX。
    ⑤运行时不变值:图2-9的17个常量
    ⑥其他不变值:NL_ARGMAX,NL_MSGMAX,NL_SETMAX,NL_TEXTMAX
    ⑦路径名可编制:FILESIZEBITS,LINK_MAX,MAX_CANON,MAX_INPUT,NAME_MAX,PATH_MAX,PIPE_BUF,SYMLINK_MAX
    在这些限制和常量中,某些可能定义在中,其余的则按具体条件可定义,可不定义。
    示例2-8:中POSIX.1最小值
    在这里插入图片描述
    遗憾的是,这些不变最小值中的某一些在实际应用中太小了。例如目前在大多数UNIX系统中,每个进程可同时打开的文件数远远超过20。另外,_POSIX_PATH_MAX的最小限制是255,这太小了,路径名可能会超过这一限制。这意味着在编译时不能使用_POSIX_OPEN_MAX和_POSIX_PATH这两个常量作为数组长度。
    图2-8中的25个不变最小值的每一个都有一个相关的实现值,其名字是将图中2-8中的名字删除前缀_POSIX后构成的。没有_POSIX_前缀的名字用于给定具体实现支持的该不变最小值的实际数值。问题是是不能确保所有这25个实现值都在头文件定义。
    例如,某个特定值可能不在此头文件中定义,其理由是,一个给定进程的实际值可能依赖于系统的存储总量。如果没有在头文件中定义它们,则不能在编译时使用它们作为数组边界。所以,POSIX.1提供了3个运行时函数以供调用,它们是:sysconfig,pathconf以及fpathconf。使用这3个函数可以运行时得到实际的实现值。但是还有一个问题,其中某些值时"可能不确定的"(逻辑上无限)。
    图2-9:运行时不变值
    在这里插入图片描述
    (3)XSI限制
    XSI定义了代表实现限制的几个常量。
    ①最小值:图2-10中列出了5个常量
    ②运行时不变值(可能不确定):IOV_MAX和PAGE_SIZE
    图2-10列出了最小值。最后两个常量值说明了POSIX.1最小值太小的情况,根据推测着可能考虑到了嵌入式POSIX.1实现。为此,Single UNIXSpecification为符合XSI的系统增加了具有较大最小值的符号。
    图2-10:
    在这里插入图片描述
    (4)函数sysconf,pathconf,fpathconf
    我们已列出了实现必须支持的各种最小值,但是怎样才能找到一个特定系统支持的限制值呢?正如前面提到的,某些限制值在编译时是可用的,而另外一些则必须在运行时确定。我们也曾提及某些限制值在一个给定的系统中可能是不会更改的,而其他限制值可能会更改,因为它们与文件和目录相关联。运行时限制可调用下面3个函数之一获得。

    #include
    long sysconf(int name);
    long pathconf(const char * pathname,int name);
    long fpathconf(int fd,int name);
    
    • 1
    • 2
    • 3
    • 4

    后面两个函数的差别是:一个用路径名作为其参数,另一个则取文件描述符作为参数。
    图2-11列出了sysconf函数所使用的name参数,它用于表示系统限制。以_SC_开始的常量用作表示运行时限制的sysconf参数。
    我们需要更详细的讨论一下这3个函数不同的返回值。
    ①如果name参数并不是一个合适的常量,这3个函数都返回-1,并把errno置为EINVAL。如2-11和图2-12的第三列给出了我们在整本书中将要涉及的限制常量。
    ②有些name会返回一个变量值或者提示该值是不确定的。不确定的值通过返回-1来体现,而不改变errno的值。
    图2-11
    在这里插入图片描述

    ③_SC_CLK_TCK的返回值是每秒的时钟滴答数,用于times函数的返回值。对于pathconf的参数pathname和fpathconf的参数fd有很多限制。如果不满足其中任何一个限制,则结果是未定义的。
    图2-12:
    在这里插入图片描述

    图2-12列出了pathconf和fpathconf函数为标识系统限制所使用的name参数。以_PC开始的常量用作标识运行时限制的pathconf和fpathconf参数。
    ①_PC_MAX_CANON和_PC_MAX_INPUT引用的文件必须是终端文件。
    ②_PC_LINK_MAX和_PC_TIMESTAMP_RESOLUTION引用的文件可以是文件或目录。如果是目录,则返回值用于目录本身,而不用与目录内的文件名项。
    ③_PC_FILESIZEBITS和_PC_NAME_MAX引用的文件名必须是目录,返回值用于该目录中的文件名。
    ④_PC_PATH_MAX引用的文件必须是目录。当所指定的目录是工作目录时,返回值是相对路径名的最大长度。
    ⑤_PC_PIPE_BUF引用的文件必须是管道,FIFO或目录。在管道或者FIFO情况下,返回值对所引用的管道或FIFO的限制值。对于目录,返回是对在该目录中创建的任一FIFO的限制值。
    ⑥_PC_SYMLINK_MAX引用的文件必须是目录。返回值是该目录中符号链接可包含字符串的最大长度。
    实例2-13:打印个pathconf和sysconf的值

    #include "apue.h"
    #include 
    #include 
    
    static void pr_sysconf(char * int);
    static void pr_pathconf(char *,char * ,int);
    
    int
    main(int argc,char * argv[])
    {
    	if(argc!=2)
    	{
    		err_quit("usage:a.out ");
    	}
    	#ifdef ARG_MAX
    		printf("ARG_MAX defined to be %ld\n",(long)ARG_MAX+0);
    	#else
    		printf("no sysmbol for ARG_MAX");
    	#endif
    	#ifdef _SC_ARG_MAX
    		pr_sysconf("ARG_MAX=",_SC_ARG_MAX);
    	#elseif
    		printf("no symbol for _SC_ARG_MAX\n");
    	#endif
    	#ifdef MAX_CANON
    		printf("MAX_CANON defined tobe %ld\n",(long) MAX_CANON+0);
    	#else
    		printf("no symbol for MAX_CANON\n");
    	#endif
    	#ifdef _PC_MAX_CANON
    		pr_pathconf("MAX_XANON=",argv[1],_PC_MAX_CANON);
    	#else
    		printf("no symbol for _PC_MAX_CANON\n");
    	#endif
    	exit(0);
    }
    static void
    pr_sysconf(char * mesg,int name)
    {
    	long val;
    	fputs(mesg,stdout);
    	errno=0;
    	if((val=sysconf(name))<0){
    		if(errno!=0){
    			if(errno==EINVAL)
    				fputs(" not supported)\n",stdout);
    			else
    				err_sys("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
    				err_sys("pathconf error,path=%s",path);
    		}
    		else {
    			fputs(" (no limit)\n",stdout);
    		}
    	}	
    	else {
    		printf("%ld\n",val);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    (5)不确定的运行时限制
    ①.路径名
    很多程序需要为路径名分配存储区,一般来说,在编译时就为其分配了存储区,而且不同的程序使用各种不同的幻数作为数组长度,如256,512,1024或标准IO常量BUFSIZE。头文件中的常量MAXPATHLEN才是正确的值,但是很多4.3BSD应用程序并未使用它。
    POSIX.1试图用PATH_MAX值来帮助我们,但是如果此值是不确定的,那么仍是毫无帮助的,2-16程序是为路径名动态分配存储区的函数。

    #include "apur.h"
    #include 
    #include 
    
    #ifdef PATH_MAX
    static long pathmax=PATH_MAX;
    #else
    static long pathmax=0;
    #endif
    
    static long posix_version=0;
    static long xsi_version=0;
    
    #define PATH_MAX_GUESS 1024
    
    char *
    path_alloc(size_t *sizep)
    {
    	char * ptr;
    	size_t size;
    	if(posix_version==0)
    		posix_version=sysconf(_SC_VERSION);
    	if(xsi_version==0)
    		xsi_version=sysconf(_SC_XOPEN_VERSION);
    	if(pathmax==0){
    		errno=0;
    		if((pathmax=pathconf("/",_PC_PATH_MAX))<0){
    			if(errno==0)
    				pathmax=PATH_MAX_GUESS;
    			else
    				err_sys("pathconf error for _PC_PATH_MAX");
    		}
    		else{
    			pathmax++;
    		}
    	}
    	
    	if((posix_version<200112L)&&(xsi_version<4))
    		size=pathmax+1;
    	else
    		size=pathmax;
    	if((ptr=malloc(size))==NULL)
    		err_sys("malloc error for pathname");
    	if(sizep!=NULL)
    		*sizep=size;
    	return(ptr);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    如果中定义了常量PATH_MAX,那么就没有任何问题;如果未定义,则需要调用pathconf。因为pathconf的返回值是基于工作目录的相对路径名的最大长度,而工作目录是其第一个参数,所以根目录为第一个参数,并将得到的返回值加1作为结果值。如果pathconf指明PATH_MAX是不确定的,那么我们就只能猜测某个值。
    对于PATH_MAX是否考虑到在路径名末尾有一个null字节这一点,2001年以前的POSIX.1版本表述得并不清楚。处于安全方面的考虑,如果操作系统的实现符合某个先前版本的标准,但并符合Single UNIX Specification的任何版本(SUS明确要求在结尾处加一个终止null字节),
    则需要在为路径名分配的存储量上加1。
    处理不确定结果情况的正确方法与如何使用分配的存储空间有关。例如,如果我们为getcwd调用分配存储空间,但分配空间太小,则会返回一个错误,并将errno设置为ERANGE。然后可调用realloc来增加分配的空间。
    ②.最大文件打开数
    支持Single UNIX Specification中XSI扩展的系统提供了getrlimit函数。它返回一个进程可以同时打开的描述符的最多个数。
    (6)POSIX.1定义了3种处理选项的方法。
    ①编译时选项定义在
    ②与文件或目录无关的运行时选项用sysconf函数来判断。
    ③与文件或目录有关的运行时选项通过调用pathconf和fpathconf函数来判断。
    如果符号常量未定义,则必须使用sysconf,pathconf或fpathconf来判断是否支持该选项。这种情况下,这些函数的name参数前缀_POSIX必须替换位_SC或_PC。对于以_XOPEN为前缀的常量,在构成name参数时必须在其前放置_SC或_PC。例如,若常量_POSIC_RAW_THREADS是未定义的,那么就可以将name参数设置为SC_RAW_THREADS,并以此调用sysconf来判断该平台是否支持POSIX线程选项。

  • 相关阅读:
    leetcode刷题日记:94. Binary Tree Inorder Traversal(二叉树的中序遍历)
    Unity中全局光照GI的总结
    Qt第三方库QicsTable简单实例(1)
    crontab定时时钟同步,输出重定向 1 * * * * /usr/sbin/ntpdate x.x.x.x > /dev/null 2>&1 &
    深度学习课后week2 编程(识别猫)
    【微服务】微服务初步认识 - 微服务技术如何学习 · 认识微服务架构
    自定义Unity组件——AudioManager(音频管理器)
    确定Mac\Linux系统的架构类型是 x86-64(amd64),还是 arm64 架构
    rust OJ实战
    高性能高可靠性高扩展性分布式防火墙架构
  • 原文地址:https://blog.csdn.net/weixin_43979090/article/details/126165778