UNIX标准化及实现

本文详细探讨了UNIX的标准化过程,包括ISO C、IEEE POSIX和Single UNIX Specification,并介绍了UNIX的不同系统实现,如SVR4、4.4BSD、Linux和Mac OS X。此外,文章还讨论了UNIX的限制,如ISO C限制、XSI限制和运行期限量,以及如何通过sysconf、pathconf和fpathconf函数获取这些限制。最后,文章提到了POSIX.1选项和功能测试宏的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、UNIX标准化:

1.ISO C

  ISO C (ISO是国际标准化组织)C程序设计语言的标准,适用于一切使用C语言编程的场合

  主要有两个版本的国际C标准:

           一个是C89,即ANSI C(ANSI是美国国家标准学会),这是通行的实际标准;

           另一个是C99,主要是在兼容C89的基础进行了扩充,如增加了restrict(强制所修饰类型只能为指针的修饰符)关键字等。

2.IEEE POSIX

IEEE是:电气与电子工程师协会,POSIX是可移植的操作系统接口,POSIXIEEE制定,通过ISO进行标准化。

该标准的目的是提高应用程序在各种UNIX系统环境之间的可移植性。

当前通行的标准为POSIX.1,其包含了 ISO C 标准库函数。

3.Single UNIX Specification

Single UNIX Specification(单一UNIX规范)是POSIX.1标准的一个超集,.定义了一些附加接口。

这些接口扩展了基本的POSIX.1规范所提供的功能,相应的接口系统全集被称为X/Open系统接口。

二、UNIX系统实现

1.SVR4(UNIX系统V第4版):是AT&T的UNIX系统实验室的产品

2.4.4BDS:此版是由加州大学伯克利分校的计算机系统研究组研究开发和分发的。

3.FreeBDS:其基础是4.4BSD-Lite操作系统

4.Linux是在POSIX标准指导下发布的独立UNIX分支,使用GPL许可证。

5.Mac OS X:Mac OS X内核称为Darwin,在Mach微内核和FreeBSD的基础上开发,主要运行于苹果计算机的PowerPC处理器。

6.Solaris:由Sun公司开发的UNIX系统版本,基于SVR4.

7.其他UNIX系统

三、限制

1.ISO C 限制

ISO C限制和常量被分为5类:

a.不变的最小值

在<limits.h>里定义的POSIX.1的不变最小值
名称描述:...的可接受的最小值
_POSIX_ARG_MAXexec函数参数的长度4,096
_POSIX_CHILD_MAX每个真实用户ID的子进程数25
_POSIX_HOST_NAME_MAXgethostname返回的主机名的最大长度255
_POSIX_LINK_MAX一个文件的链接数8
_POSIX_LOGIN_NAME_MAX登录名的最大长度9
_POSIX_MAX_CANON终端最简洁的(canonical)输入队列的字节数255
_POSIX_MAX_INPUT终端输入队列的可用空格255
_POSIX_NAME_MAX文件名的字节数,不包括终止字符null14
_POSIX_NGROUPS_MAX一个进程的同步的补充组ID的数量8
_POSIX_OPEN_MAX一个进程打开的文件数20
_POSIX_PATH_MAX路径名的字节数,包括终止符null256
_POSIX_PIP_BUF可被原子写入管道的字节数512
_POSIX_RE_DUP_MAX当使用间隔标记“\{m,n\}”时,被regexec和regcomp函数认可的基本正则表达式的重复出现次数255
_POSIX_SSIZE_MAX可以存入ssize_t对象的值32,767
_POSIX_STREAM_MAX一个进程能同时打开的标准I/O流的数量8
_POSIX_SYMLINK_MAX符号链接的字节数255
_POSIX_SYMLOOP_MAX路径名解析时可以转换的符号链接数8
_POSIX_TTY_NAME_MAX终端设备名的长度,包括终止符null9
_POSIX_TZNAME_MAX时区名的字节数6

b.不变值:SSIZE_MAX

