mallocの落とし穴 - 組み込みLinuxでのmemory overcommit

天災は忘れた頃にやって来る
寺田寅彦

組み込みLinuxにおけるメモリ確保のエラーハンドリングに関するお話。

Linuxにはmemory overcommit(オーバーコミット)と呼ばれる仕組みがあります。簡単にいうと、メモリ割り当ての段階では、仮想アドレス空間だけをわりあてて、実際に使われる段階で、実メモリを割り当てるというものです。メモリ割り当て時には、実メモリはまだ割り当てられないので、実際の物理メモリの容量以上のメモリ割当てが行われることを許されることになります。

実例を見た方が早いので、例えば以下の例。
#define BLK_NUM 100
#define BLK_SIZE (100 * 1000 * 1000) /* 100MB */

int
main(int argc, char* argv[])
{
  int i;
  char* blk[BLK_NUM];

  for (i = 0; i < BLK_NUM; i++) {
    blk[i] = malloc(BLK_SIZE);
    assert(blk[i] != NULL);
  }

  return 0;
}
mallocでヒープを100MBx1000 = 10GB取得していますが、これをコンパイルしたものを実メモリ128MBのLinuxで実行してもおそらくassert()にはひっかからず正常終了します。これはmallocの段階では、仮想メモリのみが割り当てられ、実メモリが割り当てられていないためです。つまり、アドレス空間を超えるような異常なmallocでもしない限り、mallocの戻り値は常に成功(NULL以外)で返ります。実際、malloc完了したところでプロセスのstatus値を見てみると、VSSは増えているものの、RSSは増えていないことが確認できると思います。(returnをwhile(1)やsleepにでもして、psやprocでみて見るとよくわかります)。

では、以下の分をmallocのループの下に加えるとどうなるでしょうか。
  for (i = 0; i < BLK_NUM; i++) {
    memset(blk[i], 0, BLK_SIZE);
  }
今後は、メモリが10GBもないようなシステムでは、実行途中に以下のようなログとともにプロセスが強制終了したと思います。
01:00:00 xxxxx kernel: Out of memory: Killed process 10000, UID 100, (xxx)
memsetによって書き込みが発生する段階で、実メモリが割当りあてられ、この時点でプロセスがkernelに強制的にkillされてしまいます)。いわゆるOOM-killer(out of memory killer)という仕組みで、カーネルがメモリを大量消費していると思しきプロセスを強制的にkillする仕組みです。教科書的な「mallocでは、戻り値見てメモリを取得できたか確認しましょう」というのはこの場合通用しないのです。さらに、困ったことに、必ずしも犯人とは限らないプロセスが殺される場合もあります。これだと、個別のユーザーアプリケーション側では対処のしようがありません。

そういうわけで、かなり不評なしくみでもあり、memory overcommitの仕組みについては、仕様バグだという言われようもよく見かけます。そういうことで、最近(というかkernel 2.6からなので結構前から)のLinuxには、overommitの挙動を変更する仕組みがあります。具体的には、sysctl、もしくは、直接proc/sysに値を書き込むことで設定を変更できます。以下、procのman pageから引用、
/proc/sys/vm/overcommit_memory
 このファイルにはカーネル仮想メモリーのアカウントモードが書かれている。 値は以下の通り:
  0: 発見的なオーバーコミット (heuristic overcommit) (これがデフォルトである)
  1: 常にオーバーコミットし、チェックしない。
  2: 常にチェックし、オーバーコミットしない。

/proc/sys/vm/overcommit_ratio (Linux 2.6.0 以降)
 この書き込み可能なファイルは、 オーバーコミットできるメモリーの割合をパーセントで定義する。 このファイルのデフォルト値は 50 である。 /proc/sys/vm/overcommit_memory の説明を参照。
