您的位置:首页 > 其它

自制操作系统(五) 保护模式寻址原理、字符鼠标指针显示

2016-04-21 22:27 239 查看
首先就是对前一天程序的一些优化,引入了结构体:

struct BOOTINFO {
char cyls, leds, vmode, reserve;
short scrnx, scrny;
char *vram;
};

void HariMain(void)
{
char *vram;
int xsize, ysize;
struct BOOTINFO *binfo;

init_palette();
binfo = (struct BOOTINFO *) 0x0ff0;
xsize = (*binfo).scrnx;
ysize = (*binfo).scrny;
vram = (*binfo).vram;

init_screen(vram, xsize, ysize);

for (;;) {
io_hlt();
}
}


这里面用到的结构体BOOTINFO其实只是一个存储单元。

binfo = (struct BOOTINFO *) 0x0ff0;这句获取0x0ff0开始一连串值。

作用相当于binfo_scrnx = (short *)0xff4;等几句。

另外,作为参数时,可以不用传入那么多参数。

之后就是,字符显示:

void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
int i;
char *p, d /* data */;
for (i = 0; i < 16; i++) {
p = vram + (y + i) * xsize + x;
d = font[i];
if ((d & 0x80) != 0) { p[0] = c; }
if ((d & 0x40) != 0) { p[1] = c; }
if ((d & 0x20) != 0) { p[2] = c; }
if ((d & 0x10) != 0) { p[3] = c; }
if ((d & 0x08) != 0) { p[4] = c; }
if ((d & 0x04) != 0) { p[5] = c; }
if ((d & 0x02) != 0) { p[6] = c; }
if ((d & 0x01) != 0) { p[7] = c; }
}
return;
}


这段代码显示字符A,具体原理就是用for语句将8个像素的程序循环16遍。需要在HariMain里,

初始化

static char font_A[16] = {

0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,

0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00

};

作为参数。

一个A就出现这么多代码,肯定不行。所以作者引入了OSASK字体。加入hankaku.txt文件。另需编译器makefont.exe将之编译为hankaku.bin文件。

新的Makefile:

TOOLPATH = ../z_tools/
INCPATH  = ../z_tools/haribote/

MAKE     = $(TOOLPATH)make.exe -r
NASK     = $(TOOLPATH)nask.exe
NASM     = $(TOOLPATH)nasm.exe
CC1      = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet
GAS2NASK = $(TOOLPATH)gas2nask.exe -a
OBJ2BIM  = $(TOOLPATH)obj2bim.exe
MAKEFONT = $(TOOLPATH)makefont.exe
BIN2OBJ  = $(TOOLPATH)bin2obj.exe
BIM2HRB  = $(TOOLPATH)bim2hrb.exe
RULEFILE = $(TOOLPATH)haribote/haribote.rul
EDIMG    = $(TOOLPATH)edimg.exe
IMGTOL   = $(TOOLPATH)imgtol.com
COPY     = copy
DEL      = del

default :
$(MAKE) img

ipl.bin : ipl.asm Makefile
$(NASM) ipl.asm -o ipl.bin

main.bin : main.nas Makefile
$(NASK) main.nas main.bin main.lst

c_main.gas : c_main.c Makefile
$(CC1) -o c_main.gas c_main.c

c_main.nas : c_main.gas Makefile
$(GAS2NASK) c_main.gas c_main.nas

c_main.obj : c_main.nas Makefile
$(NASK) c_main.nas c_main.obj c_main.lst

assemblyFunc.obj : assemblyFunc.nas Makefile
$(NASK) assemblyFunc.nas assemblyFunc.obj assemblyFunc.lst

hankaku.bin : hankaku.txt Makefile
$(MAKEFONT) hankaku.txt hankaku.bin

hankaku.obj : hankaku.bin Makefile
$(BIN2OBJ) hankaku.bin hankaku.obj _hankaku

c_main.bim : c_main.obj assemblyFunc.obj hankaku.obj Makefile
$(OBJ2BIM) @$(RULEFILE) out:c_main.bim stack:3136k map:c_main.map \
c_main.obj assemblyFunc.obj hankaku.obj
# 3MB+64KB=3136KB

c_main.hrb : c_main.bim Makefile
$(BIM2HRB) c_main.bim c_main.hrb 0

haribote.sys : main.bin c_main.hrb Makefile
copy /B main.bin+c_main.hrb haribote.sys

haribote.img : ipl.bin haribote.sys Makefile
$(EDIMG)   imgin:../z_tools/fdimg0at.tek \
wbinimg src:ipl.bin len:512 from:0 to:0 \
copy from:haribote.sys to:@: \
imgout:haribote.img

img :
$(MAKE) haribote.img

run :
$(MAKE) img
$(COPY) haribote.img ..\z_tools\qemu\fdimage0.bin
$(MAKE) -C ../z_tools/qemu

install :
$(MAKE) img
$(IMGTOL) w a: haribote.img

clean :
-$(DEL) *.bin
-$(DEL) *.lst
-$(DEL) *.gas
-$(DEL) *.obj
-$(DEL) c_main.nas
-$(DEL) c_main.map
-$(DEL) c_main.bim
-$(DEL) c_main.hrb
-$(DEL) haribote.sys

src_only :
$(MAKE) clean
-$(DEL) haribote.img


之后有了显示字符和字符串的函数:

