系统背景描述_计算机自制操作系统(十五):屏幕显示字库

48f394050f2bbcc7347d9f9171bb18c0.png

从本章开始,基本上我们才可以说得上是真正的进入操作系统内核编写了。操作系统内核主题太大了,大名鼎鼎的Windows和Linux是两大阵营,那我们往哪方面发展呢?本专栏一开始就说过,本次任务是面向广大计算机学者,从起步到今天,全部开发工具和环境都是在微软公司的Windows上面完成的,没有借助任何Linux平台和工具,这也是此专栏和大量晦涩操作系统制作教程的不同之处,因此我们制作的操作系统会继续沿着模仿Windows的路线前进。

一、制作"Windows"桌面

在上一章中,已经总结了我的操作系统文件组织方式,因此从此将不再介绍各个程序的作用。

1.汇编程序:Kernela.asm

主要任务:进入保护模式前设置好图形模式:320*200分辨率,并设置好背景色和调色板等基础信息。背景色设置成Windows典型的浅暗蓝。调色板主要是常用的绘图颜色,本次我绘制桌面主要用了4种颜色,因此我定了4个调色板索引。如果你不知道调色板的RGB组合值,那么就上网搜索一下:

ecca18f90d1b1e12de113198b8699478.png

......

4591e7dcbe5c7895abe61a30ecbda9f6.png

其实,调色板的颜色设置功能最好写在C语言中比较好,方便后面应用。由于我之前一直把这部分写在汇编语言之中的,就懒得修改了,后面如果有大量的应用需要再考虑移值。

colorfuncport equ 3c8h   ;设置调色板功能端口
colorsetport equ 3c9h    ;设置调色板颜色端口
dptseg     equ    7e0h     ;DPT区段地址


jmp   start      

gdt_size dw 32-1     ;GDT 表的大小 ;(总字节数减一)
gdt_base dd 0x00007e00 ;GDT的物理地址


start:
mov  ax, cs     ;从MBR跳转到此内核之后,CS=c20h ,IP=0。也即程序从偏移地址为0的地方开始放置
mov  ds, ax     ;那么就无需指定ORG,只需要把DS,ES和CS指向同一段即可。

call  setmode320
call  backgroud
call  colorset


mov     ax,dptseg
mov     es,ax          ;es用于gpt区寻址    gpt存放起始地址:0x00007e00h
call    createdpt

jmp     protectmode


setmode320:
mov ah,0
mov al,13h         ;320*200  256色
int 10h
ret


backgroud:         ;背景色设置
mov dx,  colorfuncport
mov al,  0           ;建调色板索引0号
out dx,al

mov dx,  colorsetport   ;设置浅暗蓝背景
mov al,0               ;R分量
out dx,al
mov al,0x84/4          ;G分量
out dx,al
mov al,0x84/4          ;B分量
out dx,al
ret

colorset:             ;显示色设置
mov dx,  colorfuncport
mov al,  1              ;建调色板索引1号
out dx,al

mov dx,  colorsetport     
mov al,0xc6/4           ;R分量
out dx,al
mov al,0xc6/4          ;G分量
out dx,al
mov al,0xc6/4           ;B分量
out dx,al

mov dx,  colorfuncport
mov al,  2                 ;建调色板索引2号
out dx,al

mov dx,  colorsetport      
mov al,0xff/4           ;R分量
out dx,al
mov al,0xff/4           ;G分量
out dx,al
mov al,0xff/4          ;B分量
out dx,al

mov dx,  colorfuncport
mov al, 3           ;建调色板索引3号
out dx,al

mov dx,  colorsetport     
mov al,0x84/4           ;R分量
out dx,al
mov al,0x84/4           ;G分量
out dx,al
mov al,0x84/4          ;B分量
out dx,al

mov dx,  colorfuncport
mov al, 4              ;建调色板索引4号
out dx,al

mov dx,  colorsetport     ;设置黑色
mov al,0           ;R分量
out dx,al
mov al,0           ;G分量
out dx,al
mov al,0          ;B分量
out dx,al

ret


;创建DPT子程序
createdpt:

lgdt [gdt_size] ;将DPT的地址和大小写入gdtr生效     默认DS

;创建0#描述符,它是空描述符,这是处理器的要求
mov dword [es:0x00],0x00
mov dword [es:0x04],0x00

;创建#1描述符,保护模式下的代码段描述符
mov dword [es:0x08],0xc200ffff
mov dword [es:0x0c],0x00409800

;创建#2描述符,保护模式下的数据段描述符
mov dword [es:0x10],0x0000ffff  ;(把DS的基地址定义为0,为了和C语言程序桥接)
mov dword [es:0x14],0x00c09200  ; (标志位G=1,表示以4KB为单位)

