美文网首页
VESA编程

VESA编程

作者: 思想黑客 | 来源:发表于2019-02-21 07:58 被阅读1次

    VESA编程

    自从学习操作系统开发以来,所接触到的操作系统开发资料都是关于文本模式的。然而黑色的命令行始终没有色彩斑斓的图形界面吸引眼球,所以查阅了很多资料后终于找到了真正的图形界面实现方法——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 – 返回控制器信息

    1输入:

    2AX      = 4F00h     返回VBE控制器信息

    3ES:DI   =           指向存放VbeInfoBlock结构体的缓冲区指针

    5输出:

    6AX      =           VBE返回状态

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

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

    01// Vbe Info Block

    02typedefstruct{

    03    unsigned charvbe_signature;

    04    unsigned shortvbe_version;

    05    unsigned longoem_string_ptr;

    06    unsigned charcapabilities;

    07    unsigned longvideo_mode_ptr;

    08    unsigned shorttotal_memory;

    09    unsigned shortoem_software_rev;

    10    unsigned longoem_vendor_name_ptr;

    11    unsigned longoem_product_name_ptr;

    12    unsigned longoem_product_rev_ptr;

    13    unsigned charreserved[222];

    14    unsigned charoem_data[256];

    15} 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模式信息

    1输入:

    2AX      =   4F01h       返回VBE模式信息

    3CX      =               模式号

    4ES:DI   =               指向ModeInfoBlock结构体的指针

    6输出:

    7AX      =               VBE返回状态

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

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

    01// Vbe Mode Info Block

    02typedefstruct{

    03    // Mandatory information for all VBE revisions

    04    unsigned shortmode_attributes;

    05    unsigned charwina_attributes;

    06    unsigned charwinb_attributes;

    07    unsigned shortwin_granularity;

    08    unsigned shortwin_size;

    09    unsigned shortwina_segment;

    10    unsigned shortwinb_segment;

    11    unsigned longwin_func_ptr;

    12    unsigned shortbytes_per_scan_line;

    13 

    14    // Mandatory information for VBE 1.2 and above

    15    unsigned shortxresolution;

    16    unsigned shortyresolution;

    17    unsigned charxchar_size;

    18    unsigned charychar_size;

    19    unsigned charnumber_of_planes;

    20    unsigned charbits_per_pixel;

    21    unsigned charnumber_of_banks;

    22    unsigned charmemory_model;

    23    unsigned charbank_size;

    24    unsigned charnumber_of_image_pages;

    25    unsigned charreserved1;

    26 

    27    // Direct Color fields (required for direct/6 and YUV/7 memory models)

    28    unsigned charred_mask_size;

    29    unsigned charred_field_position;

    30    unsigned chargreen_mask_size;

    31    unsigned chargreen_field_position;

    32    unsigned charblue_mask_size;

    33    unsigned charblue_field_position;

    34    unsigned charrsvd_mask_size;

    35    unsigned charrsvd_field_positon;

    36    unsigned chardirect_color_mode_info;

    37 

    38    // Mandatory information for VBE 2.0 and above

    39    unsigned longphys_base_ptr;

    40    unsigned longreserved2;

    41    unsigned shortreserved3;

    42 

    43    // Mandatory information for VBE 3.0 and above

    44    unsigned shortlin_bytes_per_scan_line;

    45    unsigned charbnk_number_of_image_pages;

    46    unsigned charlin_number_of_image_pages;

    47    unsigned charlin_red_mask_size;

    48    unsigned charlin_red_field_position;

    49    unsigned charlin_green_mask_size;

    50    unsigned charlin_green_field_position;

    51    unsigned charlin_blue_mask_size;

    52    unsigned charlin_blue_field_position;

    53    unsigned charlin_rsvd_mask_size;

    54    unsigned charlin_rsvd_field_position;

    55    unsigned longmax_pixel_color;

    56    unsigned charreserved4[189];

    57 

    58} 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模式

    01输入:

    02AX      = 4F02h     设置VBE模式

    03BX      =           需要设置的模式

    04        D0 - D8     = 模式号

    05        D9 - D10    = 保留(必须为0)

    06        D11         = 0 使用当前缺省刷新率

    07                    = 1 使用用户指定的CRTC值为刷新率

    08        D12 - D13   = 为VBE/AF保留(必须为0)

    09        D14         = 0 使用窗口帧缓冲区模式

    10                    = 1 使用线性/平坦帧缓冲区模式

    11        D15         = 0 清除显示内存

    12                    = 1 不清除显示内存

    13ES:DI   =           指向CRTCInfoBlock结构体的指针

    14 

    15输出:

    16AX      =           VBE返回状态

    17 

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

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

    具体实现方法

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

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

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

    01;-------------------------------;

    02;   Install our GDT             ;

    03;-------------------------------;

    04 

    05call    InstallGDT                                      ; install our GDT

    06 

    07;-------------------------------;

    08;   Enable A20                  ;

    09;-------------------------------;

    10 

    11call    EnableA20_KKbrd_Out

    12sti

    13 

    14;-------------------------------;

    15;   Init Vesa                   ;

    16;-------------------------------;

    17call    GetVbeInfo

    18call    SetVideoMode

    19call    EnterUnrealMode

    20 

    21;-------------------------------;

    22;   Init video                  ;

    23;-------------------------------;

    24 

    25call    VideoInit

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

    下面是EnterUnrealMode的代码:

    01;=========================================

    02;   EnterUnrealMode

    03;       enter unreal mode

    04;=========================================

    05EnterUnrealMode:

    06    cli

    07    push    ds

    08 

    09    mov     eax, cr0

    10    or      al, 1

    11    mov     cr0, eax

    12 

    13    mov     bx, 0x08

    14    mov     ds, bx

    15 

    16    and     al, 0xFE

    17    mov     cr0, eax

    18 

    19    pop     ds

    20    sti

    21    ret

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

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

    01;=============================================

    02;   GetVbeInfo

    03;       get veb controller information to vbe_info_block structure

    04;=============================================

    05GetVbeInfo:

    06    pushad

    07    mov     ax, 4F00h                       ; Get VBE information

    08    mov     di, 0x7E00                      ; Set param

    09    int10h

    10    ;call   CheckVbeReturn                  ; Check returnvalue

    11    ;call   CheckVesaVersion                ; Check vesa version

    12    ;call   ShowOEMString

    13    call    FindVideoMode                   ; Find Video Mode

    14 

    15.end:

    16    popad

    17    ret

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

    下面是FindVideoMode函数的代码:

    01;=============================================

    02;   FindVideoMode

    03;=============================================

    04FindVideoMode:

    05    pushad

    06    mov     si, [0x7E00 + vbe_info_block.video_mode_ptr]

    07 

    08.loop:

    09    push    ds

    10    mov     ax, [0x7E00 + vbe_info_block.video_mode_ptr + 2]

    11    mov     ds, ax

    12    lodsw

    13    pop     ds

    14    mov     [VideoMode], ax

    15    cmp     ax, 0xFFFF

    16    je  .notfound

    17 

    18.getmode:

    19    call    GetVideoMode

    20    ;mov        ax, word [0x8000 + mode_info_block.xresolution]

    21    ;mov        bx, word [0x8000 + mode_info_block.yresolution]

    22    ;mov        cl, byte [0x8000 + mode_info_block.bits_per_pixel]

    23    cmp     word [0x8000 + mode_info_block.xresolution], SCREEN_WIDTH

    24    jne     .cnt

    25    cmp     word [0x8000 + mode_info_block.yresolution], SCREEN_HEIGHT

    26    jne     .cnt

    27    cmp     byte [0x8000 + mode_info_block.bits_per_pixel], COLOR_DEPTH

    28    JNE     .cnt

    29    mov     ax, [0x8000 + mode_info_block.mode_attributes]

    30    and     ax, 90h                                                         ;LFB

    31    cmp     ax, 90h

    32    je      .found

    33 

    34.cnt:

    35    jmp     .loop

    36 

    37.notfound:

    38    ;mov        si, VideoModeNotFound

    39    ;call   Puts16

    40    cli

    41    hlt

    42 

    43.found:

    44    ;mov    si, VideoModeFound

    45    ;call   Puts16

    46    popad

    47    ret

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

    下面是GetVideoMode函数的的代码:

    01;=============================================

    02;   GetVideoMode

    03;=============================================

    04GetVideoMode:

    05    pushad

    06    mov     cx, [VideoMode]

    07    mov     ax, 0x4F01

    08    mov     di, 0x8000

    09    int10h

    10    ;call   CheckVbeReturn

    11    popad

    12    ret

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

    下面是SetVideoMode函数的代码:

    01;=============================================

    02;   SetVideoMode

    03;=============================================

    04SetVideoMode:

    05    pushad

    06    mov     cx, [VideoMode]                 ; video mode number

    07    mov     ax, 0x4F01

    08    mov     di, 0x8000                      ; buffer to get mode information

    09    int10h                             ; get mode information

    10    ;call   CheckVbeReturn

    11    mov     ax, [0x8000 + mode_info_block.mode_attributes]

    12    and     ax, 0000000000000001b           ; test hardware support

    13    cmp     ax, 01h

    14    je      .testLFB

    15    ;mov        si, NotSupportedInHardware

    16    ;call   Puts16

    17    cli

    18    hlt

    19 

    20.testLFB:

    21    mov     ax, [0x8000 + mode_info_block.mode_attributes]

    22    and     ax, 90h                         ; test linear frame buffer support

    23    cmp     ax, 90h

    24    je      .ok

    25    ;mov        si, NotSupportedLinearFrameBuffer

    26    ;call   Puts16

    27    cli

    28    hlt

    29 

    30.ok:

    31    mov     ax, 4F02h

    32    mov     bx, [VideoMode]

    33    add     bx, 4000h                       ; LFB mode

    34    int10h                             ; set video mode

    35    ;call    CheckVbeReturn

    36 

    37    mov     bx, [VideoMode]

    38 

    39    ;mov        word [boot_info + multiboot_info.vbe_mode], bx

    40    ;mov        dword [boot_info + multiboot_info.vbe_mode_info], 0x8000

    41 

    42    popad

    43    ret

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

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

    SetPixel16代码如下:

    01;================================================

    02;   SetPixel16

    03;       - in ax, zero based x position

    04;       - in bx, zero based y position

    05;       - in ecx, color

    06;================================================

    07SetPixel16:

    08    pushad

    09.check:

    10    cmp     ax, [VideoXResolution]

    11    jnb     .return

    12    cmp     bx, [VideoYResolution]

    13    jnb     .return

    14 

    15    push    ax

    16    xor     eax, eax

    17    pop     ax

    18 

    19    push    bx

    20    xor     ebx, ebx

    21    pop     bx

    22 

    23    xor     edi, edi

    24    mov     di, [VideoXResolution]

    25    push    eax

    26    mov     eax, edi

    27    mul     ebx                                     ; y x VideoXResolution

    28    mov     edi, eax                                ; -> edi

    29    pop     eax

    30 

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

    32 

    33    mov     eax, [VideoMemoryPtr]

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

    35 

    36.return:

    37    popad

    38    ret

    ClearScreen16的代码如下:

    01;================================================

    02;   ClearScreen16

    03;       - in eax, color

    04;================================================

    05ClearScreen16:

    06    pushad

    07 

    08    mov     edi, [VideoMemoryPtr]

    09 

    10    mov     edx, 0

    11    mov     cx, [VideoXResolution]

    12.L1:

    13    push    cx

    14    mov     cx, [VideoYResolution]

    15.L2:

    16    mov     dword [ds:edi + edx*4], eax

    17    inc     edx

    18 

    19    loop    .L2

    20 

    21    pop     cx

    22    loop    .L1

    23    popad

    24    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字节,导致无法编译。

    相关文章

      网友评论

          本文标题:VESA编程

          本文链接:https://www.haomeiwen.com/subject/ieulyqtx.html