September 2009

よい文章。読むべし。

http://www.ipa.go.jp/security/fy20/reports/tech1-tg/2_05.html

ちょっと前に転載したUlrichの効率的なdirectry readingコードについてのアーティクルで、openatを使うとセキュリティが云々言っているあたりが分かりやすく解説されている。

IngoがLKMLで説明してくれた、perfの使い方だけど、-topでしか使えないオプション等があって、まだ試せていないので、備忘録としてここに貼っておく


btw., if you run -tip and have these enabled:

CONFIG_PERF_COUNTER=y
CONFIG_EVENT_TRACING=y

cd tools/perf/
make -j install

... then you can use a couple of new perfcounters features to
measure scheduler latencies. For example:

perf stat -e sched:sched_stat_wait -e task-clock ./hackbench 20

Will tell you how many times this workload got delayed by waiting
for CPU time.

You can repeat the workload as well and see the statistical
properties of those metrics:

aldebaran:/home/mingo> perf stat --repeat 10 -e \
sched:sched_stat_wait:r -e task-clock ./hackbench 20
Time: 0.251
Time: 0.214
Time: 0.254
Time: 0.278
Time: 0.245
Time: 0.308
Time: 0.242
Time: 0.222
Time: 0.268
Time: 0.244

Performance counter stats for './hackbench 20' (10 runs):

59826 sched:sched_stat_wait # 0.026 M/sec ( +- 5.540% )
2280.099643 task-clock-msecs # 7.525 CPUs ( +- 1.620% )

0.303013390 seconds time elapsed ( +- 3.189% )

To get scheduling events, do:

# perf list 2>&1 | grep sched:
sched:sched_kthread_stop [Tracepoint event]
sched:sched_kthread_stop_ret [Tracepoint event]
sched:sched_wait_task [Tracepoint event]
sched:sched_wakeup [Tracepoint event]
sched:sched_wakeup_new [Tracepoint event]
sched:sched_switch [Tracepoint event]
sched:sched_migrate_task [Tracepoint event]
sched:sched_process_free [Tracepoint event]
sched:sched_process_exit [Tracepoint event]
sched:sched_process_wait [Tracepoint event]
sched:sched_process_fork [Tracepoint event]
sched:sched_signal_send [Tracepoint event]
sched:sched_stat_wait [Tracepoint event]
sched:sched_stat_sleep [Tracepoint event]
sched:sched_stat_iowait [Tracepoint event]

stat_wait/sleep/iowait would be the interesting ones, for latency
analysis.

Or, if you want to see all the specific delays and want to see
min/max/avg, you can do:

perf record -e sched:sched_stat_wait:r -f -R -c 1 ./hackbench 20
perf trace

Ingo
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/


Ulrich Drepper が自身のブログで、効率的なディレクトリ読み込みについてエントリを書いている。
しかし、改善案が思いっきり linux+glibc 依存なのでこれを実践できる人は少ないだろうな。と苦笑

元記事: http://udrepper.livejournal.com/18555.html

以下、抜粋


ダメなコード

DIR *dir = opendir(some_path);
struct dirent *d;
struct dirent d_mem;
while (readdir_r(d, &d_mem, &d) == 0) {
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/%s/somefile", some_path, d->d_name);
int fd = open(path, O_RDONLY);
if (fd != -1) {
... do something ...
close (fd);
}
}
closedir(dir);


オススメ
  DIR *dir = opendir(some_path);
int dfd = dirfd(dir);
struct dirent64 *d;
while ((d = readdir64(dir)) != NULL) {
if (d->d_type != DT_DIR && d->d_type != DT_UNKNOWN)
continue;
char path[PATH_MAX];
snprintf(path, sizeof(path), "%s/somefile", d->d_name);
int fd = openat(dfd, path, O_RDONLY);
if (fd != -1) {
... do something ...
close (fd);
}
}
closedir(dir);


ポイント

  • readdir_r() は意味ないよ。これは複数のスレッドが同じディレクトリストリームを読むことを可能にする関数だが、dirがローカル変数になってるから絶対他スレッドと競合しない
  • readdir()ではなく、readdir64()を使え。2GB越えのファイルで泣きたくなければ
  • Linuxは、名前長制限は個々のファイル名open()等の引数文字列長にかかるのであって、絶対パスにはかからない(まあリンクとか有るからカーネルでチェックできんしね)。だからPATH_MAXを安易に使うな。some_pathがPATH_MAXに非常に近い文字列長だったら、どうなる?openat()を使うべし
  • そもそも some_pathを足してopenする事自体がracyでダメ。パスをたぐってる間に別プロセスがリンクを張り直したりできるよ。セキュリティーホールになるよ。openat()を使うべし
  • dirent64はd_typeフィールドがあるから、openしなくてもディレクトリかどうか分かるよ


