_write_mem8: ; void write_mem8(int addr, int data);
MOV ECX,[ESP+4] ; [ESP + 4]中存放的是地址,将其读入ECX
MOV AL,[ESP+8] ; [ESP + 8]中存放的是数据,将其读入AL
MOV [ECX],AL
RET
由于函数void write_mem8(int addr, int data);的输入项为addr和data,且该两项数据类型为int,占4个字节,所以使用32位寄存器进行存储。将addr读到ECX寄存器,data的低八位读到AL寄存器(AL寄存器占8位,即一个字节),并将AL寄存器中的数据赋值给ECX寄存器中的地址处,总结该函数的功能就是将data低8位数据写入addr地址。在3.8节中提到,VRAM的地址为0xa0000~0xaffff的64KB,所以下面函数的功能就是VRAM全部写入了15,而颜色种类也是2^8=256种,这可能就是为何要取低8位的原因。
void HariMain(void)
{
int i; /*变量声明:i是一个32位整数*/
for (i = 0xa0000; i <= 0xaffff; i++) {
write_mem8(i, 15); /* 15=0x00001111,即地址0xa0000~0xaffff范围内均赋值15 */
}
for (;;) {
io_hlt();
}
}
条纹图案是指变更VRAM中地址的元素,按照某种规律进行变换。由于之前窗口设定的长度为320像素,考虑到后续程序中的颜色共有16种,所以某行的像素分别为(00,01,02,03,04,05,06,07,08,09,0A,0B,0C,0D,0E,0F,00,....)。该处用到了C语言中的 ‘&’ 操作,在位运算中经常遇到,这里就不作详细介绍。
修改前:write_mem8(i, 15); /* 15=0x00001111,即地址0xa0000~0xaffff范围内均赋值15 */
修改后:write_mem8(i, i & 0x0f); /* 地址0xa0000~0xaffff范围内均赋值(i & 0x0f) */
把指针说成地址变量,更好理解。
void HariMain(void)
{
int i; /*变量声明。变量i是32位整数*/
char *p; /*变量p,用于BYTE型地址*/
for (i = 0xa0000; i <= 0xaffff; i++) {
p = i; /* 代入地址,假设p=i=0xa1234 */
*p = i & 0x0f;/* 向0xa1234处地址(VRAM中的某一处地址)写入数值(i & 0x0f) */
/*这可以替代write_mem8(i, i & 0x0f);*/
}
for (;;) {
io_hlt();
}
}
p = (char *) 0xa0000; /*给地址变量赋值*/
for (i = 0; i <= 0xffff; i++) {
/* 下述两种说法相同,均是将地址0xa0000向前偏移i位后赋值(i & 0x0f)
与(*p+i)有本质区别,前者是地址的前移,后者是加减p地址上的值 */
*(p + i) = i & 0x0f;
// p[i] = i & 0x0f;
}
/* 初始化调色板 */
void init_palette(void){/*RGB(红绿蓝)方式,用6位十六进制数,也就是24位指定颜色*/
static unsigned char table_rgb[16*3]={
0x00, 0x00, 0x00, /* 0:黑 */
0xff, 0x00, 0x00, /* 1:亮红 */
0x00, 0xff, 0x00, /* 2:亮绿 */
0xff, 0xff, 0x00, /* 3:亮黄 */
0x00, 0x00, 0xff, /* 4:亮蓝 */
0xff, 0x00, 0xff, /* 5:亮紫 */
0x00, 0xff, 0xff, /* 6:浅亮蓝 */
0xff, 0xff, 0xff, /* 7:白 */
0xc6, 0xc6, 0xc6, /* 8:亮灰 */
0x84, 0x00, 0x00, /* 9:暗红 */
0x00, 0x84, 0x00, /* 10:暗绿 */
0x84, 0x84, 0x00, /* 11:暗黄 */
0x00, 0x00, 0x84, /* 12:暗青 */
0x84, 0x00, 0x84, /* 13:暗紫 */
0x00, 0x84, 0x84, /* 14:浅暗蓝 */
0x84, 0x84, 0x84 /* 15:暗灰 */
};
set_palette(0,15,table_rgb);
return;
}
/* 设置调色板:将rgb数组中的数据写入到start~end调色板中(参考调色板访问步骤) */
void set_palette(int start,int end,unsigned char* rgb){
int i,eflags;
eflags=io_load_eflags(); /* 记录中断许可标志的值 */
io_cli(); /* 将许可标志置为0,禁止中断 */
io_out8(0x03c8,start);
for(i=start; i<=end; i++){
io_out8(0x03c9,rgb[0]/4);/* 哪位大佬能够解答一下该处为何要除4 */
io_out8(0x03c9,rgb[1]/4);
io_out8(0x03c9,rgb[2]/4);
rgb+=3;
}
io_store_eflags(eflags); /* 恢复许可标志的值 */
return;
}
;port=0x03c9,data=rgb[0]/4=0xff/4=0x3f; 第一个数字port的存放地址:[ESP + 4],
;第二个数字data的存放地址:[ESP + 8]
_io_out8: ; void io_out8(int port,int data);
MOV EDX,[ESP+4] ; port端口 EDX=0x03c9
MOV AL,[ESP+8] ; 数据 AL =0x3f
OUT DX,AL ; 将数据0x3f写入DX端口0x03c9
RET
调色板的访问步骤:
boxfill8(p, 320, COL8_FF0000, 20, 20, 120, 120);
boxfill8(p, 320, COL8_00FF00, 70, 50, 170, 150);
boxfill8(p, 320, COL8_0000FF, 120, 80, 220, 180);
函数void boxfill8(unsigned char *vram, int xsize, unsigned char c, int x0, int y0, int x1, int y1); 输入值:vram为VRAM位置,xsize为屏幕长度像素(在3.8节设置过,为320),c为颜色,坐标(x0,y0)和(x1,y1)。该函数功能为将坐标(x0,y0)和(x1,y1)围成的长方形内的像素颜色变成c。
/* 绘制320*200内的像素屏幕框架 */
void init_screen(char *vram, int xsize, int ysize){
boxfill8(vram, xsize, COL8_008484, 0, 0, xsize - 1, ysize - 29);// 矩形
boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 28, xsize - 1, ysize - 28);// 横长条
boxfill8(vram, xsize, COL8_FFFFFF, 0, ysize - 27, xsize - 1, ysize - 27);// 横长条
boxfill8(vram, xsize, COL8_C6C6C6, 0, ysize - 26, xsize - 1, ysize - 1);// 矩形
boxfill8(vram, xsize, COL8_FFFFFF, 3, ysize - 24, 59, ysize - 24);// 横长条
boxfill8(vram, xsize, COL8_FFFFFF, 2, ysize - 24, 2, ysize - 4);// 竖长条
boxfill8(vram, xsize, COL8_848484, 3, ysize - 4, 59, ysize - 4);// 横长条
boxfill8(vram, xsize, COL8_848484, 59, ysize - 23, 59, ysize - 5);// 竖长条
boxfill8(vram, xsize, COL8_000000, 2, ysize - 3, 59, ysize - 3);// 横长条
boxfill8(vram, xsize, COL8_000000, 60, ysize - 24, 60, ysize - 3);// 竖长条
boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 24, xsize - 4, ysize - 24);// 长横条
boxfill8(vram, xsize, COL8_848484, xsize - 47, ysize - 23, xsize - 47, ysize - 4);// 竖长条
boxfill8(vram, xsize, COL8_FFFFFF, xsize - 47, ysize - 3, xsize - 4, ysize - 3);// 长横条
boxfill8(vram, xsize, COL8_FFFFFF, xsize - 3, ysize - 24, xsize - 3, ysize - 3);// 竖长条
return;
}
按照3.8节中,SCRNX=0x0ff4,SCRNY=0x0ff6,VRAM=0x0ff8。该节将读取上述几个地址中的值,并将画面背景显示部分独立封装成init_screen函数。
将3.8节中的0x0ff0~0x0ffb地址内元素整合成一个BOOTINFO结构体,包含cyls, leds, vmode,reserve 等元素(reserve在3.8节中没有,VMODE为一个字节,而VMODE的地址为0x0ff2,相邻地址0x0ff3没有命名,所以在此就用reserve 表示地址0x0ff3处)。
struct BOOTINFO {
char cyls, leds, vmode, reserve;
short scrnx, scrny;
char *vram;
};
cyls, leds, vmode,reserve这几个元素分别对应0x0ff0,0x0ff1,0x0ff2,0x0ff3,所以都是char类型。scrnx, scrny元素地址为0x0ff4,0x0ff6,均为占两个字节,所以是short类型。而vram只有一个开始位置0x0ff8,没有结束位置,所以该处使用指针类型。当创建结构体时,需要对结构体中的元素进行初始化。struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0,就是将binfo指向地址0x0ff0处,与3.8节中的元素相对应。
struct BOOTINFO *binfo = (struct BOOTINFO *) 0x0ff0;binfo->srcnx与(*binfo).scrnx的操作是相同的。
该节将字符构造出16*8像素点阵,如下图字符‘A‘。
字符’A‘可以构造成数组进行读取,下面应该构造函数将该字符进行绘制到VRAM中。函数void putfont8(char *vram, int xsize, int x, int y, char c, char *font) ;的功能就是将font数组绘制到VRAM中以坐标(x, y)为起点处,字符颜色为c。由于字符每行8个像素,所以for循环16行加上每层循环8个判断改变VRAM中的特定位置颜色。
在文件hankaku.txt中存在256个常用字符,只要明白下述函数就是实现将该.txt文件转换成机器语言即可,如果想了解请自行检索。
TOOLPATH = ../z_tools/
MAKEFONT = $(TOOLPATH)makefont.exe
BIN2OBJ = $(TOOLPATH)bin2obj.exe
#使用makefont.exe从hankaku.txt生成hankaku.bin
hankaku.bin : hankaku.txt Makefile
$(MAKEFONT) hankaku.txt hankaku.bin
#使用bin2obj.exe从hankaku.bin生成hankaku.obj
hankaku.obj : hankaku.bin Makefile
$(BIN2OBJ) hankaku.bin hankaku.obj _hankaku
在5.5中需要多次调用putfont8函数才可以打印字符串,是否可以将需要打印的字符串整体作为输入项传入函数进行打印呢?改进函数void putfonts8_asc(char *vram, int xsize, int x, int y, char c, unsigned char *s) ,可将字符串s绘制到VRAM中以坐标(x, y)为起点处,字符颜色为c。逐个读入字符串中的字符,调用putfont8函数进行打印,打印完字符后需要偏移x,不然就打印在同一位置上了。
该节主要介绍了函数sprintf,该函数在头文件stdio.h中,所以要使用该函数,就需要包含该头文件。sprintf(s, "scrnx = %d", binfo->scrnx); 的功能为binfo->scrnx的值替换到%d处,并将中间双引号的字符串写入到s中。由于binfo->scrnx=320,所以s="scrnx = 320"。
将鼠标做成16*16像素的形状,void init_mouse_cursor8(char *mouse, char bc)函数中,mouse中存放了按照鼠标形状确定的颜色,bc为背景色。现在鼠标相关信息已准备好,需要将鼠标信息打印到屏幕上。参考putfont8函数,构造函数void putblock8_8(char *vram, int vxsize, int pxsize, int pysize, int px0, int py0, char *buf, int bxsize)。pxsize,pysize为想要显示的图形(picture)的大小,px0,py0为图形显示为位置,buf和bxsize分别指定图形的存放地址和每一行含有的像素数,功能为从buf读出pxsize*pysize个图形数据(bxsize指定读出哪些连续数据的),并将该图形写到vram的(px0,py0)坐标处。
该节涉及到linux内核的GDT( 按照《深入理解linux内核》P44页 )和IDT( 按照《深入理解linux内核》P144页 ),创建SEGMENT_DESCRIPTOR和GATE_DESCRIPTOR结构体。
// GDT global(segment)descriptor table,8字节
struct SEGMENT_DESCRIPTOR{
short limit_low, base_low;
char base_mid, access_right;
char limit_high, base_high;
};
// IDT interrupt descriptor table,8字节
struct GATE_DESCRIPTOR{
short offset_low, selector;
char dw_count, access_right;
short offset_high;
};
结构体均为8字节,GDT的索引值为13位,所以GDT有2^13=8192个,而IDT的索引值为8位,所以IDT有2^8=256个。struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) 0x00270000; struct GATE_DESCRIPTOR *idt = (struct GATE_DESCRIPTOR *) 0x0026f800;由于idt的地址为0x0026f800,而IDT有256*8=2048字节,所以0x0026f800+0x800=0x00270000。并按照《深入理解linux内核》中的介绍,初始化结构体中的元素。
; limit=0x0000ffff addr=0x00270000 存放道ESP的顺序为ffff0000 00007200
_load_gdtr: ; void load_gdtr(int limit, int addr);
MOV AX,[ESP+4] ; limit 存放的是段上限 limit的低16位写进AX寄存器,即AX=0xffff
MOV [ESP+6],AX ; 将ESP寄存器中的第3 4位赋值为第1 2位数据 ESP:ffffffff 00007200
LGDT [ESP+6] ; 将0x00270000ffff装入GDTR寄存器
RET
// 该程序是以INT 0x20~0x2f接收中断信号IRQ0~15而设定的
void init_pic(void){
io_out8(PIC0_IMR, 0xff);/*IMR为8位寄存器,对应8路IRQ信号,禁止主PIC的全部中断*/
io_out8(PIC1_IMR, 0xff);/*禁止从PIC的全部中断*/
io_out8(PIC0_ICW1, 0x11);/* 边沿触发模式(edge trigger mode) */
io_out8(PIC0_ICW2, 0x20);/* IRQ0-7由INT20-27接收 */
io_out8(PIC0_ICW3, 1<<2);/* PIC1由IRQ2连接 */
io_out8(PIC0_ICW4, 0x01);/* 无缓冲区模式 */
io_out8(PIC1_ICW1, 0x11);/* 边沿触发模式(edge trigger mode) */
io_out8(PIC1_ICW2, 0x28);/* IRQ8-15由INT28-2f接收 */
io_out8(PIC1_ICW3, 2);/* PIC1由IRQ2连接 */
io_out8(PIC1_ICW4, 0x01);/* 无缓冲区模式 */
io_out8(PIC0_IMR, 0xfb); /* 11111011 对应IRQ2=0 主PIC0以外全部禁止中断 */
io_out8(PIC1_IMR, 0xff); /* 11111111 禁止从PIC所有中断 */
return;
}
由于从PIC通过第2号IRQ与主PIC相连,所以IRQ2应该置为0。ICW(initial control word):初始化控制数据。ICW有4个,分别编号为1~4,共有4个 字 节的数据。ICW1和ICW4与PIC主板配线方式、中断信号的电气特性等有关。ICW3是有关主 —从连接的设定,对主PIC而言,第几号IRQ与从PIC相连,是用8位来设定的,所以就设 定成00000100;对从PIC来说,该从PIC与主PIC的第几号相连,用3位来设定。
_asm_inthandler21:
PUSH ES
PUSH DS
PUSHAD ;相当于使所有32位寄存器入栈,PUSH EAX->PUSH EDI
MOV EAX,ESP
PUSH EAX ;相当于ADD ESP,-4 MOV [SS:ESP],EAX
MOV AX,SS
MOV DS,AX
MOV ES,AX
CALL _inthandler21
POP EAX ;相当于MOV EAX,[SS:ESP] ADD ESP,4
POPAD ;与PUSHAD指令相反,使所有32位寄存器出栈
POP DS
POP ES
IRETD
中断处理程序待我二刷《深入理解Linux内核》后再来添加自己的理解。