部内向けスキルアップ研修 「組込み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~ • 場所 – 技術支援室 • 担当 – 山田さん
© Copyright 2024 Paperzz