追記: 元エントリへのリンクを忘れていたので貼る
追記2: 識者から、個々のファイル名という表現は誤解を招くというご指摘をいただいた。その通りなので修正

最近、kosakiという人が「オレはLKMLでもっとも頻繁にOOMバグの解析を行っているデベロッパの一人である。オレが言うんだから間違いない。現在のOOMの表示と/proc/meminfoはフィールドが足りない」と真偽が定かではない主張をして、フィールドを大量に増やすパッチを、ねじ込んだ。

ちなみに、現在の /proc/meminfoはこんな感じ


$ cat /proc/meminfo
MemTotal: 6037184 kB
MemFree: 1229820 kB
Buffers: 252336 kB
Cached: 3673464 kB
SwapCached: 0 kB
Active: 1463432 kB
Inactive: 2772100 kB
Active(anon): 315332 kB
Inactive(anon): 16 kB
Active(file): 1148100 kB
Inactive(file): 2772084 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 10174448 kB
SwapFree: 10174448 kB
Dirty: 1092 kB
Writeback: 0 kB
AnonPages: 309728 kB
Mapped: 75640 kB
Shmem: 5620 kB
Slab: 379376 kB
SReclaimable: 339928 kB
SUnreclaim: 39448 kB
KernelStack: 2392 kB
PageTables: 33848 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 13193040 kB
Committed_AS: 788376 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 46116 kB
VmallocChunk: 34359682980 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 7104 kB
DirectMap2M: 3137536 kB
DirectMap1G: 3145728 kB



んで、せっかくなので、今回増えたShmem(共有メモリとtmpfs上のファイル) とKernelStask(それぞれのプロセスがカーネル内で使うスタック)をmuninの表示に反映させるプラグインを書いてみた。
ソースは以下にアップしてあるので、興味がある人は好きなように使ってもらってかまわない
(ただし、v2.6.32以降でないと利点があまり生かせないと思うが)

http://github.com/kosaki/munin-plugins

なお、標準のmemory usage プラグインとの差分は以下

  • app フィールドの廃止(内部処理として、/proc/meminfoで取得できなかった使途不明メモリをappとして表示していたので意味がなかった)

  • 代わりに anon フィールドを追加
  • slab_cache フィールドを slab(unreclaim) と slab(reclaimable) の2つに分けた
    reclaimableはinode cacheとdentry cacheでその名の通りメモリが逼迫すれば捨ててもかまわないキャッシュ unreclaimはカーネルの内部処理に使っているメモリで破棄不可能。
    この2つはシステム分析上まったく性格が違うメモリなので混在させるべきではない
  • cache フィールドを cache とshmemに分割
    tmpfs上のファイル、および共有メモリは破棄することが出来ず、スワップアウトさせないといけないという所がレギュラーファイルを大きく異なる。普通キャッシュがたくさんあるときは、まだまだスワップしないと思いたいところなので、この2つは混在させるべきではない
  • active と inactive を削除。VMの内部なんかみても何も分析できんよ。2.6.28のSplit LRU VMで意味が大きく変わっているし
  • committed も削除。JavaVMなど、使用メモリ量の数十倍も仮想メモリを予約するソフトがいるので意味のある値は取れない。だいたい普通Web Serverで使うソフトでpreforkアーキテクチャと相性悪い値取ってどうする
  • swapフィールドの表示を(他のメモリのような)色塗りから、(mappedのような)ラインに変更。スワップはメモリではない
  • mlocked フィールドを追加
  • dirty フィールドを追加
  • writeback フィールドを追加
  • 表示順を大きく変更。グラフが下から順に、アプリ系メモリ、カーネル・IO系メモリ、キャッシュ系に並ぶようになった



以下、サンプル画像
12e62339.jpg



ところで、色が気に入らないんだけど、これって変えられないの?

追記: githubの同じディレクトリにmemory_lruというプラグインも入れておいた。これはVM LRUの内訳
・Active(anon)
・Inactive(anon)
・Active(file)
・Inactive(file)
・Unevictable
を表示するもので、mlock()やshmctl(SHM_LOCK)を多用するシステムで威力を発揮する。なぜならMlockedフィールドはanonをmlockしたのかfileをmlockしたのか分からないため、あとどのくらいmlockされていないページがあるのか分からなくなるから。こっちでActive(file) + Inactive(file)を見れば自明。

