■ コラム(10章のp.249に挿入)「グローバル・ポインタの利用」 ---------------------------------------------------------------- OSのカーネルのビルドなどで,まれに _SDA_BASE_ というシンボルが無く リンクエラーになることがあります.これはいったい,どのようなシンボル なのでしょうか? OSのカーネルのリンカ・スクリプトなどを見ていると, _SDA_BASE_ というシンボルが定義されているのを多く見かけます. 多くは以下のような定義になっています. -------- ここから .data : { *(.data) } _SDA_BASE_ = . + 0x7ff0; .sdata : { *(.sdata) } .sbss : { *(.sbss) } .bss : { *(.bss) *(COMMON) } -------- ここまで これは,グローバル・ポインタを利用した高速化のためです. RISC系のCPUでは,アドレスを直接指定してメモリを読み書きすることは できません.このため変数の値を読み書きしようとすると ・変数のアドレスをレジスタに代入し, ・レジスタの指す先のメモリを読み書きする. という2つの操作が必要になります. これを変数ごとに毎回行うのはコストがかかるため,データ領域を固定で常に 指しているレジスタをひとつ決め,そのレジスタの指す先にインデックス値を 加えた位置を読み書きする(ディスプレースメント付きレジスタ間接参照) ことで,アドレス設定のコストを削減する方法があります. このためのレジスタを一般に「ベース・レジスタ」「グローバル・ポインタ」 などと呼びます. グローバル・ポインタを利用するには,以下の操作を行う必要があります. ・プログラムは -msdata を指定してコンパイルする. ・リンカ・スクリプト中で .sdata, .sbss というセクションを定義し, _SDA_BASE_ というシンボルをその付近で定義する. ・プログラムの起動時にグローバル・ポインタとなるレジスタに _SDA_BASE_ のアドレスを設定する. オプションに -msdata を指定してコンパイルすると,コンパイラは グローバル・ポインタを利用して変数参照するコードを作成します. しかしリンクの際には,グローバル・ポインタの指す位置に対する 当該の変数のアドレスのオフセット値を,ディスプレースメント値として 補填する必要があります.ところが実際に動作する際のグローバル・ポインタの 値をリンカは知ることができないため,_SDA_BASE_ というシンボルを定義し, _SDA_BASE_ からのオフセット値として計算します.プログラムの動作開始時には グローバル・ポインタとなるレジスタに _SDA_BASE_ のアドレスを設定する ことで,辻褄が合うことになります. 実際の変数参照は,グローバル・ポインタに対するディスプレースメント値加算に よって行われます.よって,参照できるアドレスの範囲に制限があります (CPU依存ですが,通常,グローバル・ポインタに対して±32KB程度です). このため,グローバル・ポインタを利用してアクセスする変数は .sdata, .sbss というセクションに集めるようにします. この際,なるべくならサイズの小さい変数を優先的に集めたほうが,たくさんの 変数を格納できてグローバル・ポインタを効率利用できるので,コンパイル時に -G というオプションで,.sdata, .sbss セクションに格納する変数のサイズ上限を 指定することができます.また .sdata, .sbss は隣合わせて配置し, その先頭で -------- ここから _SDA_BASE_ = . + 0x7ff0; -------- ここまで などのようにして,ロケーション・カウンタに 0x7ff0 や 0x8000 を加算した 値として _SDA_BASE_ を定義します.これはディスプレースメント値の指定が 正負の値になるため,グローバル・ポインタによってアクセスできる範囲を 最大限まで利用できるように,アクセス可能範囲の先頭に .sdata の先頭が 来るように調整しているためです. なお PowerPC では GPR13 をグローバル・ポインタとして利用するように ABIで定義されています.MIPSでは28番レジスタがグローバル・ポインタとして 利用され,GPという別名が付けられています. ----------------------------------------------------------------