Bashでは、プロセステーブルはどのくらいのメモリを占めていますか?

Bashでは、プロセステーブルはどのくらいのメモリを占めていますか?

バックグラウンドタスクを生成し続けるが、waitコマンドを呼び出さないbashスクリプトに関してpid漏れの可能性を確認していましたが、偶然に(straceを介して)BashがSIGCHLDを監視し、wait4(...)を自動的に呼び出すことを知っています。されました。 、私のスクリプトがwaitコマンドを呼び出さなくても。これがPID漏れがない理由です。これは良いことです。しかし、その背景PIDに対して待機コマンドを呼び出すとどうなるか考え始めました。 /proc には存在せず、エラーを返す必要があります。 Bashはこれをどのように処理しますか? Bash 4.4.19と5.1.16でいくつかの実験を行った結果、Bash waitコマンドが実際にバックグラウンドジョブキャッシュから結果を得ることがわかりました。ソースコードも確認しました。たとえば、次のようになります。ヘビーストライク 5.1.16 、buildins/wait.defライン253を参照してください。

status = wait_for_single_pid (pid, wflags|JWAIT_PERROR);

その後、job.cライン2611

r = bgp_search (pid);

意味は

/* Search for PID in the list of saved background pids; return its status if
   found.  If not found, return -1.  We hash to the right spot in pidstat_table
   and follow the bucket chain to the end. */

私の実験は

テスト1:

bash <<'EOF'
  bash -c 'sleep 1; exit 9' &
  PID=$!
  echo $PID
  sleep 2
  ls -d /proc/$PID
  wait $PID
  echo wait result: $?
EOF

結果:

16079
ls: cannot access '/proc/16079': No such file or directory
wait result: 9

これは、Bash waitコマンドがキャッシュを使用するという証拠です(私はこれを確認するためにstraceも使用し、 wait4-1を返す最後のシステムコールを明確に示します。

wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 9}], 0, NULL) = 397
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 399
wait4(-1, 0x7ffd79fc3fd0, WNOHANG, NULL) = -1 ECHILD (No child processes)

もちろん、disown -a以前に実行した場合は、コード127:を返しますwait。これはまた、バックグラウンドpidがバックグラウンドタスクリストから削除された場合、waitコマンドが正しいコードで終了しないことを確認します。waitwait: pid xxxxxx is not a child of this shell

これは、Bash waitコマンドがバックグラウンドジョブ管理情報にキャッシュされた結果を使用していると結論付けます。

もしそうなら、私の質問は次のようになります。たとえば、スクリプトが継続的にバックグラウンドジョブを生成する場合

テスト2:

while true; do
  echo hi &
done

それでは、バックグラウンドジョブキャッシュがますます大きくなり、メモリリークが発生しますか?

このスクリプトをテストしましたが、メモリリークがないようですが、なぜリークがないのですか?

編集:もう少し明確に言えば、上記のスクリプトはメモリが不足すると予想されますが、実際には私が観察したようにメモリが不足していません。なぜですか?

編集:上記の質問はtest2まだ最も興味深い質問です。なぜメモリが足りないのですか?

編集:もう一度テストしましたが、数秒後にメモリが不足しました。

テスト3:

bash <<'EOF'
  while true; do
    sleep 10 &
    echo $!
  done
EOF

明らかにする

...
bash: fork: retry: Resource temporarily unavailable
bash: fork: retry: Resource temporarily unavailable
bash: fork: Interrupted system call

いいですね。これで期待どおりに動作します。メモリ不足です。

申し訳ありません。私の質問は次のとおりです。これが意図されていますか?バックグラウンドジョブが継続的に生成されるという警告を聞いたことはありません。これまで私が知っている唯一の解決策は、disownバックグラウンドジョブ管理を停止したり、バックグラウンドジョブ(cmd&)として管理せずにプロセスを開始するなどの他のトリックを使用することです。

編集:私の答え:これは意図的に設計されています。これは、Bashがすべてのアクティブなタスクを追跡し、短時間でアクティブなタスクが多い場合、メモリが不足していることを意味します。したがって、これは矛盾しませんtest2

編集:Bashバックグラウンドジョブ終了コードキャッシュがアクティブジョブではなく最後のジョブの終了コードをキャッシュするだけでなく、すべてのジョブの終了コードもキャッシュすることを示すために別のテストを追加しました。

テスト4:

bash -x <<'EOF'
  bash -c '/bin/sleep 3; exit 1' &
  PID1=$!
  bash -c '/bin/sleep 6; exit 2' &
  PID2=$!
  wait $PID1
  echo exit code of first process is: $?
  wait $PID2
  echo exit code of second process is: $?
  wait $PID1
  echo Get exit code of first process again, result is: $?
EOF

結果:

+ PID1=2357449
+ bash -c '/bin/sleep 3; exit 1'
+ PID2=2357450
+ wait 2357449
+ bash -c '/bin/sleep 6; exit 2'
+ echo exit code of first process is: 1
exit code of first process is: 1
+ wait 2357450
+ echo exit code of second process is: 2
exit code of second process is: 2
+ wait 2357449
+ echo Get exit code of first process again, result is: 1
Get exit code of first process again, result is: 1

答え1

まず、最大PID(Resourcesで見られる/proc/sys/kernel/pid_max)が通常32768に制限されているため、メモリが枯渇することはできません。したがって、より多くのプロセスを実行しても、最終的にpidはカーネルによって回収されるため、カーネルは最大pid数を回収します。メモリに保持するPIDはbash<32768です。