追記2: memory_ext と memory_lruを統合して、v2 を作った。現在のフィールドは以下

  • anon
    anonymousメモリ。v1と違ってmlockされたページは抜かれていてswap可能なもののみを集計
  • page_tables
  • kernel_stack
  • swap_cache
  • shmem
  • vmalloc_used
  • unevictable
    mlocked の代わりに導入。mlock以外にもshmctl(SHM_LOCK)のようなあらゆるページ固定されたメモリを集計
    また表示がライン表示から積み重ね表示に変更。これを実現するためanonとcacheはページ固定されたメモリは抜くように仕様変更
  • slab_unreclaim
  • slab_reclaimable
  • file_cache_dirty
    キャッシュのうちダーティーなページ
  • file_cache_clean
    キャッシュのうちクリーンなページ。これとfreeを足した量で空きメモリが十分か判断する
  • free
  • swap_used
  • mapped
  • writeback


ポイントは以下

  • bufferフィールドは削除し、file_cacheに統合した。あれはfreeコマンド等古いコマンドがBuffersカラムがなくなると発狂するので表示されているだけで、この2つを分けることによっていかなる分析も出来はしない
  • file_cache_cleanフィールドを実現。"file_cache_clean + free" でシステムの余力を簡単に把握できるようになった
  • Mlock フィールドを Unevictableフィールドに変換。ロックされた量が他と同じように積み重ね表示出来るようになった


画面イメージはこんな感じ
86bc9996.jpg


途中で赤(unevictable)が増えているのは、anonymousメモリを1GBほどmlock()したから。mlockするとanonではなくなってしまうのが奇妙に思う人もいると思うけど

anon: スワップ可能メモリ
cache_clean: 破棄可能メモリ
cache_dirty: writebackが終われば破棄可能メモリ
unevictable: 破棄不可能メモリ

という分類にする事によって、システム高負荷時の挙動を予測しやすくするのが狙い

追記3: 追記2で書いたv2を revert した。すまん、あれバグってる。Unevictableでもdirtyになりうるので、{A|ina}ctive(file) - Dirty は正しくない計算式。これは、さらにもう1つフィールド追加がいるね。絶対反対されそうなので、作戦がいるけど。

そろそろ次の原稿を書く時期か、憂鬱。
前回はほぼ Nyaruru さんターゲットに記事を書いたのに全然反応をもらえなかったので、今回は普通に書こう。いや、書くべき。
たまには一般読者に受ける方向を狙わねば。

さて、なに書こう

カーネルスレッドのnice値が-5から0に特に議論もなく変えられたのでハングして困ってるぞ。とのこと。

Next patсh -
http://www.kernel.org/diff/diffview.cgi?file=%2Fpub%2Flinux%2Fkernel%2F%2Fv2.6%2Fsnapshots%2Fpatch-2.6.31-git2.bz2;z=548

This patch defines the core processes that are working with nice leve equal to
zero , as in the BFS. :)

Why?

VirtualBox, Vmware, QEMU, Firefox, Azureus, and many subsystems and
applications began working with large timeouts. In appearance similar to
hang.





Compare

2.6.31-git2 with KTHREAD_NICE_LEVEL = 0
2.6.31-git2 with KTHREAD_NICE_LEVEL = -5

Diff.

diff --git a/kernel/kthread.c b/kernel/kthread.c
index 5fe7099..eb8751a 100644
--- a/kernel/kthread.c
+++ b/kernel/kthread.c
@@ -16,6 +16,8 @@
#include
#include

+#define KTHREAD_NICE_LEVEL (-5)
+
static DEFINE_SPINLOCK(kthread_create_lock);
static LIST_HEAD(kthread_create_list);
struct task_struct *kthreadd_task;
@@ -143,6 +145,7 @@ struct task_struct *kthread_create(int (*threadfn)(void
*data),
* The kernel thread should not inherit these properties.
*/
sched_setscheduler_nocheck(create.result, SCHED_NORMAL,
¶m);
+ set_user_nice(create.result, KTHREAD_NICE_LEVEL);
set_cpus_allowed_ptr(create.result, cpu_all_mask);
}
return create.result;
@@ -218,6 +221,7 @@ int kthreadd(void *unused)
/* Setup a clean context for our children to inherit. */
set_task_comm(tsk, "kthreadd");
ignore_signals(tsk);
+ set_user_nice(tsk, KTHREAD_NICE_LEVEL);
set_cpus_allowed_ptr(tsk, cpu_all_mask);
set_mems_allowed(node_possible_map);