c.运行期可增长的值:CHARCLASS_NAME_MAX、COLL_WEIGHTS_MAX、LINE_MAX、NGROUPS_MAX、RE_DUP_MAX;

d.运行期可变化的值(可能不定):ARG_MAX、CHILD_MAX、HOST_NAME_MAX、LOGIN_NAME_MAX、OPEN_MAX、PAGESIZE、RE_DUP_MAX、STREAM_MAXS、 SYMLOOP_MAX、TTY_NAME_MAX和TZNAME_MAX;

e.路径名变量的值(可能不定):FILESIZEBITS、LINK_MAX、MAX_CANON、MAX_INPUT、NAME_MAX、PATH_MAX、PIPE_BUF和SYMLINK_MAX。

在这44个限量和常量中,有些可能定义在<limits.h>里,而其它的可能有也可能没有定义,取决于特定的条件 。

2.XSI限制

XSI同样定义了处理实现限量的常数,包括:

a.不变最小量:下表中的10个常量;

b.数字限量:LONG_BIT和WORD_BIT;

c.运行期不变值,可能不确定:ATEXIT_MAX、IOV_MAX和PAGE_SIZE。


<limits.h>里定义的XSI不变最小值
名称
描述
最小可接受值
典型值
NL_ARGMAXprintf和scanf调用数字的最大值99
NL_LANGMAXLANG环境变量的最大字节数1414
NL_MSGMAX最大消息数量32,76732,767
NL_NMAX多对一映射字符的最大字节数未定义1
NL_SETMAX最大集合数量255255
NL_TEXTMAX消息字符串的最大字节数_POSIX2_LINE_MAX2,048
NZERO默认的进程优先级2020
_XOPEN_IOV_MAXreadv和writev使用的iovec结构的最大数量1616
_XOPEN_NAME_MAX文件名的最大字节数255255
_XOPEN_PATH_MAX路径名的最大字节数1,0241,024

上表中有许多值是用来处理消息类别。最后两个值证明了POSIX.1的最小值太小了,很可能只允许嵌入式的POSIX.1系统,所以单一UNIX规范在XSI标准里加入更大的最小值。

3.函数synconf、pathconf和fpathconf

我们已经列出一个系统实现必须支持的各种最小值,但是我们怎么知道一个特定系统真正支持了那些限量呢?如我们早先所述,这些限量中有些可以在编译期可用,而一些只有在运行期得到。我们也提到过有些限量在某个系统上是固定的,而其它可能是变化的,因为它们与文件或目录相关。运行期限量可以通过调用以下三个函数来得到:


#include <unistd.h>
long sysconf(int name);
long pathconf(const char* pathname, int name);
long fpathconf(int filedes, int name); 

这三个函数成功都返回一个相应的值,而失败则返回-1。

最后两个函数的区别在于一个接受路径名作为参数,而另一个接受文件描述符作为参数。


下表列出了sysconf得到系统限量所用的名字参数,都以_SC_开头:

sysconf的名字参数以及相应的限量
限量名
描述
名字参数
AGR_MAXexec函数参数的最大长度,以字节为单位_SC_ARG_MAX
ATEXIT_MAX可以被atexit函数注册的函数的最大数量_SC_ATEXIT_MAX
CHILD_MAX一个真实用户ID可以拥有的最大进程数_SC_CHILD_MAX
clock ticks/second每秒钟的时钟周期_SC_CLK_TCK
COLL_WEIGHTS_MAX在本地定义文件里可以赋值给LC_COLLATE命令关键字的重量的最大数量_SC_SOLL_WEIGHTS_MAX
HOST_NAME_MAXgethostname返回的主机名的最大长度_SC_HOST_NAME_MAX
IOV_MAXreadv和writev使用的iovec结构的最大数量_SC_IOV_MAX
LINE_MAX实用工具的输入行的最大长度_SC_LINE_MAX
LOGIN_NAME_MAX登录名的最大长度_SC_LOGIN_NAME_MAX
NGROUPS_MAX每个进程的同步补充组ID数_SC_NGROUPS_MAX
OPEN_MAX一个进程打开文件的最大数量_SC_OPEN_MAX
PAGESIZE系统内存页的字节数_SC_PAGESIZE
PAGE_SIZE系统内存页的字节数_SC_PAGE_SIZE
RE_DUP_MAXregexec和regcomp函数使用间隔符\{m,n\}可以识别的基本表达式的重复次复_SC_RE_DUP_MAX
STREAM_MAX在任何时候一个进程的标准流的最大数量;如果有定义,这个值必须与FOPEN_MAX一样_SC_STREAM_MAX
SYMLOOP_MAX路径名解析时可以处理的符号链接数_SC_SYMLOOP_MAX
TTY_NAME_MAX终端设备名的长度,包括终止符null_SC_TTY_NAME_MAX
TZNAME_MAX时区名的最大字节数_SC_TZNAME_MAX

