ComProcでDOSを動かす

>100 Views

December 13, 24

スライド概要

自作CPU「ComProc」上で自作DOSを動かしてみた経験を語ります。

microSDカード上のファイルを一覧したり、保存された実行ファイルを読み込んで実行する機能をもったDOSを作りました。このDOSは、ComProc CPUを用いたポケコン的なデバイスであるComProc PCで動作します。(現時点ではDOSというよりプログラムローダーという方が正しいです。)

DOS上でlsコマンドを打てばファイルが一覧でき、実行ファイル名を入力するとそれをメモリに読み込んで実行できます。p.8で紹介しているAPP.EXEを起動するデモ動画はこちらから閲覧できます→ https://www.youtube.com/watch?v=7l5x11tXYaY

profile-image

サイボウズ・ラボ株式会社で教育向けのOSやCPU、コンパイラなどの研究開発をしています。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

ComProcでDOSを動かす 2024年11月30日 第42回 自作OSもくもく会 @uchan_nos 1

2.

自己紹介 ⚫内田公太 @uchan_nos ⚫サイボウズ・ラボ株式会社 ◼ コンピュータ技術エバンジェリスト ◼ 教育用OS・言語処理系・CPUの研究開発 ⚫osdev-jp創始者の1人 ⚫代表著作 ◼ 「ゼロからのOS自作入門」 ◼ 「自作エミュレータで学ぶx86アーキテクチャ」 ◼ 「コンパイラとCPUどっちも作ってみた」 ◼ Software Design 2023年4月号 第1特集 第2章 コンピュータが計算できる理由 2

3.

ComProcプロジェクトとは ⚫ComProc=Compiler+Processor ⚫CPUとコンパイラを自作する、uchan主導のプロジェクト ◼ CPUとコンパイラを作るプロジェクトは珍しくない ◼ ComProcプロジェクトはCPUとコンパイラを同時並行に進化させる点で、 他のプロジェクトとは一線を画す ⚫ComProcプロジェクトの構成要素 ◼ CPU回路:Verilogで記述されたCPUの実装 ◼ ComProcボード:FPGAボードを中核とした基板 ◼ コンパイラ実装:ComProcプロジェクトのもう一つの主役 ◼ アセンブラ実装:アセンブリ言語プログラムを受け取って機械語へ変換 3

4.

ComProc PC ⚫自作CPU用の周辺機器を搭載 したボード ComProc PC Rev.1で キーボード入力を試している様子 ←前作の基板 ComProc CPU Board Rev.4 はドットマトリクスLEDを 装備しており、CPU本体の 開発段階では便利だった。 ◼ 4行キャラクタLCD ◼ 128×64ピクセルOLED ◼ 64キーキーボード ◼ 輝度センサー(CdS) ◼ 圧電スピーカー ⚫FPGAボードはTang Nano 9K ◼ 右上の黒い基板 ◼ microSDカードスロット有り 4

5.

本日の話題一覧 ⚫自己紹介 ⚫作ったDOSの紹介 ⚫やったこと ◼ 大きなプログラムを動かせるように ◼ SDカードのファイル読み取り ◼ 位置独立実行ファイルのサポート ◼ プログラムメモリへの命令転送 ◼ 関数ポインタの導入 5

6.

本日の話題一覧 ⚫自己紹介 ⚫作ったDOSの紹介 ⚫やったこと ◼ 大きなプログラムを動かせるように ◼ SDカードのファイル読み取り ◼ 位置独立実行ファイルのサポート ◼ プログラムメモリへの命令転送 ◼ 関数ポインタの導入 6

7.

作ったDOSの紹介 ⚫DOS: Disk Operating System ◼ 「磁気ディスク装置を使用可能としたオペレーティングシステム」 • https://ja.wikipedia.org/wiki/DOS_%28OS%29 ◼ SDカードからデータを読み取るプログラムを作ったので「DOS」と呼ぶ ことにした ⚫作ったDOS ◼ SDカードに保存されたプログラムを読み、実行する • ルートディレクトリに*.EXEとして保存 ◼ システムコールはまったく無く、単にプログラムへジャンプするだけ ◼ 現状、プログラムローダーと呼ぶ方が正しい 7

8.

作ったDOSのデモ 「SDv1…」はDOSの初 期表示。 lsでSDカードのファイ ルを一覧する。 ld <file>で実行ファイ ルをメモリに読み出す。 runでメモリ上のプログ ラムを起動する。 「waiting enter...」は app.exeの出力。Enterを 押すとOSに処理が戻り、 終了コードが表示される。 8

9.