;创建#3描述符,保护模式下的堆栈段描述符
mov dword [es:0x18],0x00007a00
mov dword [es:0x1c],0x00409600
ret

protectmode:
in al,0x92    ;打开A20地址线
or al,0000_0010B
out 0x92,al

cli ;保护模式下中断机制尚未建立,应禁止中断

mov eax,cr0  ;打开保护模式开关
or eax,1
mov cr0,eax

;进入保护模式... ...
jmp dword 0x0008:inprotectmode ;16位的描述符选择子:32位偏移


[bits 32]
inprotectmode:

mov ax,00000000000_10_000B ;加载数据段选择子(0x10)
mov ds,ax

mov ax,00000000000_11_000B ;加载堆栈段选择子
mov ss,ax                  ;7a00-7c00为此次设计的堆栈区
mov esp,0x7c00             ;7c00固定地址为栈底,

over : 
jmp over  +0x24

2.C语言程序:Kernelc.c

主要任务:定义了绘制矩形(直线可以看成是特殊的矩形)的函数---参数包括矩形的位置、大小和填充颜色。目前所有的图形绘制都可以通过调用这个函数来实现。

#define   Displayaddr       0xa0000  /*显示缓冲区内存地址*/ 
#define   xsize             320      /*分辨率*/ 
#define   ysize             200

void Main(void)
{

squareness(0, ysize - 28, xsize -  1, ysize - 28,1); /*任务栏*/
squareness(0, ysize - 27, xsize -  1, ysize - 27,2);
squareness(0, ysize - 26, xsize -  1, ysize -  1,1);

squareness(3, ysize - 24, 59,         ysize - 24,2);  /*"开始按钮"*/
squareness(2, ysize - 24,  2,         ysize -  4,2);
squareness(3, ysize -  4, 59,         ysize -  4,3);
squareness(59,ysize - 23, 59,         ysize -  5,3);
squareness(2, ysize -  3, 59,         ysize -  3,4);
squareness(60,ysize - 24, 60,         ysize -  3,4);

squareness( xsize - 47, ysize - 24, xsize -  4, ysize - 24, 3); /*"时间区域"*/
squareness( xsize - 47, ysize - 23, xsize - 47, ysize -  4, 3);
squareness( xsize - 47, ysize -  3, xsize -  4, ysize -  3, 2);
squareness( xsize -  3, ysize - 24, xsize -  3, ysize -  3, 2);

while (1)

{;}

}


void drawpoint(int x,int y,int color)
{
 *(char *)(Displayaddr+xsize*y+x) =color;
}


void squareness(int startx,int starty,int endx,int endy,int color)
{  
int x,y=0;
for (y=starty;y<=endy;y++)  
{
   for (x=startx;x<=endx;x++)
       {drawpoint(x,y,color);
        }
}
}

运行test.bat之后,我们绘制的桌面成功了:

b63cd7be44947ec463e5ac9493c80cc0.png

(二) 桌面揭迷

这个"Windows"桌面一下看起来有点立体和神秘,我们来分析它的产生过程:

1.先画任务栏与主窗口的区分线,用纯白色(R=G=B=0xff)即可。

7c09584d20416e379adc055149ccb39d.png

2.在区分线下面用颜色更暗的纯色画矩形(增加灰度),任务栏就突出效果了。

d50accfee2958151249352edc418c87b.png

3.任务栏上画“开始”按钮:为了显示按钮是凸出的效果,使用比任务栏更浅的灰度画线。

28dc089cf70edb1720f170e5820303d2.png

4.在“开始”按钮的下半部,用较深的灰度进行外框渲染,这样按钮的立体效果立现:

cb3a3280f1f74ececd48f9803c0edd60.png

5.任务栏上画“时间”区域:为了显示区域的内凹效果,使用比任务栏更暗的灰度画线。

31570f5333908b6bbd3efd9f942a2534.png

5.在“时间”区域的下半部,用较浅的灰度进行外框渲染(和按钮方法相反),这样内凹的立体效果立现:

9517e3f1fe1c64f5878d12f75109bd89.png

总结:没有一点美术和艺术功底,你都不要想能当程序员!

二、显示英文字符

在文本模式下,显示字符非常的容易,直接往内存送字符的ASCII即可。但是图形模式下,一切都变了,需要自己构造象素点阵来实现字符的显示。原理如下图:

22fd7e069cfe7f8e1fafd2936d312421.png

我们只要在屏幕上点亮这个8*16象素矩阵中为1的地方,就可以成功打印出字母"A",而这个8*16的字符点阵可以用C语言的数组来记录:

	char font_A[16] = {
		0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
		0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
	};

这样,我们在C程序里增加一个函数来显示字符,这个函数的参数包括:显示位置和颜色。