下表列出了pathconf和fpathconf函数使用的名字参数,都以_PC_开头:


pathconf和fpathconf的名字参数以及相应限量
限量名描述名字参数
FILESIZEBITS为了表示在指定目录下允许的普通文件大小的最大值,所使用的有符号整型值所需的最小比特位数_PC_FILESIZEBITS
LINK_MAX一个文件链接数的最大值_PC_LINK_MAX
MAX_CONON一个终端最简洁的输入队列的最大字节数_PC_MAX_CONON
MAX_INPUT一个终端的输入队列可用空间的字节数_PC_MAX_INPUT
NAME_MAX文件名的最大字节数,不包括终止符null_PC_NAME_MAX
PATH_MAX相对路径名的最大字节数,包括终止符null_PC_PATH_MAX
PIPE_BUF可以原子写入管道的最大字节数_PC_PIP_BUF
SYMLINK_MAX一个符号链接的字节数_PC_SYMLINK_MAX

下面再深入讨论下这三个函数的不同的返回值:


1、如果名字参数不正确,这三个函数都会返回-1并设置errno值为EINVAL。

2、一些名字参数可以返回一个变量的值,或者表明这个值是不确定的。通过返回-1但不改变erron的值来表示一个不确定的值;

3、_SC_CLK_TC的返回值是每秒钟的时钟周期,用来与times函数的返回值共同使用。

在传递路径名给pathconf和域参数给fpathconf时有些约束。如果这些约束中任意一个没有满足,则结果无定义:

1、_PC_MAX_CONON和_PC_MAX_INPUT的对应的文件并须是一个终端文件;

2、_PC_LINK_MAX的对应的文件既可以是文件也可以是目录。如果相应文件是一个目录,则返回值对应于目录本身,而不是目录下的文件;

3、_PC_FILESIZEBITS和_PC_NAME_MAX的相应文件必须是目录。返回值对应于这个目录下的文件;

4、_PC_PATH_MAX相应的文件必须是目录。当这个目录是工作目录时,返回值是相对路径名的最大长度。(不幸的是,这不是我们想知道的绝对路径的真实长度,稍后再来讨论这个问题。)

5、_PC_PIPE_BUF的相应文件必须是个管道,FIFO或者目录。前两种情况下返回值为相应管道或FIFO的限量。最后一种情况的返回值为指定目录下创建的任一FIFO的限量。

6、_PC_SYMLINK_MAX的相应文件必须是一个目录。返回值为目录里的符号链接可以包含的字符串的最大长度。

4.不确定的运行期限量(Indeterminate Runtime Limits)

我们之前提到了有运行期变量可以是不确定的。问题在于如果这些限量没有在<limits.h>里定义,那我们就不能在编译期使用它们。而且如果它们的值是不确定的,那么可能在运行期也没有定义!我们来看下两个特殊的情况:为路径名分配内存,以及决定文件描述符的数量。

路径名(Pathname)

许多程序需要为路径名分配内存。典型地,内存在编译期分配,而一些魔数(没有一个是正确的值)被用来作为数组尺寸:256、512、1024或都标准I/O常量BUFSIZ。定义在<sys/parm.h>里的4.3BSD常量MAXPATHLEN才是正确的值,但许多程序都不用它。

POSIX.1试图用PATH_MAX来解决这个问题,但如果这个值是不确定的,那我们仍然不走运。本文使用下面这个函数来动态地为路径名分配内存:

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

    #ifdef PATH_MAX
    static int pathmax = PATH_MAX;
    #else
    static int pathmax = 0;
    #endif

    #define SUSV3 200112L

    static long posix_version = 0;

    /* If MATH_MAX is indeterminate, no guarantee this is adquate */
    #define PATH_MAX_GUESS 1024

    void err_sys(const char* msg);

    char *
    path_alloc(int *sizep) /* also return allocated size, if nonnull */
    {
        char *ptr;
        int size;
        
        if (posix_version == 0)
            posix_version = sysconf(_SC_VERSION);

        if (pathmax == 0) { /*first time through */
            errno = 0;
            if ((pathmax = pathconf("/", _PC_PATH_MAX)) < 0) {
                if (errno == 0)
                    pathmax = PATH_MAX_GUESS; /* it's indeterminate */
                else
                    err_sys("pathconf error for _PC_PATH_MAX");
            } else {
                pathmax++; /* add one since it's relative to root */
            }
        }
        if (posix_version < SUSV3)
            size = pathmax + 1;
        else
            size = pathmax;

        if ((ptr = malloc(size)) == NULL)
            printf("malloc error for pathname");

        if (sizep != NULL)
            *sizep = size;
        return(ptr);
    }

    void err_sys(const char* msg) {
        printf("%s\n", msg);
        exit(1);
    }
如果PATH_MAX在<limits.h>里定义了,那就使用它,不然就调用pathconf。如果第一个参数是工作路径,那么pathconf的返回值是一个相对路径名的最大尺寸,所以我们把根目录作为第一个参数,而后在结果上加1。如果pathconf指出PATH_MAX是不确定的,那我们只有猜测一个值了。

SUSv3之前的标准并没有清楚说明PATH_MAX最末尾是否包括一个null的字节。如果操作系统实现信赖于这些早期的标准,则我们需要加一个字节的内存,这样会更安全些。

处理不确定结果情况的正确方法取决于分配的空间怎样被使用。如果分配的内存是为了函数调用getcwd--比如:返回当前工作目录的绝对路径名--而且如果分配的空间太小的话,那errno会返回一个错误ERANGE。我们可以接着调用realloc来增加这块分配的空间并再次尝试。我们可以持续这样做,直到getcwd成功为止。


打开文件的最大数量(Maximum Number of Open Files)

一个后台进程(daemon process)(或守护进程、精灵进程):运行在后台,不与任何终端关联的进程。

一些程序用以下代码,设想常量NOFILE定义在<sys/param.h>头文件里:

#include <sys/param.h>
for (i = 0; i < NOFILE; i++)
       close(i);

其它一些程序使用一些版本的<stdio.h>提供的常量_NFILE作为上限。有些把它硬编码为20.

我们本希望用POSIX.1的值OPEN_MAX来决定这个值,但如果这个值是不确定的,我们仍然会有问题。如果我们把代码写成下面的模样,而同时OPEN_MAX是不确定的,那会造成死循环,因为sysconf会返回-1:

#include <unistd.h>
for (i = 0; i < sysconf(_SC_OPEN_MAX); i++)
       close(i);
我们最好的解决方案是仅仅关闭所有不超过一个任意上限(比如256)的描述符。和我们的pathname例子一样,这个并不保证可以在所有情况下都工作,但这是我们能做的最好的事情。看如下代码:

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

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

    /*
     * If OPEN_MAX is indeterminate, we're not
     * guaranteed that this is adequate.
     */
    #define OPEN_MAX_GUESS 256

    void err_sys(const char* msg);

    long
    open_max(void)
    {
        if (openmax == 0) { /* first time through */
            errno = 0;
            if ((openmax = sysconf(_SC_OPEN_MAX)) < 0) {
                if (errno == 0)
                    openmax = OPEN_MAX_GUESS; /* it's indeterminate */
                else
                    err_sys("sysconf error for _SC_OPEN_MAX");
            }
        }
        return(openmax);
    }