DOSと起動されるプログラムの関係 0000h 2000h プログラムメモリ OSの機械語 .text FP 4000h むファイル。 ⚫.textはプログラムメモリへ置かれ、 ⚫.dataはデータメモリへ置かれる。 ⚫.textの先頭をCALLする。 4000h 0000h 0100h GP 2000h ⚫APP.EXEは2つのセクション.dataと.textを含 データメモリ OS用静的データ .data ローカル変数 OSのローカル変数 ⚫.textの先頭には「CALL main」が置かれてい て、main関数が実行される。 ⚫GPが.dataの先頭を指す。 ⚫スタックフレームはOS用の領域と連続する。 9

10.

本日の話題一覧 ⚫自己紹介 ⚫作ったDOSの紹介 ⚫やったこと ◼ 大きなプログラムを動かせるように ◼ SDカードのファイル読み取り ◼ 位置独立実行ファイルのサポート ◼ プログラムメモリへの命令転送 ◼ 関数ポインタの導入 10

11.

大きなプログラムを動かせるように ⚫各種の最適化による効率化 項目 最適化前 最適化後 変化率 符号無し整数 1641W 1634W -0.42% FPが変化するときだけCPUSH/CPOPを発行 1634W 1624W -0.61% 引数が1個の場合にST/LDのペアを消去 1624W 1609W -0.92% なるべく即値付きCALLを使用 1451W 1341W -7.6% ⚫ハーバードアーキテクチャ化によるビット幅拡張 ◼ CALLの即値が14ビット=16Kワードに ◼ 16ビット即値を一発で転送 11

12.

本日の話題一覧 ⚫自己紹介 ⚫作ったDOSの紹介 ⚫やったこと ◼ 大きなプログラムを動かせるように ◼ SDカードのファイル読み取り ◼ 位置独立実行ファイルのサポート ◼ プログラムメモリへの命令転送 ◼ 関数ポインタの導入 12

13.

SDカードのファイル読み取り ⚫SPI通信回路の追加 ◼ CS、SCLK、MOSI、MISOの4ピン ⚫SDカード制御の各種コマンド実装 ⚫ブロック単位での読み込み実験 http://elm-chan.org/docs/mmc/mmc.html ⚫FAT16ドライバの実装 ◼ 実験では2GBのmicroSDカードを使用 ◼ WindowsでFATを選びフォーマットしたらFAT16になった ◼ FAT16は16ビットCPUで扱いやすくて助かる~ 13

14.

ファイル読み込みの手順(おさらい) 0000h SDカードの記憶域 パーティションテーブル 1. MBRのパーティションテーブル からPBRのセクタ番号を得る 2. PBRから各種情報を得る(1クラ スタあたりのセクタ数など) 3. ルートディレクトリエントリか ら目的のファイルを探し、開始 クラスタ番号を得る 4. クラスタ番号からセクタ番号を 計算し、ファイル内容を読む MBR 55AA 0200h 0800h BPB_SecPerClus, BPB_RsvdSecCnt, BPB_FATSz16, … 55AA PBR FAT1/FAT2 ルートディレクトリエントリ ファイルデータ 14

15.

本日の話題一覧 ⚫自己紹介 ⚫作ったDOSの紹介 ⚫やったこと ◼ 大きなプログラムを動かせるように ◼ SDカードのファイル読み取り ◼ 位置独立実行ファイルのサポート ◼ プログラムメモリへの命令転送 ◼ 関数ポインタの導入 15

16.

位置独立実行ファイルのサポート ⚫DOSというからにはSDカードに置いたプログラムを実行したい ⚫今までは、0番地から実行開始する前提があった ⚫これからは、OSとアプリが両方0番地だと困る ◼ ComProc MCUには「仮想アドレス」が無いので。 ⚫主な変更 ⚫CALLをIP相対化 ⚫グローバル変数領域を指すGPを導入 16

17.

CALLをIP相対化 ⚫今まで即値付きCALLは絶対アドレス指定だった ⚫CALLをIP+simm14に改めた ⚫JMPやJZは元からIP相対 ⚫これで、プログラムをどこに配置しても正常に分岐できる! mnemonic 17 12 7 0 説明 ---------------------------------------CALL simm14 |0000 simm14 | コールスタックに ip をプッシュし、ip+simm14 にジャンプ JMP simm12 |000100 simm12 | ip+simm12 にジャンプ ADD fp,simm12 |000101 simm12 | fp += simm12 JZ simm12 |000110 simm12 | stack から値をポップし、0 なら ip+simm12 にジャンプ 17

18.

グローバル変数領域を指すGPを導入 ⚫今まで、メモリアクセス命令(LD/ST)のベースは4種 ◼ 0:絶対アドレス ◼ FP:スタックフレーム先頭 ◼ IP:実行中の命令アドレス ◼ CSTACK:Cstack[0] • 古い時代、Cstack[0]にはFPが保存されていた。 ⚫IP相対は、ハーバードアーキテクチャ化により無意味に ⚫CSTACK相対は、コンパイラの最適化により未使用に ⚫ということで、ベースを以下3種に改めた ◼ 0:絶対アドレス ◼ FP:スタックフレーム先頭 ◼ GP:グローバル領域先頭 18

