Chinaunix首页 | 论坛 | 博客
  • 博客访问: 522559
  • 博文数量: 252
  • 博客积分: 6057
  • 博客等级: 准将
  • 技术积分: 1635
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-21 10:17
文章分类

全部博文(252)

文章存档

2013年(1)

2012年(1)

2011年(32)

2010年(212)

2009年(6)

分类:

2010-09-07 19:11:35


161 views

下载:Vesa Demo

下载:Vesa Source

自从学习操作系统开发以来,所接触到的操作系统开发资料都是关于文本模式的。然而黑色的命令行始终没有色彩斑斓的图形界面吸引眼球,所以查阅了很多资料后终于找到了真正的图形界面实现方法——VESA。

先上图:

这是一个800X600分辨率32位色(实际上是一个24位色,最高字节没用)的demo,当然你也可以把分辨率设置成1024X768或者1280X1024。

什么是VBE?

VBE的全称是VESA BIOS Extension。

什么是VESA?

VESA的全称是Video Electronics Standards Association即视频电子标准协会,是由代表来自世界各地的、享有投票权利的超过165家成员公司的董事会领导的非盈利国际组织。

VESA致力于开发、制订和促进个人计算机(PC)、工作站以及消费类电子产品的视频接口标准,为显示及显示接口业界提供及时、开放的标准,保证其通用性并鼓励创新和市场发展。

VBE视频模式

先来看看VBE的模式号及其对应的分辨率与颜色:

VBE最高可以支持1280X1024的分辨率,24位真彩色,完全可以满足我们创建图形化操作系统的需求。

下面是一张视频标准图:

只可惜VBE的标准比较老,不支持宽屏显示器。

用到的VBE函数

要实现图形模式就要用到vbe函数,vbe函数标准定义了一系列VGA ROM BIOS服务扩展。这些vbe函数可以在实模式下通过10h中断调用或者直接通过高性能的32位程序和操作系统调用。

我们的demo是通过实模式下的int 10h中断来调用VBE函数的。我们将使用以下三个函数:

功能00h – 返回控制器信息

输入:
AX = 4F00h 返回VBE控制器信息
ES:DI = 指向存放VbeInfoBlock结构体的缓冲区指针

输出:
AX = VBE返回状态

备注: 其他的寄存器被保留。

这个函数返回一个VbeInfoBlock结构体,该结构体定义如下:

// Vbe Info Block
typedef struct {
unsigned char vbe_signature;
unsigned short vbe_version;
unsigned long oem_string_ptr;
unsigned char capabilities;
unsigned long video_mode_ptr;
unsigned short total_memory;
unsigned short oem_software_rev;
unsigned long oem_vendor_name_ptr;
unsigned long oem_product_name_ptr;
unsigned long oem_product_rev_ptr;
unsigned char reserved[222];
unsigned char oem_data[256];
} VbeInfoBlock;

我解释一下上面的结构体中比较重要的几个变量。

vbe_signature是VBE标识,应该填充的是”VESA”

vbe_version是VBE版本,如果是0300h则表示3.0版本

oem_string_ptr是指向oem字符串的指针,该指针是一个16位的selector:offset形式的指针,在实模式下可以直接使用。

video_mode_ptr是指向视频模式列表的指针,与oem_string_ptr类型一样

total_memory是64kb内存块的个数

oem_vendor_name_ptr是指向厂商名字符串的指针

oem_product_name_ptr是指向产品名字符串的指针

功能01 – 返回VBE模式信息

输入:
AX = 4F01h 返回VBE模式信息
CX = 模式号
ES:DI = 指向ModeInfoBlock结构体的指针

输出:
AX = VBE返回状态

备注: 所有其他的寄存器保留。

这个函数返回一个ModeInfoBlock结构体,该结构体定义如下:

// Vbe Mode Info Block
typedef struct {
// Mandatory information for all VBE revisions
unsigned short mode_attributes;
unsigned char wina_attributes;
unsigned char winb_attributes;
unsigned short win_granularity;
unsigned short win_size;
unsigned short wina_segment;
unsigned short winb_segment;
unsigned long win_func_ptr;
unsigned short bytes_per_scan_line;

// Mandatory information for VBE 1.2 and above
unsigned short xresolution;
unsigned short yresolution;
unsigned char xchar_size;
unsigned char ychar_size;
unsigned char number_of_planes;
unsigned char bits_per_pixel;
unsigned char number_of_banks;
unsigned char memory_model;
unsigned char bank_size;
unsigned char number_of_image_pages;
unsigned char reserved1;

// Direct Color fields (required for direct/6 and YUV/7 memory models)
unsigned char red_mask_size;
unsigned char red_field_position;
unsigned char green_mask_size;
unsigned char green_field_position;
unsigned char blue_mask_size;
unsigned char blue_field_position;
unsigned char rsvd_mask_size;
unsigned char rsvd_field_positon;
unsigned char direct_color_mode_info;

// Mandatory information for VBE 2.0 and above
unsigned long phys_base_ptr;
unsigned long reserved2;
unsigned short reserved3;

// Mandatory information for VBE 3.0 and above
unsigned short lin_bytes_per_scan_line;
unsigned char bnk_number_of_image_pages;
unsigned char lin_number_of_image_pages;
unsigned char lin_red_mask_size;
unsigned char lin_red_field_position;
unsigned char lin_green_mask_size;
unsigned char lin_green_field_position;
unsigned char lin_blue_mask_size;
unsigned char lin_blue_field_position;
unsigned char lin_rsvd_mask_size;
unsigned char lin_rsvd_field_position;
unsigned long max_pixel_color;
unsigned char reserved4[189];

} VbeModeInfoBlock;

解释一下几个我们要用到的比较重要的字段。

首先是mode_attributes字段,这个字段描述了图形模式的一些重要属性。其中最重要的是第4位和第7位。第4位为1表示图形模式 (Graphics mode),为0表示文本模式(Text mode)。第7位为1表示线性帧缓冲模式(Linear frame buffer mode),为0表示非线性帧缓冲模式。我们主要要检查这两个位。

xresolution,表示该视频模式的X分辨率。

yresolution,表示该视频模式的Y分辨率。

bits_per_pixel,表示该视频模式每个像素所占的位数。

phys_base_ptr,这是一个非常重要的字段,它给出了平坦内存帧缓冲区的物理地址,你可以理解为显存的首地址。如果每个像素占32位的 话,屏幕左上角第一个点所占的缓冲区就是phys_base_ptr所指的第一个4个字节。按照先行后列的顺序,每个像素点所占缓冲区依次紧密排列。我们 要想在屏幕上画出像素点,就得操作以phys_base_ptr为起始的物理内存空间。

功能02 – 设置VBE模式

输入:
AX = 4F02h 设置VBE模式
BX = 需要设置的模式
D0 - D8 = 模式号
D9 - D10 = 保留(必须为0)
D11 = 0 使用当前缺省刷新率
= 1 使用用户指定的CRTC值为刷新率
D12 - D13 = 为VBE/AF保留(必须为0)
D14 = 0 使用窗口帧缓冲区模式
= 1 使用线性/平坦帧缓冲区模式
D15 = 0 清除显示内存
= 1 不清除显示内存
ES:DI = 指向CRTCInfoBlock结构体的指针

输出:
AX = VBE返回状态

备注: 所有其他的寄存器保留

这个函数就是用来设置我们的视频模式,通过用功能01查找我们所需要模式,然后用功能02即可设置我们所需要的模式。

具体实现方法

因为我们要操作显存,对于一个1280X1024 32bit的视频模式来说,需要用到5M的内存,而我们知道在实模式下我们只能用段:位移的方式访问1M的地址空间,而我们的显存是在这1M的地址空间之外,那么我们如何才能在实模式下访问32位的地址空间呢?

这里有一种方法,那就是进入,是实模式的一个变体,在这种模式下我们的代码还是16位的实模式方式,但是我们可以使用32位的代码段来访问32位地址空间,这样我们就可以向显存中写数据来画图了。

