More Related Content
Similar to 【学習メモ#3rd】12ステップで作る組込みOS自作入門 (20)
【学習メモ#3rd】12ステップで作る組込みOS自作入門
- 5. 組込みOSにおけるメモリの扱い
● 組込みOSでは仮想メモリのような複雑な機構は
実装しないだからマイコン・ボードの持つメモ
リの扱いはシビア
– どのように割り当てて利用するか、調整することが
必要
● 具体的には以下の3つのことをする必要がある
– CPUのメモリ構成を知る
– リンカ・スクリプトによるメモリ配分方法
– コンパイラのメモリの配置方法
- 6. 静的変数の読み書き
● 今のままだと変数の書き換えができない
– 下記のコードは期待通りなら「a」「14」になるは
ずだが、「a」「a」になってる
#include "lib.h"
volatile int value = 10;
int main(void)
{
serial_init(SERIAL_DEFAULT_DEVICE);
puts("Hello World!n");
putxval(value, 0); puts("n"); /* a */
value = 20;
putxval(value, 0); puts("n"); /* a(valueの値がかわっていない) */
- 7. 書き換えができない原因
● ROMにある変数の初期値がRAMにコピーされてい
ないから
● ROM上にあると値を書き換えることができない
– よく考えると当たり前のこと
– ROMをプログラム側で書き換えることができればシ
ステムはわやくそにいじれてしまう
● 「ROM = ブートローダ」と考えるとしっくりく
るんじゃないか。プログラム側からブードロー
ダを書き換えられるって大問題
- 8. メモリの基礎
● メモリにはROMとRAMがある
● ROMは読み込み専用で書き込み不可だが、電源
OFFでも内容は保持される
– ROMには数種類あるが、H8/3069Fで扱うROMはフラッ
シュROMという特別な操作により内容の書き換えが
可能なROM
● RAMは読み書き可能だが、電源OFFで内容は失わ
れる
- 9. H8/3069F内蔵のROM・RAM
● 組込み向けのCPUではある程度の容量を持つROM
とRAMをあらかじめ内蔵しているケースが多い
● H8/3069Fは512KBのフラッシュROMと16KBのRAM
を内蔵している
● マッピングされているアドレスは次のスクリー
ンショットの通り
- 11. メモリと領域
● CPUはメモリにあるプログラムしか実行できな
いので、プログラムをメモリにコピーしなけれ
ばならない
● コピーされたプログラムはそのままの機械語
コードではなくいくつかの領域から構成されて
いる
● また、実行形式ファイルの内部もいくつかの領
域に分かれており、その領域単位でメモリへの
コピーが行われている
● メモリ上にコピーされたプログラムは、次の3
つの領域を持つ
- 12. メモリ上の3つの領域
● テキスト領域
– CPUが実行する機械語コードが置かれる
● データ領域
– 初期値を持つ静的変数などが置かれる
● BSS領域
– 初期値を持たない静的変数などが置かれる
- 13. 静的変数と自動変数
● 静的変数
– メモリ上に割り当てられた変数
– 関数外やstaticで定義した変数がこれ。グローバル
変数のことかな
● 自動変数
– スタックに割り当てられた変数
– 関数内で定義した変数はこれ。ローカル変数ってこ
とかな
- 14. 静的領域
● 静的変数はメモリ上のどこかに固定で割り当て
られる
● この固定の割当先を静的領域と呼ぶ
● 静的領域は次の2つの領域から構成される
– データ領域...初期値を持つ変数はここ
– BSS領域...初期値を持たない変数はここ
● 初期値のある変数はプログラムの実行開始時に
初期値を設定する必要がある
– なので、データ領域は変数の初期値を書き込んだ状
態で実行形式ファイル上に作成される
- 15. 領域のヘッダ
● 実行形式ファイルの内部も領域分けされている
– 単純に機械語の命令がベタに書かれているわけでは
ない
● 分割された領域はその領域情報やファイル情報
をヘッダとして持っている
● 実行形式ファイルにも何種類かフォーマットが
あるが、多くのフォーマットは最低でも3つの
領域は持っている
● gccが生成する実行形式ファイルのフォーマッ
トはELF形式
- 16. 実行形式ファイルのフォーマット
● 実行形式ファイルにも何種類かフォーマットが
あるが、多くのフォーマットは最低でも3つの
領域は持っている
● gccが生成する実行形式ファイルのフォーマッ
トはELF形式
● ELF形式のファイルは、readelfコマンドで解析
することができる。
– h8300-elf-readelf -a kzload.elf
- 17. readelfが出力したデータ
/Users/sandai/12step/src/02/bootload% h8300-elf-readelf -a kzload.elf
ELF Header:
Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00
.
.
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .vectors PROGBITS 00000000 000074 000100 00 WA 0 0 4
[ 2] .text PROGBITS 00000100 000174 0002b8 00 AX 0 0 2
[ 3] .text.startup PROGBITS 000003b8 00042c 000042 00 AX 0 0 2
[ 4] .rodata PROGBITS 000003fc 000470 00002b 00 A 0 0 4
.
.
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000074 0x00000000 0x00000000 0x003fa 0x003fa RWE 0x1
LOAD 0x000470 0x000003fc 0x000003fc 0x0002b 0x0002b R 0x1
.
.
Symbol table '.symtab' contains 58 entries:
Num: Value Size Type Bind Vis Ndx Name
.
.
40: 000003fc 12 OBJECT LOCAL DEFAULT 4 _regs
.
.
43: 00000000 256 OBJECT GLOBAL DEFAULT 1 _vectors
44: 00000278 46 NOTYPE GLOBAL DEFAULT 2 _putc
45: 000002a6 36 NOTYPE GLOBAL DEFAULT 2 _puts
.
.
57: 000003b8 66 NOTYPE GLOBAL DEFAULT 3 _main
- 18. セクション
● ELF形式は、ファイルの内部をセクションとい
う単位で区切って管理している
– .textセクション...テキスト領域
– .dataセクション...データ領域
– .bssセクション...bss領域
– その他いくつかセクションが存在する
● さっきの出力内容は書籍と違いがいくつかある
– .dataがない
– _mainが.text.startupセクションに置かれている
(書籍では.textに置かれている)
● 原因はgccのバージョンが違うから?
- 19. プログラムはROM上で動いている
● 現状のコードはh8writeによってH8内部のフ
ラッシュROMに書き込まれている
● CPUはROMから命令を読み込み、逐次実行してい
るだけ。これを「プログラムはROM上で動いて
いる」と呼ぶ
● プログラムがRAMにコピーされずに動いてい
るってことかな
– ノイマン型であればメモリにロードするイメージし
かないんだけど、メモリにロードする前に逐次処理
してるってことでいいのか。ROMの方が遅いだろう
ね
- 20. 変数の実体と配置1
● コンピュータは変数の値を実体としてメモリに
記憶する
– 自動変数はレジスタに配置されることもあるが、こ
こでは深く言及しない
● 変数を読み書きするとはその記憶したメモリを
読み書きするということ
● 変数とは特定のメモリ領域に付けられた名前で
しかない
- 21. 変数の実体と配置2
● 変数の割り当て方法は大きく分けて2通り
– 自動変数はスタックに割り当てる
– 静的変数は静的領域(データ、bss領域)に割り当て
る
● 自動変数はスタックに割り当てられ、関数を抜
けたら捨てられる
● ただし、lib.cやserial.cで利用している自動
変数はstartup.sでスタック・ポインタをRAM上
に設定しているため、読み書きできている状態
にある
- 22. 静的変数の書き換え
● ROM上に配置された変数を読み書きするコード
は、ROM上の領域を読み書きする機械語コード
に置き換えられる
● しかし、ROMへの書き込みはできないので変数
の値を書き換えるコードでも実際は書き換えら
れない
● 今後進めていく上で静的変数の値を書き換えら
れないというのは面倒
● そこで静的変数のデータをROM上ではなくRAM上
に配置する必要がある
- 24. リンカ・スクリプト(ld.scr)
● リンカ・スクリプトは実行形式ファイルを作成
する際に、機械語コードや静的領域をどのよう
にアドレス割り当てするのか、リンカに対して
指示するためのファイル
– 物理的メモリにセクションをどのようにマッピング
するかを定義するってことかな
– リンクの段階でプログラムをメモリにどう配置する
のか決定しているわけね
● 静的領域をROMからRAMへ配置を変えるなら、こ
のリンク・スクリプトによって行う
- 25. id.scrのコード解説1
● ld.scrに書かれている.textや.dataがセクショ
ンの定義になる
● 先頭に. = 0x0;とあるが、「.」はロケーショ
ン・カウンタ
– 現在のカレント・アドレスを表す
● . = 0x0はロケーション・カウンタをゼロに初
期化していることになるので、以降のセクショ
ンはゼロ番地から配置される
- 26. id.scrのコード解説2
.vectors : {
vector.o(.data)
}
● .vectorsというセクションを作成し、vector.o
の.dataセクションを配置
● . = 0x0の直後なので、フラッシュROMの先頭に
配置していることになるね
– 割り込みベクタはこのゼロ番地から始まる
– そして割り込みベクタの先頭はリセット・ベクタな
ので、ここにstartup.sの_start関数が割り当てら
れているってわけ
- 27. id.scrのコード解説3
.text : {
*(.text)
}
● .textセクションの配置
● 各オブジェクト・ファイルの.textセクション
がここに配置される
● *は正規表現で使う*と似たようなもんかなとも
思ったけど、なんか違うような...
– とりあえずこの場合全てのオブジェクト・ファイル
の.textセクションをここに配置するって意味にな
ると思う
- 28. id.scrのコード解説4
.rodata : {
*(.strings)
*(.rodata)
*(.rodata.*)
}
● const定義した変数や文字列リテラルなどが配
置される
● .stringsは文字列リテラルか?
– コンパイラは文字列リテラルが出てきたときに、メモリに
割り当てて、C言語中で文字列が書かれた箇所はその文字列
のメモリ上のアドレス値に書き換えられる
● .rodataは「Read only data」の意味
– プログラムの実行中に書き換えられることの無いやつはこ
こ。割込みベクタや.textセクション(機械語コード)とか
- 29. ロケーション・カウンタ
● .vectorsの後に.textを配置しているが、この
場合.textは.vectorsの末尾から配置される
● このように、セクションはロケーション・カウ
ンタの指す位置に配置され、ロケーション・カ
ウンタはセクションを配置するたびにそのサイ
ズ分だけ増加していく
● 便利だねー
- 30. 静的変数の配置先
● .rodataの直後に静的領域が割り当てられてい
る。つまりROM上に存在することになるので、
値を書き換えられないわけだ
.rodata : {
.
.
}
.data : {
*(.data)
}
.bss : {
*(.bss)
*(COMMON)
}
- 31. id.scrで配置先をRAMにしてみる
● 静的変数を以下のように配置するとどうなるか
.rodata : {
*(.strings)
*(.rodata)
*(.rodata.*)
}
. = 0xffbf20;
.data : {
*(.data)
}
.bss : {
*(.bss)
*(COMMON)
}
- 32. RAMに配置する
● 前のコードはつまりロケーション・カウンタを
RAMに設定して、静的領域だけRAMに配置される
ようにしている
– 0xffbf20はRAMのアドレス
● ROM上でないから書き換えができるかとおもい
きや、これは正しく動作しない
- 33. RAMに配置しても動作しない理由
● h8writeで実行形式ファイルを書き込むわけだ
けど、h8writeはフラッシュROMに対して行われ
る
● だからRAMに設定したところで意味はない
– ROMじゃないよってh8writeでエラーも表示される
● 仮にRAMに書き込めたとしても、電源を落とし
たら初期値が消えてしまう
– 次に電源ONしたときにそのデータが無いってこと。
コードに書かれていても実体となるデータがなけ
りゃどうしようもないさ
- 34. 書き換えができない問題点
● 問題点は次の2つ
– ①変数の本体をROMに置くと
● 書き込みができない
– ②変数の本体をRAMに置くと
● 電源OFFで値が消えてしまう
● じゃあどうすればいいのか?
– まずROMに書き込んでそれからRAMにコピーして、プ
ログラムからはRAM上のデータにアクセスするよう
にしたらいいじゃないか
- 35. 静的変数を書き換え可能にする
● ①変数の初期値をROMに保存するようにしてフ
ラッシュROMに書き込む
● ②電源ONでプログラムを起動したときに、プロ
グラムの先頭付近でフラッシュROMの変数の初
期値をRAMにコピー
● そうしてプログラムから変数にアクセスすると
きは、RAM上のコピー先のアドレスに対してア
クセスされるようにする
- 36. 物理アドレスと論理アドレス
● ROMからRAMへコピーしてRAMでデータを操作す
るということは、初期値が配置されるアドレス
とプログラムが変数にアクセスするときのアド
レスが違うということになる
– ROMのアドレスに実際の初期値があるけど、プログ
ラムから操作するときはRAMのアドレスにあるデー
タだからね
● ここではROMのアドレスを物理アドレス、RAMの
アドレスを論理アドレスと呼ぶ
– 仮想メモリの用語とかぶるけど、readelfに合わせ
るために物理アドレスや論理アドレスを使っている
- 37. 物理アドレス
● 物理アドレス(Physical Address)はロード・ア
ドレス(Load Address)とも呼ばれる
– PAだったりLAと略されたり、LMA(Load Memory
Address)と呼ばれることもある
● ここでは単純にROM上のアドレスを物理アドレ
スと考えれば良い
- 38. 論理アドレス
● 論理アドレス(Logical Address)はリンク・ア
ドレス(Link Address)とも呼ばれる
– LAと略されたり、仮想アドレス(Virtual Address)
と呼ばれVAと略されることがある
● 単純にRAM上のアドレスが論理アドレスと考え
れば良い
- 39. 現状のPAやVAの保存先
● 実行形式ファイルにPAやVAが保存されている
● readelf出力結果のうち、VirtAddrが
VA、PhysAddrがPA
● 今のところどちらも0x3fc
– つまりどっちもROM上のアドレスを指している
● これからPhysAddrをRAM上に割り当てる必要が
ある
– これを一般にVA≠PAにする」と言う
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg
Align
LOAD 0x000074 0x00000000 0x00000000 0x003fa 0x003fa RWE 0x1
LOAD 0x000470 0x000003fc 0x000003fc 0x0002b 0x0002b R 0x1
- 40. セグメント
● ELF形式はセクションの他にセグメントという
管理単位を持っている
● Program Headersはセグメントの一種
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000074 0x00000000 0x00000000 0x003fa 0x003fa RWE 0x1
LOAD 0x000470 0x000003fc 0x000003fc 0x0002b 0x0002b R 0x1
.
.
- 41. ロードとローダ
● プログラムの実行時にセグメント情報が参照さ
れメモリに展開される。これをロードと呼ぶ
● ロードを行うプログラムをローダと呼ぶ
● ローダが実行形式ファイルのセグメントを参照
して、そのとおりにメモリ上に展開するってわ
け
- 42. セクションとセグメントの違い
● セクションはリンク時に同じ内容の領域をリン
カがまとめるためのもの
● セグメントはプログラムの実行時にローダが参
照してメモリ上に展開するためのもの
– ローダが参照するのはセグメントであって、セク
ションではない
● 用語の出現場所が似てるから混乱しやすいな
- 44. プログラムの修正
● 修正したファイル
– ld.scr
● 「VA≠PA」対策
– main.c
● 静的変数の書き換えサンプル追加
– startup.s
● スタックの設定の修正
● これらのうち、重要な「VA≠PA」対策について
述べておく
- 45. VA≠PA対策
● 静的領域をRAM上でプログラムから操作できる
ようにするにあたって行ったことは次の通り
– .dataセクションの実際のデータをROMに配置
– これをプログラム側からはRAMに配置されているよ
うにみせる
– みせても実際にデータは配置されていないの
で、ROMの.dataセクションをRAMにコピーして操作
できるようにする
- 46. リンカ・スクリプトで行うこと
● ROMに配置するのと、RAMに配置されたようにみ
せかけるのはリンカ・スクリプトで行う
● 具体的には、まずMEMORYコマンドでセクション
の各領域を定義
● それから、.dataセクションにそれらの領域を
割り当てる
● 「> data」がRAMに配置することを意味して、
「AT> rom」がROM上の物理アドレスに配置する
ことを意味する
- 47. リンカ・スクリプトのコード
● いろいろ省略しているが次の通り
MEMORY
{
.
.
rom(rx) : o = 0x000100, l = 0x7fff00
.
.
data(rwx) : o = 0xfffc20, l = 0x000300
.
.
}
.data : {
_data_start = .;
*(.data)
_edata = .;
} > data AT> rom
- 48. 「> data」と「AT> rom」
● .dataセクションの実際のデータをROMに配置
– 「AT> rom」の部分がこれにあたる
● プログラム側からはRAMに配置されているよう
にみせる
– 「> data」の部分がこれにあたる
● こうして.dataセクションのデータはROMに配置
されることになるが、プログラム側から
は.dataセクションはRAM上にあるようにみえる
● つまり、今の状態ではデータがRAMにあるよう
にみえるだけで、実際のデータはROMにある
– 次は変数のデータをROMからRAMにコピーして、実際
に操作できるようにする
- 49. ROMの静的領域をRAMにコピー
● コピーはリンカ・スクリプトではなく、main.c
でのプログラムの先頭で行う
● .bssセクションについてはコピーではなくゼロ
クリアをして、初期値を持たない変数に対して
初期値を設定している
● こういった処理をまとめて初期化と呼んだりす
る
memcpy(&data_start, &erodata, (long)*edata – (long)&data_start)
memset(&bss_start, 0, (long)&ebss - (long)&bss_start)
- 50. ROMの静的領域をRAMにコピー
● 詳しい説明は書籍を参照してほしいが、前の
コードの意味は以下の通り
– ROM上にある.dataセクションにある変数の初期値を
RAMにコピーしている
– RAM上の.bssセクションをゼロクリアして、.bssセ
クションに配置された変数のメモリ上の値をゼロに
初期化している
● .bssセクションはコピーしているわけではない
● ここまでしてようやくC言語を普通に扱えるよ
うになった
● 当然ながら、先ほどの初期化コードの前に静的
変数を定義すると正常に動作しない
- 51. ビルドで失敗
● main.cで静的領域の書き換えができるかどうか
のプログラムを実行するだけだが、その前に
● ビルドこけた...
– gcc4.7使ってるけど、たぶんここが問題だな
/Users/sandai/12step/tools/lib/gcc/h8300-
elf/4.7.1/../../../../h8300-elf/bin/ld: section .text.startup
[0000000000000000 -> 0000000000000081] overlaps section .vectors
[0000000000000000 -> 00000000000000ff]
collect2: error: ld returned 1 exit status
make: *** [kzload] Error 1
- 52. ld.scrの修正
● .text.startupが何なのかよくわからんが、ど
うやら_main関数らしい
● _main関数はたぶん.textセクションなのでそこ
にぶっこんだ
.text : {
_text_start = . ;
*(.text.startup) ←こいつね
*(.text)
_etext = . ;
} > rom
- 53. 再度プログラム実行
● うまくいったー!あせったー
/Users/sandai/12step/src/03/bootload% sudo cu -l
/dev/tty.usbserial-FTG6PQ4H
Password:
Connected.
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
~.
Disconnected.
- 54. 書籍との違い
● _regs変数がどうも定数の扱いになってる
– .rodataセクションに配置されているみたい
– ROMに入っていて問題はないかな?
– まあたぶんプログラム側から操作するような変数
じゃなかったからいいけど
● 大きな違いはここと、あとは.text.startupだ
なあ
- 56. まとめ
● とりあえずこれでH8でC言語を普通に扱えるよ
うになった
– 具体的には静的領域をROMからRAMにコピーすること
によって静的変数の書き換えが可能となった
● 組込みプログラミングというのは自前で用意し
なきゃいけないことが多い