我们可能想尝试调用close函数,直到得到一个返回的错误,但是这个从close(EBADF)返回的错误并不能区分它是一个无效的文件描述符,还是这个文件描述符并没有打开。如果我们尝试使用这种技术,但描述符9并没有打开而描述符10被打开,我们可以终止在9而不能关闭10。如果OPEN_MAX溢出的话,dup函数确实返回一个特定的错误,但只能重复这个描述符两百多次才能得到这个值。

一些系统实现会返回LONG_MAX当作限值,来有效的表示没有限制。Linux的ATEXIT_MAX就是其一。这不是个好方法,因为它可能会导致程序很坏的行为。

比如,我们可以用Bourne-again shell的内置ulimit命令来改变我们进程可以同时打开的文件数的最大值。如果这个限制要设置成无限制的话,通常需要一个特殊(超级用户)的权限。但一旦它被设置为无限的话,sysconf会返回LONG_MAX作为OPEN_MAX的值。程序信赖于这个值作为关闭的文件描述符的上限,而尝试关闭2,147,483,647个文件描述符是非常浪费时间的,而它们之中大多数都没有被使用。

支持UNIX单一规范里的XSI扩展的系统会提供getrlimit函数。它能用来返回一个进程能打开的描述符的最大数量。利用这个函数,我们可以确保我们的进程没有打开超过预设的上限数的文件,从而避免上述问题。

OPEN_MAX被POSIX称为运行期不变量,意味着它的值不能在一个进程的生命周期内改变。但在支持XSI扩展的系统上我们可以调用setrlimit函数在为一个运行着的进程改变这个值。(这个值也可以在C shell上用limit命令改变,或在Bourne、Bourne-again和Knon shell上用ulimit函数。)如果我们的系统支持这个功能,我们可以改变上述代码:每次调用open_max函数时,都调用sysconf,而不仅仅在第一次调用时。

5.在Linux的shell下

若要知道系统当前都有哪些运行时限制,可以使用如下命令:

find /proc/sys -name *max*

此命令行可打印出当前运行的内核中各种资源限制的名称,可以直接用cat(1)等命令查看其内容。通常可以root权限修改,但一般通过使用命令ulimit(1)修改。

四、POSIX.1 选项

选项说明了系统具体实现的可选功能,包括。如JOB_CONTROL、THREADS、CHOWN_RESTRICTED等。

其值为负数时表示当前系统不支持这些可选功能,为正数时表示支持,为0时可用sysconf、pathconf、fpathconf等函数的返回值确定是否支持;

资源限制通常是与具体的系统实现相关的,为了保证可移植性,应定义功能测试宏强制程序运行时的限制符合标准。

五、功能测试宏

功能测试宏包括了两个常量:_POSIX_C_SOURCE(POSIX标准)与_XOPEN_SOURCE(SUS标准)。如果在C程序源文件中定义了这两个宏,则告诉编译器,资源限制使用POSIX及SUS的定义,而不使用当前系统具体实现中的定义。这两个宏应定义为一个值,如:

#define _POSIX_C_SOURCE 200112L
#define _XOPEN_SOURCE 600

第一个使得源文件的系统限制遵循POSIX.1标准,第二个为SUS v3标准。也可以不定义在头文件中,而通过编译选项加入宏定义,对于gcc(1)为-D选项。

六、基本系统数据类型

包括了size_t、time_t、uid_t、off_t、pthread_t等数据类型,在各个具体的系统实现中通常由C的typedef语句重定义的,其原型可能是某种整型数也可能是个struct结构。使用这些数据类型而不直接用C中的int、long等基本数据类型也是为了考虑程序的可移植性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值