void putfont_A(int x, int y, char color)
{
        char font_A[16]={
		0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
		0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
	};
	int i;
	char *p, d /* data */;
	for (i = 0; i < 16; i++) {
		p = Displayaddr + (y + i) * xsize + x;
		d = font_A[i];
		if ((d & 0x80) != 0) { p[0] = color; }
		if ((d & 0x40) != 0) { p[1] = color; }
		if ((d & 0x20) != 0) { p[2] = color; }
		if ((d & 0x10) != 0) { p[3] = color; }
		if ((d & 0x08) != 0) { p[4] = color; }
		if ((d & 0x04) != 0) { p[5] = color; }
		if ((d & 0x02) != 0) { p[6] = color; }
		if ((d & 0x01) != 0) { p[7] = color; }
	}
}

程序运行结果:

0cff0329bc0c1fabdf487d06212a3afa.png

三、显示中文字符

中文字符和英语字符是一个原理,只不过中文字符编码需要更多的位数,我们可以采用16*16的象素矩阵来存放:

b579fabe821572f0afb22cf2ea1dd978.png
汉字编码矩阵

可以用两个8*16的点阵数组来存放汉字信息:

        char font_Jiang1[16] = {
		0x00, 0x10, 0x08, 0x04, 0x00, 0x20, 0x10, 0x08,
		0x04, 0x00, 0x00, 0x04, 0x08, 0x11, 0x20, 0x00
	};

        char font_Jiang2[16] = {
                0x00, 0x00, 0x00, 0x00, 0xf8, 0x20, 0x20, 0x20,
                0x20, 0x20, 0x20, 0x20, 0x20, 0xfc, 0x00, 0x00
	};

这样,我们就可以在C程序添加一个显示汉字的函数:

void putfont_Jiang(int x, int y, char color)
{

        char font_Jiang1[16] = {
		0x00, 0x10, 0x08, 0x04, 0x00, 0x20, 0x10, 0x08,
		0x04, 0x00, 0x00, 0x04, 0x08, 0x11, 0x20, 0x00
	};

        char font_Jiang2[16] = {
                0x00, 0x00, 0x00, 0x00, 0xf8, 0x20, 0x20, 0x20,
                0x20, 0x20, 0x20, 0x20, 0x20, 0xfc, 0x00, 0x00
	};

	int i;
	char *p,d1,d2; 
	for (i = 0; i <16; i++) {
		p = Displayaddr + (y + i) * xsize + x;
		d1 =  font_Jiang1[i];
                d2 =  font_Jiang2[i];

		if ((d1 & 0x80) != 0) { p[0] = color; }
		if ((d1 & 0x40) != 0) { p[1] = color; }
		if ((d1 & 0x20) != 0) { p[2] = color; }
		if ((d1 & 0x10) != 0) { p[3] = color; }
		if ((d1 & 0x08) != 0) { p[4] = color; }
		if ((d1 & 0x04) != 0) { p[5] = color; }
		if ((d1 & 0x02) != 0) { p[6] = color; }
		if ((d1 & 0x01) != 0) { p[7] = color; }
		if ((d2 & 0x80) != 0) { p[8] = color; }
		if ((d2 & 0x40) != 0) { p[9] = color; }
		if ((d2 & 0x20) != 0) { p[10] = color; }
		if ((d2 & 0x10) != 0) { p[11] = color; }
		if ((d2 & 0x08) != 0) { p[12] = color; }
		if ((d2 & 0x04) != 0) { p[13] = color; }
		if ((d2 & 0x02) != 0) { p[14] = color; }
		if ((d2 & 0x01) != 0) { p[15] = color; }
	}
}

将程序装载之后启动计算机,显示效果如下:

12c238208ee2c38482b32af5b8ae2263.png

补充一下:这个字符显示效果看上去有点“粗糙”,原因在于我们设置的分辨率320*200比较低,显示出来的字符就显得特别的大。为了优化显示效果,可以切换到640*480或1024*768更高的分辨率,具体方法详见本专栏“计算机自制操作系统(十一):终于突破1MB内存”。

四、组成字库

可以看到,一个16字节的二进制就可以表示一个英文字符,1个32字节的二进制就可以表示一个中文字符。那我们把所有的字符放置在一起做成目标文件obj就组成字库,再和操作系统程序进行链接,就可以使用它来显示所有字符了。

最终这些字库的数据组织,翻译到汇编语言的格式就是:

_fontpool:
DO font1    ;DO表示16字节
DO font2
DO font3
......

但是字符组成字库的时候也不能乱放,我们要用字符的ASCII码值作为索引,这样要访问某个字符的时候用:fontpool+ASCII*16即可。

如果用C语言程序访问字库的话就显得更直接:定义一个short型指针: short *font,用font来指向fontpool,要访问某个字符的时候用:font+ASCII即可。

最后,提供一下本次字库显示完整的C语言程序代码:

#define   Displayaddr       0xa0000  /*显示缓冲区内存地址*/ 
#define   xsize             320      /*分辨率*/ 
#define   ysize             200

void Main(void)
{

squareness(0, ysize - 28, xsize -  1, ysize - 28,1); /*任务栏*/
squareness(0, ysize - 27, xsize -  1, ysize - 27,2);
squareness(0, ysize - 26, xsize -  1, ysize -  1,1);

squareness(3, ysize - 24, 59,         ysize - 24,2);  /*"开始按钮"*/
squareness(2, ysize - 24,  2,         ysize -  4,2);
squareness(3, ysize -  4, 59,         ysize -  4,3);
squareness(59,ysize - 23, 59,         ysize -  5,3);
squareness(2, ysize -  3, 59,         ysize -  3,4);
squareness(60,ysize - 24, 60,         ysize -  3,4);

squareness( xsize - 47, ysize - 24, xsize -  4, ysize - 24, 3); /*"时间区域"*/
squareness( xsize - 47, ysize - 23, xsize - 47, ysize -  4, 3);
squareness( xsize - 47, ysize -  3, xsize -  4, ysize -  3, 2);
squareness( xsize -  3, ysize - 24, xsize -  3, ysize -  3, 2);


putfont_A(20,20,2);
putfont_Jiang(50,50,2);

while (1)

{;}

}


void drawpoint(int x,int y,int color)
{
 *(char *)(Displayaddr+xsize*y+x) =color;
}


void squareness(int startx,int starty,int endx,int endy,int color)
{  
int x,y=0;
for (y=starty;y<=endy;y++)  
{
   for (x=startx;x<=endx;x++)
       {drawpoint(x,y,color);
        }
}
}

void putfont_A(int x, int y, char color)
{
        char font_A[16]={
		0x00, 0x18, 0x18, 0x18, 0x18, 0x24, 0x24, 0x24,
		0x24, 0x7e, 0x42, 0x42, 0x42, 0xe7, 0x00, 0x00
	};
	int i;
	char *p, d /* data */;
	for (i = 0; i < 16; i++) {
		p = Displayaddr + (y + i) * xsize + x;
		d = font_A[i];
		if ((d & 0x80) != 0) { p[0] = color; }
		if ((d & 0x40) != 0) { p[1] = color; }
		if ((d & 0x20) != 0) { p[2] = color; }
		if ((d & 0x10) != 0) { p[3] = color; }
		if ((d & 0x08) != 0) { p[4] = color; }
		if ((d & 0x04) != 0) { p[5] = color; }
		if ((d & 0x02) != 0) { p[6] = color; }
		if ((d & 0x01) != 0) { p[7] = color; }
	}
}



void putfont_Jiang(int x, int y, char color)
{

        char font_Jiang1[16] = {
		0x00, 0x10, 0x08, 0x04, 0x00, 0x20, 0x10, 0x08,
		0x04, 0x00, 0x00, 0x04, 0x08, 0x11, 0x20, 0x00
	};

        char font_Jiang2[16] = {
                0x00, 0x00, 0x00, 0x00, 0xf8, 0x20, 0x20, 0x20,
                0x20, 0x20, 0x20, 0x20, 0x20, 0xfc, 0x00, 0x00
	};

	int i;
	char *p,d1,d2; 
	for (i = 0; i <16; i++) {
		p = Displayaddr + (y + i) * xsize + x;
		d1 =  font_Jiang1[i];
                d2 =  font_Jiang2[i];

		if ((d1 & 0x80) != 0) { p[0] = color; }
		if ((d1 & 0x40) != 0) { p[1] = color; }
		if ((d1 & 0x20) != 0) { p[2] = color; }
		if ((d1 & 0x10) != 0) { p[3] = color; }
		if ((d1 & 0x08) != 0) { p[4] = color; }
		if ((d1 & 0x04) != 0) { p[5] = color; }
		if ((d1 & 0x02) != 0) { p[6] = color; }
		if ((d1 & 0x01) != 0) { p[7] = color; }
		if ((d2 & 0x80) != 0) { p[8] = color; }
		if ((d2 & 0x40) != 0) { p[9] = color; }
		if ((d2 & 0x20) != 0) { p[10] = color; }
		if ((d2 & 0x10) != 0) { p[11] = color; }
		if ((d2 & 0x08) != 0) { p[12] = color; }
		if ((d2 & 0x04) != 0) { p[13] = color; }
		if ((d2 & 0x02) != 0) { p[14] = color; }
		if ((d2 & 0x01) != 0) { p[15] = color; }
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值