Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
SlideShare a Scribd company logo
12ステップで作る組込みOS自作入門
      3rdステップ




                @sandai
【参考書籍】
12ステップで作る組込みOS自作入門
【内容】
1ステップずつ、実際に動かしながらプログラムを発展さ
せていく方式で無理なく学べる。OSやハードウェアに詳
しくない方にも理解できるよう
に十分な説明を提供

坂井 弘亮(著)
カットシステム(2010/5)

【税込価格】
4,410円

【サポートページ】
http://kozos.jp/books/makeos/
もくじ
1.メモリ構成
2.リンカ・スクリプト
3.「VA≠PA」の対応とプログラム実行
4.まとめ
1.メモリ構成
組込みOSにおけるメモリの扱い
●   組込みOSでは仮想メモリのような複雑な機構は
    実装しないだからマイコン・ボードの持つメモ
    リの扱いはシビア
    –   どのように割り当てて利用するか、調整することが
        必要
●   具体的には以下の3つのことをする必要がある
    –   CPUのメモリ構成を知る
    –   リンカ・スクリプトによるメモリ配分方法
    –   コンパイラのメモリの配置方法
静的変数の読み書き
●   今のままだと変数の書き換えができない
    –   下記のコードは期待通りなら「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の値がかわっていない) */
書き換えができない原因
●   ROMにある変数の初期値がRAMにコピーされてい
    ないから
●   ROM上にあると値を書き換えることができない
    –   よく考えると当たり前のこと
    –   ROMをプログラム側で書き換えることができればシ
        ステムはわやくそにいじれてしまう
         ● 「ROM = ブートローダ」と考えるとしっくりく

           るんじゃないか。プログラム側からブードロー
           ダを書き換えられるって大問題
メモリの基礎
●   メモリにはROMとRAMがある
●   ROMは読み込み専用で書き込み不可だが、電源
    OFFでも内容は保持される
    –   ROMには数種類あるが、H8/3069Fで扱うROMはフラッ
        シュROMという特別な操作により内容の書き換えが
        可能なROM
●   RAMは読み書き可能だが、電源OFFで内容は失わ
    れる
H8/3069F内蔵のROM・RAM
●   組込み向けのCPUではある程度の容量を持つROM
    とRAMをあらかじめ内蔵しているケースが多い
●   H8/3069Fは512KBのフラッシュROMと16KBのRAM
    を内蔵している
●   マッピングされているアドレスは次のスクリー
    ンショットの通り
H8/3069Fのメモリ・マップ




92頁 図3.1 H8/3069Fの内蔵 ROM・RAMの配置 より
メモリと領域
●   CPUはメモリにあるプログラムしか実行できな
    いので、プログラムをメモリにコピーしなけれ
    ばならない
●   コピーされたプログラムはそのままの機械語
    コードではなくいくつかの領域から構成されて
    いる
●   また、実行形式ファイルの内部もいくつかの領
    域に分かれており、その領域単位でメモリへの
    コピーが行われている
●   メモリ上にコピーされたプログラムは、次の3
    つの領域を持つ
メモリ上の3つの領域
●   テキスト領域
    –   CPUが実行する機械語コードが置かれる
●   データ領域
    –   初期値を持つ静的変数などが置かれる
●   BSS領域
    –   初期値を持たない静的変数などが置かれる
静的変数と自動変数
●   静的変数
    –   メモリ上に割り当てられた変数
    –   関数外やstaticで定義した変数がこれ。グローバル
        変数のことかな
●   自動変数
    –   スタックに割り当てられた変数
    –   関数内で定義した変数はこれ。ローカル変数ってこ
        とかな
静的領域
●   静的変数はメモリ上のどこかに固定で割り当て
    られる
●   この固定の割当先を静的領域と呼ぶ
●   静的領域は次の2つの領域から構成される
    –   データ領域...初期値を持つ変数はここ
    –   BSS領域...初期値を持たない変数はここ
●   初期値のある変数はプログラムの実行開始時に
    初期値を設定する必要がある
    –   なので、データ領域は変数の初期値を書き込んだ状
        態で実行形式ファイル上に作成される
領域のヘッダ
●   実行形式ファイルの内部も領域分けされている
    –   単純に機械語の命令がベタに書かれているわけでは
        ない
●   分割された領域はその領域情報やファイル情報
    をヘッダとして持っている
●   実行形式ファイルにも何種類かフォーマットが
    あるが、多くのフォーマットは最低でも3つの
    領域は持っている
●   gccが生成する実行形式ファイルのフォーマッ
    トはELF形式
実行形式ファイルのフォーマット
●   実行形式ファイルにも何種類かフォーマットが
    あるが、多くのフォーマットは最低でも3つの
    領域は持っている
