[自制操作系统] 图形界面&VBE工具&MMIO显存&图形库/字库

来源:互联网 发布:大数据时代的财务核算 编辑:程序博客网 时间:2024/06/06 14:09

这里写图片描述

本文记录了在JOS(或在任意OS)上实现图形界面的方法与一些图形库的实现。
本文中支持的新特性:

  • 支持基本图形显示
  • 支持中英文显示(中英文点阵字库)

相关:VBE VESA MMIO 点阵字库
Github : https://github.com/He11oLiu/MOS

About VESA

Video Electronics Standards Association(视频电子标准协会,简称“VESA”)是制定计算机和小型工作站视频设备标准的国际组织,1989年由NEC及其他8家显卡制造商赞助成立。创立VESA的原始目的是要制定分辨率为800x600的SVGA视频显示标准。其后,VESA公告一系列的个人电脑视频周边功能的相关标准。

VBE 功能调用

参考博客CSDN博客

VBE功能调用

  • AH必须等于4FH,表明是VBE标准
  • AL等于VBE功能号,0<= AL <= 0BH
  • BL等于子功能号,也可以没有子功能
  • 调用INT 10H
  • 返回值在AX中
    • AL=4FH:支持该功能
    • AL!=4FH:不支持该功能
    • AH=00H:调用成功
    • AH=01H:调用失败
    • AH=02H:当前硬件配置不支持该功能
    • AH=03H:当前的显示模式不支持该功能

具体功能

此部分参考VESA编程——GUI离我们并不遥远,原作者博客已关闭。

功能0x00:返回控制器信息

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

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

// Vbe Info Blocktypedef 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        = 0x4F01      返回VBE模式信息    CX        =             模式号      ES:DI     =             指向VBE特定模式信息块的指针  输出:      AX        =             VBE返回值  

这个函数返回一个ModeInfoBlock结构体,其中重要的部分如下:

  • 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返回状态

JOS实现

  • Qemu需要添加-vga std

    QEMUOPTS = -drive file=$(OBJDIR)/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::$(GDBPORT) -vga std
  • boot中实模式中获取VBE,设置VBE

    sticall    getvideomode  call    setvideomode  cli

    先获取VBE模式,填充di,然后切换模式,设置VBE模式。

    根据上面查的VESA资料,调用函数,实现这两个功能。

    getvideomode:        mov $0x4f01, %ax  # get mode               mov $0x105,  %cx   # mode 0x105      mov $0x8000, %di  # mode info block address      int $0x10         # VBE int        retsetvideomode:        movw $0x4f02, %ax   # set mode       movw $0x105,  %bx       movw $0x8000, %di        int $0x10           # VBE int      movl 40(%di), %eax  # get memory address        movl %eax, info_vram      movw 18(%di), %ax   # get x resolution        movw %ax, info_scrnx      movw 20(%di), %ax   # get y resolution        movw %ax, info_scrny      ret

    这里设计了一个结构体来存放从boot传来的东西。

    struct boot_info{short scrnx, scrny;char *vram;};
  • init的时候,设计一个获取boot_info的模块

    static void get_boot_info(void){struct boot_info *info = (struct boot_info *)(KADDR(0x0ff0));// Init Graph infograph.scrnx = info->scrnx;graph.scrny = info->scrny;graph.vram = info->vram;}

    这个地方我选择初始化memory layout之后,开启真正的页表的时候才获取信息。所以这里要用KADDR进行物理地址到KVA的转化。

  • 设计一个全局用于保存图像相关信息的结构体

    struct graph_info{  short scrnx,scrny;  char *vram;};extern struct graph_info graph;
  • 利用MMIO映射一片显存

    void graph_init(){  int i;  // Init Graph MMIO  graph.vram =      (char *)mmio_map_region((physaddr_t)graph.vram,                              graph.scrnx * graph.scrny);  cprintf("====Graph mode on====\n");  cprintf("   scrnx = %d\n",graph.scrnx);  cprintf("   scrny = %d\n",graph.scrny);  cprintf("MMIO VRAM = %#x\n",graph.vram);  cprintf("=====================\n");  // Draw Screen  for (i = 0; i < graph.scrnx * graph.scrny; i++)      *(graph.vram + i) = 0x34;}

补充图像库

上面基本已经实现了图像显示基本平台。现在补充一些常用的图像库。

#define PIXEL(x, y) *(graph.vram + x + (y * graph.scrnx))int draw_screen(uint8_t color){    int i;    for (i = 0; i < graph.scrnx * graph.scrny; i++)        *(graph.vram + i) = color;    return 0;}int draw_pixel(short x, short y, uint8_t color){    if ((x >= graph.scrnx) || (y >= graph.scrny))        return -1;    *(graph.vram + x + (y * graph.scrnx)) = color;    return 0;}int draw_rect(short x, short y, short l, short w, uint8_t color){    int i, j;    w = (y + w) > graph.scrny ? graph.scrny : (y + w);    l = (x + l) > graph.scrnx ? graph.scrnx : (x + l);    for (j = y; j < w; j++)        for (i = x; i < l; i++)            *(graph.vram + i + j * graph.scrnx) = color;    return 0;}

字库实现

这部分也是老生常谈了,板子上各种系统都实现过点阵字库。

int draw_ascii(short x, short y, char *str, uint8_t color){    char *font;    int i, j, k = 0;    for (k = 0; str[k] != 0; k++)    {        font = (char *)(ascii_8_16 + (str[k] - 0x20) * 16);        for (i = 0; i < 16; i++)            for (j = 0; j < 8; j++)                if ((font[i] << j) & 0x80)                    PIXEL((x + j), (y + i)) = color;        x += 8;    }    return k;}int draw_cn(short x, short y, char *str, uint8_t color){    uint16_t font;    int i, j, k;    int offset;    for (k = 0; str[k] != 0; k += 2)    {        offset = ((char)(str[k] - 0xa0 - 1) * 94 +                  ((char)(str[k + 1] - 0xa0) - 1)) *                 32;        for (i = 0; i < 16; i++)        {            font = cn_lib[offset + i * 2] << 8 |                   cn_lib[offset + i * 2 + 1];            for (j = 0; j < 16; j++)                if ((font << j) & 0x8000)                    PIXEL((x + j), (y + i)) = color;        }        x += 16;    }    return 0;}

直接把之前单片机的点阵字库拿过来,不过单片机当时开发环境是win,找的字库寻址模式是GB2312的。这里把这个文件的编码改为GB2312来正确编码中文即可。实现效果见文章头。

frambuffer

实际上,直接对显存写是很不负责任的行为。很早之前在写java的界面的时候,就接触了双缓冲技术,其实与显示有关的思想都是差不多的,我们应该提供一个framebuffer。当完成一个frame后,再将这个frame update到显存中。

uint8_t *framebuffer;void init_framebuffer(){    if((framebuffer = (uint8_t *) kmalloc((size_t)(graph.scrnx*graph.scrny)))== NULL)        panic("Not enough memory for framebuffer!");}void update_screen(){    memcpy(graph.vram,framebuffer,graph.scrnx*graph.scrny);}

经过实现kmallockfree,已经可以分配这个缓冲区,并直接向缓冲区写入,最后再进行update

#define PIXEL(x, y) *(framebuffer + x + (y * graph.scrnx))int draw_xx(){    xxx;    update_screen();}

总结

至此,基本的GUI底层接口已基本实现,后面的就是各种数据结构的设计,窗口树设计之类。这里暂不打算继续深究,转而研究其余内核的东西。