下面是我们代码中main函数的片段:

	;-------------------------------;
; Install our GDT ;
;-------------------------------;

call InstallGDT ; install our GDT

;-------------------------------;
; Enable A20 ;
;-------------------------------;

call EnableA20_KKbrd_Out
sti

;-------------------------------;
; Init Vesa ;
;-------------------------------;
call GetVbeInfo
call SetVideoMode
call EnterUnrealMode

;-------------------------------;
; Init video ;
;-------------------------------;

call VideoInit

在上面的代码中,我们要进入首先要调用InstallGDT来安装GDT,接着调用EnableA20_KKbrd_Out来打开A20地址总线,然后调用EnterUnrealMode来进入。

下面是EnterUnrealMode的代码:

;=========================================
; EnterUnrealMode
; enter unreal mode
;=========================================
EnterUnrealMode:
cli
push ds

mov eax, cr0
or al, 1
mov cr0, eax

mov bx, 0x08
mov ds, bx

and al, 0xFE
mov cr0, eax

pop ds
sti
ret

在上面的代码中,我们先暂时进入保护模式,接着将ds设为0×08,0×08是所安装的GDT的代码段的选择子。接着我们又返回到实模式,这样,处理器会使用所缓存的描述符,就像在保护模式中那样,我们就可以在实模式下访问4GB的内存空间了。

在main函数中,我们调用GetVbeInfo来获取VBE控制器的信息,下面是这个函数的代码:

;=============================================
; GetVbeInfo
; get veb controller information to vbe_info_block structure
;=============================================
GetVbeInfo:
pushad
mov ax, 4F00h ; Get VBE information
mov di, 0x7E00 ; Set param
int 10h
;call CheckVbeReturn ; Check return value
;call CheckVesaVersion ; Check vesa version
;call ShowOEMString
call FindVideoMode ; Find Video Mode

.end:
popad
ret

在这个函数中,我们会调用FindVideoMode函数来查找我们所需要的模式,并将其存放在VideoMode变量中。

下面是FindVideoMode函数的代码:

;=============================================
; FindVideoMode
;=============================================
FindVideoMode:
pushad
mov si, [0x7E00 + vbe_info_block.video_mode_ptr]

.loop:
push ds
mov ax, [0x7E00 + vbe_info_block.video_mode_ptr + 2]
mov ds, ax
lodsw
pop ds
mov [VideoMode], ax
cmp ax, 0xFFFF
je .notfound

.getmode:
call GetVideoMode
;mov ax, word [0x8000 + mode_info_block.xresolution]
;mov bx, word [0x8000 + mode_info_block.yresolution]
;mov cl, byte [0x8000 + mode_info_block.bits_per_pixel]
cmp word [0x8000 + mode_info_block.xresolution], SCREEN_WIDTH
jne .cnt
cmp word [0x8000 + mode_info_block.yresolution], SCREEN_HEIGHT
jne .cnt
cmp byte [0x8000 + mode_info_block.bits_per_pixel], COLOR_DEPTH
JNE .cnt
mov ax, [0x8000 + mode_info_block.mode_attributes]
and ax, 90h ;LFB
cmp ax, 90h
je .found

.cnt:
jmp .loop

.notfound:
;mov si, VideoModeNotFound
;call Puts16
cli
hlt

.found:
;mov si, VideoModeFound
;call Puts16
popad
ret

上面的函数是一个循环,根据VbeInfoBlock中的模式列表来查找每一个模式,调用GetVideoMode函数来获取 ModeInfoBlock结构体,并将ModeInfoBlock中的字段与我们所要设置的视频模式比较,如果找到我们所需要的模式,就将其模式号储存 起来。

下面是GetVideoMode函数的的代码:

;=============================================
; GetVideoMode
;=============================================
GetVideoMode:
pushad
mov cx, [VideoMode]
mov ax, 0x4F01
mov di, 0x8000
int 10h
;call CheckVbeReturn
popad
ret

main函数中接着调用了SetVideoMode函数,这个函数会设置我们刚刚查找到的视频模式,这样我们的视频模式就设置完了。进入到后我们就可以操作我们的内存并画出图像了。