●   gccが生成する実行形式ファイルのフォーマッ
    トはELF形式
●   ELF形式のファイルは、readelfコマンドで解析
    することができる。
    –   h8300-elf-readelf -a kzload.elf
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
セクション
●   ELF形式は、ファイルの内部をセクションとい
    う単位で区切って管理している
    –   .textセクション...テキスト領域
    –   .dataセクション...データ領域
    –   .bssセクション...bss領域
    –   その他いくつかセクションが存在する
●   さっきの出力内容は書籍と違いがいくつかある
    –   .dataがない
    –   _mainが.text.startupセクションに置かれている
        (書籍では.textに置かれている)
●   原因はgccのバージョンが違うから?
プログラムはROM上で動いている
●   現状のコードはh8writeによってH8内部のフ
    ラッシュROMに書き込まれている
●   CPUはROMから命令を読み込み、逐次実行してい
    るだけ。これを「プログラムはROM上で動いて
    いる」と呼ぶ
●   プログラムがRAMにコピーされずに動いてい
    るってことかな
    –   ノイマン型であればメモリにロードするイメージし
        かないんだけど、メモリにロードする前に逐次処理
        してるってことでいいのか。ROMの方が遅いだろう
        ね
変数の実体と配置1
●   コンピュータは変数の値を実体としてメモリに
    記憶する
    –   自動変数はレジスタに配置されることもあるが、こ
        こでは深く言及しない
●   変数を読み書きするとはその記憶したメモリを
    読み書きするということ
●   変数とは特定のメモリ領域に付けられた名前で
    しかない
変数の実体と配置2
●   変数の割り当て方法は大きく分けて2通り
    –   自動変数はスタックに割り当てる
    –   静的変数は静的領域(データ、bss領域)に割り当て
        る
●   自動変数はスタックに割り当てられ、関数を抜
    けたら捨てられる
●   ただし、lib.cやserial.cで利用している自動
    変数はstartup.sでスタック・ポインタをRAM上
    に設定しているため、読み書きできている状態
    にある
静的変数の書き換え
●   ROM上に配置された変数を読み書きするコード
    は、ROM上の領域を読み書きする機械語コード
    に置き換えられる
●   しかし、ROMへの書き込みはできないので変数
    の値を書き換えるコードでも実際は書き換えら
    れない
●   今後進めていく上で静的変数の値を書き換えら
    れないというのは面倒
●   そこで静的変数のデータをROM上ではなくRAM上
    に配置する必要がある
2.リンカ・スクリプト
リンカ・スクリプト(ld.scr)
●   リンカ・スクリプトは実行形式ファイルを作成
    する際に、機械語コードや静的領域をどのよう
    にアドレス割り当てするのか、リンカに対して
    指示するためのファイル
    –   物理的メモリにセクションをどのようにマッピング
        するかを定義するってことかな
    –   リンクの段階でプログラムをメモリにどう配置する
        のか決定しているわけね
●   静的領域をROMからRAMへ配置を変えるなら、こ
    のリンク・スクリプトによって行う
id.scrのコード解説1
●   ld.scrに書かれている.textや.dataがセクショ
    ンの定義になる
●   先頭に. = 0x0;とあるが、「.」はロケーショ
    ン・カウンタ
    –   現在のカレント・アドレスを表す
●   . = 0x0はロケーション・カウンタをゼロに初
    期化していることになるので、以降のセクショ
    ンはゼロ番地から配置される
id.scrのコード解説2
.vectors : {
        vector.o(.data)
}

●   .vectorsというセクションを作成し、vector.o
    の.dataセクションを配置
●   . = 0x0の直後なので、フラッシュROMの先頭に
    配置していることになるね
    –   割り込みベクタはこのゼロ番地から始まる
    –   そして割り込みベクタの先頭はリセット・ベクタな
        ので、ここにstartup.sの_start関数が割り当てら
        れているってわけ
id.scrのコード解説3
.text : {
        *(.text)
}

●   .textセクションの配置
●   各オブジェクト・ファイルの.textセクション
    がここに配置される
●   *は正規表現で使う*と似たようなもんかなとも
    思ったけど、なんか違うような...
    –   とりあえずこの場合全てのオブジェクト・ファイル
        の.textセクションをここに配置するって意味にな
        ると思う
id.scrのコード解説4
.rodata : {
        *(.strings)
        *(.rodata)
        *(.rodata.*)
}

●   const定義した変数や文字列リテラルなどが配
    置される
