「30日でできる!OS自作入門」をRustで。12日目
Posted on 2019-06-28
「30日でできる!OS自作入門 」のC言語の部分をできるだけRustですすめてみる。今回は12日目の内容。
タイマの導入
今回はまずタイマを導入する。
PIT(Programmable Interval Timer)を設定し、タイマからの割り込みを受け付けられるようにする。
// timer.rs
use crate::asm::out8;
use crate::interrupt::PIC0_OCW2;
pub static mut COUNTER: u32 = 0;
const PIT_CTRL: u32 = 0x0043;
const PIT_CNT0: u32 = 0x0040;
pub fn init_pit() {
, 0x34);
out8(PIT_CTRL, 0x9c);
out8(PIT_CNT0, 0x2e);
out8(PIT_CNT0unsafe { COUNTER = 0; }
}
pub extern "C" fn inthandler20() {
, 0x60); // IRQ-00受付完了をPICに通知
out8(PIC0_OCW2unsafe { COUNTER += 1; } // カウントアップ
}
10msごとにカウントアップするように設定した。COUNTER
はとりあえずstatic mut
にしている。
割り込み処理なので、IDTに登録する必要がある。
// descriptor_table.rs
pub fn init() {
use crate::interrupt::{inthandler21, inthandler2c};
use asm::{load_gdtr, load_idtr};
// GDTの初期化
for i in 0..=(LIMIT_GDT / 8) {
let gdt = unsafe { &mut *((ADR_GDT + i * 8) as *mut SegmentDescriptor) };
*gdt = SegmentDescriptor::new(0, 0, 0);
}
let gdt = unsafe { &mut *((ADR_GDT + 1 * 8) as *mut SegmentDescriptor) };
*gdt = SegmentDescriptor::new(0xffffffff, 0x00000000, AR_DATA32_RW);
let gdt = unsafe { &mut *((ADR_GDT + 2 * 8) as *mut SegmentDescriptor) };
*gdt = SegmentDescriptor::new(LIMIT_BOTPAK, ADR_BOTPAK, AR_CODE32_ER);
, ADR_GDT);
load_gdtr(LIMIT_GDT
// IDTの初期化
for i in 0..=(LIMIT_IDT / 8) {
let idt = unsafe { &mut *((ADR_IDT + i * 8) as *mut GateDescriptor) };
*idt = GateDescriptor::new(0, 0, 0);
}
// 割り込みの設定
let idt = unsafe { &mut *((ADR_IDT + 0x21 * 8) as *mut GateDescriptor) };
*idt = GateDescriptor::new(handler!(inthandler21) as u32, 2 * 8, AR_INTGATE32);
let idt = unsafe { &mut *((ADR_IDT + 0x2c * 8) as *mut GateDescriptor) };
*idt = GateDescriptor::new(handler!(inthandler2c) as u32, 2 * 8, AR_INTGATE32);
let idt = unsafe { &mut *((ADR_IDT + 0x20 * 8) as *mut GateDescriptor) }; // <- 追加
*idt = GateDescriptor::new(handler!(inthandler20) as u32, 2 * 8, AR_INTGATE32); // <- 追加
, ADR_IDT);
load_idtr(LIMIT_IDT}
interrupt.rsの初期化処理もPITを許可するように修正する
// interrupt.rs
pub fn allow_input() {
, 0xf8); // <- PITも許可
out8(PIC0_IMR, 0xef);
out8(PIC1_IMR;
init_keyboard()}
最後に lib.rsで初期化とカウンターの表示を行う。
// lib.rs
#[no_mangle]
#[start]
pub extern "C" fn haribote_os() {
// 省略
;
sti()interrupt::allow_input();
timer::init_pit(); // <- 追加
;
init_palette();
enable_mouse()// 省略
loop {
, 160, Color::LightGray, 40, 28, 119, 43);
boxfill(buf_win_addrlet mut writer = ScreenWriter::new(Some(buf_win_addr), vga::Color::Black, 40, 28, 160, 52);
write!(writer, "{:>010}", unsafe { timer::COUNTER }).unwrap(); // <- static mutのCOUNTERを使うように
.refresh(shi_win, 40, 28, 120, 44);
sheet_manager// 省略
結果
10msごとにカウントアップ、つまり1秒に100カウントアップするカウンターが表示されるようになった。
タイムアウトの実装
次にタイムアウトをハンドリングできるようにする。
複数のタイマを TimerManager
にもたせる。
// timer.rs
use lazy_static::lazy_static;
use spin::Mutex;
use crate::asm::{cli, load_eflags, out8, store_eflags};
use crate::fifo::Fifo;
use crate::interrupt::PIC0_OCW2;
const PIT_CTRL: u32 = 0x0043;
const PIT_CNT0: u32 = 0x0040;
pub fn init_pit() {
, 0x34);
out8(PIT_CTRL, 0x9c);
out8(PIT_CNT0, 0x2e);
out8(PIT_CNT0}
pub extern "C" fn inthandler20() {
, 0x60); // IRQ-00受付完了をPICに通知
out8(PIC0_OCW2let mut tm = TIMER_MANAGER.lock();
.count += 1;
tmif tm.next > tm.count {
return;
}
let mut timeout_count = 0;
for i in 0..tm.counting {
= i;
timeout_count let timer_index = tm.timers[i as usize];
let t = tm.timers_data[timer_index];
if t.timeout > tm.count {
break;
}
{
let mut t_mut = &mut tm.timers_data[timer_index];
.flag = TimerFlag::USED;
t_mut}
let fifo = unsafe { &*(t.fifo_addr as *const Fifo) };
.put(t.data).unwrap();
fifo}
.counting -= timeout_count;
tmfor i in 0..tm.counting {
.timers[i as usize] = tm.timers[(timeout_count + i) as usize];
tm}
if tm.counting > 0 {
.next = tm.timers_data[tm.timers[0]].timeout;
tm} else {
.next = 0xffffffff;
tm}
}
const MAX_TIMER: usize = 500;
#[derive(Debug, Clone, Copy)]
pub struct Timer {
pub timeout: u32,
pub flag: TimerFlag,
pub fifo_addr: u32,
pub data: u8,
}
impl Timer {
pub fn new() -> Timer {
{
Timer : 0,
timeout: TimerFlag::AVAILABLE,
flag: 0,
fifo_addr: 0,
data}
}
}
pub struct TimerManager {
pub count: u32,
pub next: u32,
pub counting: u32,
pub timers: [usize; MAX_TIMER],
pub timers_data: [Timer; MAX_TIMER],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimerFlag {
,
AVAILABLE,
USED,
COUNTING}
impl TimerManager {
pub fn new() -> TimerManager {
{
TimerManager : 0,
count: 0,
next: 0,
counting: [0; MAX_TIMER],
timers: [Timer::new(); MAX_TIMER],
timers_data}
}
pub fn alloc(&mut self) -> Result<usize, &'static str> {
for i in 0..MAX_TIMER {
if self.timers_data[i].flag == TimerFlag::AVAILABLE {
self.timers_data[i].flag = TimerFlag::USED;
return Ok(i);
}
}
Err("CANNOT ASSIGN TIMER")
}
pub fn set_time(&mut self, timer_index: usize, timeout: u32) {
let mut timer = &mut self.timers_data[timer_index];
.timeout = timeout + self.count;
timer.flag = TimerFlag::COUNTING;
timerlet eflags = load_eflags();
;
cli()let mut insert_index: usize = 0;
for i in 0..self.counting {
= i as usize;
insert_index let t = self.timers_data[self.timers[i as usize]];
if t.timeout >= t.timeout {
break;
}
}
let mut j = self.counting as usize;
while j > insert_index {
self.timers[j] = self.timers[j - 1];
-= 1;
j }
self.counting += 1;
self.timers[insert_index] = timer_index;
self.next = self.timers_data[self.timers[0]].timeout;
;
store_eflags(eflags)}
pub fn init_timer(&mut self, timer_index: usize, fifo: &Fifo, data: u8) {
let mut timer = &mut self.timers_data[timer_index];
.fifo_addr = fifo as *const Fifo as u32;
timer.data = data;
timer}
pub fn free(&mut self, i: usize) {
let mut timer = &mut self.timers_data[i];
.flag = TimerFlag::AVAILABLE;
timer}
}
lazy_static! {
pub static ref TIMER_MANAGER: Mutex<TimerManager> = Mutex::new(TimerManager::new());
}
TimerManager
に順番を保持する用の配列と、実際のTimer
のデータを保持する配列をもつのはSheet
の場合と同様となる。
本にある通り、配列にある順番がtimeout
順になっており、これにより1つずつタイムアウトしているかを見ていけばよい形になっている。
これを使って、画面に時間経過で表示されるようなものを作る。
// lib.rs
#[no_mangle]
#[start]
pub extern "C" fn haribote_os() {
// 省略
let timer_buf1 = Fifo::new(8);
let timer_buf2 = Fifo::new(8);
let timer_buf3 = Fifo::new(8);
descriptor_table::init();
// ...
;
enable_mouse()
let timer_index1 = TIMER_MANAGER.lock().alloc().unwrap();
TIMER_MANAGER.lock()
.init_timer(timer_index1, &timer_buf1, 1);
.lock().set_time(timer_index1, 1000);
TIMER_MANAGERlet timer_index2 = TIMER_MANAGER.lock().alloc().unwrap();
TIMER_MANAGER.lock()
.init_timer(timer_index2, &timer_buf2, 1);
.lock().set_time(timer_index2, 300);
TIMER_MANAGERlet timer_index3 = TIMER_MANAGER.lock().alloc().unwrap();
TIMER_MANAGER.lock()
.init_timer(timer_index3, &timer_buf3, 1);
.lock().set_time(timer_index3, 50);
TIMER_MANAGER// 省略
loop {
;
cli()if let Some(t) = TIMER_MANAGER.try_lock() {
, 160, Color::LightGray, 40, 28, 119, 43);
boxfill(buf_win_addrlet mut writer =
ScreenWriter::new(Some(buf_win_addr), vga::Color::Black, 40, 28, 160, 52);
write!(writer, "{:>010}", t.count).unwrap();
.refresh(shi_win, 40, 28, 120, 44);
sheet_manager}
// 省略
} else if timer_buf1.status() != 0 {
let _ = timer_buf1.get().unwrap();
;
sti()let mut writer = ScreenWriter::new(
Some(buf_bg_addr),
vga::Color::White,
0,
64,
*SCREEN_WIDTH as usize,
*SCREEN_HEIGHT as usize,
;
)write!(writer, "10[sec]").unwrap();
.refresh(shi_bg, 0, 64, 56, 80);
sheet_manager} else if timer_buf2.status() != 0 {
let _ = timer_buf2.get().unwrap();
;
sti()let mut writer = ScreenWriter::new(
Some(buf_bg_addr),
vga::Color::White,
0,
80,
*SCREEN_WIDTH as usize,
*SCREEN_HEIGHT as usize,
;
)write!(writer, "3[sec]").unwrap();
.refresh(shi_bg, 0, 80, 56, 96);
sheet_manager} else if timer_buf3.status() != 0 {
let i = timer_buf3.get().unwrap();
;
sti()if i != 0 {
TIMER_MANAGER.lock()
.init_timer(timer_index3, &timer_buf3, 0);
boxfill(,
buf_bg_addr*SCREEN_WIDTH as isize,
Color::White,
8,
96,
15,
111,
;
)} else {
TIMER_MANAGER.lock()
.init_timer(timer_index3, &timer_buf3, 1);
boxfill(,
buf_bg_addr*SCREEN_WIDTH as isize,
Color::DarkCyan,
8,
96,
15,
111,
;
)}
.lock().set_time(timer_index3, 50);
TIMER_MANAGER.refresh(shi_bg, 8, 96, 16, 112)
sheet_manager} else {
;
sti()}
10秒、3秒の経過で表示、および点滅する矩形を表示するようになっている。
実行結果
実行してみると以下の通り、秒数に応じて表示が切り替わっていることがわかる。gifだと画面がチラついて見えるが、実際はチラつきはなかった。
12日目は以上となる。ここまでの内容のコードはyoshitsugu/hariboteos_in_rustのday12としてタグを打ってある。