void putfont8(char *vram, int xsize, int x, int y, char c, char *font)
{
int i;
char *p, d /* data */;
for (i = 0; i < 16; i++) {
p = vram + (y + i) * xsize + x;
d = font[i];
if ((d & 0x80) != 0) { p[0] = c; }
if ((d & 0x40) != 0) { p[1] = c; }
if ((d & 0x20) != 0) { p[2] = c; }
if ((d & 0x10) != 0) { p[3] = c; }
if ((d & 0x08) != 0) { p[4] = c; }
if ((d & 0x04) != 0) { p[5] = c; }
if ((d & 0x02) != 0) { p[6] = c; }
if ((d & 0x01) != 0) { p[7] = c; }
}
return;
}

void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s)
{
extern char hankaku[4096];
for (; *s != 0x00; s++) {
putfont8(vram, xsize, x, y, c, hankaku + *s * 16);
x += 8;
}
return;
}


鼠标的显示就是一样的道理了:

void init_mouse_cursor8(char *mouse, char bc)
/* 准备鼠标指针 */
{
static char cursor[16][16] = {
"**************..",
"*OOOOOOOOOOO*...",
"*OOOOOOOOOO*....",
"*OOOOOOOOO*.....",
"*OOOOOOOO*......",
"*OOOOOOO*.......",
"*OOOOOOO*.......",
"*OOOOOOOO*......",
"*OOOO**OOO*.....",
"*OOO*..*OOO*....",
"*OO*....*OOO*...",
"*O*......*OOO*..",
"**........*OOO*.",
"*..........*OOO*",
"............*OO*",
".............***"
};
int x, y;

for (y = 0; y < 16; y++)
{
for (x = 0; x < 16; x++)
{
if (cursor[y][x] == '*')
{
mouse[y * 16 + x] = COL8_000000;
}
if (cursor[y][x] == 'O')
{
mouse[y * 16 + x] = COL8_FFFFFF;
}
if (cursor[y][x] == '.')
{
mouse[y * 16 + x] = bc;
}
}
}
return;
}

void putblock8_8(char *vram, int vxsize, int pxsize,
int pysize, int px0, int py0, char *buf, int bxsize)
{
int x, y;
for (y = 0; y < pysize; y++)
{
for (x = 0; x < pxsize; x++)
{
vram[(py0 + y) * vxsize + (px0 + x)] = buf[y * bxsize + x];
}
}
return;
}


此时c_main.c里main函数如下:

void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;
char s[40], mcursor[256];
int mx, my;

init_palette();
init_screen(binfo->vram, binfo->scrnx, binfo->scrny);
mx = (binfo->scrnx - 16) / 2;
my = (binfo->scrny - 28 - 16) / 2;
init_mouse_cursor8(mcursor, COL8_008484);
putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
sprintf(s, "(%d, %d)", mx, my);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);

for (;;)
{
io_hlt();
}
}


可见,先初始化调色板、再初始化屏幕、再初始化鼠标,然后画鼠标,显示字符。

这章涉及保护模式寻址,关于GDT。

2016.06.06我已经非常透彻理解了保护模式寻址,画了下面的图:



虽然人们在谈到中断(Interrupt)时,总会拿轮询(Polling)来做“反面”例子,但中断和轮询并不是完全对立的两个概念,呃,它们是对立统一的。
“CPU执行完每条指令时,都会去检查一个中断标志位”,这句话是所有关于中断长篇大论的开场白,但很容易被人忽略,其实,这就是中断的本质。
举个例子,CPU老板是一家公司的光杆司令,所有的顾客都要他亲自跑去处理,还要跟有关部门打点关系,CPU觉得顾客和公关这两样事它一个人搞不来,这就是轮询;终于这家公司升级发展了,CPU老板请了一个秘书,所有的顾客都先由秘书经手,CPU心情好的时候就去看一下,大部分时间都忙着去公关了,这时它觉得轻松了很多,这就是中断了~~
也就是说,中断和轮询是从CPU老板的角度来看的,不管怎样,事件都还是有人来时刻跟踪才能被捕获处理,不过是老板还是秘书的问题。所有的中断(或者异步,回调等)背后都有一个轮询(循环,listener)。

通往32位之路:
PIC关闭一切中断

; PIC关闭一切中断
;    根据AT兼容机的规格、初始化PIC
;    必须在CLI之前进行,到CLI挂起
;    随后进行PIC初始化

MOV        AL,0xff
OUT        0x21,AL
NOP                        ; 如果连续进行OUT命令,有些机种不可以
OUT        0xa1,AL

CLI                        ; 禁止CPU级别中断


设置A20

; OPEN A20GATE

CALL    waitkbdout
MOV        AL,0xd1
OUT        0x64,AL
CALL    waitkbdout
MOV        AL,0xdf            ; enable A20
OUT        0x60,AL
CALL    waitkbdout


切换到保护模式。

INSTRSET指令,是为了能够使用386以后的LGDT,EAX,CR0等关键字。

通过带入CR0而切换到保护模式时,要马上执行JMP指令。因为变成保护模式后,机器语言的解释要发生变化。CPU为了加快指令的执行速度而使用了管道这一机制,就是说,前一条指令还在执行的时候,就开始解释下一条甚至是再下一条指令。因为模式变了,就得重新解释一遍,所以加入了JMP指令

; 保护模式

[INSTRSET "i486p"]                ; 开始使用486命令

LGDT    [GDTR0]            ; 临时GDT
MOV        EAX,CR0
AND        EAX,0x7fffffff    ; bit31设0,禁止分页
OR        EAX,0x00000001    ; bit0设1,为了切换到保护模式
MOV        CR0,EAX
JMP        pipelineflush
pipelineflush:
MOV        AX,1*8            ;  可读写的段,32bit
MOV        DS,AX
MOV        ES,AX
MOV        FS,AX
MOV        GS,AX
MOV        SS,AX
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: