Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1315485
  • 博文数量: 554
  • 博客积分: 10425
  • 博客等级: 上将
  • 技术积分: 7555
  • 用 户 组: 普通用户
  • 注册时间: 2006-11-09 09:49
文章分类

全部博文(554)

文章存档

2012年(1)

2011年(1)

2009年(8)

2008年(544)

分类:

2008-04-07 18:37:59

过去,运行于 Solaris 操作系统上的 32 位应用程序只能通过 C 库中的标准 I/O 使用最多 255 个文件描述符。Solaris 10 及更高版本操作系统中的 FILE 扩展工具允许行为良好的 32 位应用程序通过标准的 I/O 功能使用任何有效的文件描述符。

行为良好的应用程序指满足以下所有三个要求的应用程序:

  • 不直接访问 FILE 结构中任何由与任何标准 I/O 流相关的 FILE 指针指向的成员字段。
  • 检查所有标准 I/O 函数的返回值以便发现错误状态
  • 当报告错误状态时表现正常

本文详细介绍了 FILE 扩展工具所引入的运行时和编程解决方案。以下讨论仅针对 32 位应用程序,因为 64 位应用程序不受 256 个文件描述符限制的影响。

背景资料

在网上快速搜索 将会产生无数个 Solaris 操作系统限制 Stdio 最多只能使用 256 个指示符的结果。 解释了该问题:32 位标准输入输出例程应该支持大于 255 个文件指示符。错误报告链接到少数其他与标准输入输出的 256 个文件指示符限制相关的问题。

Solaris 操作系统上有此限制,是因为 unsigned char 用于存储与标准输入输出流相关的文件指示符的值。查看您可以在 Solaris 操作系统上的头部 /usr/include/stdio_impl.h 中发现的 FILE 结构的定义(该 Solaris 操作系统没有本文讨论的 解决方案):

struct __FILE_TAG       
{
#ifdef _STDIO_REVERSE
unsigned char *_ptr;
int _cnt;
#else
int _cnt;
unsigned char *_ptr;
#endif
unsigned char *_base;
unsigned char _flag;
unsigned char _file;
/* UNIX System file descriptor */

unsigned __orientation:2;
unsigned __ionolock:1;
unsigned __seekable:1;
unsigned __filler:4;
};

 

名称 __FILE_TAG 只是 FILE 的别名。请参阅

成员字段 _file 保存了文件描述符,该文件描述符被声明为 unsigned char 类型。一个 unsigned char 在内存中占 8 位。因此 _file 可以保存的最大值为 2^8 = 256。换句话说,_file 对每个 32 位进程限制访问 256 个文件描述符。该限制被清楚地记录在 的手册页。

Sun 过去没有从 8 位 unsigned char 转变为 16 位 int 来适应更多的文件描述符。这样做会破坏与早期 Solaris 操作系统版本的双边兼容性,因为这会更改 FILE 结构的大??/p>

解决方案

从2007年7月发布的Solaris 10开始,Sun将会以扩展的 FILE 工具的形式提供运行时和编程解决方案,以减轻 stdio(标准输入输出)的256个文件描述符限制。运行 版本或任何 将会拥有这些功能。本文的 补丁和缺陷 一节包含了在任何运行从 Solaris 10 3/05 到 Solaris 10 11/06 版本的系统上安装扩展的 FILE 工具的说明。

运行时解决方案

像这一节的标题暗示的一样,运行时解决方案不需要更改任何源代码或重新编译对象,以克 服 C 库函数的 256 个文件描述符限制。但是,现有 32 位应用程序的默认行为将不会更改,除非您明确启动扩展的 FILE 工具。启用了此功能的应用程序将可以关联任何具有标准 I/O -- 或 stdio -- 流的有效文件描述符。任何 3 以内的值以及由 ulimit -n 从用于启动应用程序的 shell 返回的值都是有效的文件描述符。文件描述符 0、1、和 2 被保留用作默认 stdinstdoutstderr I/O流。

您可以增加 shell 中每个流程的最大文件描述符数目,从默认的 256 到任何少于或等于该命令的返回值的值均可。

echo 'rlim_fd_max/D' | mdb -k | awk '{ print $2 }'

 

要调整 shell 中的文件描述符限制,请在 sh/ksh/bash 运行 ulimit -n 或者在 csh运行 limit descriptors ,这里 max_file_descriptors为您想要的文件描述符的最大数目。

每个进程任何时候可以打开的文件的数目的默认限制为 65,536。您可以使用系统可调节的参数 来调节此限制。尽管可以通过调节 rlim_fd_max 参数来打开大量文件,但是当打开成百上千的文件时,虚拟内存空间会成为32位进程的限制。当进程达到虚拟内存的限制时,stdio 会调用失败,出现 Not enough space(没有足够空间)错误。

在运行 32 位应用程序之前,通过执行以下操作启用扩展的 FILE 工具:

  1. 调高 shell 中文件描述符的最大数目。
  2. 预载扩展的 FILE 工具,/usr/lib/extendedFILE.so.1

注意:extendedFILE.so.1 不是库,而是扩展的 FILE 工具的启用程序。

这里是如何从 ksh 启用扩展的 FILE 工具的方法:

% ulimit -n
256

% echo 'rlim_fd_max/D' | mdb -k | awk '{ print $2 }'
65536

% ulimit -n 65537
ksh: ulimit: exceeds allowable limit

% ulimit -n 65536

% ulimit -n
65536

% export LD_PRELOAD_32=/usr/lib/extendedFILE.so.1
% application [arg1 arg2 .. argn]

 

以下示例显示了启用或未启用扩展的 FILE 工具的简单 32 位进程的行为。用于测试的是一个简单的 C 程序,尝试使用 fopen() 接口打开 65,536 个文件。

% cat fopentestcase.c

#include
#include

#define NoOfFILES 65536

int main()
{
char filename[10];
FILE *fds[NoOfFILES];
int i;

for (i = 0; i < NoOfFILES; ++i)
{
sprintf (filename, "/tmp/%d.log", i);
fds[i] = fopen(filename, "w");

if (fds[i] == NULL)
{
printf("\nNumber of open files = %d. " \
"fopen() failed with error: ", i);
perror("");
exit(1);
}
else
{
fprintf (fds[i], "some string");
}
}
return (0);
}

 

使用 shell 中文件描述符的默认最大数目重现失败:

% cc -o fopentestcase fopentestcase.c

% ulimit -a | grep descriptors
nofiles(descriptors) 256

% ./fopentestcase
Number of open files = 253. fopen() failed with error:
Too many open files

 

提高文件描述符限制,启用扩展的 FILE 工具,并运行再次运行测试,查看运行时解决方案的运行。

% ulimit -n 5000

% ulimit -a | grep descriptors
nofiles(descriptors) 5000

% export LD_PRELOAD_32=/usr/lib/extendedFILE.so.1

% ./fopentestcase
Number of open files = 4996. fopen() failed with error:
Too many open files

% ulimit -n 65536

% ulimit -a | grep descriptors
nofiles(descriptors) 65536

% ./fopentestcase
Number of open files = 65532. fopen() failed with error:
Too many open files

 

看到缺少一个文件描述符 -- 不包括 stdinstdoutstderr 各自的 0、1 和 2 -- 在上述示例中。当启用扩展的 FILE 工具时,文件描述符 196 默认将变得不可分配,以最小化无记录的数据损坏。有关更多信息,请参阅下一节 环境变量

这里是确认此陈述的 pfiles 输出:

% pfiles `pgrep fopentestcase` | egrep "log|:"
...
195: S_IFREG mode:0644 dev:102,7 ino:7380 uid:209044 ...
/tmp/192.log
197: S_IFREG mode:0644 dev:102,7 ino:7381 uid:209044 ...
/tmp/193.log
...

 

环境变量

