今回は簡易的なプロンプトと、簡単に実装できるコマンドを追加する。
※過去回の記事とソースコードは下記から入手できる
https://github.com/Shadow5523/osdev/blob/master/README.md
また、ここで使用するコードはGitHubからダウンロードできる。
https://github.com/Shadow5523/osdev/releases/tag/version0.8.0
目次
今回やったこと
前回から追加した機能は以下の通り。
ほんとはシステムコールやメモリ管理あたりをもっと整備しなきゃいけないけど、なかなか目に見える形で進捗がでないので(たぶん見た目は第3回目くらいからほとんど変わってない…)モチベーションアップの為にまずプロンプトなどを追加してみた。次回以降からこの辺の整備を行うことにしてみる。
sh_strcmp追加
1.string.cに新たな関数(sh_strcmp)を追加する。内容は標準ライブラリのstrcmp関数と同じで、文字列を比較し同じなら0を、違えば文字列1 – 文字列2の差を整数型として返すようにする。
1 2 3 4 5 6 7 8 9 10 11 |
int sh_strcmp(const char* s1, const char* s2){ const unsigned char *p1 = (const unsigned char *) s1; const unsigned char *p2 = (const unsigned char *) s2; unsigned char c1, c2; do { c1 = (unsigned char) *p1++; c2 = (unsigned char) *p2++; if (c1 == '\0') { return c1 - c2; } } while (c1 == c2); return c1 - c2; } |
2.string.hへ関数のプロトタイプを追加。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#ifndef _STRING_H_ #define _STRING_H_ #include <stddef.h> #include <stdint.h> size_t sh_strlen(const uint8_t*); void sh_strrev(unsigned char*); char* sh_strcpy(char*, const char*); char* sh_strcat(char*, const char*); int sh_strcmp(const char*, const char*); //追加 void* sh_memcpy(void* restrict, const void* restrict, size_t); #endif _STRING_H_ |
プロンプト追加
1.いよいよプロンプトの実装を行う。まずterminal.cの処理に少し変更を加える。いままでこのOSではテキストパットのように文字を入力したりそれを消したりする範囲がterminal全体になっているが、これをプロンプト名を除いた最終行のみに変更を加える。drivers/terminal.cを開き以下のように編集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
... //terminal_putchar関数のみ変更を加える void terminal_putchar(uint8_t c){ if (c == '\n') { c = 0; if (++t_row >= VGA_HEIGHT) { terminal_uponerow(); --t_row; t_column = -1; } else { t_column = -1; } } else if (c == '\t') { c = 0; t_column += 4; } else if(c == '\b') { c = 0; t_buffer[(t_row * VGA_WIDTH + t_column) - 1] = vga_entry(' ', t_color); //文字を消す領域がプロンプトの手前なら処理を続行 if ( t_column > pmstr_len + 2 ) { if (--t_column <= pmstr_len) { t_column = pmstr_len; /*プロンプト時は以下は無効 t_column = VGA_WIDTH if (--t_row < 2) { t_row = 2; t_column = 0; } */ } } return; } terminal_putentryat(c, t_color, t_column, t_row); if (++t_column >= VGA_WIDTH) { t_column = 0; if (++t_row >= VGA_HEIGHT) { terminal_uponerow(); --t_row; } } } ... |
2.プロンプト名の長さを保持する共有変数をdrivers/terminal.cとkernel/kernel.cで宣言する。externで変数宣言を行うと外部のソースファイルで宣言した変数や関数を使用することができる。
1 2 3 4 5 6 7 8 |
#include "../include/terminal.h" #include "../include/vga.h" extern size_t pmstr_len; //追加 void terminal_initialize(void){ ...省略... |
3.kernel/kernel.cにも同じように追加する。また、コマンドラインの入力文字の長さを保持する共有変数iを宣言。(これはここのソース内でしか使えない)
1 2 3 4 5 6 7 8 |
#include "../include/kernel.h" extern key_buf kb; extern size_t pmstr_len; //追加 size_t pmstr_len; //追加 static size_t i; //追加 ... 省略 ... |
4.kernel/kernel.cで入力文字列を返す関数input_line()を追加する。
… prompt_nameでプロンプト名を渡し、cmdlineに入力文字列が格納される。また入力された文字が’\n’であった場合は0が返りcmdlineには完全な文字列が格納され、それ以外の場合は-1を返す。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
int input_line(char* prompt_name, char* cmdline){ asm volatile("cli"); if (!kb.len) { asm volatile("sti"); } else { char c = kb.pdata[kb.read]; --kb.len; ++kb.read; if (kb.read == 128) { kb.read = 0; } asm volatile("sti"); if (c == '\n') { cmdline[i] = '\0'; return 0; } else if (c == '\b') { cmdline[i] == '\0'; if (i > 0) { --i; } } else { cmdline[i++] = c; } sh_printf("%c", c); } return -1; } |
5.プロンプト本体である関数prompt()を作成する。ここでプロンプト名の定義やinput_line()で受け取った文字列をコマンドとしてexecuting()へ渡している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
... void prompt(void){ int result = -1; char cmdline[1024]; char *prompt_name = "prompt"; pmstr_len = sh_strlen(prompt_name); sh_printf("\n%s> ", prompt_name); kb.len = 0; kb.write = 0; kb.read = 0; i = 0; for (;;) { if ((result = input_line(prompt_name, cmdline)) != -1) { if (i) { if (executing(cmdline) == -1) { sh_printf("\nCommand not found!"); } } sh_printf("\n%s> ", prompt_name); result = -1; i = 0; } } } ... |
コマンドの追加とkernel本体の修正
1.受け取った文字列を判定し実際にコマンドを実行する関数executing()を作成する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
... int executing(char* cmdline){ if (!sh_strcmp(cmdline, "clear")) { terminal_initialize(); return 0; } else if (!sh_strcmp(cmdline, "reboot")){ outb(0x64, 0xFE); return 0; } else { return -1; } } ... |
terminalの初期化には、OS起動時に呼び出すterminal_initialize()関数を実行し、rebootにはキーボードコントローラ(0x64)に0xFEを書き込むことによって再起動を実現している。
2.kernel_main()の最後にprompt()を呼び出すコードを追加する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
... void kernel_main(multiboot_info_t* mbt, uint32_t magic){ terminal_initialize(); sh_printf("Initialize Terminal... OK\n"); gdt_init(); pic_init(); idt_init(); key_init(); getmmap(mbt); sh_printf("Hello, kernel World! \n\n"); prompt(); //追加 } ... |
3.最後にinclude/kernel.hに今回追加した関数のプロトタイプを宣言する。
1 2 3 4 5 6 7 |
... int input_line(char*, char*); void prompt(void); int executing(char*); ... |
確認
make実行後、生成されたISOイメージをVMWareから起動し、「clear」「reboot」等のコマンドが正常に動けばOK