下面是SetVideoMode函数的代码:

;=============================================
; SetVideoMode
;=============================================
SetVideoMode:
pushad
mov cx, [VideoMode] ; video mode number
mov ax, 0x4F01
mov di, 0x8000 ; buffer to get mode information
int 10h ; get mode information
;call CheckVbeReturn
mov ax, [0x8000 + mode_info_block.mode_attributes]
and ax, 0000000000000001b ; test hardware support
cmp ax, 01h
je .testLFB
;mov si, NotSupportedInHardware
;call Puts16
cli
hlt

.testLFB:
mov ax, [0x8000 + mode_info_block.mode_attributes]
and ax, 90h ; test linear frame buffer support
cmp ax, 90h
je .ok
;mov si, NotSupportedLinearFrameBuffer
;call Puts16
cli
hlt

.ok:
mov ax, 4F02h
mov bx, [VideoMode]
add bx, 4000h ; LFB mode
int 10h ; set video mode
;call CheckVbeReturn

mov bx, [VideoMode]

;mov word [boot_info + multiboot_info.vbe_mode], bx
;mov dword [boot_info + multiboot_info.vbe_mode_info], 0x8000

popad
ret

在Demo中我设置的是800X600 32位模式,每个像素占4字节,最高字节没有什么用处,第三字节是红色,第二字节是绿色,最低字节是蓝色,每个颜色值在0-255之间,总共有2的24次方种颜色。

main函数调用了VideoInit函数,这个函数的作用是初始化图形驱动,图形驱动提供了两个API来操作图形,一个是SetPixel16,它的作用是画一个点,第二个是ClearScreen16,它的作用是用指定的颜色清除屏幕。

SetPixel16代码如下:

;================================================
; SetPixel16
; - in ax, zero based x position
; - in bx, zero based y position
; - in ecx, color
;================================================
SetPixel16:
pushad
.check:
cmp ax, [VideoXResolution]
jnb .return
cmp bx, [VideoYResolution]
jnb .return

push ax
xor eax, eax
pop ax

push bx
xor ebx, ebx
pop bx

xor edi, edi
mov di, [VideoXResolution]
push eax
mov eax, edi
mul ebx ; y x VideoXResolution
mov edi, eax ; -> edi
pop eax

add edi, eax ; y x VideoXresolution + x -> edi

mov eax, [VideoMemoryPtr]
mov dword [ds:eax + edi*4], ecx ; write color

.return:
popad
ret

ClearScreen16的代码如下:

;================================================
; ClearScreen16
; - in eax, color
;================================================
ClearScreen16:
pushad

mov edi, [VideoMemoryPtr]

mov edx, 0
mov cx, [VideoXResolution]
.L1:
push cx
mov cx, [VideoYResolution]
.L2:
mov dword [ds:edi + edx*4], eax
inc edx

loop .L2

pop cx
loop .L1
popad
ret

小结

关于VBE编程的大部分内容都讲完了,要实现GUI首先要能够打开视频模式,接着你可以写一个驱动,这样你就可以调用驱动的API来画图,然后你可 以实现自己的图形库,实现更加高级的图形函数,然后你就可以实现操作系统的API,这些API调用自己的图形库来画窗口,按钮等,这样就可以实现你自己的 GUI了。

本文中涉及到很多的内容,如GDT、实模式、保护模式、A20地址总线等,如果读者不知道什么是GDT、A20地址总线以及如何设置GDT和打开A20地址总线等,请参考【翻译】操作系统开发系列中关于GDT和A20地址总线的部分。

上面提供了Demo和Demo的源文件,将VMWare的软盘设置为Demo中的镜像文件,并将VMWare设置为软盘启动,启动后即可看到效果。

由于512字节引导扇区的限制,Demo中的很多代码都被我注释掉了,以减少代码量,Demo中的代码编译后几乎刚好占用了所有的512字节,增加一两条指令就有可能溢出这512字节,导致无法编译。

参考文档

VBE文档

« Prev:




阅读(873) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~