サイズはbashまたあなた次第ですnproc(最大ユーザープロセス数)制限。

次のスクリプトを使用すると、bashでこれが正しいかどうかを簡単に確認できます。

#!/bin/bash
declare -a pids_list=()
for i in {1..4196}; do
    (exit 0) & waitpid=$! && wait $waitpid
    pids_list+=($waitpid)
done
export KEPT=0 DISCARDED=0
for i in "${pids_list[@]}"
do
    wait $i 2>/dev/null
    if [ $? -ne 127 ] # If the child is not found in the jobs table, wait returns 127
    then
        let KEPT++
    else
        let DISCARDED++
        KEPT=0
    fi
done
echo KEPT=$KEPT DISCARDED=$DISCARDED

この例では、バックグラウンドで4096 + 100 = 4196のジョブを実行し、pids_list配列にpidを保持しながら各ジョブが完了するのを待ちます。すべての操作が完了したら、pids_list配列を繰り返して、bashがまだ状態を維持していることを確認します。

私の場合、デフォルトの最大プロセス制限は4096です。

$ ulimit -u
4096

このコードをスクリプトまたはソースコードとして実行すると、最初の100個のPIDの状態が削除され、最後の4096個のPIDのみがメモリに保持されることが確認されます。

$ check_pid_table.sh
KEPT=4096 DISCARDED=100

制限を 1024 に減らすと、保持されるプロセスの数はこれだけです。

$ ulimit -u 1024
$ check_pid_table.sh
KEPT=1024 DISCARDED=3172

制限を増やすと、すべてのPIDが保持されます(ただし、もう一度 - 最大制限までpid_max)。

$ ulimit -u 8192
$ check_pid_table.sh
KEPT=4196 DISCARDED=0

Bashでは、プロセステーブルはどのくらいのメモリを占めていますか?

bashまた、保持する必要があるさまざまなPID数によって消費されるメモリ量を確認することもできます。私がここで使うものはtime(1)bashプロセスで使用されているメモリを確認するコマンドです。

%Mtime確認中Maximum resident set size of the process during its lifetime, in Kbytes.

$ ulimit -u 65536
$ for i in {1..8} {25..32}; do 
>   /usr/bin/time -f "number of procs=$i KB, memory=%M KB" bash -c '
>     for (( i=$0 ; i>0 ; i-- )); do 
>       echo >/dev/null & wait $!
>     done' $(($i*1024))
> done
number of procs=1 KB, memory=2904 KB
number of procs=2 KB, memory=2936 KB
number of procs=3 KB, memory=2968 KB
number of procs=4 KB, memory=3000 KB
number of procs=5 KB, memory=3032 KB
number of procs=6 KB, memory=3064 KB
number of procs=7 KB, memory=3096 KB
number of procs=8 KB, memory=3128 KB
number of procs=25 KB, memory=3672 KB
number of procs=26 KB, memory=3704 KB
number of procs=27 KB, memory=3736 KB
number of procs=28 KB, memory=3768 KB
number of procs=29 KB, memory=3796 KB
number of procs=30 KB, memory=3796 KB
number of procs=31 KB, memory=3796 KB
number of procs=32 KB, memory=3796 KB

各1Kプロセスブロックがメモリ消費を約32K増加させることがわかりますbash。これは、各プロセス項目が32ビットを使用することを意味します。

しかし、32KB(限度max_pid)に近づくほどメモリが静的になることがわかります。これは私が言ったように最終的にpidがリサイクルされるからです(そしてすでに私のシステムで多くのプロセスが実行されています)。

答え2

漏れではありません。記憶が失われないからです。シェルはPIDを追跡するため、理論的には最終的にメモリが不足する可能性がありますが、これはすべて予想され、メモリ使用量を管理します。

POSIXでは、シェルはアクティブなPIDと最後に終了したPIDの結果のみを追跡します。

wait [-n] [n ...]指定された各子プロセスを待って終了ステータスを返します。それぞれはnプロセスIDでもジョブ仕様でもかまいません。ジョブ仕様が提供されると、ジョブパイプラインのすべてのプロセスが待機します。指定しない場合、n現在アクティブなすべての子プロセスが待機し、戻り状態は0です。-nこのオプションが指定されている場合は、ジョブがwait終了するのを待ってから終了ステータスを返します。n存在しないプロセスまたはジョブを指定した場合、戻り状態は127です。そうでない場合、戻り状態は最後の待機プロセスまたはジョブの終了状態です。

bashしかし、あなたは(少なくとも4.4.12から5.2まで)正しいです。POSIXと互換性がないbash --posix動作でもPOSIXと互換性がありません。代わりに、すべてのバックグラウンドプロセスの状態が維持されます。これは「test4」でうまく実証されました。 POSIX準拠の方法を使用して結果を比較しますdash

exit code of first process is: 1
exit code of second process is: 2
Get exit code of first process again, result is: 127

bashファイルのソースコードを見るnojobs.c特に関数alloc_pid_listwait_builtininから呼び出されます)wait.def)、各管理PIDの配列に追加の12バイトエントリを使用しますpid_list。他の理由で、システムリソースを使い果たす前に、アレイサイズの増加によってシステムリソースが使い果たされる可能性が低くなります。

関連情報