一个快速确定新系统上各类限制值的工具
对于在不同 Unix 系统之间移植程序,最重要的事情就是确定新系统的一些编译时、运行时固定或不固定的限制值了。例如文件路径最大长度 PATH_MAX、进程最大可打开文件句柄数 OPEN_MAX、用户可加入的附加用户组最大数量 NGROUPS_MAX、进程命令行参数最大字节数 ARG_MAX、内存页大小 PAGESIZE、线程栈大小默认值 STACKSIZE、临时文件最大数量 TMP_MAX 等等。甚至有些系统特征也可以通过查询来提前确定,例如是否支持读写锁、是否支持异步 IO、是否支持实时信号等等,这样程序就可以根据系统提供的能力来选择不同的接口去实现同样的功能。
回头来看 apue 第二章有关系统限制与选项这一块的内容,由于掺杂了 UNIX 标准化的内容,显得很没有条理,例如将各种限制按标准划分为:
- ISO C
- 编译时限制 (头文件常量)
- POSIX
- 不变的最小值 (声明遵循 POSIX 的系统必需支持到至少这么多,不能再小了)
- 不变值 (SSIZE_MAX)
- 运行时可增加的值
- 运行时不变的值
- 路径名可变值
- XSI
- 不变的最小值 (同上)
- 数值限制
- 运行时不变值
看得人一头雾水,其实如果抛开标准,单按限制的性质来分,就比较简单啦:
- 编译时限制 (通过头文件常量)
- 运行时限制
- 不与文件系统相关部分(通过 sysconf)
- 与文件系统相关部分(通过 pathconf)
也就是说,要确定一个系统限制,一共分两步:
- 确定相应的符号常量有没有在头文件定义,如果有,直接拿来用;
- 如果没有,走 sysconf 或 pathconf 查询。
对于系统选项,稍等复杂一点,分三步:
- 符号常量定义为 -1,平台不支持该选项;
- 符号常量定义大于 0,平台支持相应的选项;
- 符号常量定义为 0,需要进一步依靠 sysconf 或 pathconf 来查询。
到这里,你大概已经想到怎么自己制作一个工具了,那就是按上面的逻辑 coding 呗,可以把想要查的常量作为输入,查到的结果打印到控制台,就像下面这样:
1 #include "../apue.h" 2 #include <errno.h> 3 #include <limits.h> 4 5 static void pr_sysconf (char*, int); 6 static void pr_pathconf (char *, char *, int); 7 8 int 9 main (int argc, char *argv[]) 10 { 11 if (argc != 2) 12 err_quit ("usage: conf <dirname>"); 13 14 #ifdef _POSIX_OPEN_MAX 15 printf ("_POSIX_OPEN_MAX defined to be %d\n", _POSIX_OPEN_MAX); 16 #else 17 printf ("no symbol for _POSIX_OPEN_MAX\n"); 18 #endif 19 20 #ifdef OPEN_MAX 21 printf ("OPEN_MAX defined to be %d\n", OPEN_MAX); 22 #else 23 printf ("no symbol for OPEN_MAX\n"); 24 #endif 25 26 #ifdef _SC_OPEN_MAX 27 pr_sysconf ("sysconf (_SC_OPEN_MAX) = ", _SC_OPEN_MAX); 28 #else 29 printf ("no symbol for _SC_OPEN_MAX\n"); 30 #endif 31 32 #ifdef _POSIX_NAME_MAX 33 printf ("_POSIX_NAME_MAX defined to be %d\n", _POSIX_NAME_MAX); 34 #else 35 printf ("no symbol for _POSIX_NAME_MAX\n"); 36 #endif 37 38 #ifdef NAME_MAX 39 printf ("NAME_MAX defined to be %d\n", NAME_MAX); 40 #else 41 printf ("no symbol for NAME_MAX\n"); 42 #endif 43 44 #ifdef _PC_NAME_MAX 45 pr_pathconf ("pathconf (_PC_NAME_MAX) = ", argv[1], _PC_NAME_MAX); 46 #else 47 printf ("no symbol for _PC_NAME_MAX\n"); 48 #endif 49 50 exit (0); 51 } 52 53 static void 54 pr_sysconf (char *msg, int name) 55 { 56 long val; 57 fputs (msg, stdout); 58 errno = 0; 59 if ((val = sysconf (name)) < 0) { 60 if (errno != 0) { 61 if (errno == EINVAL) 62 fputs ("(not supported)\n", stdout); 63 else 64 err_sys ("sysconf error"); 65 } 66 else 67 fputs ("(no limit)\n", stdout); 68 } 69 else 70 printf ("%ld\n", val); 71 } 72 73 74 static void 75 pr_pathconf (char *msg, char *path, int name) 76 { 77 long val; 78 fputs (msg, stdout); 79 errno = 0; 80 if ((val = pathconf (path, name)) < 0) { 81 if (errno != 0) { 82 if (errno == EINVAL) 83 fputs ("(not supported)\n", stdout); 84 else 85 err_sys ("pathconf error, path = %s", path); 86 } 87 else 88 fputs ("(no limit)\n", stdout); 89 } 90 else 91 printf ("%ld\n", val); 92 }
这个程序处理了两个常量: OPEN_MAX 与 NAME_MAX,对于每个常量,它都尝试检测 _POSIX_XXX 是否存在,不同点在于,前者不依赖于文件系统,所以最后调用 sysconf 检测;后者依赖文件系统,所以调用 pathconf 检测。至于为何在 pr_sysconf / pr_pathconf 中清理 errno,可以参考我之前写过的一篇文章:[apue] sysconf 的四种返回状态 。这个程序要求一个路径,可以这样输入参数来使之工作:
|
为方便工具灵活的处理各种不同输入,之样直接写死常量肯定是不灵活了,有的同学已经想到了将常量作为命令行参数传入,这个办法也可以。不过这里借鉴书上一种使用 awk 自动生成 c 代码的方法,将上面的例子 awk 化:
1 #! /bin/awk -f 2 BEGIN { 3 printf("#define _GNU_SOURCE \n") 4 printf("#include \"../apue.h\" \n") 5 printf("#include <errno.h>\n") 6 printf("#include <limits.h> \n") 7 printf("#include <sys/param.h> \n") 8 printf("\n") 9 printf("static void pr_sysconf (char*, int); \n") 10 printf("static void pr_pathconf (char *, char *, int); \n") 11 printf("\n") 12 printf("int \n") 13 printf("main (int argc, char *argv[])\n") 14 printf("{\n") 15 printf(" if (argc != 2)\n") 16 printf(" err_quit (\"usage: conf <dirname>\"); \n") 17 printf("\n") 18 FS=":" 19 while (getline < "sysconf.sym" > 0) { 20 printf("#ifdef %s\n", $1) 21 printf(" printf (\"%s defined to be %%d\\n\", %s+0); \n", $1, $1) 22 printf("#else \n") 23 printf(" printf (\"no symbol for %s\\n\"); \n", $1) 24 printf("#endif \n") 25 printf("\n") 26 printf("#ifdef _%s\n", $1) 27 printf(" printf (\"_%s defined to be %%d\\n\", _%s+0); \n", $1, $1) 28 printf("#else \n") 29 printf(" printf (\"no symbol for _%s\\n\"); \n", $1) 30 printf("#endif \n") 31 printf("\n") 32 printf("#ifdef _POSIX_%s\n", $1) 33 printf(" printf (\"_POSIX_%s defined to be %%d\\n\", _POSIX_%s+0); \n", $1, $1) 34 printf("#else \n") 35 printf(" printf (\"no symbol for _POSIX_%s\\n\"); \n", $1) 36 printf("#endif \n") 37 printf("\n") 38 printf("#ifdef P%s\n", $1) 39 printf(" printf (\"P%s defined to be %%d\\n\", P%s+0); \n", $1, $1) 40 printf("#else \n") 41 printf(" printf (\"no symbol for P%s\\n\"); \n", $1) 42 printf("#endif \n") 43 printf("\n") 44 printf("#ifdef _SC_%s\n", $1) 45 printf(" pr_sysconf (\"sysconf (_SC_%s) = \", _SC_%s); \n", $1, $1) 46 printf("#else \n") 47 printf(" printf (\"no symbol for _SC_%s\\n\"); \n", $1) 48 printf("#endif \n") 49 printf("printf (\"\\n\"); \n") 50 printf("\n") 51 } 52 close ("sysconf.sym") 53 while (getline < "pathconf.sym" > 0) { 54 printf("#ifdef %s\n", $1) 55 printf(" printf (\"%s defined to be %%d\\n\", %s+0); \n", $1, $1) 56 printf("#else \n") 57 printf(" printf (\"no symbol for %s\\n\"); \n", $1) 58 printf("#endif \n") 59 printf("\n") 60 printf("#ifdef _%s\n", $1) 61 printf(" printf (\"_%s defined to be %%d\\n\", _%s+0); \n", $1, $1) 62 printf("#else \n") 63 printf(" printf (\"no symbol for _%s\\n\"); \n", $1) 64 printf("#endif \n") 65 printf("\n") 66 printf("#ifdef _POSIX_%s\n", $1) 67 printf(" printf (\"_POSIX_%s defined to be %%d\\n\", _POSIX_%s+0); \n", $1, $1) 68 printf("#else \n") 69 printf(" printf (\"no symbol for _POSIX_%s\\n\"); \n", $1) 70 printf("#endif \n") 71 printf("\n") 72 printf("#ifdef _PC_%s\n", $1) 73 printf(" pr_pathconf (\"pathconf (_PC_%s) = \", argv[1], _PC_%s); \n", $1, $1) 74 printf("#else \n") 75 printf(" printf (\"no symbol for _PC_%s\\n\"); \n", $1) 76 printf("#endif \n") 77 printf("printf (\"\\n\"); \n") 78 printf("\n") 79 } 80 close ("pathconf.sym"); 81 exit 82 } 83 END { 84 printf(" exit (0); \n") 85 printf("}\n") 86 printf("\n") 87 printf("static void \n") 88 printf("pr_sysconf (char *msg, int name) \n") 89 printf("{\n") 90 printf(" long val; \n") 91 printf(" fputs (msg, stdout); \n") 92 printf(" errno = 0; \n") 93 printf(" if ((val = sysconf (name)) < 0) { \n") 94 printf(" if (errno != 0) { \n") 95 printf(" if (errno == EINVAL)\n") 96 printf(" fputs (\"(not supported)\\n\", stdout); \n") 97 printf(" else \n") 98 printf(" err_sys (\"sysconf error\"); \n") 99 printf(" }\n") 100 printf(" else\n") 101 printf(" fputs (\"(no limit)\\n\", stdout); \n") 102 printf(" }\n") 103 printf(" else\n") 104 printf(" printf (\"%%ld\\n\", val); \n") 105 printf("}\n") 106 printf("\n") 107 printf("\n") 108 printf("static void \n") 109 printf("pr_pathconf (char *msg, char *path, int name) \n") 110 printf("{\n") 111 printf(" long val; \n") 112 printf(" fputs (msg, stdout); \n") 113 printf(" errno = 0; \n") 114 printf(" if ((val = pathconf (path, name)) < 0) { \n") 115 printf(" if (errno != 0) { \n") 116 printf(" if (errno == EINVAL)\n") 117 printf(" fputs (\"(not supported)\\n\", stdout); \n") 118 printf(" else \n") 119 printf(" err_sys (\"pathconf error, path = %%s\", path); \n") 120 printf(" }\n") 121 printf(" else\n") 122 printf(" fputs (\"(no limit)\\n\", stdout); \n") 123 printf(" }\n") 124 printf(" else\n") 125 printf(" printf (\"%%ld\\n\", val); \n") 126 printf("}\n") 127 }
其实原理很简单啦,就是把每一行都用 awk printf 来生成,只是针对常量部分,使用一个循环,分别从文件中读取常量来进行动态生成。这里需要提供两个文件: sysconf.sym 与 pathconf.sym,分别对应不依赖文件系统的常量与依赖文件系统的常量,因为最后它们的调用方法不同,之前已经说明过了。
与书上不同,这里没有让用户分别提供常量的各个名称,以 OPEN_MAX 为例,书上的 sym 文件内容是 ”OPEN_MAX _PC_OPEN_MAX“ 为一行内容,分别指定常量的编译期名称与运行期查询名称。这里感觉有些啰嗦,直接让用户提供一个原始名称 OPEN_MAX,然后我在 awk 脚本里做了一些处理,去拼接生成各种名称。例如还以 OPEN_MAX 为例,它会尝试以下名称:
- OPEN_MAX (原名称)
- _OPEN_MAX (_XXX)
- _POSIX_OPEN_MAX (_POSIX_XXX)
- POPEN_MAX (PXXX)
- _PC_OPEN_MAX (_PC_XXX)
主要起作用的规则是第 1 个与第 3 个,第 2 个规则对于形如 XOPEN_UNIX 的常量有用 (同时存在 _XOPEN_UNIX 与 _PC_XOPEN_UNIX);第 4 个规则对于形如 THREAD_KEYS_MAX 的常量有用 (同时存在 _POSIX_THREAD_KEYS_MAX / PTHREAD_KEYS_MAX / _PC_THREAD_KEYS_MAX);最后一个规则对于 pathconf.sym 而言是 _PC_XXX。
有了这个代码模板和常量定义文件之后,就可以通过 Makefile 将它们串在一起啦:
1 all: conf 2 3 conf: conf.o apue.o 4 gcc -Wall -g $^ -o [email protected] 5 6 conf.o: conf.c ../apue.h 7 gcc -Wall -g -c $< -o [email protected] 8 9 conf.c: conf.awk sysconf.sym pathconf.sym 10 ./$^ > [email protected] 11 12 apue.o: ../apue.c ../apue.h 13 gcc -Wall -g -c $< -o [email protected] 14 15 clean: 16 @echo "start clean..." 17 -rm -f *.o core.* *.log *~ *.swp conf conf.c 18 @echo "end clean" 19 20 .PHONY: clean
这样每次修改 sym 文件后,直接 make 就可以将新的常量包含进去了。下面是运行效果:
我运行的环境是 CentOS 6.7,通过 grep 去掉没有定义的常量 (grep -v 'no symbol') 之后,和在另一台机器上 (NeoKylin)上的运行结果做个对比,如下:
其中 CentOS 是 32 位,而中标麒麟是 64 位。不过好像也看不出来什么很大的差异,而且有些限制通过这样的查询也不一定准确,例如最大打开文件句柄数,很可能就和 ulimit 设置相关,如果想找到确切的限制值的话,建议还是使用 getrlimit 去获取,会更准确一些。