●   .stringsは文字列リテラルか?
    –   コンパイラは文字列リテラルが出てきたときに、メモリに
        割り当てて、C言語中で文字列が書かれた箇所はその文字列
        のメモリ上のアドレス値に書き換えられる
●   .rodataは「Read only data」の意味
    –   プログラムの実行中に書き換えられることの無いやつはこ
        こ。割込みベクタや.textセクション(機械語コード)とか
ロケーション・カウンタ
●   .vectorsの後に.textを配置しているが、この
    場合.textは.vectorsの末尾から配置される
●   このように、セクションはロケーション・カウ
    ンタの指す位置に配置され、ロケーション・カ
    ウンタはセクションを配置するたびにそのサイ
    ズ分だけ増加していく
●   便利だねー
静的変数の配置先
●   .rodataの直後に静的領域が割り当てられてい
    る。つまりROM上に存在することになるので、
    値を書き換えられないわけだ
.rodata : {
  .
  .
}

.data : {
        *(.data)
}

.bss : {
        *(.bss)
        *(COMMON)
}
id.scrで配置先をRAMにしてみる
●   静的変数を以下のように配置するとどうなるか
.rodata : {
        *(.strings)
        *(.rodata)
        *(.rodata.*)
}

. = 0xffbf20;

.data : {
       *(.data)
}

.bss : {
      *(.bss)
      *(COMMON)
}
RAMに配置する
●   前のコードはつまりロケーション・カウンタを
    RAMに設定して、静的領域だけRAMに配置される
    ようにしている
    –   0xffbf20はRAMのアドレス
●   ROM上でないから書き換えができるかとおもい
    きや、これは正しく動作しない
RAMに配置しても動作しない理由
●   h8writeで実行形式ファイルを書き込むわけだ
    けど、h8writeはフラッシュROMに対して行われ
    る
●   だからRAMに設定したところで意味はない
    –   ROMじゃないよってh8writeでエラーも表示される
●   仮にRAMに書き込めたとしても、電源を落とし
    たら初期値が消えてしまう
    –   次に電源ONしたときにそのデータが無いってこと。
        コードに書かれていても実体となるデータがなけ
        りゃどうしようもないさ
書き換えができない問題点
●   問題点は次の2つ
    –   ①変数の本体をROMに置くと
        ●   書き込みができない
    –   ②変数の本体をRAMに置くと
        ●   電源OFFで値が消えてしまう
●   じゃあどうすればいいのか?
    –   まずROMに書き込んでそれからRAMにコピーして、プ
        ログラムからはRAM上のデータにアクセスするよう
        にしたらいいじゃないか
静的変数を書き換え可能にする
●   ①変数の初期値をROMに保存するようにしてフ
    ラッシュROMに書き込む
●   ②電源ONでプログラムを起動したときに、プロ
    グラムの先頭付近でフラッシュROMの変数の初
    期値をRAMにコピー
●   そうしてプログラムから変数にアクセスすると
    きは、RAM上のコピー先のアドレスに対してア
    クセスされるようにする
物理アドレスと論理アドレス
●   ROMからRAMへコピーしてRAMでデータを操作す
    るということは、初期値が配置されるアドレス
    とプログラムが変数にアクセスするときのアド
    レスが違うということになる
    –   ROMのアドレスに実際の初期値があるけど、プログ
        ラムから操作するときはRAMのアドレスにあるデー
        タだからね
●   ここではROMのアドレスを物理アドレス、RAMの
    アドレスを論理アドレスと呼ぶ
    –   仮想メモリの用語とかぶるけど、readelfに合わせ
        るために物理アドレスや論理アドレスを使っている
物理アドレス
●   物理アドレス(Physical Address)はロード・ア
    ドレス(Load Address)とも呼ばれる
    –   PAだったりLAと略されたり、LMA(Load Memory
        Address)と呼ばれることもある
●   ここでは単純にROM上のアドレスを物理アドレ
    スと考えれば良い
論理アドレス
●   論理アドレス(Logical Address)はリンク・ア
    ドレス(Link Address)とも呼ばれる
    –   LAと略されたり、仮想アドレス(Virtual Address)
        と呼ばれVAと略されることがある
●   単純にRAM上のアドレスが論理アドレスと考え
    れば良い
現状の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
セグメント
●   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
  .
  .
ロードとローダ
●   プログラムの実行時にセグメント情報が参照さ
    れメモリに展開される。これをロードと呼ぶ
●   ロードを行うプログラムをローダと呼ぶ
●   ローダが実行形式ファイルのセグメントを参照
    して、そのとおりにメモリ上に展開するってわ
    け
セクションとセグメントの違い
●   セクションはリンク時に同じ内容の領域をリン
    カがまとめるためのもの