上で、「犯人と思しきプロセス」がkillされるというのが、ここでいうheuristic overcommitというやつですね。どの閾値でoom-killerが呼ばれるかは、 以下の式になります。
CommitLimit = (total_RAM - total_huge_TLB) * overcommit_ratio / 100 + total_swap
組み込みでmallocでとったメモリを確実に確保したいというのであれば、overcommit_memory=2にしてovercommit_ratioを100に近い値にしておけば、基本的にはmallocでNULL以外がかえってくれば、そのメモリは使えるというこが一応保証されることにはなります。

では、組み込みの場合は、overcommit_memory=2を使うべきなんでしょうか?

残念ながらそうとは言い切れません。仮想メモリとして確保した分だけ、実メモリも消費する(実際割り当てられないとしても総和はこえないようコントロールされる)ということになると、以下のような場合に相当の実メモリが必要になります。
  • 各プロセス、スレッド毎のスタック用メモリ
    • 通常スレッドあたり2MB
    • 通常のスレッドならそこまでメモリは使わないのでovercommit=0なら必要量のpageのみが実メモリとして割り当てられるが、overcommit=2ならまるまる必要になる。
  • forkする際のfork元プロセスが使用しているメモリ
    • 通常、forkして時にはfork元プロセスのメモリ空間がコピーされるが、いわゆるcopy on writeという仕組みで、書き込みが行われない限りは、この段階では実際のメモリコピーは発生しない。
    • overcommit=2だと、その時点で実メモリの容量も予約されてしまう
    • 結果としては、fork(あるいは、中でfork実行するsystem関数など)が失敗することがある。

メモリ容量が少ない組み込みでは、こういう隠れた予約メモリがばかにならないので、結果としてovercommit=2で使うのはなかなか厳しいのではないかと。言い換えると、overcommit memoryという仕組みがあるおかげで、こういうケースにもうまく対応できているのだと考えられます。

じゃあ、一回メモリ不足はどうやってハンドリングしたらいいんだという話になりますが、基本的には組み込みなんだからメモリ使用量くらいちゃんと把握しましょうというのが基本ですが、エラー検出の方法としては、メモリ総量をwatchして不適切なプロセスがいればkillする、あるいは、リブートさせるという仕組みを自分でいれる方法になるのかと思います。ちなみに、oom-killerで中途半端にシステムを混乱させるくらいなら、kernel panicに落として、watchdogリセットにでももっていきたいということであれば、以下のproc設定(manpage引用)も検討候補になります。
/proc/sys/vm/panic_on_oom (Linux 2.6.18 以降)
 このファイルは、メモリー不足時にカーネルパニックを 起こすか起こさないかを制御する。
 このファイルに値 0 を設定すると、 カーネルの OOM-killer がならず者のプロセスを kill する。 普通は、OOM-killer がならず者のプロセスを kill することができ、 システムは何とか動き続けることができる。

 このファイルに値 1 を設定すると、 メモリー不足の状況が発生すると、カーネルは普通はパニックする。

memory overcommitにかぎらず、本当に必要なところまで処理を遅らせるという遅延処理は、パフォーマンスを最大化する上で非常に有用ですが、エラー処理が難しくなるので注意が必要ですね。

Linuxカーネル解析入門 (I・O BOOKS)Linuxカーネル Hacks ―パフォーマンス改善、開発効率向上、省電力化のためのテクニック UNIXという考え方―その設計思想と哲学

mbotでSTEM入門 - シャフトが壊れた時の修理方法

アトムは完全ではないぜ。なぜなら、悪い心を持たねぇからな。
手塚治虫 -『鉄腕アトム』

2020年、小学校でのプログラミング教育が必修化されることになり、「STEM教育」用の教材が増えてきました。STEMとは、"Science, Technology, Engineering and Mathematics" すなわち科学・技術・工学・数学の教育分野を総称する言葉で、これらを統合的に学ぶ機会を子どもたちに提供することで、次世代を担う人材に育てようという教育方針です。最近では、これに芸術(Airt)のAを加えてSTEAM教育という言葉も聞かれます。ニューヨーク市立大学教授のキャシー・デビッドソン氏がインタビューで語ったとされる「2011年度にアメリカの小学校に入学した子供たちの65%は、大学卒業時に今は存在していない職業に就くだろう」という予測も話題になりました。

さて、そんな中で種類も増えてきているSTEM教育用の教材ですが、やはり子供心をくすぐるのは自分でプログラミングできるロボットでしょう。子供向けのロボット教室も結構増えているようですが、最近はscratch(スクラッチ)というビジュアルプログラミング環境で、子供でもかんたんにプログラムが作れる環境が整っているので、キットさえ買えば自宅でも簡単にロボットプログラミングが始められます。

最初にどれを買うべきか

いろんなロボットが発売されていますが、最初の選択肢としてあがるのは以下あたりでしょうか。

LEGO midstorms

おそらく最も有名なのはLEGOのmindstormsでしょう。カスタム部品も抱負で、LEGOブランドの安心感もありますが、何分価格が高い。。大人も一緒に本格的にはじめたいというのならいいかもしれませんが、最初の子供用にはちょっと高いかな。

スタディーノではじめるうきうきロボットプログラミング

専用の解説書もあって、LEDチカチカから自動ドア、床ゆきお掃除ロボットと段階をおっていろんなものを作る楽しみがあります。これもなかなか楽しそうなので、次のmbotと迷いました。

Makeblock mBot

結局買ったのは、こちらのmbot距離センサ(超音波センサ)、ライントレースセンサ光センサブザーLEDと一通り入門に必要と思われるセンサが最初から搭載されており、bluetooth対応なので、付属のリモコンやPCあるいはスマホ、タブレットから操作までできます。これで1万円代中盤くらいなので、midstormと比べるとかなりお手頃です。プログラミング環境も、scratchベースのビジュアルプログラミング環境があるので、プログラミング経験のない小学生でもかんたんにはじめられます。ipad版ならPCもいらないのでさらにお手軽です (残念ながらiphone版はないのと、私の家のipad2はBLE(Bluetooth4.0)非対応なのでだめでしたが...新しいipadほしい..)。なお、mbotには、bluetooth版以外に赤外線版なるものもあるようですが、それだとリモートでプログラムの書き込み/デバッグができない(USBつながないといけない)ので、買うなら絶対bluetooth版がおすすめです。

私はサンワダイレクトさんから発売されているものを買ったのですが、詳しい説明がはいっていたので子供(小学校高学年)だけでかんたんに組み立てていました。プログラミングは、最初はさすがに少し説明しましたが、試行錯誤で距離センサーみてよけるものは結構手軽に作れてました。ビジュアルプログラミングなかなかあなどれないです。子供だけでできるように日本語の解説本(Makeblock公式 mBotで楽しむ レッツ! ロボットプログラミング)も購入しましたが、基本から順番に説明されていて、なかなかよかったです。

mBotの折れたシャフトの修理

なお、うちのmbotですが、完成してまだ電源をいれる前に、足で踏まれてシャフトがポッキリおれるという事故がありました。。修理部品をさがしてみたのですが、すぐには見つからず途方にくれそうになったのですが、そうえば、組立時にシャフトのあまりらしきものがあったのを思い出しました。「もしや、スペア部品?でも、何の説明もなかったけど・・・」と思って、もう少しぐぐってみると、以下の動画が見つかりました。
どうやら、シャフトは机からの落下等でよく折れるので、予備部品が最初からはいっていたようです。助かった。。
修理方法は動画の通りなのですが、モーターからキャップ外すのが結構固くて苦労しました。ドライバでひっかけて思い切ってやるしかないようです。

ということで、トラブルもありましたが、やっぱり目に見えて動くというのは、PC上に閉じたプログラミングとはまた全く違う楽しみがありますね。子供用と言いつつ、自分用にセンサ買い足したくなったきました ^^)

4865940847ロボットを動かそう! mBotで おもしろプログラミング
石井モルナ MAKO.
リックテレコム 2017-04-08

本のおすすめ

4873115655

4274065979

4822236862

4274068579

4822255131

B00SIM19YS


プロフィール

  • Author:proger
  • 組み込み関係で仕事してます

ブログ内検索