Used benchmarks.

# cyclictest and signaltest -
http://www.osadl.org/Realtime-test-utilities-cyclictest-and-s.rt-test-cyclictest-signaltest.0.html

----------
CYCLE TEST
----------

-T: 0 ( 5263) P: 0 I:1000 C: 98345 Min: 8 Act:656014 Avg:287390 Max:
656450
-T: 1 ( 5264) P: 0 I:1500 C: 65680 Min: 7 Act:481583 Avg:236140 Max:
482343
-T: 2 ( 5265) P: 0 I:2000 C: 49358 Min: 7 Act:286071 Avg:111300 Max:
287453
-T: 3 ( 5266) P: 0 I:2500 C: 39453 Min: 7 Act:370028 Avg:116111 Max:
372481
+T: 0 ( 6634) P: 0 I:1000 C: 98888 Min: 7 Act:113011 Avg:28733 Max:
113817
+T: 1 ( 6635) P: 0 I:1500 C: 65953 Min: 8 Act:72013 Avg:25026 Max:
73110
+T: 2 ( 6636) P: 0 I:2000 C: 49468 Min: 6 Act:66076 Avg:17455 Max:
67486
+T: 3 ( 6637) P: 0 I:2500 C: 39580 Min: 7 Act:52514 Avg:12882 Max:
53256

----------
SIGNAL TEST
----------

-T: 0 ( 5285) P: 0 C: 100000 Min: 13 Act: 23 Avg: 30 Max: 9229
-T: 1 ( 5286) P: 0 C: 100000 Min: 13 Act: 99 Avg: 662 Max: 17282
-T: 2 ( 5287) P: 0 C: 100000 Min: 13 Act: 110 Avg: 662 Max: 17294
-T: 3 ( 5288) P: 0 C: 100000 Min: 13 Act: 119 Avg: 662 Max: 18645
+T: 0 ( 6698) P: 0 C: 100000 Min: 13 Act: 22 Avg: 24 Max: 7898
+T: 1 ( 6699) P: 0 C: 100000 Min: 13 Act: 104 Avg: 654 Max: 15728
+T: 2 ( 6700) P: 0 C: 100000 Min: 13 Act: 114 Avg: 654 Max: 15740
+T: 3 ( 6701) P: 0 C: 100000 Min: 13 Act: 124 Avg: 654 Max: 16102

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/


kzk さんに教えてもらったネタ

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6336770

Javaは新しいプロセス作るときに、ファイルディスクリプタを全部閉じようとするけども、実装がバグっているのでデッドロックが起きる可能性があるそうだ。

てきとうに見つけたクロスリファレンスサイトから引用すると
http://www.jiema.org/xref/openjdk/jdk7/jdk/src/solaris/native/java/lang/UNIXProcess_md.c#293

fork-and-exec処理の時のディスクリプタ全クローズ処理で、よりにもよってopendir()。ここでmalloc()が発生。あぼーん。

なんで、UNIX系OSってcloseall()システムコールがないんだろうね。困ったもんだ


追記: Linux限定の話でいうと /proc/{pid}/statusのFDSizeフィールドがmax fdを表しているので、3からFDSizeまでcloseしていけば、OKなんじゃないかという気がしてきた。誰か検証プリーズ

追記2: ついでにRubyの実装をちらりと見たけど、NOFILEまでしかクローズしていないから最大ファイルディスクリプタが動的なシステムだとリークしているっぽい。今度、akrさんあたりに真相を聞いてみよう。

追記3: glibc malloc はpthread_atfork()を使って、fork前後でmutexを取っているので、forkとexecの間でmalloc()呼んでも平気。これはtcmallocのような外部mallocを使った場合のみに問題になりそう

追記4: いくつかのページで sysconf(_SC_OPEN_MAX)を薦めているところがあったけど、これ、Linuxではうまくいかない。getrlimit()で現在のlimitを取っているだけなので、ファイルを開いた後で最大値を下げられたら、ちゃんとハンドリングできない。まあ、普通の人はそんな事しない。という指摘は正しいと思います

追記5: forkとexecの間はasync signal safeしかダメって話は以前にも書いたので、そっちも見てね
http://mkosaki.blog46.fc2.com/blog-entry-886.html

追記6: kzkさんに指摘されて気づいたけど、これが記念すべき1000エントリ目かー。妙に感慨深いな

追記7: kzkさんの関連エントリも参照のこと。
http://kzk9.net/blog/2009/09/deadlock_on_process_builder.html

↑このページのトップヘ