目的
Windowを表示したいと思っています。
以下の記事を3つ組み合わせて、VRAMを実機にも対応させ、タイトルバーっぽいものも追加しました。
今はただの試しです。ここから直して、Window毎に画面バッファを持たせたり、ドライバを作ってPS/2マウスに対応させたりしようと思っています。
自作OS Windowを表示する為の準備
x86 VBEのVRAM開始アドレスを調べる
x86 VBEのVRAM開始アドレスを取得
コード
nasm -f bin boot.asm -o boot.bin
nasm -f elf32 entry.asm -o entry.o
#gcc -m32 -ffreestanding -c mainc.c -o mainc.o
gcc -O0 -m32 -ffreestanding -fno-pic -fno-stack-protector -c mainc.c -o mainc.o
ld -m elf_i386 -T linker.ld -o kernel.elf entry.o mainc.o
objcopy -O binary kernel.elf kernel.bin
cat boot.bin kernel.bin > disk.img
qemu-system-i386 -drive format=raw,file=disk.img -monitor stdio
boot.asm
[org 0x7C00]
bits 16
start:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
sti
; BIOSに因るCS:IP=0x07C0:0x0000及び0x0000:0x7C00を統一する
jmp 0x0000:real_start
real_start:
; VBEのVRAMをBIOSに聞く
mov ax, 0x4F01
mov cx, 0x115 ; 聞きたいモード
mov di, modeinfo
int 0x10
; 800x600 24bpp (モード 0115h)
mov ax, 0x4F02 ; VBE Set Mode
mov bx, 0x4115 ; 0115h (24bpp) + ビット14
int 0x10
; vbe_vram_addrへVBEのアドレスを置く
mov eax, [modeinfo + 0x28]
mov [vbe_vram_addr], eax
; --- INT 13h CHS 読み込み ---
mov ah, 0x02 ; BIOS: Read Sectors
mov al, 2 ; 読み込むセクタ数
mov ch, 0 ; シリンダ = 0
mov dh, 0 ; ヘッド = 0
mov cl, 2 ; セクタ = 2 (ブートローダの次)
mov dl, 0x80 ; ドライブ番号=HDD
mov bx, 0x7E00 ; ES:BX = 読み込み先
int 0x13
jc disk_error
jmp 0x0000:0x7E00 ; 読み込んだコードへjmp
disk_error:
hlt
jmp disk_error
; VBE返り値44バイト modeinfo + 0x28 = VRAM開始アドレス
modeinfo: resb 0x2C
times 506-($-$$) db 0
vbe_vram_addr: resd 1 ; VBE VRAM開始アドレス
times 510-($-$$) db 0
dw 0xAA55
entry.asm
entry.asm
bits 16
global start
extern kernel_main
start:
cli
xor ax, ax
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x7C00
; GDTロード
lgdt [gdt_descriptor]
; プロテクトモード有効化
mov eax, cr0
or eax, 1
mov cr0, eax
; 32ビットコードへ移行
jmp 0x08:pm_start
; -----------------------------
gdt_start:
dq 0x0000000000000000 ; NULL
gdt_code:
dw 0xFFFF ; limit 0-15
dw 0x0000 ; base 0-15
db 0x00 ; base 16-23
db 0x9A ; code segment
db 0xCF ; flags
db 0x00 ; base 24-31
gdt_data:
dw 0xFFFF
dw 0x0000
db 0x00
db 0x92 ; data segment
db 0xCF
db 0x00
gdt_end:
gdt_descriptor:
dw gdt_end - gdt_start - 1
dd gdt_start
; -----------------------------
[bits 32]
pm_start:
; データセグメント設定
mov ax, 0x10
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
mov esp, 0x9FC00
call kernel_main
.hlt_loop:
hlt
jmp .hlt_loop
mainc.c
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
#define SCREEN_W 800
#define SCREEN_H 600
// Windowの情報
typedef struct {
int x, y; // window左上の座標
int w, h; // windowの縦横の長さ
} window_t;
/*
ピクセル描画
*fb : VRAM
x : X座標
y : Y座標
r : 赤
g : 緑
b : 青
*/
static inline void put_pixel(volatile u8 *fb, int x, int y, u8 r, u8 g, u8 b) {
int p = (y * SCREEN_W + x) * 3; // ポインタを描画するピクセルに合わせる
fb[p] = b;
fb[p+1] = g;
fb[p+2] = r;
}
/*
Window描画
*fb : VRAM
*win : Windows情報構造体
r : 赤
g : 緑
b : 青
*/
void fill_rect(volatile u8 *fb, window_t *win, u8 r, u8 g, u8 b) {
for (int y = win->y; y < win->y + win->h; y++) {
for (int x = win->x; x < win->x + win->w; x++) {
put_pixel(fb, x, y, r, g, b);
}
}
}
void draw_title_bar(volatile u8 *fb, window_t *win) {
window_t bar = {win->x + 2, win->y + 2, win->w - 4, 20};
fill_rect(fb, &bar, 160, 160, 160);
}
void kernel_main(void) {
u32 fb_addr = *(volatile u32 *)0x7DFA; // VBEの開始VRAM(boot.asmで格納している)
volatile u8 *fb = (volatile u8 *)fb_addr; // ピクセル毎に操作する為に8bitずつ操作(そもそも1ピクセル24bitだから8bit毎に操作するしかない)
// 背景(黒)
window_t bg = {0, 0, SCREEN_W, SCREEN_H};
fill_rect(fb, &bg, 0, 0, 0);
// 灰色window描画
window_t win = {100, 80, 400, 300}; // x, y, 幅, 高さ
fill_rect(fb, &win, 600, 600, 600); // 灰色
draw_title_bar(fb, &win);
// 黄色window
window_t win2 = {600, 100, 200, 150};
fill_rect(fb, &win2, 255, 255, 0); // 黄色(R=255, G=255, B=0)
draw_title_bar(fb, &win2);
while (1) asm volatile ("hlt");
}
大連に出張中で、ホテルで書いているので結構雑です。