两个环境变量控制扩展的 FILE 工具的行为:_STDIO_BADFD_STDIO_BADFD_SIGNAL

  • -- 该变量使用任何 3 至 255 之间的整数值,该值将作为文件描述符变得不可分配。设置此环境变量将对行为未知的软件提供一种保护机制,比如没有源代码的第三方库,这样应用程序将不会经历没有记录的数据损坏。缺少此环境变量时,默认的值 196 将会在运行时被标记为不可分配的文件描述符。

    如您所知,在扩展的 FILE 工具出现之前,在 Solaris 操作系统上构建的对象代码将不会期望任何不符合 8 位 unsigned char 的文件描述符,并且将无法理解如何处理扩展的FILE指针。因为这些原因,范围被限制在3 至 255,所以当实际的描述符其实是扩展的文件描述符时(即,任何大于255的值),通过重新引用 FILE -> _file 而不是 fileno(3C) 函数查询文件描述符值的代码将会收到不能分配或错误的文件描述符。

  • -- 此环境变量用于当未经检查的代码尝试修改不能分配的文件描述符时指定发送到进程的信号。此变量使用代表任何有效信号的整数或字符串。有关 Solaris 操作系统上所有支持的信号的有效值或字符串的更多信息,请参阅 。如果在该扩展 FILE 工具的使用期间检测到某个意外事件发生,则该变量使指定的信号被发送到应用程序。默认信号为 SIGABRT
何时不使用此解决方案

如果应用程序执行以下操作,则不要启用扩展的 FILE 工具:

  • 直接取消引用FILE结构的 _file 成员。
  • 使用 fileno() 宏(已从 Solaris 2.7 版的头部去除)而不是 fileno(FILE) 函数来获取底层文件描述符的值。

当启用此功能后,大于 255 的文件描述符将被存储在一个应用程序未知的备用位置,而环境变量 _STDIO_BADFD 保存的不可分配或错误文件描述符将被存储在 FILE -> _file 成员字段中。当实际的底层文件描述符大于 255 时,应用程序不恰当地访问 FILE -> _file 成员字段将会产生不可分配的错误文件描述符,因此导致未记录的数据错误。

如果进程截断了由 fileno(FILE) 函数返回的值,也会发生数据损坏。例如,如果 fileno() 函数返回的 16 位或 32 位的 int 值被存储在一个 8 位的 unsigned char 变量中,则会进行截断。访问被截断的文件描述符可能会产生错误。

以下运行时的错误消息就清楚显示了应用程序修改 stdio 中的 FILE 结构的内部文件描述符成员字段。

Application violated extended FILE safety mechanism.
Please read the man page for extendedFILE.
Aborting

 

当您收到这样的错误消息时,请停止对应用程序使用扩展的 FILE 工具。如果可能,通过将所有对 FILE -> _file 的引用替换为对 fileno(FILE) 的调用来修复资源。忽略此运行时错误将会导致数据损坏。

示例

以下的小示例展示了两个环境变量 _STDIO_BADFD _STDIO_BADFD_SIGNAL 的用法,并其显示了当代码违反FILE安全机制时后续程序崩溃。

在任何运行于没有扩展的 FILE 解决方案的 Solaris 操作系统上编译以下代码并构建一个库。注意:运行 Solaris 10 3/05 到 Solaris 10 11/06 版本的未打补丁的系统不包含扩展的 FILE 解决方案。但是,通过应用最新的 kernel 和 libc 补丁,在这些系统上安装扩展的 FILE 工具是可以的。相关说明请参阅本文的 补丁和缺陷 一节。

% cat thirdpartysrc.c

#include

void manipulatefd (FILE *fptr)
{
;
;
fprintf(stdout, "\n%s : manipulatefd(): " \
"underlying file descriptor = %d\n", \
__FILE__, fptr -> _file);
fptr -> _file = 123;
fprintf(fptr, "This call is gonna fail!\n");

;
;
}

% cc -G -o /tmp/libthirdparty.so thirdpartysrc.c

 

在任何运行具有扩展的 FILE 解决方案的 Solaris 操作系统的系统上,通过将对象代码和上一步中创建的库链接起来编译以下代码并建立一个可执行文件:

% cat enableextfile.c

#include
#include

#define NoOfFiles 500

void manipulatefd(FILE *);

int main ()
{
FILE *fptr;
int i;

for (i = 0; i < NoOfFiles; i++)
{
fptr = fopen("/tmp/enable_test.txt", "w");

if (fptr == NULL)
{
perror("fopen failed. ");
exit(1);
}

printf("\nfd = %d", fileno(fptr));

if (fileno(fptr) % 400 == 0)
{
manipulatefd(fptr);
}
}

return(0);
}

% export LD_LIBRARY_PATH=/tmp:$LD_LIBRARY_PATH
% cc -o enableextfile -lthirdparty enableextfile.c

 

将每个进程最大文件描述符限制提高为任何大于 255 的数字,设置环境变量 _STDIO_BADFD _STDIO_BADFD_SIGNAL,通过预载 /usr/lib/extendedFILE.so.1 启动扩展的 FILE 工具,最后运行可执行文件:

% ulimit -n
256

% ulimit -n 500

% ulimit -n
500

% export _STDIO_BADFD=196
% export _STDIO_BADFD_SIGNAL=SIGABRT
% export LD_PRELOAD_32=/usr/lib/extendedFILE.so.1

% ./enableextfile
fd = 3
fd = 4
fd = 5
...
...
fd = 398
fd = 399
fd = 400
thirdpartysrc.c : manipulatefd():
underlying file descriptor = 196
Application violated extended FILE safety mechanism.
Please read the man page for extendedFILE.
Aborting

Abort(coredump)

% /usr/bin/pstack core
core 'core' of 10172: ./enableextfile
d1f28e65 _lwp_kill (1, 6) + 15
d1ee2102 raise (6) + 22
d1ec0dad abort (0, 80677e0, d1f60000, ...) + cd
d1f01d54 _file_get (80677e0) + b4
d1efeb21 _findbuf (80677e0) + 31
d1ef2f16 _ndoprnt (d1f70344, 80471d4, 80677e0, 0) + 46
d1ef669f fprintf (80677e0, d1f70344) + 9f
d1f702cb manipulatefd (80677e0) + 3b
0805097f main (1, 8047214, 804721c) + 9f
0805084a _start (1, 8047360, 0, 8047370, ... ) + 7a

 

如果应用程序没有显示任何以前提到的参数,则它可以利用此运行时解决方案而不管其年纪 -- 即,即使在 Solaris 7 或更早版本上建立的应用程序也可能继续正常工作。

有关更多示例,请参阅 的手册页面。

编程解决方案

此节用于新应用程序以及开发人员可以轻易修改的应用程序。

两个编程接口将允许访问大于 256 的文件描述符 FILE 池,提供的最大文件描述资源限制已经被提高了。注意默认的最大文件描述符限制仍为 256。

增强的标准 I/O 打开调用:fopen(3C)fdopen(3C)popen(3C)

为了降低修改现有资源以利用扩展的 FILE 功能的工作量,stdio 打开调用(比如 fopen(3C)fdopen(3C) popen(3C))的现有模式字符串增加了新标记:F。例如:

FILE *fptr = fopen("dummy.txt", "rF");

int fd = creat("dummy2.txt", S_IWUSR);
FILE *stream = fdopen(fd, "wF");

FILE *ptr = popen("/usr/bin/ls *.txt", "rF");

 

如果该模式字符串的最后一个字符为 F,则 32 位应用程序将被允许将流和通过值大于 255 的文件描述符访问的文件关联起来。在 64 位应用程序的情况下,应用程序将不加记录地忽略模式字符串中的字符F。除了这个小的增强,stdio 打开调用的现有语法没有更改。

stdio 打开调用的模式字符串中的F仅用于不执行以下操作的代码:

  • 直接取消引用 FILE 结构的成员字段。
  • 返回一个 FILE 指针给调用程序

