ブート・ローダー

部内向けスキルアップ研修
「組込みOS自作入門」
2013年8月
4thステップ担当:中村
目次
• はじめに
• ブート・ローダー
• シリアル経由でのファイル転送
• XMODEMを実装する(もくもく会)
• アセンブラ・プログラミング
はじめに
前回やったこと
• ROM、RAM、自動変数、静的変数、データ領
域に関する学習
今回やること
• ブート・ローダー
• XMODEMプロトコル
• アセンブラ・プログラミング
4.1
ブート・ローダー
4.1.1 ブート・ローダーの必要性
• これまでは、作成したプログラムをフラッシュ
ROMへ書き込んでROM上で実行してきたが、
実はROMは書き込み回数に上限がある。
→メーカー保証は100回程度
(通常利用の範囲ならば1000回程度)
• 電源OFFしても消えないので、電源ONと同時
に起動するOSが作成できるが、短所がある。
4.1.1 ブートローダの必要性
•
•
そこで、OSの実行形式ファイルを直接フラッ
シュROMに書き込む代わりに、OSの実行形式
ファイルをシリアル経由でダウンロードし、そ
れをRAM上に展開して起動するプログラムを
フラッシュROMに書き込む
↓
ブート・ローダー(boot loader)
ブート・ストラップ(bootstrap)
電源ONでブート・ローダーを起動し、ブー
ト・ローダーでOSをダウンロードしてRAM上
でOSを展開し起動するという2段構成
4.1.1 ブートローダの必要性
• 良いところ
– 気軽にOSのダウンロードが繰り返せる
– 効率よく開発をすすめることができる
• 悪いところ
– RAMは電源OFFにすると内容が消えるので、
起動の度にOSをダウンロードしなければなら
ない。
4.1.1 ブートローダの必要性
• ROM化
– 開発中はブートローダーを利用してRAM上に
展開して起動し、製品化の段階でフラッシュ
ROMに書き込んでROM上から起動する
• 今回はOSのROM化まではやらない
• OSをダウンロードしてRAM上で展開し、起動す
るブート・ローダーを作成する
4.2 シリアル経由でのファイル転送
• ブート・ローダーの機能
– シリアル経由でOSの実行形式ファイルをダウ
ンロードし、RAM上に一旦保存
– 保存した実行形式ファイルを、RAM上に展開
– RAM上に展開したOSを実行
• シリアル通信には、XMODEMプロトコルを使用
する
4.2.1 XMODEMプロトコル仕様
•
送信側
1. 受信側から定期的に送信されるNAKを受け
たら、送信を開始する
2. ブロック単位でデータ送信
3. ACKが返ってきたら次を送信、NAKが返っ
てきたら再送
4. データ終わりはEOTを送信し、ACKが返っ
てきたら終了
5. 中断したい場合はCANを送信し、CANを受
信したら中断
4.2.1 XMODEMプロトコル仕様
•
受信側
1. 準備ができたらNAKを送信し、受信開始。
2. SOHを受けたら、ブロックとして受信。
成功したらACKを返し、失敗したらNAKを
返す。
3. EOTを受けたらACKを返して終了。
4. 中断したい場合はCANを送信し、CANを受
信したら中断。
4.2.1 XMODEMプロトコル仕様
フィールド
サイズ
意味
a
1バイト
SOH (Start Of Header)
b
1バイト
ブロック番号
c
1バイト
チェック。ブロック番号を反転。
d
128バイト
データ部。空きはEOFで埋める。
e
1バイト
データ部のチェックサム。データ
部を256で割った余り
a b c
d
XMODEMのブロック・フォーマット
e
4.2.1 XMODEMプロトコル仕様
•
ACK(ACKnowledge)
– 受信成功時の応答として送信されるコード
•
NAK(NegativeAcKnowledge)
– エラー時の応答として送信されるコード
XMODEMデータフロー概略図
受信側
送信側
→ NAK →
(送信開始を要求)
← SOH ←
(ブロック開始)
← 01 ←
(ブロック番号)
← FE ←
(ブロック番号を反転させたもの)
← ・・・ ←
(実際のデータ 128 バイト)
← sum ←
(チェックサム)
→ ACK →
← SOH ←
(ブロック開始)
← 02 ←
(ブロック番号)
← FD ←
(ブロック番号を反転させたもの)
(上記手順を、データがなくなるまで繰り返す)
← EOT ←
→ ACK →
(転送終了)
4.3 XMODEMを実装する
•
もくもく会
– 今回追加するファイル
xmodem.h, xmodem.c
– 今回修正するファイル
main.c
ld.scr
serial.h,serial.c
lib.h, lib.c
Makefile
コマンド動作を実装
バッファ領域を追加
文字の受信を実装
ライブラリ関数を追加
4.3 XMODEMを実装する
•
手順
1. 前回のフォルダ「3.2」をコピーして「4.1」
を作成
2. ファイルの追加・修正
3. make → make image
4. ディップスイッチを書き込みモード
(1,1,0,1)に設定後、make write
5. Tera termを起動して、ディップスイッチを
実行モード(1,0,1,0)に設定後、電源ON
6. リセットボタンを押すとプロンプト「kzload
> 」が出るので、「load」を入力
4.3 XMODEMを実装する
•
手順
6.リセットボタンを押すとプロンプト「kzload
> 」が出るので、「load」を入力
7.直ちに、メニューから、ファイル-転送XMODEM-送信を選択する。ファイル選択
ウィンドウが開くので、とりあえず、
「defines.h」を転送してみる。
8.間に合わなかった場合は、再度、リセットボタ
ンを押してやり直す。
9.完了したら、dumpコマンドを使って、転送し
たファイルを確認
4.3 XMODEMを実装する
•
手順
10. cygwinで転送元ファイルを表示し、転送し
たファイルを比較
$ hexdump –c defines.h
11.転送データはブロック単位で送信されるた
め、受信したファイルは余った部分が0x1aで
埋められている事を確認して下さい。
(XMODEMの仕様。気にしない!)
アセンブラ・プログラミング
• OSを自作する場合、アセンブラ(assembler)
の知識は必須
→「スタート・アップ」「割り込みの入口と
と出口」「スレッドのディスパッチ」の3箇
所はアセンブラでないと書けない!
• とりあえず、処理の「目的」を意識して、内
容にアタリをつけて読むと理解しやすい
(全部命令を覚える必要はない)
4.5.1
スタック
• C言語でプログラムを書くとき、すべての変
数を静的変数にするのはムダ
→ 常時メモリを占有し続けるのはムダ
• 関数に入った時のみ獲得され、returnによっ
て関数から抜けるときには捨てられ、メモリ
領域が使いまわせる「自動変数」が必要
4.5.1
•
スタック
効率よくメモリを利用する手順
1. ある程度の容量の領域を予め確保
2. 現在、領域のどこまでを利用しているかを示す
ポインタを用意
3. 関数呼び出しにより自動変数のための領域が必
要になった場合には、必要分だけポインタをず
らす
4. さらに関数呼び出しされた場合には、更にポイ
ンタを必要分だけずらす
5. 関数から戻るときには、ポインタを戻す
4.5.1
スタック
•
スタックを管理するために利用されるポイ
ンタをスタック・ポインタ
•
関数単位でスタック上に確保される領域を
スタック・フレーム
4.5.2
アセンブラ
•
CPUはメモリ上にある機械語命令を逐次実行して
いく
•
しかしメモリ上には数値しか保存できない
•
したがって、機械語命令とは数値のことで、数値
が命令としての意味を持っている
4.5.3
CPUのレジスタ
•
レジスタとは、CPU内部にある記憶領域のこと
•
CPUは加算や減算などの数値計算を行うための回
路を持っているが、これらの回路の入力や出力は
レジスタに接続
•
CPU(RISC)は、メモリ上の値を直接操作できず、
CPU上のレジスタに読み込んでから処理
4.5.3
•
CPUのレジスタ
「c = a + b」の演算の場合
1. 変数aが配置されている位置のメモリの値を、レジスタ
1に読み込む
2. 変数bが配置されている位置のメモリの値を、レジスタ
2に読み込む
3. レジスタ1とレジスタ2を加算し、結果をレジスタ3に格
納する
4. レジスタ3の値を、変数cが配置されている位置のメモ
リに書き込む
4.5.3
CPUのレジスタ
•
メモリ上の値をレジスタに読み込むことをロード
•
レジスタの値をメモリ上に書き込むことをストア
•
メモリ上のデータのロードやストア、演算など汎
用的に利用されるレジスタを汎用レジスタと呼ぶ
4.5.4 プログラム・カウンタ(PC)
•
汎用レジスタとは別の重要なレジスタで、「プロ
グラム・カウンタ(program counter)」がある
–
•
CPUはPCを加算しつつPCの指すメモリ先の命令を
逐次実行しながら処理を進めていく
–
•
CPUが現在実行中の命令アドレスを指す
(正確には、次の命令のアドレス)
PCは次の命令を指す位置まで自動で加算される
PCの役割は、いわゆるジャンプ命令
(C言語でいうところのgoto)
4.5.5 ニーモニックとアセンブラ
•
オペコードとオペランド
–
–
–
01
01
03
02
アセンブリの命令を表す部分をオペコードと呼ぶ。命令
に対する引数に相当する部分をオペランドと呼ぶ
機械語を数値で表すのは人間には読みづらいので、適当
な単語を使って人間がわかりやすいようにしたのがニー
モニック
c = a + b の機械語プログラム例
01
02
03
03
80
80
01
80
00
04
02
10
→
→
→
→
1番レジスタに変数a(アドレス:0x8000)の値をロード
2番レジスタに変数a(アドレス:0x8004)の値をロード
1番レジスタと2番レジスタを加算し3番レジスタに格納
3番レジスタの値を変数c(アドレス:0x8010)にストア
4.5.5 ニーモニックとアセンブラ
•
ニーモニック
–
–
–
–
0x01は「ロード」を行う機械語命令なので、「ld」
0x02は「ストア」を行う機械語命令なので、「st」
0x03は「加算」を行う機械語命令なので、「add」
以上のように定義した場合の、c = a + b の機械語プ
ログラムのニーモニック表記
ld r1, 0x8000 → 1番レジスタに変数a(アドレス:0x8000)の値をロード
ld r2, 0x8004 → 2番レジスタに変数a(アドレス:0x8004)の値をロード
add r3, r1, r2→ 1番レジスタと2番レジスタを加算し3番レジスタに格納
st r3,0x8010 → 3番レジスタの値を変数c(アドレス:0x8010)にストア
4.5.5 ニーモニックとアセンブラ
•
インストラクション
–
機械語の命令はインストラクションとも呼ばれる
–
どの数値がどの命令として動作するかという決まりを、
「命令セット」「インストラクション・セット」と呼ぶ
–
命令セットはCPUごとに違う
(H8の命令セットとPentium系の命令セットは異なる)
4.5.5 ニーモニックとアセンブラ
•
アセンブリ言語、アセンブル
–
ニーモニックで表した機械語プログラム表記をアセンブ
リ言語という
–
アセンブリ言語で書いたコードを機械語コードに変換す
る作業をアセンブルという
–
アセンブル変換するプログラムをアセンブラと呼ぶ
–
機械語コードをアセンブラに逆変換することを逆アセン
ブルと呼ぶ
4.5.6
•
実際にアセンブラを読んでみよう
–
•
H8のアセンブラ
方法はいくつかあるが、今回は最後に生成された実行
形式ファイルを逆アセンブルしたものを確認する
手順
–
–
–
–
–
$
先程、作成したフォルダ「4.1」をコピーして「4.2」
フォルダを作成
lib.cファイルに関数を追記(P.155 リスト4.13)
main.cに関数呼び出しを追記(P.155 リスト4.14)
make を実行
Cygwinで以下のコマンドを実行
/usr/local/h8300-elf/bin/objdump –d kzload.elf
逆アセンブルの結果
•
0000010c <_main>:
–
•
000004f8 <_func>:
–
•
main関数の先頭
func()関数の先頭
00000162 jsr @0x4f8:24
–
func()の呼び出し箇所だろうと想像できる
ニーモニックの細かい文法は気にせず、想像で読
んでいくことがコツ
逆アセンブルの結果
•
0000015a: mov.w #0x2, r1
–
•
0000015e: mov.w #0x1, r0
–
•
r1レジスタに2を代入
r0レジスタに1を代入
H8はR0~R7という16ビットレジスタを8個持っ
ている。拡張レジスタとしてE0~E7という16
ビットレジスタがあり、これらを組み合わせて
ER0~ER7という32ビットレジスタとして利用
することができる(long型整数やポインタ)
main()関数部
•
79 01 00 02
–
–
•
mov.w #0x2, r1
「79」はオペコード、後ろ3バイトがオペランドで、
1バイト目は値の格納先レジスタ、後は代入値
R1に0x0002という値を代入
ビッグ・エンディアンとリトル・エンディアン
–
–
「0x0002」を格納するとき、ビッグ・エンディアン
は「0x00」「0x02」、リトル・エンディアンは「0x02」、
「0x00」とひっくり返して格納する
CPUによって異なり、H8はビッグ・エンディアン。
Pentium系CPUはリトル・エンディアン。近年の多く
はビッグ・エンディアン。
main()関数部
•
イミディエイト値(即値)
–
–
オペランドにレジスタに代入する「0x0002」という値
が、そのまま記述
命令内に直接記述される数値
func()関数部
•
mov.l er6, @-er7
H8ではER7がスタック・ポインタとして利用される
スタック・ポインタを減算してスタック4バイト領域
を確保してスタック・ポインタの指す先にER6の値を
格納(ストア)する
– ER6が上書きされてしまうので、ER6の値(内容)を
スタックに退避している
– スタックを獲得するのにER7の値を減算している。ア
ドレス値が少なくなる方向に伸びること(下方伸長)
– 「@er7」レジスタの値をアドレス値として、アドレ
スが指す先のメモリの意味(レジスタ間接)
→メモリ参照の方法をアドレッシング・モード
–
–
func()関数部
•
アドレッシング・モード
H8は8種類のアドレッシング・モードを持つ
No 、 アドレッシングモード、 記号
(1) レジスタ直接 Rn
(2) レジスタ間接 @ERn
(3) ディスプレースメント付きレジスタ間接 @(d:16, ERn)/@
(d:24, ERn)
(4) ポストインクリメントレジスタ間接@ERn+
プリデクリメントレジスタ間接@-ERn
(5) 絶対アドレス @aa:8/@aa:16/@aa:24
(6) イミディエイト #xx:8/#xx:16/#xx:32
(7) プログラムカウンタ相対 @(d:8,PC) /@(d:16, PC)
(8) メモリ間接 @@aa:8
func()関数部
•
アドレッシング・モード
–
「@-er7」レジスタの加減算とメモリ・アクセスを1
命令で同時に行う
–
スタック操作に2命令が利用されると、その命令の間
で割り込みが入りスタック操作された時に、スタック
の整合性が取れなく可能性がある
–
スタック操作は1命令で行える必要がある
func()関数部
•
mov.l er7,er6
–
スタック・ポインタであるER7の値をER6にコピーし
ている
–
ER6はフレーム・ポインタと呼ばれる使われ方をして
おり、スタック・フレームの先頭を指している
–
ER6もER7も、ポインタとして利用されるため、アド
レスを保持する必要があるので、32ビットレジスタ
が利用される。
func()関数部
•
subs #4, er7
–
スタック・ポインタであるER7をさらに4バイトだけ
減算して、4バイトの領域を確保
–
func()の内部で利用している自然変数「c」の領域
–
「#4」の4は定数値を表すイミディエイト値(即値)
func()関数部
•
add.w r1,r0
–
–
•
R0とR1の値を加算して、R0に代入
「a +b 」の処理(1 + 2)をしている
mov.w r0, @(0xfffe:16, er6)
–
–
加算結果はR0に格納されているが、自動変数「c」
はスタック上に存在
フレーム・ポインタを減算してアドレスを計算して、
そこに加算結果を代入
func()関数部
•
mov.w @(0xfffe:16, er6), r0
–
–
–
–
スタック上に格納されている変数「c」の値をr0に代
入することでモリチの準備をし、R0を関数の戻り値
とする
元々、R0に「c」の値が入っていたからムダな処理
「c」をvolatile定義したため、最適化処理が行われ
ていない
「加算して、結果をスタックに保存して、戻り値を準
備する」という一連の処理が最適化されていない
func()関数部
•
adds #4, er7
–
•
mov.l @er7+, er6
–
–
•
スタック・ポインタを4バイト加算することで、自動
変数「c」の為に確保していたスタックを開放
スタック上に対比されていたER6の値をER6に読み込
んで(ロード)、ER6を元に戻す
ロード後にスタック・ポインタであるER7は4バイト
加算
rts
–
スタック上から戻り先アドレスを取得し、ジャンプし
て関数の呼び出し元に戻る
4.5.7 役割の決まっているレジスタ
•
•
•
•
•
関数の呼び出しは「jsr」、関数からの復帰は「rts」
jsrを実行すると、スタック・ポインタである
ER7の指す先にプログラム・カウンタの値を保
存してから、プログラム・カウンタを書き換え
てジャンプ
rtsを実行すると、ER7の指す先の値をプログラ
ム・カウンタに代入して呼び出し元に戻る
ER7は、jsrやrtsを実行すると自動的に利用され、
値も変更される。
ER7は、CPUがスタック・ポインタとして明示
的に操作しているレジスタ
4.5.8
•
スタート・アップ
startup.sのソースコード
_start:
mov.l #_stack,sp
jsr
@_main
•
「_start」はラベル(label)と呼ばれ、アセン
ブラ内でラベルが利用されると、定義されてい
る位置のアドレスに置き換わる
4.5.8
•
•
•
•
スタート・アップ
スタート・アップで行うべきことは、スタッ
ク・ポインタの初期値設定
スタック・ポインタを適切に設定してから出な
いと、C言語の関数呼び出しを正常に行う事がで
きない
jsrでメイン関数を呼び出しているので、その前
に「mov.l」でスタック・ポインタを設定
「_stack」のアドレス値をspに代入しているが、
spはER7ど同義で、stack pointer のこと
4.5.8
•
スタート・アップ
「bra」は戻り先アドレスをスタックに保存しな
い単純なジャンプ
1:
bra
•
•
1b
「1b」というラベルはない。1bが書かれた箇所
より前で「1:」という記述がある最も近い物を
指す
main()の呼び出しから戻ってきた時に暴走しな
いようにとりあえず無限ループをおいて、実行
がそれ以上進まないようにしている
今日やったこと
•
XMODEMによるファイル転送ができるように
なった
•
ブート・ローダーから起動できるまで、あと少
し!
•
アセンブラに関する学習
•
アセンブラはアタリをつけて読む
–
–
スタックの確保、レジスタの退避、自動変数の作成
レジスタの復旧とスタックの開放、戻り先へのジャン
プ
次回の開催予定
• 日時
– 9月10日(火) 13:00~
• 場所
– 技術支援室
• 担当
– 山田さん