はじめての組込みOS (3) ~ 静的変数の読み書き ~
今回は12ステップで作る組み込みos自作入門のステップ3を進めていく.
ステップ3では,ROMやRAMの違いやデータ領域,スタック,リンカスクリプトなどメモリ周りの学習がメイン.
組込みOSでのメモリ利用
通常のアプリケーションプログラミングでは,CPUのメモリ構成やメモリマップを把握する必要はほぼない.というのも汎用OSには仮想メモリという機構があるからである.
一方で組込みOSにはメモリを管理する機能が存在しないので,メモリの利用について考慮する必要がある. 具体的には
といった3点がある.
メモリの種類
今回でてきた用語は以下の3つ
用語 | 説明 |
---|---|
ROM(Read Only Memory) | read専用でプログラムからはwrite不可..しかし電源OFFでも内容を保持. |
フラッシュROM | 電気的に内容の書き換えが可能なROM |
RAM(Random Access Memory) | read, write可能だが,電源OFFで消失. |
ROMもRAMもCPUとは独立した別モジュールではあるけれど,ある程度の容量を確保したものがCPUに予め内蔵されている場合が多い.
メモリと領域
プログラム実行の際,実行形式ファイルはメモリ上に展開されるわけだが,ただコピーされるわけではなくメモリ上の担当領域に分割して配置される.
領域名 | 保持する内容 |
---|---|
テキスト領域 | CPUが実行する機械語のコード |
データ領域 | 初期値を持つstatic変数など |
BSS領域 | 初期値を持たないstatic変数 |
wikipediaのページによるとBSS領域のBSSとは
だそうだ.これぐらいの理解でそっとしておく.
BSS領域について更に補足をすると,確保時にCPUやコンパイラ次第ではあるが,多くの場合で変数はゼロ,ポインタはNULLに初期化される.組込みシステムではあえてゼロクリアさせることで,コード上での初期化処理をなくし,データサイズを節約する場合によく使われる手段だそうだ.
実行形式ファイルのセクション
実行形式ファイルも同様に領域が分けられている.どんな領域に分かれているのかについては詳しく書かれていなかったが,複数の領域情報やファイルの情報を「ヘッダ」として持っている.これはお馴染みの通り,ファイルや領域の先頭に付加される.
変数や関数がどのようにメモリ上に配置されているのか確認してみる.
今回利用しているgccは実行形式ファイルをELF(Executable and Linkable Format)形式というフォーマットで出力する.
ELF形式はreadelfコマンドで解析できるのだが,Macにはデフォルトではいっていないため導入からやった. ↓のようにbrewコマンドを叩く.El Captain(10.11)はサポートしてないよと注意されるが試した所,動いていたので無視(´・ω・`)
$ brew install binutils Warning: You are using OS X 10.11. We do not provide support for this pre-release version. You may encounter build failures or other breakage.
インストールが完了したら,
$ greadelf -a kzload.elf
と叩く.brewでいれたbinutilsのコマンドの先頭にはgがつくようだ.
↑のコマンドの結果の一部だが,↓のようなセクションに分かれていることがわかる. .textはテキスト領域に,.dataはデータ領域に対応している.
セクションヘッダ: [番] 名前 タイプ アドレス Off サイズ 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 0002dc 00 AX 0 0 2 [ 3] .rodata PROGBITS 000003dc 000450 00001f 01 AMS 0 0 1 [ 4] .data PROGBITS 000003fc 000470 00000c 00 WA 0 0 4 [ 5] .shstrtab STRTAB 00000000 00047c 000038 00 0 0 1 [ 6] .symtab SYMTAB 00000000 0005f4 0003c0 10 7 44 4 [ 7] .strtab STRTAB 00000000 0009b4 000167 00 0 0 1
また,変数の配置場所もこのコマンドから確認できる. ↓から_regという構造体の配列がアドレス:000003fcに配置されている. ここから↑のセクションヘッダヘッダを見てみると,.data領域のアドレスが000003fcなのでdata領域に配置されていることが確認できる. 同様にして見てみると,main関数(アドレス:0000010c)は.text領域(00000100)に配置されていることがわかる.
シンボルテーブル '.symtab' は 60 個のエントリから構成されています: 番号: 値 サイズ タイプ Bind Vis 索引名 ︙ ︙ 42: 000003fc 12 OBJECT LOCAL DEFAULT 4 _regs ︙ ︙ ︙ 59: 0000010c 66 NOTYPE GLOBAL DEFAULT 2 _main
リンカスクリプト
実行形式ファイルのデータのメモリ上への配置はリンクの段階で行われる.
つまり,リンクの際にはプログラムのどの部分がどのメモリアドレスに配置されるかをリンカに指定しなければならないが,これを行うのがリンカスクリプトである.
書籍においては,ステップ2の時点でのコードでは変数がROMに配置がされてしまっており,値の変更ができない. 変更できるようリンカスクリプトを書いて,プログラムをh8へ転送する際にはROMに書き込み,プログラム起動時には変数の初期値をROMからRAMにコピーし更に変数アクセスの際には,RAM上のアドレスに対しアクセスするように設定する.
その他でてきた用語を整理しておく.
- 変数の初期値が配置されるアドレス
- プログラムが変数にアクセスする際のアドレス
ここでの物理アドレスや仮想アドレスは厳密には相応しくないように思えるが,readelfの出力からこう呼んである.
プログラムヘッダ: タイプ オフセット 仮想Addr 物理Addr FileSiz MemSiz Flg Align LOAD 0x000074 0x00000000 0x00000000 0x003fb 0x003fb RWE 0x1 LOAD 0x000470 0x000003fc 0x000003fc 0x0000c 0x0000c RW 0x1
ELF形式はセッションの他にセグメントという管理単位を持つ.
↑はセグメント一覧である..プログラム実行時セグメント情報が参照され,メモリ上に展開される作業をロード(load)と呼び,ロードを行うプログラムをローダ(loader)と呼ぶ.
ここでELF形式のセッションとセグメントをまとめると
- セッション
- リンク時に同じ内容の領域をリンカがまとめるためのもの
- セグメント
- プログラム実行時にローダが参照してメモリ上に展開するためのもの
参照
静的変数の書き換え対応
コンパイルからh8へのプログラム転送までの作業メモ
(書籍のP.36-参照)
$ pwd ~/workspace/12step/src/03/bootload # ファームウェア作成 $ make # h8writeでフラッシュROMに書き込むため,.mot形式に変換 $ make image
h8をwriteモードに.
ディップスイッチを左からON,ON,OFF,ONにする.
この状態で電源ON.
# h8writeの実行 $ make write # シリアル接続する # 接続解除する場合は「~.」を押下する. $ sudo cu -l /dev/tty.usbserial-FTZ4UUFE
フラッシュROMからの起動起動には ディップスイッチを左からON,OFF,ON,OFFにした状態で電源ON. で横にあるリセットボタンを押す.
C言語メモ
- extern 修飾子
- memcpy 関数
- memset 関数
書籍の通りにごねごね進めて,プログラム実行して変数の変更まで完了 ・ω・!
参照
一言
ただの変数の読み書きといえども0からやるとなかなか手間がかかる.