2

kernal32.dll FindFirstFileからファイル情報を取得することにより、ディレクトリの全体的なサイズを返す32ビットのc#アプリケーションを作成しています。これにより、各ディレクトリを通常の方法で列挙することに成功し、リソースの使用量を非常に低く抑えることができます。

これがどのように機能するかの概要は次のとおりです。

  • ステップ1-すべてのサブディレクトリを取得し、FindFirstFileを使用してこのディレクトリ内の各ファイルのサイズ情報を収集するルートディレクトリを列挙します。
  • ステップ2-子スレッド(最大20)を生成して、サブディレクトリに対してステップ1を実行します
  • ステップ3-ディレクトリが使い果たされ、すべてのファイル情報が収集されるまで繰り返します。

これは、FileSystem.GetFilesがkernal32メソッドを利用してファイル情報を取得する私のクラスである次のコードサンプルで見ることができます。

      private static void recurseDirectories(string directoryA, bool paramInitialPass)
    {
        try
        {
            string[] currentDirs;
            if (paramInitialPass)
            {
                currentDirs = new string[1];
                currentDirs[0] = rootDirectory;
            }
            else
                currentDirs = Directory.GetDirectories(directoryA);

            for (int i = 0; i < currentDirs.Length; i++)
            {

                string threadInfo = currentDirs[i];
                numThreadsQueued++;
                ThreadPool.QueueUserWorkItem(new WaitCallback(getDirectoryFileInformation), (object)threadInfo);
                while (numThreadsQueued - directoriesProcessed > 20)
                {
                    Thread.Sleep(30);
                }
                if (paramInitialPass)
                    recurseDirectories(directoryA, false);
                else
                    recurseDirectories(currentDirs[i], false);
            }
        }
        catch
        {

        }
        return;
    }


    private static void getDirectoryFileInformation(object paramDirectoryFilePathA)
    {
        try
        {
            string directoryPathA = (string)paramDirectoryFilePathA;
            List<FileData> filesDirectoryA = new List<FileData>();
            if (Directory.Exists(directoryPathA))
            {
                    filesDirectoryA = FileSystem.GetFiles(directoryPathA);
            }
            foreach(FileData file in filesDirectoryA)
            {
                Interlocked.Add(ref sizeOfFiles, file.Size);
                Interlocked.Increment(ref numberOfFiles);
            }               
        }
        catch (Exception e)
        {

        }
        finally
        {
            Interlocked.Increment(ref directoriesProcessed);
        }
    }

これらの2つのメソッドは、次のコードを使用して呼び出されます。

 ThreadPool.SetMaxThreads(30, 500);
 Thread.CurrentThread.Priority = ThreadPriority.Normal;
 rootDirectory = share["Path"].ToString();
 recurseDirectories(share["Path"].ToString(), true);
 while (numThreadsQueued != directoriesProcessed)
 {
        Thread.Sleep(1000);
 }

このコードは、ほとんどのディレクトリを列挙している間、問題なく実行されました。CPUを3%未満に保ち、15 MBのメモリを使用しながら、約8分で合計ファイルサイズとファイル数を取得する3TBファイル共有を再帰的に実行できます。

今問題が来る...

小さなディレクトリ(1〜200 GB)のサイズを取得するとき、ディレクトリのプロパティを見るときにWindowsが言うこととの大きな違いは見られません。ただし、大きなディレクトリ(2〜3 TB)のサイズを取得するときに、いくつかの大きな違いに気づきました。

例えば:

別のサーバーに複製されたDFSRであるディレクトリD:\TestDirを見ているとしましょう。Windowsによると、このディレクトリはディスク上で2,949,944,019,217バイト、つまり2,974,186,774,528バイト(それぞれ2.68TBまたは2.70TB)です。私のプログラムによると、このディレクトリは3,009,619,048,759バイトまたは2.737TBです。FSRMによると、同じディレクトリでのクォータ設定の使用量は2.71TBです。

違いの一部は、Windowsのサイズに隠しファイルが含まれていないことが原因ですが、ディレクトリ内の隠しファイルの合計サイズ(87GB)をWindowsの値に追加すると、最大2.78 GBになりますが、これはまだ私の値とは異なります。誰かが私がこれらのサイズの違いを引き起こしている他の何かに光を当てることができますか?また、FSRMがクォータの使用を決定する方法を知っている人はいますか?

最終的には、FSRMクォータをデータを使用する監視システムに置き換えたいのですが、データがWindowsの指示と一致しない場合、ディスク使用量について誤ったアラームが発生する可能性があります。

4

2 に答える 2

1

いくつかの詳細なテストの後、これはkernal32.dllFindFirstFileメソッドのバグになりました。

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern SafeFindHandle FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData)

この関数は、名前、サイズ、最終変更時刻など、特定のファイルに関する情報を含むクラス「WIN32_FIND_DATA」を返します。この関数によって返されるサイズをSystem.IO.FileInfoによって返されるサイズと比較するテストを実行しました。クラスと非常に小さなファイルのセットでいくつかの明らかな違いを見つけました。約150万個のファイルを含むファイル共有に対してこれを実行すると、2つのファイルのサイズが大幅に異なり、次のように返されます。

FILE 1
FileInfoに準拠したサイズ:18158717658バイト
WIN32_FIND_DATAに準拠したサイズ:978848478バイト

FILE 2
FileInfoに準拠したサイズ:18211490304バイト
WIN32_FIND_DATAに準拠したサイズ:1031621124バイト

どちらの場合も、サイズの違いはほぼ正確に16GBです。

この問題を回避するために、引き続きKernal32.dll関数を使用してファイルパスを取得しますが、FileInfoを使用してサイズを取得します。これにより、パフォーマンスに影響を与えることなく、良好な結果が得られるようです。

于 2013-01-02T14:41:46.480 に答える
0

アプリケーションが32ビットとしてコンパイルされているとおっしゃいました。64ビットシステムで実行していますか?たとえば、32ビットアプリケーションC:\Windows\System32が実際にを読み取ろうとすると、ファイルシステムのリダイレクトが発生する可能性がありますC:\Windows\SysWOW64。p/invokeが必要になる場合がありますWow64DisableWow64FsRedirection

したがって、FileInfo非常に大きなファイルのサイズを正しく報告することはできますが、それでも回答の一貫性が保たれる可能性がありますが、それでも正しくありません。とにかくp/invokeを使用しているのはなぜですか?

さらに、NTFSファイルシステムは、単一のファイルに複数のディレクトリエントリがあるハードリンクをサポートしています。ただし、コンテンツにディスク領域を使用するのは1回だけです。「リンクカウント」メタデータを読み取り、ファイルサイズをそのフィールドで割ることで、おそらくこれを処理できます。この場合、Win32APIをp/invokeする必要があります。構造GetFileInformationByHandleEx内の情報の代わりに(クエリ権限でファイルを開いた後)使用することもできます。WIN32_FIND_DATA

この問題は見た目よりも難しいです。

于 2013-01-02T15:22:28.060 に答える