●   セグメントはプログラムの実行時にローダが参
    照してメモリ上に展開するためのもの
    –   ローダが参照するのはセグメントであって、セク
        ションではない
●   用語の出現場所が似てるから混乱しやすいな
3.「VA≠PA」の対応とプログラム実
          行
プログラムの修正
●   修正したファイル
    –   ld.scr
         ● 「VA≠PA」対策


    –   main.c
         ● 静的変数の書き換えサンプル追加


    –   startup.s
         ● スタックの設定の修正


●   これらのうち、重要な「VA≠PA」対策について
    述べておく
VA≠PA対策
●   静的領域をRAM上でプログラムから操作できる
    ようにするにあたって行ったことは次の通り
    –   .dataセクションの実際のデータをROMに配置
    –   これをプログラム側からはRAMに配置されているよ
        うにみせる
    –   みせても実際にデータは配置されていないの
        で、ROMの.dataセクションをRAMにコピーして操作
        できるようにする
リンカ・スクリプトで行うこと
●   ROMに配置するのと、RAMに配置されたようにみ
    せかけるのはリンカ・スクリプトで行う
●   具体的には、まずMEMORYコマンドでセクション
    の各領域を定義
●   それから、.dataセクションにそれらの領域を
    割り当てる
●   「> data」がRAMに配置することを意味して、
    「AT> rom」がROM上の物理アドレスに配置する
    ことを意味する
リンカ・スクリプトのコード
●   いろいろ省略しているが次の通り
MEMORY
{
         .
         .
         rom(rx)           : o = 0x000100, l = 0x7fff00
         .
         .
         data(rwx)         : o = 0xfffc20, l = 0x000300
         .
         .
}

.data : {
        _data_start = .;
        *(.data)
        _edata = .;
} > data AT> rom
「> data」と「AT> rom」
●   .dataセクションの実際のデータをROMに配置
    –   「AT> rom」の部分がこれにあたる
●   プログラム側からはRAMに配置されているよう
    にみせる
    –   「> data」の部分がこれにあたる
●   こうして.dataセクションのデータはROMに配置
    されることになるが、プログラム側から
    は.dataセクションはRAM上にあるようにみえる
●   つまり、今の状態ではデータがRAMにあるよう
    にみえるだけで、実際のデータはROMにある
    –   次は変数のデータをROMからRAMにコピーして、実際
        に操作できるようにする
ROMの静的領域をRAMにコピー
●   コピーはリンカ・スクリプトではなく、main.c
    でのプログラムの先頭で行う
●   .bssセクションについてはコピーではなくゼロ
    クリアをして、初期値を持たない変数に対して
    初期値を設定している
●   こういった処理をまとめて初期化と呼んだりす
    る
memcpy(&data_start, &erodata, (long)*edata – (long)&data_start)
memset(&bss_start, 0, (long)&ebss - (long)&bss_start)
ROMの静的領域をRAMにコピー
●   詳しい説明は書籍を参照してほしいが、前の
    コードの意味は以下の通り
    –   ROM上にある.dataセクションにある変数の初期値を
        RAMにコピーしている
    –   RAM上の.bssセクションをゼロクリアして、.bssセ
        クションに配置された変数のメモリ上の値をゼロに
        初期化している
         ● .bssセクションはコピーしているわけではない


●   ここまでしてようやくC言語を普通に扱えるよ
    うになった
●   当然ながら、先ほどの初期化コードの前に静的
    変数を定義すると正常に動作しない
ビルドで失敗
●   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
ld.scrの修正
●   .text.startupが何なのかよくわからんが、ど
    うやら_main関数らしい
●   _main関数はたぶん.textセクションなのでそこ
    にぶっこんだ
.text : {
      _text_start = . ;
      *(.text.startup) ←こいつね
      *(.text)
      _etext = . ;
} > rom
再度プログラム実行
●   うまくいったー!あせったー
/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.
書籍との違い
●   _regs変数がどうも定数の扱いになってる
    –   .rodataセクションに配置されているみたい
    –   ROMに入っていて問題はないかな?
    –   まあたぶんプログラム側から操作するような変数
        じゃなかったからいいけど
●   大きな違いはここと、あとは.text.startupだ
    なあ
4.まとめ
まとめ
●   とりあえずこれでH8でC言語を普通に扱えるよ
    うになった
    –   具体的には静的領域をROMからRAMにコピーすること
        によって静的変数の書き換えが可能となった
●   組込みプログラミングというのは自前で用意し
    なきゃいけないことが多い

More Related Content

【学習メモ#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にコピーすること によって静的変数の書き換えが可能となった ● 組込みプログラミングというのは自前で用意し なきゃいけないことが多い