19.

GPに対応するコンパイラの修正 ⚫今まで、グローバル変数や文字列定数は絶対アドレスでアクセス していた ⚫GP相対でアクセスするように修正 ◼ 今まで:st zero+0x0142 ◼ これから:st gp+0x0042 ⚫絶対アドレスモードは0x0000~0x0100にあるメモリマップトI/O 用に残してある 0000h GP FP 4000h データメモリ メモリマップトI/O グローバル変数+ 文字列定数 ローカル変数 19

20.

GPを設定するビルトイン関数 ⚫GPを書き換えたくなるが、C言語の標準には機能がない ◼ 一般に、インラインアセンブラかビルトイン関数として実現 ◼ 今回はビルトイン関数にしてみた ⚫void __builtin_set_gp(unsigned int); ◼ 単に pop gp に置き換えられる ◼ __builtin_set_gp(41); → push 41; pop gp ⚫ビルトイン関数とした理由:引数を受け取れるから ⚫現状、インラインアセンブラは、C側から値を受け取れない 20

21.

実行ファイルの形式 ⚫先頭4バイトがヘッダ ◼ 0~1:.textの命令数 ◼ 2~3:.dataのバイト数 ◼ ヘッダ領域はCコードレベルで確保する→ ◼ アセンブラがヘッダに値を埋める // app.c unsigned int pmem_len; unsigned int dmem_len; 《後略》 ⚫ファイル先頭が.data、その後ろに.textが続く ◼ .text先頭は512バイトアライメント ⚫.textの先頭から実行開始するというルール ◼ コンパイラは.textの先頭に「start: call main」を出力する。 ◼ コンパイラを改造すれば、すぐmainを呼ぶのではなく、何らかの初期化処 理を挟むことも可能。 22

22.

本日の話題一覧 ⚫自己紹介 ⚫作ったDOSの紹介 ⚫やったこと ◼ 大きなプログラムを動かせるように ◼ SDカードのファイル読み取り ◼ 位置独立実行ファイルのサポート ◼ プログラムメモリへの命令転送 ◼ 関数ポインタの導入 23

23.

プログラムメモリへの命令転送 ⚫SDカードから読んだデータは、まずdmemへ置かれる ◼ SDカード上のデータ→SPIデータレジスタ(dmem上にマップ) ⚫命令として実行するにはpmemに置く必要がある ⚫演算スタック→pmem転送のための命令を新設:SPHA、SPLA ◼ Dmemから演算スタックを経由してpmemへ転送する設計 0000h dmem 0000h SPIデータレジスタ 0100h 4000h OS用静的データ OSのローカル変数 転送する 4000h pmem OSの機械語 .text 24

24.

__builtin_write_pmem ⚫SPHA、SPLAを発行するビルトイン関数 ⚫int __builtin_write_pmem(int addr, int hi, int lo); ◼ spha; splaに置き換わる ⚫関数の引数は右から順にスタックに積まれる ◼ __builtin_write_pmem(addr, high, low)の場合 push low push high この時点の 0 addr push addr スタック 1 high spha 2 low spla ⚫SPHA、SPLAはアドレスを残すので、連続実行できる! 27

25.

本日の話題一覧 ⚫自己紹介 ⚫作ったDOSの紹介 ⚫やったこと ◼ 大きなプログラムを動かせるように ◼ SDカードのファイル読み取り ◼ 位置独立実行ファイルのサポート ◼ プログラムメモリへの命令転送 ◼ 関数ポインタの導入 28

26.

関数ポインタの導入 ⚫OSからアプリを呼ぶために、特定の番地をCALLしたい ⚫関数ポインタ! ⚫簡易な関数ポインタ型を導入 ⚫「普通の型」の後に「(」が来たら関数ポインタとみなす ◼ 普通の型:intとか、char*とか ⚫関数ポインタの文法:TYPE ( * ID ) ( ) ◼ 今のところ、引数リストには何も書けない ◼ 関数呼び出しでは引数のチェックをしていないので、問題無し 関数ポインタの使用例: OSがアプリを呼ぶところ int (*f)() = 0x1000; __builtin_set_gp(block_buf); int ret_code = f(); 29

27.

まとめ 本日の話題一覧 ⚫ 自己紹介 ⚫ 作ったDOSの紹介 ⚫ やったこと このスライドでは ⚫アプリを起動し ⚫31を入力し ⚫1F(=31)が表示される までの道のりを説明した ◼ 大きなプログラムを動かせるように ◼ SDカードのファイル読み取り ◼ 位置独立実行ファイルのサポート ◼ プログラムメモリへの命令転送 ◼ 関数ポインタの導入 30