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

なんと、システムコールを使わずに直接そのアドレスを参照していた! 目からウロコ。
ユーザーモードからカーネル空間のメモリは読めないものと思い込んでいたのが敗因。確かにこれならシステムコール経由よりもずっとオーバーヘッドが少ない。
カーネルの実装に依存してしまうこととのトレードオフだが、こういうこともアリなんだな。