如果应用程序展示了任何以前提到的模式,则字符 F 一定不能附加到模式字符串中以启动扩展的 FILE 功能。当模式的最后一个字符是F时,如果 32 位的应用程序直接使用 FILE 结构中的成员字段,则会出现数据损坏。

当扩展的 FILE 指针被返回到用户未知的二进制代码(即意外代码)时也可能出现数据损坏,因为调用程序可能不知道怎么处理它。该接口没有对误操作 FILE 指针提供任何保护。如果 FILE 指针必须返回到任何意外代码,请考虑在代码中以高水平使用 启用扩展的_FILE_stdio(3C)

要用法实例,更改下行后重建测试:

fds[i] = fopen(filename, "w");

 

更改为:

fds[i] = fopen(filename, "wF");

 

现在提高 shell 中的文件描述符限制,然后再次运行测试查看结果:

% cc -o fopentestcaseF fopentestcase.c

% ulimit -n 10000

% ulimit -a | grep descriptors
nofiles(descriptors) 10000

% ./fopentestcaseF
Number of open files = 9996. fopen() failed with error:
Too many open files

 

记下对任何特定库缺少的连接以使其工作。所有 stdio 例程仍是 libc 的一部分。

有关详细信息,请参阅手册页 、和。

新的编程接口:enable_extended_FILE_stdio(3C)

如果 FILE 指针不是被限制在单个功能环境内,您可以使用新的编程接口 enable_extended_FILE_stdio(3C) 来启用扩展的 FILE 功能。此接口通过对行为未知的软件提供一些保护机制来最小化数据损坏,比如没有源代码的第三方库。例如,通过使用此接口,当应用程序不适当地取消对 FILE -> _file 的引用时,用户可以选择任何信号在运行时发送到进程。

该新接口在 /usr/include/stdio_ext.h头部中定义如下:

int enable_extended_FILE_stdio(int, int);

 

第一个参数是整数,指定了应用程序想要选择作为不可分配的文件描述符的文件描述符(从 3 至 255)。相反,将其设置为-1将会要求 enable_extended_FILE_stdio(3C) 选择一个合理的不可分配的文件描述符。这等同于当为扩展的 FILE 启用 runtime solution 时设置环境变量 _STDIO_BADFD

第二个参数是整数,指定当不可分配的文件描述符被用作所有系统调用(除了 close(2) closefrom(3C))的文件描述符时,要发送给进程的信号。一些应用程序可能尝试关闭它们没有打开的文件描述符。该例外可防止应用程序因为这样无害的调用崩溃。

如果忽略 -1,则默认的信号 SIGABRT 将被发送到进程。值 0 会通过禁止发送信号忽略任何 FILE -> _file 取消引用。否则,指定的信号将被发送到进程。有关 Solaris 操作系统上完整的信号了列表,请参阅 手册页。这等同于当为扩展的FILE启用运行时解决方案时设置环境变量 _STDIO_BADFD_SIGNAL

enable_extended_FILE_stdio(3C) 功能只在 32 位编译环境下可用。

要使扩展的 FILE 工具发挥作用,将进程的文件描述符默认最大限制从 256 提高到任何小于或等于进程在任何时间可以打开的文件数的硬性限制。有关详细信息,请查看 kernel 可调节的 。您这么做,您可以从 shell 中使用 ulimit/limit 命令,或者通过在 /usr/include/sys/resource.h 中定义的函数 getrlimit(2)/setrlimit(2) 才有编程来进行。

接下来的小编程实例展示了三点:

  • 通过 getrlimit(2)/setrlimit(2) 接口设置文件描述符限制。
  • 此新功能启用扩展的 FILE 工具的用法
  • 当某个意外代码通过直接更改 FILE -> _file 的值滥用底层文件描述符时,应用程序会崩溃

编译以下代码,并通过链接对象代码和库 libthirdparty.so(在本文的 运行时解决方案一节中创建)建立一个可执行文件。

% cat enableextfilestdio.c

#include
#include
#include
#include

#define NoOfFiles 500

void manipulatefd(FILE *);

