1. ホーム
  2. Qt

LinuxカーネルOOM機構の詳細解析

2022-02-24 15:14:02

<スパン         リナックス カーネルは、アプリケーションの要求に応じてメモリを割り当てます。通常、アプリケーションはメモリを割り当てますが、実際にはすべて使用しないため、パフォーマンスを向上させるために、この未使用メモリを他の用途のために確保することができます。 ) を使って、この "free" のメモリーを間接的に使用し、全体のメモリー使用効率を向上させることができます。これは一般的には問題ありませんが、ほとんどのアプリケーションが独自のメモリを消費する場合、これらのアプリケーションの必要メモリを合計すると物理メモリ(スワップを含む)の容量を超えるため、カーネル(OOMキラー)が一部のプロセスを強制終了してシステムが正常に動作するための領域を解放しなければならないという問題が発生します。一部の人がお金を引き出しても、銀行には十分な預金があるので平気ですが、全国民(あるいは大多数)がお金を引き出して、みんながそれを終わらせようとすると、銀行は困ります。

<スパン 例えば、ある日突然、あるマシンがリモートでsshにログインできなくなった。しかし、pingは通る。つまり、ネットワークの障害ではなく、sshdプロセスがOOMキラーによって殺されたことが原因だ。マシンを再起動し、システムログ /var/log/messages を見ると、Out of Memory:Killprocess 1865 (sshd) のようなエラーメッセージが見つかります。 もう一つの例として、時々VPS MySQL が明白な理由なく常にハングアップするか、VPS がしばしばクラッシュし、ターミナルにログオンすると、一般的なメモリ不足の問題が明らかになります。これは通常、アプリケーションがある時点で大量のメモリを要求し、システムがメモリ不足に陥り、Linux カーネルの Out of Memory (OOM) キラーが作動したために起こります。関連するログファイル(/var/log/messages)をチェックすると、次のようなメッセージが表示されます。 Out of memory:Kill process message.

  ...

  メモリ不足:プロセス9682(mysqld)をkillする スコア9または子供を犠牲にする

  Killed process 9682, UID 27,(mysqld) total-vm:47388kB, anon-rss:3744kB, file-rss:80kB

  httpdはoom-killerを起動した:gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0

  httpd cpuset=/ mems_allowed=0

  Pid: 8911, comm: httpd Not tainted2.6.32-279.1.1.el6.i686 #1

  ...

  21556 ページキャッシュの総ページ数

  スワップキャッシュのページ数 21049

  スワップキャッシュの統計:追加12819103,削除12798054,検索3188096/4634617

  空きスワップ=0kB

  総スワップ量 = 524280kB

  131071ページ RAM

  0ページ HighMem

  3673ページ予約済み

   67960ページ

  124940ページ(非共有

<スパン <スパン         リナックス カーネルには、OOMキラーと呼ばれる機構がある (Out-Of-Memory killer) は、メモリを過剰に消費するプロセスを監視し、特にメモリの消費が早い場合は、カーネルがプロセスを強制終了して、メモリ不足になるのを防ぐ機能です。

<スパン <スパン <スパン         カーネルがシステムメモリの不足を検出し、プロセスを選択し、終了させるプロセスは、カーネルのソースコード linux/mm/oom_kill.c で見ることができます。 システムがメモリ不足になると out_of_memory() が起動し、select_bad_process() が呼び出されて kill する "bad" プロセスが選択されます。この "bad" プロセスの決定と選択のプロセスは oom_badness() によって決定され、最もメモリを消費するプロセスが最悪とされる。

/**

 * oom_badness - kill するタスクの候補を決定するためのヒューリスティック関数

 * @p: どのタスクを計算するかというタスク構造体

 * @totalpages:ページ割り当てに許容される現在のRAMの総量

 *

 * どのタスクを殺すかを決定するヒューリスティックは、できるだけ単純で、かつ、簡単であるように作られています。

 * 可能な限り予測可能であること。に対して最も高い値を返すことが目標です。

 * タスクが最も多くのメモリを消費し、その後のOOMの失敗を避けることができます。

 */

unsigned long oom_badness(struct task_struct *p,struct mem_cgroup *memcg,

                                  const nodemask_t *nodemask, unsigned longtotalpages)

{ <未定義

ロングポイント

           long adj;

           if (oom_unkillable_task(p, memcg, nodemask))

                     0を返します。

           p = find_lock_task_mm(p);

           if (!p)

                     0を返します。

           adj = (long)p->signal-> oom_score_adj ;

           if (adj == OOM_SCORE_ADJ_MIN) {. <未定義

                     task_unlock(p)。

                     0を返します。

           }

           /*

            * バッドネススコアのベースラインは、それぞれのRAMの割合です。

            * タスクの rss、pagetable、swap 領域を使用します。

            */

points = get_mm_rss(p->mm) + atomic_long_read(&p->mm->nr_ptes)+ (邦訳:「邦訳」)。

                      get_mm_counter(p->mm, MM_SWAPENTS)を使用します。

           task_unlock(p)。

           /*

            * ルートプロセスは、__vm_enough_memory() と同様に、3%のボーナスを得ることができます。

            * LSMで使用される実装です。

            */

if (has_capability_noaudit(p,CAP_SYS_ADMIN))

                     adj -= (points * 3) / 100;

           /* oom_score_adj 単位に正規化する */

adj *= totalpages / 1000;

           ポイント += adj;

           /*

            * ルート・ボーナス、ルート・ボーナスの有無にかかわらず、適格なタスクに対しては決して0を返さない。

            * oom_score_adj (oom_score_adj はここでは OOM_SCORE_ADJ_MIN にはできません)。

            */

           returnpoints > 0 ?points : 1;

<スパン <スパン 上記の oom_kill.c のコードから、oom_badness() が各プロセスに点数をつけ、その点数によって kill するプロセスを決めていることがわかりますが、これは adj によって調整できます (点数が低いほど kill される可能性は低くなります) 。ユーザ空間で各プロセスの oom_adj カーネルパラメータを操作することで、どのプロセスが OOM Killer によって kill されにくいかを判断することができる。例えば、MySQL プロセスが簡単に殺されないようにするには、MySQL が動作しているプロセス番号を見つけて <スパン を調整します。 /proc/PID/oom_score_adj を-15にする( なお ポイント <スパン 小さければ小さいほど、殺されにくい )
重要なシステムプロセスがトリガーされるのを防ぐ(OOM) のメカニズムでは、カーネルは特定のアルゴリズムを使って各プロセスのスコアを計算し、どのプロセスを kill するかを決定しています。各プロセスの oom スコアは /proc/ <スパン PID /oom_score で発見。 各プロセスは oom_score 属性を持っています。 oom killer は oom_score が大きいプロセスを kill し、oom_score が 0 の場合はカーネルがプロセスを kill しないようにします。 を設定することです。 /proc/PID/oom_adj あなたはoom_score、oom_adjの範囲を変更することができます[-17、15]、ここで15は最大 -16は最小、-17はOOMの使用を禁止することです、なぜ-17ではなく、他の値(デフォルト値は0です)、これはLinuxカーネルで定義されている、カーネルのソースコードを見ることができるチェック:パスがlinux-xxxxx /包含 / uapi / Linux /oom.h.h です。

       oom_score はプロセスの oom_adj 値を n とし、2の n 乗で計算され、oom_score のスコアが高いほど、まずカーネルに kill されることになります。oom_adj=-17 のとき、oom_score は 0 になるので、パラメータ /proc/PID/oom_adj を -17 に設定すると、カーネルがプロセスを kill するのを禁止することができます。
       上記のMySQLの例は、以下のように解決すると、mysqlのポイントを減らし、killされる可能性を減らすことができる。
              # ps aux | grep mysqld
              mysql 2196 1.6 2.1 623800 44876 ? Ssl 09:42 0:00 /usr/sbin/mysqld
              # cat /proc/2196/oom_score_adj
              0
              # echo -15 > /proc/2196/oom_score_adj
       もちろん、あるプロセスがカーネルに殺されないようにするには、次のような方法があります。
              echo -17 > /proc/$PID/oom_adj
       例えば、sshdがkillされないようにするには、以下のようにします。
              pgrep -f "/usr/sbin/sshd" | while read PID;do echo -17 > /proc/$PID/oom_adj;done
       OOMの仕組みの有効性を確認するために、テストをしてみましょう。
       まず、私のシステムの既存のメモリサイズを見てください、はい96G+、物理的に表示値より少し大きいです。

       そして、現在最大のプロセスは何かを見てみると、トップビューで、私は現在2つのJavaプログラムプロセスのみを実行しており、それぞれ4.6G、さらに後ろにredisプロセスが21Mを食べ、iscsiサービスが32M、gdmが25Mを占め、他のプロセスはわずか数Mです。


       このプログラムは85Gのメモリを確保するように指定して、へぇー、明らかに動くんだ、と思って実行し、topでチェックすると、1位は私のbigmem、RESは物理メモリで、85Gを食い尽くしているのです。
       あなたは殺されたくない場合は、しばらくの間、85Gでbigmem安定保つときに、カーネルは自動的にそのプロセスを、成長プロセスを殺されていない、観察するために続けて実行することができます。
       pgrep -f "bigmem" | while read PID; do echo -17 > /proc/$PID/oom_adj;done
       上記のコマンドを実行する前と後では、明らかに効果が比較され、カーネルのOOMメカニズムが実際にどのように動作しているかが理解できるはずです。
       例えば、sshd プロセスが oom_killer 機能の影響を受けない場合、SSH セッションから生成されるすべてのプロセスはその影響を受けないことに注意してください。これは、OOM が発生した場合に oom_killer 機能がシステムを救済する能力に影響を与える可能性があります。
       これは /proc/sys/vm/panic_on_oom で制御でき、 panic_on_oom が 1 の場合は直接パニックになり、0 の場合はカーネルが oom killer で一部のプロセスを kill します。(デフォルトは0)
              # sysctl -w vm.panic_on_oom=1
              vm.panic_on_oom = 1 //1 はオフ、デフォルトは 0 で OOM キラーオン
              # sysctl -p
       OOM Killer の挙動をカーネルパラメータで調整することで、システムがそこでプロセスを kill し続けることを回避できます。例えば、OOM が発生した直後にカーネルパニックを起こし、10秒後にカーネルパニックで自動的にシステムを再起動させることができます。# sysctl -p
              # sysctl -w vm.panic_on_oom=1
              vm.panic_on_oom = 1

              # sysctl -w kernel.panic=10
              kernel.panic = 10
       あるいは
              # echo "vm.panic_on_oom=1" >> /etc/sysctl.conf
              # echo "kernel.panic=10" >> /etc/sysctl.conf
       もちろん、必要であれば、メモリの過剰割り当てを完全に禁止することも可能で、その場合はOOMの問題は発生しません(ただし、これは推奨されません)。
              # sysctl -w vm.overcommit_memory=2
              # echo "vm.overcommit_memory=2" >> /etc/sysctl.conf
       vm.overcommit_memory は、カーネルがメモリを割り当てる際に行うチェックの方法を示します。この変数は、0,1,2の3つの値を取ることができます。異なる値の取り扱いは、カーネルソースコードmm/mmap.cの__vm_enough_memory関数で定義されています。
       0:ユーザー空間でより多くのメモリが要求された場合、カーネルは残りの利用可能なメモリを推定しようとします。この場合、マクロは OVERCOMMIT_GUESS となり、カーネルは次のように計算する: total NR_FILE_PAGES + total SWAP + total free memory available in slab, and if more space is requested than this value, the value added to the total free memory minus totalreserve_pages(?).if requested space exceeds this value.カーネルはこの値を計算する。要求されたスペースがまだこの値を超えている場合、割り付けは失敗します。
       1:このパラメータを1に設定すると、マクロはOVERCOMMIT_ALWAYSとなり、カーネルは主に科学計算用に、メモリがなくなるまでオーバーコミットすることを許可します。
       2: このパラメータを2に設定すると、マクロはOVERCOMMIT_NEVERとなり、カーネルは決してメモリを使いすぎない、つまりシステムのメモリアドレス空間全体がRAM値のswap+50%を超えないアルゴリズムを使用します。50%はovercommit_ratioに設定され、カーネルは次のように計算します:総メモリ量×vm. vm.overcommit_ratioのデフォルト値は50です。
       上記は大まかな説明で、実際の計算では、非ルートプロセスの場合、計算上3%の領域が確保されますが、ルートプロセスではそのような制限はありません。詳しくはソースコードをご覧ください。
OOM Killerによって殺される可能性が最も高いプロセスを特定します。
ユーザー空間のプロセススコアは、各プロセスの oom_adj カーネルパラメータを操作することで調整できることが分かっていますが、このスコアは oom_score カーネルパラメータでも見ることができ、例えばプロセス番号 981 で omm_score を見ると、このスコアは前述の omm_score_adj パラメータで調整(-15)されていて 3 になっています。
# cat /proc/981/oom_score
18
# echo -15 > /proc/981/oom_score_adj
# cat /proc/981/oom_score
3
次の bash スクリプトは、現在のシステムで oom_score のスコアが最も高い(OOM Killer によって殺される可能性が最も高い)プロセスを表示するために使用することができます。
for proc in $(find /proc -maxdepth 1 -regex '/proc/[0-9]+'); do
printf "%2d %5d %sn" \.
"$(cat $proc/oom_score)" \
"$(ベースネーム $proc)" \.
"$(cat $proc/cmdline | tr '\0' ' ' | head -c 50)"
done 2>/dev/null | sort -nr | head -n 10
# chmod +x oomscore.sh
# . /oomscore.sh
18 981 /usr/sbin/mysqld
4 31359 -bash
4 31056 -bash
1 31358 sshd: root@pts/6
1 31244 sshd: vpsee [priv].
1 31159 -bash
1 31158 sudo -i
1 31055 sshd: root@pts/3
1 30912 sshd: vpsee [priv].
1 29547 /usr/sbin/sshd -D
注意事項
1. Kernel-2.6.26 より前のバージョンの oomkiller アルゴリズムは十分な精度を持ちません。RHEL 6.x の 2.6.32 ではこの問題が解決されています。
2. 子プロセスは、親プロセスの oom_adj を継承する。
3. OOMはメモリリーク問題の解決には不向きです。
4. 4. free はまだ十分なメモリがあることを確認しますが、プロセスが特別なメモリアドレス空間を占有している可能性があるため、まだ OOM をトリガーすることがあります。
Reference: http://laoxu.blog.51cto.com/4120547/1267097
http://blog.chinaunix.net/uid-26490154-id-3063309.html
http://www.linuxeye.com/Linux/2119.html
http://www.vpsee.com/2013/10/how-to-configure-the-linux-oom-killer/
https://access.redhat.com/documentation/zh-CN/Red_Hat_Enterprise_Linux/6/html/Performance_Tuning_Guide/s-memory-captun.html
http://blog.sina.com.cn/s/blog_7429b9c801012evk.html
http://skyou.blog.51cto.com/2915693/558461
http://c.biancheng.net/cpp/html/2827.html
http://blog.jobbole.com/45748/
私の記憶食の手順で。
#include <stdlib.h>
#include <stdio.h>
int main(){
while(1){。
malloc(1)です。
}
は0を返します。
}

oom_score はプロセスの oom_adj 値を n としたとき、2の n 乗で計算されます。oom_score のスコアが高いほど、最初にカーネルによって殺されることになります。oom_adj = -17 のとき、 oom_score は 0 になります、したがって パラメータ /proc/ を設定することで PID /oom_adj 17の場合 カーネルがプロセスを強制終了しないようにします。

上記のMySQLの例は、以下のように解決すれば、mysqlのポイントを減らし、killされる可能性を減らすことができる。

# ps aux | grep mysqld

mysql 2196 1.6 2.1 623800 44876 ?        Ssl 09:42 0:00 /usr/sbin/mysqld

# cat /proc/2196/oom_score_adj

0

# echo -15 > /proc/2196/oom_score_adj

そして、もちろん、あるプロセスがカーネルによって殺されないようにすることは、次のように行うことができます。

echo -17> /proc/$PID/oom_adj

<スパン 例えば、sshd を防ぐために がkillされないようにするには、次のようにします。

pgrep-f "/usr/sbin/sshd" | while read PID;do echo -17 > /proc/$PID/oom_adj;done

OOMを検証するために の仕組みがうまく機能するか、テストしてみましょう。

まず、私のシステムで利用可能なメモリの量を見てみましょう。 それ以上、物理的に見た値より少し大きい。

<スパン そして、現時点で最大のプロセスは何かを見てみると、トップは 確認すると、現在は2つのjavaプロセスだけを動かしていて、それぞれ4.6G、さらに奥のredisプロセスが21M、iscsiサービスが32M、gdmが25M、他のプロセスは数M程度です。


<スパン 現在、私自身はC言語を使用しています bigmemというプログラムを書いて、85Gのメモリを確保するように指定すると、明らかに動作する。そして実行し、topでチェックすると、最初の行は私のbigmemで、RESは物理メモリで、85Gを食い尽くした。


<スパン 見ていてください、bigmemのとき がしばらく 85G で安定したままだと、カーネルは自動的にそのプロセスを kill しますが、成長プロセスは kill されないので、もし killed されたくない場合は

pgrep -f "bigmem" | while read PID; do echo -17 > /proc/$PID/oom_adj;done

前と上記のコマンドの実行後に、効果は明らかに比較される、あなたはカーネルのOOMを理解することができます 実際に仕組みが動くようになりました。

任意にチューニングされたプロセスから派生したプロセスは、そのプロセスの oom_score を継承することに注意してください . たとえば、sshd プロセスが oom_killer 機能の影響を受けない場合、SSH セッションによって生成されたすべてのプロセスはその影響を受けません。これは、OOM が発生した場合に oom_killer 機能がシステムを救済する能力に影響を与える可能性があります。

<スパン もちろん、カーネルパラメータを変更することで、メモリ上のOOMを無効にすることもできます。 が、これはカーネルパニックの引き金になります。 深刻なメモリ不足に陥ったとき、カーネルは2つの選択肢を持つ。1. いくつかのプロセスを終了させ、メモリを解放する。によって /proc/sys/vm/panic_on_oom panic_on_oom が 1 のときは直接パニックになり、0 のときは oomkiller でいくつかのプロセスを kill するように制御できます。(デフォルトは0)

# sysctl -wvm.panic_on_oom=1

vm.panic_on_oom = 1 //1 はオフ、デフォルトは 0 で OOM キラーオン

# sysctl -p

OOM Killer の挙動をカーネルパラメータで調整することで、システムがそこでプロセスを kill し続けるのを回避することができます。例えば、OOM を引き起こした直後にカーネルパニックを引き起こし、カーネルパニックが 10 秒後に自動的にシステムを再起動させることができます。

# sysctl -w vm.panic_on_oom=1

vm.panic_on_oom = 1

# sysctl -w kernel.panic=10

kernel.panic = 10

または

# echo"vm.panic_on_oom=1" >> /etc/sysctl.conf

# echo "kernel.panic=10">> /etc/sysctl.conf

<スパン もちろん、必要であればメモリのオーバーアロケーションを完全に禁止することもできますし、その時点では OOM 問題は(これは推奨できませんが)、その

# sysctl -w vm.overcommit_memory=2

# echo "vm.overcommit_memory=2" >> /etc/sysctl.conf

<スパン vm.overcommit_memory は、カーネルがメモリを割り当てる際に行うチェックの方法を示します。この変数は、0,1,2の3つの値を取ることができます。異なる値の取り扱いは、カーネル・ソースコード mm/mmap.c の __vm_enough_memory 関数で定義されています。

0:ユーザー空間でより多くのメモリが要求された場合、カーネルは残りの利用可能なメモリを推定しようとします。この場合、マクロは OVERCOMMIT_GUESS となり、カーネルは次のように計算します: total NR_FILE_PAGES + total SWAP + slab で解放できるメモリの総量、この値より多くの領域が要求された場合、この値を空きメモリの総量から totalreserve_pages(?) を引いた値に追加してください。要求されたスペースがまだこの値を超えている場合、割り当ては失敗します。

1: このパラメータの値を1に設定すると、マクロはOVERCOMMIT_ALWAYSとなり、カーネルは主に科学計算用に、使い切るまで過剰なメモリ使用を許可します。

2: このパラメータを 2 に設定すると、マクロは OVERCOMMIT_NEVER となり、カーネルは オーバーコミットしないメモリ カーネルは、メモリ総量×vm.overcommit_ratio/100+SWAP総量を計算し、要求した容量がこの値を超えると割り当てに失敗します。vm.overcommit_ratio/100は、カーネルがメモリ総量×vm.overcommit_ratio/100+SWAP総量で計算します。overcommit_ratioのデフォルト値は50です。

上記は大まかな説明です。実際の計算では、ルートプロセスでない場合、3%の容量が確保されますが、ルートプロセスにはこの制限はありません。詳しくはソースコードをご覧ください。

<スパン

OOM Killerによって殺される可能性が最も高いプロセスを特定します。

       プロセススコアはユーザ空間で各プロセスの oom_adj カーネルパラメータを操作して調整できることが分かっており、このスコアは oom_score カーネルパラメータでも見ることができます。例えば、プロセス番号 981 の omm_score を見ると、前述の omm_score_adj パラメータ (-15) で調整されて 3 となります。

              #cat /proc/981/oom_score

              18

              # echo -15 >/proc/981/oom_score_adj

              # cat /proc/981/oom_score

              3

       次の bash スクリプトは、現在のシステムで oom_score のスコアが最も高い(そして OOM Killer によって殺される可能性が最も高い)プロセスを表示するために使用できます。

              #! /bin/bash

              for proc in $(find /proc -maxdepth1 -regex '/proc/[0-9]+'); do

printf "%2d %5d %sn" \.

"$(cat $proc/oom_score)" \

"$(ベースネーム $proc)" ୧⃛(๑⃙⃘◡̈︎๑⃙⃘)

"$(cat $proc/cmdline | tr '\0' ' ' | head-c 50)"

              done 2>/dev/null | sort -nr |head -n 10

              # chmod +x oomscore.sh

              # . /oomscore.sh

              18 981 /usr/sbin/mysqld

              4 31359 -bash

              4 31056 -bash

              1 31358 sshd: root@pts/6

1 31244 sshd: vpsee [priv].

              1 31159 -bash

              1 31158 sudo -i

              1 31055 sshd: root@pts/3

              1 30912 sshd: vpsee [priv].

              1 29547 /usr/sbin/sshd -D

<スパン 注意事項

<スパン 1.カーネル-2.6.26
oomkillerの以前のバージョン アルゴリズムの精度が十分でないため、RHEL 6.x バージョン 2.6.32 でこの問題を解決しています。

2. 子プロセスは、親プロセスの oom_adj を継承します。 .

3. OOM メモリリークの解決には不向き(メモリリーク) という問題があります。

4. <スパン 時々、無料 は、まだ十分なメモリがあるかどうかを確認しますが、それでもプロセスが特別なメモリアドレス空間を占有している可能性があるため、OOM をトリガーします。

<スパン 参考 http://laoxu.blog.51cto.com/4120547/1267097

   http://blog.chinaunix.net/uid-26490154-id-3063309.html

                   http://www.linuxeye.com/Linux/2119.html

                   http://www.vpsee.com/2013/10/how-to-configure-the-linux-oom-killer/

                   https://access.redhat.com/documentation/zh-CN/Red_Hat_Enterprise_Linux/6/html/Performance_Tuning_Guide/s-memory-captun.html

                   http://blog.sina.com.cn/s/blog_7429b9c801012evk.html

   http://skyou.blog.51cto.com/2915693/558461

メモリ喰いプログラムで

#include <stdlib.h>

#include <stdio.h>

int main(){ <未定義

while(1){ <未定義

malloc(1)です。

}

を返します。

}