最後のリンカスクリプトの記述パターンは以下のようなものがある。
.data : {
_data_start = . ;
*(.data)
_edata = . ;
} > data AT> rom
[内容]VA≠PA
すべてのオブジェクトファイルの「.data」セクションを集めて実行形式ファイルの.dataセクションとして、メモリのdata領域に配置するようにし、リンクの対象とする。
さらに、今回新しいAT> romの記述は、すべてのオブジェクトファイルの「.data」セクションの内容をromにもおくが、リンクの対象ではない。しかし、初期値はromに配置する。
さらに、_data_startと_edataはRAMの方のアドレス。
[AT>romのいろいろ説明]
AT>romの説明はいろいろとあるようです。
本だとリンクはRAM上のアドレスをベースにして実施、ロードはROM上に行われるようなアドレス配置の指令との説明。
具体的にいうならリンカがそのようなアドレス配置でセグメント情報を作成し、実行形式ファイルを作成するとのこと。
wikipediaの説明だとここ 。
ロード時に領域romに配置し、実行時はramに配置。
[そもそもなんで?]
①h8writeはROMを更新する仕様なのでRAMを対象にしていない。
※実際に確認してないがh8writeでRAMにアクセスしようとしてもエラーが出るとのこと。
※RAMに書けたとしても電源落とすと値が消えてしまうので電源を落としても消えないROMに初期値を記憶しておく必要がある。
②ROMは変数として使うのは無理。
そのためプログラム実行時にRAMに値を格納する処理が必要とのこと。
[これから実施する内容]
文脈からの推測も多少あるがこんなかんじで理解してみた。
1.プログラム実行前はROMにプログラムが記憶されていてRAMは無関係。
2.プログラム実行すると静的変数のRAM領域をとる処理が実行される。初期化はできない。
3.初期化する情報をROMに格納していてそれを活用。
4.ROMから初期化情報を取り出しRAMにコピーする。
[ROMにある初期化情報について]
ここでPhysAddrが違ってたしかにROMに割り当てられVA≠PAになっているようだ。
静的変数の初期化でこうする理由は、以下で定義した場合のELF形式の中身を見てみるとわかる。
(bssセクションもここではVA≠PAとした)
.data : {
_data_start = . ;
*(.data)
_edata = . ;
} > data
readelfでAT>romがあるものとないものとで差分をとってみたら以下になる。
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000094 0x00000000 0x00000000 0x00100 0x00100 RW 0x1
LOAD 0x000194 0x00000100 0x00000100 0x00421 0x00421 R E 0x1
ATあり LOAD 0x0005b8 0x00fffc20 0x00000521 0x00010 0x00014 RW 0x1
---
ATなし LOAD 0x0005b8 0x00fffc20 0x00fffc20 0x00010 0x00014 RW 0x1
VirtAddr:RAMの変数のアドレス
PhysAddr:ROMの初期値がおかれるアドレス
たしかにPhysAddrに注目するとROM領域の0x00000521を指している。
[振り返ると!]
いままでの確認をすると、
ROMにもRAMにも*(.data)分の大きさのメモリがとられる。
プログラムの処理の中ではRAMの方のメモリを使用。
ROMには初期値を配置する。
それをAT >romという記述だけで、リンカが解釈し、意図した実行形式ファイルを作ってしまうということのようだ。
ELFファイルから実際に以下の内容が確認できればよいのだろう。
①ROMに領域がおかれることRAMにもおかれること
②ROMに初期値が格納されること
③RAMがプログラムの実行で活用されること
①を確認。
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000094 0x00000000 0x00000000 0x00100 0x00100 RW 0x1
LOAD 0x000194 0x00000100 0x00000100 0x00421 0x00421 R E 0x1
LOAD 0x0005b8 0x00fffc20 0x00000521 0x00010 0x00014 RW 0x1
セグメントはリンカの産物なので(オブジェクトファイルにはない!)、いろいろ推測していくしかありませんが本の説明としては以下のように説明があります。
3番目のセグメントで.dataセクションと.bssセクションはメモリ上で連続しているため、ひとつのセグメントにまとめられます。
ほかのセグメントは「FileSize=MemSize」になっていますが、ここだけ違います。
FileSizeは実行形式ファイル中でのサイズ、MemSizeはメモリ上に展開されるときのサイズです。
BSS領域は実行形式ファイル中で実体を持ちません。
このためFileSizeは.dataセクションのサイズのみとなり、.bssセクションのサイズは加算されません。
しかし実際にメモリ上に領域確保する際にはサイズ情報が必要なため、MemSizeは.dataセクションと.bssセクションの合計になります。
3番目のセグメントは、前半に.dataセクション後半に.bssセクションが配置されており、ロード作業は.dataセクション分だけ行えばよいことになります。
ここでいってる.bssセクションとは以下のようにリンカスクリプトに記述され.dataセクションと同じく「> data AT> rom」の記載があります。
.bss : {
_bss_start = . ;
*(.bss)
*(COMMON)
_ebss = . ;
} > data AT> rom
②③を確認。
これでROMの情報をプログラム実行時取得されたRAMにある静的変数への初期化を実現。
memcpy(&data_start, &erodata, (long)&edata - (long)&data_start);
memset(&bss_start, 0, (long)&ebss - (long)&bss_start);
また、ROMの初期値の場所について。
erodataとは、ここで集めたセクションの内容。
.rodata : {
_rodata_start = . ;
*(.strings) ・・・文字列リテラル
*(.rodata)
*(.rodata.*)
_erodata = . ;
} > rom
赤字のところ周辺に初期値が置かれているらしい。
ここが少し腑に落ちないがリンカがROMの情報とRAMの情報の並び順が同じという前提があり、*(.rodata.*)がそれに対応するという知識前提の処理なのだろう。
実際に動かしてみる。
Hello World!
global_data = 10
global_bss = 0
static_data = 20
static_bss = 0
overwrite variables.
global_data = 20
global_bss = 30
static_data = 40
static_bss = 50
OKのようだ。