int main ()
{
FILE *fptr;
struct rlimit rlp;
int i;

(void) getrlimit (RLIMIT_NOFILE, &rlp);
/* set the desired number of file descriptors */
rlp.rlim_cur = NoOfFiles;

if (setrlimit (RLIMIT_NOFILE, &rlp) == -1)
{
perror ("setrlimit(): ");
exit (1);
}

if (enable_extended_FILE_stdio (-1, -1) == -1)
{
perror ("enable_extended_FILE_stdio(3C): ");
exit (1);
}

for (i = 0; i < NoOfFiles; i++)
{
fptr = fopen ("/tmp/enable_test.txt", "w");

if (fptr == NULL)
{
perror("\nfopen failed. ");
exit (1);
}

printf ("\nfd = %d", fileno(fptr));

if (fileno (fptr) % 400 == 0)
{
manipulatefd (fptr);
}
}

return (0);
}

% export LD_LIBRARY_PATH=/tmp:$LD_LIBRARY_PATH
% cc -o enableextfilestdio -lthirdparty enableextfilestdio.c

% ./enableextfilestdio
fd = 3
fd = 4
fd = 5
...
...
fd = 398
fd = 399
fd = 400
thirdpartysrc.c : manipulatefd():
underlying file descriptor = 196
Application violated extended FILE safety mechanism.
Please read the man page for extendedFILE.
Aborting
Abort
(core dumped)

 

有关更多信息,请参阅 的手册页面。

警告:在 Solaris 操作系统的下一个主要客户版中,_file 变成了 _magic

为了确保使用扩展的 FILE 机制的安全性,FILE 结构中的 _file 成员已经在 中被有意地重命名为 _magic(正在开发的 Solaris 操作系统的下一个主要客户版本的每月快照),并且在 源代码基础中也是如此(建立39后)。这个更改将破坏包含任何对FILE -> _file的引用的源代码的兼容性。注意:此警告不适用于 Solaris 10 操作系统,包括更新版本。

以下 diff 输出显示了在 FILE 结构的定义中引入以适应扩展的 FILE 工具的更改:

- unsigned char _file;  /* UNIX system file descriptor */
+ unsigned char _magic; /*Old home of the file descriptor*/
+ /* Only fileno(3C) can retrieve the value now */


- unsigned __filler:4;
+ unsigned __extendedfd:1; /* enable extended FILE */
+ unsigned __xf_nocheck:1; /*no extended FILE runtime check*/

+ unsigned __filler:10;

 

因此,在运行 或任何 的系统上以及在 Solaris 10 以后版本上(当其可用时)编译具有对 FILE -> _file 的引用的代码会失败,错误代码如下。

"filename.c", line xx: undefined struct/union member: _file
cc: acomp failed for filename.c

 

正式名称为 _file 的字段中的值可能不再包含 FILE 的文件描述符。如果该代码只是读取 _file 的值,使用更合适的 fileno(FILE) 函数代替所有这些引用。请参阅 的手册页。不应该再向 _file 指定新值。

对运行时性能的影响

当启用扩展的 FILE 工具后,访问小于或等于 255 的文件描述符时对性能没有影响。但是在访问大于或等于 256 的文件描述符时,由于在备用位置存储和检索文件描述符,性能会有轻微降低。

补丁和缺陷

如果您的系统运行的是 Solaris 10 操作系统的任何现有版本 -- 即,Solaris 10 3/05 到 Solaris 10 11/06 -- 您可以在系统上使用以下三个用于硬件平台的补丁(或更高版本)安装扩展的 FILE 工具:

SPARC 平台:

x86/x64 平台:

如果应用程序代码与 链接,则 Sun Studio 软件编译器套件随附的 C++ 标准库,使用 -library=stlport4 编译器选项,确保在 SPARC 平台上为 Sun Studio 11 软件打上 121017-07 或更新补丁,在 x86/x64 平台上打上 121018-07 或更新补丁,以便利用扩展的 FILE

以上文章转自于 : http://developers.sun.com.cn/

 

阅读(1119) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~