「30日でできる!OS自作入門」をRustで。19日目
「30日でできる!OS自作入門 」のC言語の部分をできるだけRustですすめてみる。今回は19日目の内容。
cat(type)コマンドを追加する
前回に引き続きコマンド追加となる。
今回はcat
に相当する機能ということで、例によって本ではtype
になっているがここではcat
として実装する。
前回、コマンド文字列を抽出処理を関数として書いたが、Rustのsplit
でも書けそうなことに気づいたので、split
を使う方針に変更する。
// console.rs
fn exec_cmd(
: [u8; 30],
cmdline: isize,
cursor_y: &mut SheetManager,
sheet_manager: usize,
sheet_index: usize,
memtotal-> isize {
) let sheet = sheet_manager.sheets_data[sheet_index];
let mut cursor_y = cursor_y;
macro_rules! display_error {
$error: tt, $cursor_y: tt) => {
(write_with_bg!(
,
sheet_manager,
sheet_index.width,
sheet.height,
sheet8,
$cursor_y,
Color::White,
Color::Black,
30,
$error
;
)= newline($cursor_y, sheet_manager, sheet_index);
cursor_y return newline(cursor_y, sheet_manager, sheet_index);
};
}
let cmdline_strs = cmdline.split(|s| *s == 0 || *s == b' ');
let mut cmdline_strs = cmdline_strs.skip_while(|cmd| cmd.len() == 0);
let cmd = cmdline_strs.next();
if cmd.is_none() {
display_error!("Bad Command", cursor_y);
}
let cmd = from_utf8(&cmd.unwrap()).unwrap();
if cmd == "mem" {
// 省略
あわせて、display_error
という文字列を表示するだけのマクロを用意してエラメッセージ表示を完結に記述できるようにした。
ファイルの中身を表示するにあたり、FileInfo
のメソッドとしてファイルの中身の先頭番地を返せるようにしておく。
// file.rs
impl FileInfo {
pub fn content_addr(&self) -> usize {
self.clustno as usize * 512 + 0x003e00 + ADR_DISKIMG
}
}
これを使って、表示ロジックを書いていく。
// console.rs
fn exec_cmd(
// 省略
-> isize {
) // 省略
} else if cmd == "cat" {
// ファイル名となるところを抽出
let mut filename = cmdline_strs.skip_while(|strs| strs.len() == 0);
let filename = filename.next();
if filename.is_none() {
display_error!("File Not Found", cursor_y);
}
let filename = filename.unwrap();
// 拡張子の前後でわける
let mut filename = filename.split(|c| *c == b'.');
let basename = filename.next();
let extname = filename.next();
let mut b = [b' '; 8];
let mut e = [b' '; 3];
if let Some(basename) = basename {
for fi in 0..b.len() {
if basename.len() <= fi {
break;
}
if b'a' <= basename[fi] && basename[fi] <= b'z' {
// 小文字は大文字で正規化しておく
= basename[fi] - 0x20;
b[fi] } else {
= basename[fi];
b[fi] }
}
} else {
display_error!("File Not Found", cursor_y);
}
if let Some(extname) = extname {
for fi in 0..e.len() {
if extname.len() <= fi {
break;
}
if b'a' <= extname[fi] && extname[fi] <= b'z' {
= extname[fi] - 0x20;
e[fi] } else {
= extname[fi];
e[fi] }
}
}
let mut target_finfo: Option<FileInfo> = None;
for findex in 0..MAX_FILE_INFO {
let finfo = unsafe {
*((ADR_DISKIMG + ADR_FILE_OFFSET + findex * core::mem::size_of::<FileInfo>())
as *const FileInfo)
};
if finfo.name[0] == 0x00 {
break;
}
if finfo.name[0] != 0xe5 {
if (finfo.ftype & 0x18) == 0 {
let mut filename_equal = true;
for y in 0..finfo.name.len() {
if finfo.name[y] != b[y] {
= false;
filename_equal break;
}
}
for y in 0..finfo.ext.len() {
if finfo.ext[y] != e[y] {
= false;
filename_equal break;
}
}
if filename_equal {
= Some(finfo);
target_finfo break;
}
}
}
}
if let Some(finfo) = target_finfo {
let content_length = finfo.size;
let mut cursor_x = 8;
for x in 0..content_length {
let chr = unsafe { *((finfo.content_addr() + x as usize) as *const u8) };
if chr == 0x09 {
// タブ
loop {
write_with_bg!(
,
sheet_manager,
sheet_index.width,
sheet.height,
sheet,
cursor_x,
cursor_yColor::White,
Color::Black,
1,
" "
;
)+= 8;
cursor_x if cursor_x == MAX_CURSOR_X {
= 8;
cursor_x = newline(cursor_y, sheet_manager, sheet_index);
cursor_y }
if (cursor_x - 8) & 0x1f == 0 {
// 32で割り切れたらbreak
break;
}
}
} else if chr == 0x0a {
// 改行
= 8;
cursor_x = newline(cursor_y, sheet_manager, sheet_index);
cursor_y } else if chr == 0x0d {
// 復帰
// 何もしない
} else {
write_with_bg!(
,
sheet_manager,
sheet_index.width,
sheet.height,
sheet,
cursor_x,
cursor_yColor::White,
Color::Black,
1,
"{}",
as char
chr ;
)+= 8;
cursor_x if cursor_x == MAX_CURSOR_X {
= 8;
cursor_x = newline(cursor_y, sheet_manager, sheet_index);
cursor_y }
}
}
= newline(cursor_y, sheet_manager, sheet_index);
cursor_y } else {
display_error!("File Not Found", cursor_y);
}
} else {
やたらと長くなってしまったが、やっていることは割と素直で、ファイルを探し、あれば中身を読み込んで表示、ということをしている。
タブや改行はそれぞれ別に処理するようにしている。
実行結果
以下の内容のテキストファイルをimgに読み込ませておく
aiueo
tab tab tab tab
cat
を実行してみると、ファイルの中身が正しく表示された。
File Allocation Table (FAT)を参照するようにする
上記のcat
だと512バイトを超すファイルの場合に、うまく表示されないことがある。
その場合、FATを参照にして次の512バイトがどこに配置されているかを知ることができる。
まずは準備する。
// file.rs
impl FileInfo {
// FATを参考にしてファイルをロードする
pub fn load_file(&self, buf_addr: usize, fat: &[u32; MAX_FAT], img_addr: usize) {
let mut size = self.size as usize;
let mut buf_addr = buf_addr as usize;
let mut clustno = self.clustno as usize;
loop {
if size <= 512 {
for i in 0..size {
let buf = unsafe { &mut *((buf_addr + i) as *mut u8) };
*buf = unsafe { *((img_addr + clustno * 512 + i) as *const u8) };
}
break;
}
for i in 0..512 {
let buf = unsafe { &mut *((buf_addr + i) as *mut u8) };
*buf = unsafe { *((img_addr + clustno * 512 + i) as *const u8) };
}
-= 512;
size += 512;
buf_addr = fat[clustno] as usize;
clustno }
}
}
// FAT情報を復号化
pub fn read_fat(fat: &mut [u32; MAX_FAT], img: [u8; MAX_FAT * 4]) {
let mut j = 0;
for i in (0..MAX_FAT).step_by(2) {
+ 0] = ((img[j + 0] as u32) | (img[j + 1] as u32) << 8) & 0xfff;
fat[i + 1] = ((img[j + 1] as u32) >> 4 | (img[j + 2] as u32) << 4) & 0xfff;
fat[i += 3;
j }
}
これを使って、先程のファイルの中身のロード部分を書き換える。
// console.rs
pub extern "C" fn console_task(sheet_index: usize) {
// 省略
let fat_addr = memman.alloc_4k(4 * MAX_FAT as u32).unwrap();
let fat = unsafe { &mut *(fat_addr as *mut [u32; (MAX_FAT)]) };
, unsafe {
read_fat(fat*((ADR_DISKIMG + 0x000200) as *const [u8; (MAX_FAT * 4)])
});
// 省略
// 引数にfatを追加
, cursor_y, sheet_manager, sheet_index, memtotal, fat);
exec_cmd(cmdline// 省略
fn exec_cmd(
: [u8; 30],
cmdline: isize,
cursor_y: &mut SheetManager,
sheet_manager: usize,
sheet_index: usize,
memtotal: &[u32; MAX_FAT],
fat-> isize {
) // 省略
if let Some(finfo) = target_finfo {
let content_addr = memman.alloc_4k(finfo.size).unwrap() as usize;
.load_file(content_addr, fat, ADR_DISKIMG + 0x003e00);
finfolet mut cursor_x = 8;
for x in 0..finfo.size {
let chr = unsafe { *((content_addr + x as usize) as *const u8) };
if chr == 0x09 {
// 省略
実行結果の画面は以前と変わらないので省略する。
アプリケーションの起動
ここまでで、OS上でアプリケーションを起動できる準備が整った。
簡単なアプリケーションとして以下のHLTするだけのアプリケーションを起動する。
fin:
HLT
JMP fin
流れとしては、まずはcat
と同様、ファイルを探す。見つかった場合、今度はcat
とは異なり、GDTでセグメントを登録し、farjmpでアプリケーションを起動する。
cat
と共通処理である、ファイルを探す部分を関数として抽出しておく。
// console.rs
fn search_file(filename: &[u8]) -> Option<FileInfo> {
let mut target_finfo = None;
// 拡張子の前後でわける
let mut filename = filename.split(|c| *c == b'.');
let basename = filename.next();
let extname = filename.next();
let mut b = [b' '; 8];
let mut e = [b' '; 3];
if let Some(basename) = basename {
for fi in 0..b.len() {
if basename.len() <= fi {
break;
}
if b'a' <= basename[fi] && basename[fi] <= b'z' {
// 小文字は大文字で正規化しておく
= basename[fi] - 0x20;
b[fi] } else {
= basename[fi];
b[fi] }
}
} else {
return None;
}
if let Some(extname) = extname {
for fi in 0..e.len() {
if extname.len() <= fi {
break;
}
if b'a' <= extname[fi] && extname[fi] <= b'z' {
= extname[fi] - 0x20;
e[fi] } else {
= extname[fi];
e[fi] }
}
}
for findex in 0..MAX_FILE_INFO {
let finfo = unsafe {
*((ADR_DISKIMG + ADR_FILE_OFFSET + findex * core::mem::size_of::<FileInfo>())
as *const FileInfo)
};
if finfo.name[0] == 0x00 {
break;
}
if finfo.name[0] != 0xe5 {
if (finfo.ftype & 0x18) == 0 {
let mut filename_equal = true;
for y in 0..finfo.name.len() {
if finfo.name[y] != b[y] {
= false;
filename_equal break;
}
}
for y in 0..finfo.ext.len() {
if finfo.ext[y] != e[y] {
= false;
filename_equal break;
}
}
if filename_equal {
= Some(finfo);
target_finfo break;
}
}
}
}
target_finfo}
hlt
コマンドとして先ほどのHLT
するだけのasmファイルをアセンブルしたhlt.bin
を探して起動するようにする。
// console.rs
} else if cmd == "hlt" {
let finfo = search_file(b"hlt.bin");
if finfo.is_none() {
display_error!("File Not Found", cursor_y);
}
let finfo = finfo.unwrap();
let content_addr = memman.alloc_4k(finfo.size).unwrap() as usize;
.load_file(content_addr, fat, ADR_DISKIMG + 0x003e00);
finfolet gdt_offset = 1003; // 1,2,3はdesciptor_table.rsで、1002まではmt.rsで使用済
let gdt = unsafe { &mut *((ADR_GDT + gdt_offset * 8) as *mut SegmentDescriptor) };
*gdt = SegmentDescriptor::new(finfo.size - 1, content_addr as i32, AR_CODE32_ER);
0, gdt_offset * 8);
farjmp(.free_4k(content_addr as u32, finfo.size).unwrap(); memman
実行結果
hlt
と入力してエンターキーを押すと、以下のような画面のままコンソール画面が反応しなくなり、HLT
されていそうなことがわかる。尚、入力フォームウィンドウは問題なく反応する。
また、hlt.asm
の内容を以下の通り変更すると、意図通り全体がフリーズするようになる。
CLI
fin:
HLT
JMP fin
19日目は以上となる。ここまでの内容のコードはyoshitsugu/hariboteos_in_rustのday19としてタグを打ってある。