做一个支持图形界面的操作系统上.docx
- 文档编号:30170078
- 上传时间:2023-08-05
- 格式:DOCX
- 页数:17
- 大小:45.09KB
做一个支持图形界面的操作系统上.docx
《做一个支持图形界面的操作系统上.docx》由会员分享,可在线阅读,更多相关《做一个支持图形界面的操作系统上.docx(17页珍藏版)》请在冰豆网上搜索。
做一个支持图形界面的操作系统上
做一个支持图形界面的操作系统(上)
副标题:
作者:
谢煜波文章来源:
本站原创点击数:
713更新时间:
2004-9-18
(转载及引用请注明明原作者及出处)
(pdf:
(源代码:
做一个支持图形界面的操作系统(上)
Version0.01
(对应pyos版本号:
2004_09_18_14_00)
哈尔滨工业大学谢煜波
(email:
xieyubo@网址:
)
(QQ:
13916830哈工大紫丁香BBSID:
iamxiaohan)
前言
图形界面(GUI)几乎被现在所有的主流操作系统及应用程序使用,这是因为它提供了极好的人机交互接口,微软大名鼎鼎的Windows就是一个非常成功而明显的例子,据说微软的理念有一条就是“让电脑变得越来越傻瓜,任何人都可以操作它”,很显然,要实现这个梦想,GUI界面是必须的。
如果有操作系统的支持,编写具有图形界面的程序是一件相对来说比较容易的事情,因为操作系统为你管理显卡,为你提供了各种各样诸如画点、画线、画矩形、填充等各种图形函数,你只需要将你所希望显示的东西,通过直接调用这样一些函数生成就行了,操作系统会为你完成余下的一切。
然而,如果你打算自己从头编写一个操作系统,而没办法使用已有的操作系统所提供的便利功能的时候,又应当怎样完成这样一个图形界面呢?
本文打算继续以pyos系统为例,简单描述一下怎样让你自己的操作系统支持图形界面。
如果你想更好的理解本篇的内容,你需要对操作系统的引导过程有些许了解,这可以参考一下本文的参考文献1,另外,你还需要对汇编语言有所了解,这可以参考一下本文的参考文献2。
本文的实验代码由汇编及C语言完成,如果你对C语言不太了解,可以参考一下本文的参考文献3。
标准的GUI界面应当包括图形化界面的显示及对用户输入的支持,而现在在GUI界面下用的最广泛的用户输入设备就是鼠标了。
因此本文打算以两部份分别进行描述。
上篇描述图形界面的显示,下篇描述对鼠标的支持。
由于知识及水平所限,对于其中不当及错误之处或者您有任何建议,非常欢迎您与我联系。
我会在哈工大纯C论坛上()上对本文进行跟踪反馈,本文所描述的资料及源代码也可以在上面找到。
一、显卡接口标准VESA简介
所谓标准,其实就是一种协议,比如显卡接口标准,就是显卡与主机之间进行通信的协议,通过这个协议,主机就能操作及控制显卡。
比如主机要让显卡在(x,y)画一个点,需要对显卡进行什么操作,这在协议上都有明确的说明与规定,因此,如果我们要操作一个显卡,只需要按照协议上的说明与规定进行就行了,这里的协议就如同一个说明书一样。
随着显卡的发展,先后出现了很多种协议,比如EGA协议,CGA协议,VGA协议等,而现在用得最广泛的是由国际视频电子标准协会(VideoElectronicsStandardsAssociation)制定的称为VESA的协议,现在最新的协议版本是3.0,不过由于目前并不是所有的显卡均支持此项协议,特别是众多的虚拟机都不支持,因此本文将以2.0版本作为描述的基础,由于各版本是向下兼容的,因此基于2.0的程序完全可以不经修改的应用在3.0版本上。
VESA标准包括了很多的子标准,其中对于操作系统编写最有用的就是VBE标准(VesaBiosExtension),在实际的系统编写中,我们按照此标准,通过调用BIOS的0x10号中断,而对显卡进行操作,在调用此号中断的时候,ax寄存器中存放的就是你想使用的显卡的功能。
比如VESA2.0标准规定:
0x4F00号功能可以返回显卡所支持的VESA标准的信息,0x4F01号功能可以返回所指定的显示模式的信息,诸如行列像素是多少,每像素的字节数是多少之类。
调用这些功能是非常方便的,比如,通过阅读VESA标准,我们知道0x4F02号功能可以用来设置显示模式,调用此功能时,bx中存放的是欲设置的显示模式的编号,因此,如果我们想将显卡的显示模式设为0x111模式,那么我们应当编写如下的代码:
movax,0x4F02;;设置中断功能号,表示使用0x4F02号功能
movbx,0x111;;设置显示模式号,表示使用0x111显示模式
int0x10;;调用BIOS的0x10号中断,设置显卡功能
执行了上面的代码之后,显卡就被设置成了0x111号显示模式,那么这个模式有些什么显示特性呢?
请看下面的表格:
(表1VESA标准定义的显示模式(部份))
模式号
分辨率
色彩
0x100
640*400
256
0x101
640*480
256
0x102
800*600
16
0x103
800*600
256
0x104
1024*768
16
0x105
1024*768
256
0x106
1280*1024
16
0x107
1280*1024
256
0x10D
300*200
1:
5:
5:
5
0x10E
320*200
5:
6:
5
0x10F
320*200
8:
8:
8
0x110
640*480
1:
5:
5:
5
0x111
640*480
5:
6:
5
0x112
640*480
8:
8:
8
0x113
800*600
1:
5:
5:
5
0x114
800*600
5:
6:
5
0x115
800*600
8:
8:
8
0x116
1024*768
1:
5:
5:
5
0x117
1024*768
5:
6:
5
0x118
1024*768
8:
8:
8
0x119
1280*1024
1:
5:
5:
5
0x11A
1280*1024
5:
6:
5
0x11B
1280*1024
8:
8:
8
上面的表格列出了VESA标准所定义的部份显示模式,其中色彩有两种表示形式。
一种是所谓的“调色板”模式,一种是所谓的“真彩”模式,“调色板”模式主要是为了兼容以前的老式显卡,那种显卡上的显存数量一般说来都非常的少,因此,显卡上一次最多只能存储256种(8位)或更少的16种色彩(4位),比如本表前面部份所示。
显卡上把这些色彩组织成为一个表,这个表就称之为一个调色板,然后每点的色彩信息其实就是一个下标,用于从调色板中检索出真正的色彩。
比如一个点的色彩是2,则表示使用调色板中的第三个色彩(因为从0开始编号,所以2则对应调色板中第三项)。
很显然,不是所有的色彩都能被记录在小小的调色板中,因此一般说来,对于一种给定色彩,我们需要用调色板中与它最相近的色彩进行显示。
现在的显卡一般都有很大的显存,因此它可以完整的存放一个点的色彩信息。
我们知道,任何一种色彩都可用不同强度(亮度)的红(R)、绿(G)、兰(B)三种色彩合成,因此,我们要记录或给出一个点的色彩信息,只需要给出红、绿、兰这三种色彩各自的强度(亮度)就行了,因此,就出现了多种不同的编码方法,比如“5:
6:
5”模式,就表示用最高的5位表示红色的强度,用中间的6位,表示绿色的强度,用最后的5位表示兰色的强度,这样表示一个色彩,总共需要16位,即2B,而“8:
8:
8”则表示,最高的8位表示红色的强度,随后的8位,表示绿色的强度,最后的8位表示兰色的强度,一个色彩用24位,即3字节进行表示。
依次类推。
由于这些色彩都被真实的记录下来了,因此这种模式又被称为“真彩”模式,如表1的后面部份所示。
由上面描述可知,我们可以根据我们的需要,选择适当的显示模式,然后调用VBE标准中所定义的中断设置显示卡。
VBE标准描述了大量的功能,这里不打算将它全部描述,只介绍下面行文所需要的功能,如果你想了解整个VBE标准所描述的功能,请参考本文的参考文献4。
下面我们再描述一个下面需要用到的0x4F01功能。
这个功能可以把显卡所支持的显示模式的对应信息返回到用户所指定的地址中。
它可以如下调用:
movax,0x4F01;;表示使用0x4F01功能,以获得显示模式信息
movcx,0x111;;表示欲获得0x111显示模式的信息
moves,0x9000
movdi,0x0001;;上面两句表示把信息放在es:
di
;;(此处为0x9000:
0x0001)处,这是一块内存的
;;起始地址,而此块内存至少256B大小
返回的信息是一个256字节的,很庞大的结构,这里我们只介绍我们下面行文感兴趣的部份。
在返回的结构体中偏移量为40的地方,即es:
di+40处,用4字节存放了一个线性地址,这就是这个显卡在此模式下显存的线性地址,因此,如果我们直接向这个地址写入数据,那么这数据就会被直接写到显存上,这样就可以显示出我们所需要显示的信息了。
这就是所谓的“直接写屏”。
返回的结构体中还包括了该显卡在此模式下每行行像素,列像素等其它众多信息,如果你需要详细了解,请参看本文的参考文献4。
二、用Pyos进行实验
2.1引导代码分析
令人非常高兴的是,这次实验我们不需要了解太多的基础知识,而就可以很快的进入实验了,下面我们就来看看我们这次的实验。
在实验前,我们需要先设定一个我们需要实验的显示模式,这里我们暂且定为640*480,采用的色彩模式为5:
6:
5模式,即0x111模式。
我们先来看看我们这次实验的最终结果:
现在,让我们先来看看我们的引导代码:
main:
;;主程序
;;下面设置段寄存器
movax,BOOT_SEG
movds,ax
movax,TEMP_DATA_SEG
movss,ax
movsp,0xffff
mov[BOOT_DRIVER],dl;;得到启动的驱动器号
callopen_a_20;;打开a20地址线
callsave_boot_driver;;保存驱动器号
callshow_message;;显示启动信息
callread_setup;;读入setup程序
jmpdwordSETUP_SEG:
SETUP_OFFSET;;跳转到setup处执行
这是pyos引导程序(boot.asm)的主函数代码,它现在已经很简单了,最开始,你需要设定一下段寄存器,比如初始化一下数据段寄存器(DS),堆栈段寄存器(SS),及堆栈指针(SP)等,然后,通过read_setup这个子程序,读入setup程序(steup.asm),将其读到SETUP_SEG:
SETUP_OFFSET处(SETUP_SEG及SETUP_OFFSET都是在boot.asm最开头定义的常量),关于各子程序的详细代码,请参看本实验的源代码,其中均有很详尽的注释。
下面,让我们看看setup(setup.asm)程序:
main:
;;初始化寄存器,因为Bios中断及call会用到堆栈或ss寄存器
;;在CPU启动或复位时是由BIOS初始化的,而现在进行了段转移,需要我们重新设置
movax,SETUP_SEG
movds,ax
movax,TEMP_DATA_SEG
movss,ax
movsp,0xffff
;;显示启动信息
callshow_message
;;取得启动驱动器号
callget_boot_driver
;;设置显示模式
callset_vesa_model
;;读入kernel程序
callread_kernel
;;读入字库
callread_font_lib
;;读入hit的图片
callread_hit_pbmp
;;下面开始为进入保护模式而进行初始化工作
lgdt[gdt_descriptor];;载入gdt的描述符
;;下面设置进入32位保护模式运行
cli;;关中断
moveax,cr0
oreax,1
movcr0,eax
jmpdword0x8:
KERNEL_ENTRY
上面这段程序也是非常简单的,首先,重新初始化一下段寄存器,然后设置图形显示模式,之后,读入kernel程序(这是真正的操作系统的内核代码),然后再读入字库(这点在下面会有较为详尽的描述),随后再读入一张hit的图片(hit是“哈尔滨工业大学”的英文缩写,图片采用的是一种bmp格式,这在下面也会有详细描述),之后进行转换到保护模式下的工作,最后切换到保护模式下的kernel程序处,即真正的操作系统内核代码中执行。
有关这部分切换到保护模式下的工作,本文的参考文献1中有非常详细的描述,如果你对此部份有疑惑,可以参看一下。
下面我们来看看这次的核心子程序set_vesa_model的代码,其余代码请参看本实验的源程序。
set_vesa_model:
;;设置显卡模式
pushes
pushfs
;;设置显卡模式
movax,0x4f02
movbx,0x4111;;640*480(5:
6:
5)
int0x10
;;取得该模式下显存线性地址
movbx,SETUP_SEG
moves,bx
movdi,VESA
;;调用0x4F01功能号,获得信息
movax,0x4f01
movcx,0x111
int0x10
;;存入线性地址
moveax,[es:
VESA+40]
movbx,TEMP_DATA_SEG
movfs,bx
mov[fs:
1],eax
popes
popfs
ret
程序非常简单,下面简单的解释一下,进入程序后,我们最先就设置了显卡的显示模式:
;;设置显卡模式
movax,0x4f02
movbx,0x4111;;640*480(5:
6:
5)
int0x10
上面这几行代码在前面描述VBE标准时就介绍过了,这里我们设置的是640*480(5:
6:
5),即对应的0x111模式(见表1),不过,你会发现这里送入bx的是0x4111而不是0x111,这是为什么呢?
这主要是因为,我们要使用线性地址模式,也就是说通过直接访问物理内存空存来访问所有的显存空间,因此,这时的送入bx的就应当是0x4000|模式号,对于此处就是:
0x4000|0x111=0x4111(将模式号与0x4000进行与操作,这也是VBE标所规定的)。
随后,我们用0x4F01功能取得此模式下显卡显存的线性地址:
;;取得该模式下显存线性地址
movbx,SETUP_SEG
moves,bx
movbx,TEMP_DATA_SEG
movfs,bx
movdi,VESA
;;调用0x4F01功能号,获得信息
movax,0x4f01
movcx,0x111
int0x10
上面由于es=TEMP_DATA_SEG,di=VESA,所以读取出来的信息存放于TEMP_DATA_SEG:
VESA处,随后,从信息中取出显卡在此模式下显存的线性地址:
;;存入线性地址
moveax,[es:
VESA+40]
movbx,TEMP_DATA_SEG
movfs,bx
mov[fs:
1],eax
popes
popfs
ret
上面代码很简单,由于显卡在此模式下显存的线性地址是存在于返回的信息块中偏移量为40之处,所以,用“moveax,[es:
VESA+40]”从中读取到eax中,然后,再把其放入[fs:
1]处,由于fs等于TEMP_DATA_SEG,所以,是存放在了TEMP_DATA_SEG:
1处。
上面程序中的SETUP_SEG,VESA,TEMP_DATA_SEG都是在程序最初所定义的常量,详情请参看源代码。
2.2pyos内存结构
由于本实验的pyos还没有文件系统及内存管理与分配程序,因此,在本实验中,pyos采用的是磁盘及内存的绝对定位,即所有数据在内存中的位置都是固定的,下面就让我们来看看pyos的内存结构。
0x0000:
0x7c00:
此处存放的是boot.asm的代码,由系统加电时BIOS自动读入
0x1000:
0x0000:
此处存放的是setup.asm的代码,由boot.asm读入
0x2000:
0x0000:
此处存放的是内核代码(主要是kernle.c),由setup.asm读入
0x6000:
0x0000:
此处存放的是hit的图片数据,由setup.asm读入
0x7000:
0x0000:
此处存放的是英文点阵字库数据,由setup.asm读入
0x8000:
0x0000:
此处存放的是中文点阵字库数据,由setup.asm读入
0x9000:
0x0000:
此处存放的是启动驱动器号,由boot.asm存入
0x9000:
0x0001:
此处存放的是显卡显存的线性地址
2.3pyos图形驱动
在程序由setup.asm最后通过“jmpdword0x8:
KERNEL_ENTRY”跳入内核代码之后,操作系统开始正式运作,下面就让我们来看看这个内核代码:
#include"system.h"
#include"vesa.h"
voidkernel_main()
{
//系统初始化
system_init();
//清屏
unsignedshortcolor=vesa_compond_rgb(255,255,255);
//画矩形
vesa_draw_rect(0,0,639,479,color,1);
…………(其余画图代码略)
for(;;);
}
上面的代码非常简单,它首先进行了一下系统初始化,然后进行清屏,pyos清屏采用的是最蜗牛的一种办法,它把整个屏幕当做一个矩形(x:
0~639,y:
0~479),然后用一种色彩去画这个矩形中的每一个点。
vesa_compond_rgb()这个函数用于把用户输入的R(红),G(绿),兰(B)值合成为一个16位长的整数(因为采用的是5:
6:
5的模式,因此总共一个点的色彩用16位整数表示),然后,它调用了vesa_draw_rect()函数来画这个矩形,我们就来看看这个函数:
//画矩形函数
voidvesa_draw_rect(unsignedintx1,unsignedinty1,unsignedintx2,unsignedy2,unsignedshortcolor,intdose_fill_it)
{
vesa_draw_x_line(y1,x1,x2,color);
vesa_draw_x_line(y2,x1,x2,color);
vesa_draw_y_line(x1,y1,y2,color);
vesa_draw_y_line(x2,y1,y2,color);
if(dose_fill_it){
vesa_fill_rect(x1,y1,x2,y2,color);
}
}
非常简单,它首先画矩形的边框,然后,根据用户的输入参数决定是否调用vesa_fill_rect()函数对这个矩形进行填充。
下面我们就来看看这个填充函数:
//矩形填充函数
voidvesa_fill_rect(unsignedintx1,unsignedinty1,unsignedintx2,unsignedinty2,unsignedshortcolor)
{
for(intx=x1;x for(inty=y1;y vesa_draw_point(x,y,color); } } } 晕! 太简单了,原来就是不停的调用vesa_draw_point()函数,用同一个色彩画出矩形中的所有点,于是乎,我们还得溯本求源,来看看vesa_draw_point()这个函数: //画点函数 voidvesa_draw_point(unsignedintx,unsignedinty,unsignedshortcolor) { staticconstunsignedintx_max=639;//每行像素数 staticconstunsignedinty_max=479;//每列像素数 //防止越界 if(x>x_max){ x=x_max; } if(y>y_max){ y=y_max; } //取得显存线性地址 unsignedshort*video=(unsignedshort*)(*((unsignedint*)0x90001)); //计算点的偏移量 unsignedintoffset=y*(x_max+1)+x; *(video+offset)=color; } 这个函数是整个PyosVESA显卡驱动的根基,所有画线、画矩形、填充矩形都是由它完成的,现在我们就来分析一下,注意下面的一行代码: //取得显存线性地址 unsignedshort*video=(unsignedshort*)(*((unsignedint*)0x90001)); 首先,在2.2节中我们知道了0x9000: 0001处存放了显卡显存的线性地址(这个线性地址是32位,即4B),因此,把0x9000: 0001转换为线性地址就是0x90001,然后,我们在C语言中把它转强行转换成一个指向unsignedint型的指针,然后通过*操作符,取出了这个指针,即地址为0x90001处所存的数据,也就是取得了存放于此处的显存的线性地址,然后把它强性转换为一个unsignedshort型的指针,这是因为,每个点都是用2B字节,也就是都是用一个unsignedshort类型的数据表示的,这个线性地址也是(0,0)点的地址。 随后,我们通过列坐标: x,行坐标: y,算得了(x,y)点的偏移量,最后直接把色彩信息赋值给这个(x,y)点所在的内存地址,那么(x,y)点处就显现出了我们
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 一个 支持 图形界面 操作系统