在Perl中打开超过10,000个文件的问题

7

我需要在Perl脚本中打开超过10,000个文件,因此我请求系统管理员将我的账户限制更改为14,000。 ulimit -a现在显示以下设置:

core file size        (blocks, -c) unlimited
data seg size         (kbytes, -d) unlimited
file size             (blocks, -f) unlimited
open files                    (-n) 14000
pipe size          (512 bytes, -p) 10
stack size            (kbytes, -s) 8192
cpu time             (seconds, -t) unlimited
max user processes            (-u) 29995
virtual memory        (kbytes, -v) unlimited

更改后,我运行了一个测试Perl程序,它打开/创建256个文件,并在脚本结束时关闭256个文件句柄。当它创建253个文件时,程序会因打开太多文件而崩溃。我不明白为什么会出现这个错误。

我正在使用Solaris 10平台。以下是我的代码:

my @list;
my $filename = "test";

for ($i = 256; $i >= 0; $i--) {
    print "$i " . "\n";
    $filename = "test" . "$i";
    if (open my $in, ">", ${filename}) {
        push @list, $in;
        print $in $filename . "\n";
    }
    else {
        warn "Could not open file '$filename'. $!";
        die;
    }
}

for ($i = 256; $i >= 0; $i--) {
    my $retVal = pop @list;
    print $retVal . "\n";
    close($retVal);
}

3
你是否有其他正在运行的进程打开了这些文件? - lc.
for ($i = 256; $i >= 0; $i--) 失败时,会生成 257 个文件。请问您在失败时得到了什么输出? - Borodin
4个回答

16
根据这篇文章,这是32位Solaris的默认限制。程序通常只能使用前256个文件编号。STDIN、STDOUT和STDERR占用了0、1和2,剩下253个。绕过它不是一个简单的过程,ulimit做不到这一点,我不知道Perl是否会遵守它。
在Perlmonks上有关此问题的讨论,提供了一些建议的解决方法,如FileCache
虽然Solaris的限制是不可原谅的,但通常拥有数百个打开的文件句柄表明您的程序可能设计得更好。

非常感谢提供的信息。FileCache 起作用了。它是如何工作的,以及它是如何克服操作系统限制的?JAVA 和 C 程序是否有单独的模块像 FileCache 一样使用多个文件句柄? - Arav
1
是的,除非它们已经按照文章中所述进行了特殊编写和编译,否则它会影响Solaris 10上的每个32位程序。这里有一个Java人遇到了同样的问题和另一个人。尽管Oracle拥有Solaris和Java,但我怀疑他们已经为Java解决了这个问题......但如果他们没有解决,我也不会感到太惊讶。我不知道Java和C程序员如何解决这个问题。 - Schwern
1
C程序员通过编译64位或使用扩展的FILE API来解决这个问题,可以通过源代码更改或LD_PRELOAD选项实现。 - alanc

8
你可能可以通过FileCache核心模块来解决此限制(保持比系统允许更多的文件打开)。
使用cacheout代替open,我能够在Linux上打开100334个文件:
#david@:~/Test$ ulimit -n
1024

#david@:~/Test$ perl plimit.pl | head
100333 
100332 
100331 
100330 
100329 

#david@:~/Test$ perl plimit.pl | tail
test100330
test100331
test100332
test100333

#david@:~/Test$ ls test* | wc -l
100334


您的脚本的修改版本(plimit.pl)

my @list;

use FileCache;

$mfile=100333;

my $filename="test";
for($i = $mfile; $i >= 0; $i--) {
    print "$i " . "\n" ;
    $filename = "test" . "$i";
    #if (open my $in, ">", ${filename}) {
     if ($in = cacheout( ">", ${filename}) ) {
        push @list,$in;
        print $in  $filename . "\n";
    } else {
        warn "Could not open file '$filename'. $!";
        die;
    }
}
for($i = $mfile; $i >= 0; $i--) {
    my $retVal = pop @list;
    print $retVal . "\n";
    close($retVal);
}

更新

FileCache 会在您超过系统最大文件描述符或建议的最大 maxopen(在 sys/param.h 中定义的 NOFILE)时自动关闭和重新打开文件。

在我的情况下,我使用的是 Linux 系统,最大值为 256:

#david@:~/Test$ grep -B 3 NOFILE /usr/include/sys/param.h 

/* The following are not really correct but it is a value 
   we used for a long time and which seems to be usable.  
   People should not use NOFILE and NCARGS anyway.  */
#define NOFILE      256

使用lsof(列出打开文件)命令,您的脚本的修改版本最多打开了100334个文件中的260个:
#david@:~/Test$ bash count_of_plimit.sh
20:41:27 18
new max is 18
20:41:28 196
new max is 196
20:41:29 260
new max is 260
20:41:30 218
20:41:31 258
20:41:32 248
20:41:33 193
max count was 260


count_of_plimit.sh

 #!/bin/bash
 # count open files with lsof
 #
 # latest revision: 
 #   ftp://lsof.itap.purdue.edu/pub/tools/unix/lsof/
 # latest FAQ: 
 #  ftp://lsof.itap.purdue.edu/pub/tools/unix/lsof/FAQ

 perl plimit.pl > out.txt &
 pid=$!

##adapted from https://dev59.com/VXI-5IYBdhLWcg3w0MDx#1661498
HOW_MANY=0
MAX=0
while [ -r "/proc/${pid}" ]; 
do
    HOW_MANY=`lsof -p ${pid} | wc -l`
    #output for live monitoring
    echo `date +%H:%M:%S` $HOW_MANY
    # look for max value
    if [ $MAX -lt $HOW_MANY ]; then
        let MAX=$HOW_MANY
        echo new max is $MAX
    fi 
    # test every second
    sleep 1
done
echo max count was $MAX

非常感谢提供的信息。FileCache 起作用了。FileCache 是如何工作的,它是如何克服操作系统限制的?这是否适用于 JAVA 和 C 程序,是否有单独的模块像 FileCache 可以使用多个文件句柄? - Arav
1
@Arav - FileCache文档包含了它如何工作的准确描述。我不知道C或Java有类似的模块。 - David L.

4

在Windows和Linux系统上,使用您的程序和以下简单程序进行测试,未遇到您所描述的错误。

my @files;
for (;;) {
   print 1+@files, "\n";
   open my $fh, '<', $0 or die $!;
   push @files, $fh;
   last if @files == 500;
}

输出:

1
2
...
498
499
500

我认为这不是Perl的限制,而是系统的限制。

请注意,在尝试打开进程的第257个句柄(STDIN + STDOUT + STDERR + 253 = 256)时失败,这让我相信进程可以拥有的打开文件句柄数量必须适合您的系统上的8位。您可以尝试编写等效的C程序,并在同一台机器上运行它以验证此问题。

#include <stdio.h>
#include <stdlib.h>

int main() {
   int i = 0;
   for (;;) {
      ++i;
      printf("%d\n", i);
      if (fopen("/bin/sh", "r") == NULL) {
         perror("fopen");
         exit(1);
      }

      if (i == 500)
         break;
   }

   return 0;
}

更新:这已经被确认 在这里。感谢Schwern。


非常感谢提供的信息。我尝试运行C程序,但是它给了我一个错误提示:/usr/ucb/cc: 未安装语言可选软件包。 - Arav
已检查gcc,但未安装。我没有root用户ID,我可以下载并使用它吗?在哪里可以找到适用于Solaris 10的cc编译器? - Arav

0

您最多只能使用256个文件。您忘记了STDIN、STDOUT和STDERR。您的253个文件加上默认的3个文件等于256个文件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接