AndroidのbionicのTLSの実装はカーネル依存
AndroidのbionicのTLS(Thread Local Storage)の話
この前、goldfishのCPUをcortex-A8に置き換えたとき、kernelのTLSのconfigでハマりました。
http://trackback.blogsys.jp/livedoor/kmckk/1427864
TLSレジスタを使わないようにする。
実はこれがわからずにハマりました。理由はよくわかりませんが、OMAP3530用のソースではTLSレジスタを使わないようになっていたので、それと同じにしてみたら無事に起動するようになりました。
armv6k(マルチコア)とarmv7 ではcp15にTLSレジスタが新設されました。これはカーネルモードでは読み書き可能、ユーザーモードでは読み込みのみ可能なレジスタで、ここにTLSの先頭アドレスを格納することで、TLSへのアクセスを簡略化できるというものです。それ以前のアーキテクチャではカーネル空間のメモリ 0xffff0ff0 のアドレスにTLSの先頭アドレスを格納し、ユーザーモードからはシステムコール get_tlsを使ってここをアクセスするようにしていました。
カーネルのソースを見る限り、TLSレジスタを使うようにHAS_TLS_REG=yでカーネルをコンフィグしていても、従来通りget_tlsのシステムコールは利用できて、TLSの先頭アドレスの格納場所が 0xffff0ff0番地からTLSのレジスタに変わるだけでした。ユーザーモードからはget_tls のシステムコールを使っている限り、カーネルのHAS_TLS_REGがyでもnでも影響がないはずです。
しかし、HAS_TLS_REG=yだとinitプロセスが異常終了するため起動に失敗するのに、HAS_TLS_REG=nだと問題がないのは、てっきりカーネルかqemuのどちらかにバグがあるのだろうと思っていました。
先日のAndroidをCortex-A9マルチコア上でSMPで動かすためのパッチを見ていて、この疑問は氷解しました。
bionic/libc/private/bionic_tls.h
/* get the TLS */ #ifdef __arm__ # define __get_tls() ( *((volatile void **) 0xffff0ff0) ) #else extern void* __get_tls( void ); #endif
なんと、システムコールを使わずに直接そのアドレスを参照していた! 目からウロコ。
ユーザーモードからカーネル空間のメモリは読めないものと思い込んでいたのが敗因。確かにこれならシステムコール経由よりもずっとオーバーヘッドが少ない。
カーネルの実装に依存してしまうこととのトレードオフだが、こういうこともアリなんだな。