本文将介绍在本人JOS
中实现的简单图形界面应用程序接口,应用程序启动器,以及一些利用了图形界面的示例应用程序。
Github : https://github.com/He11oLiu/MOS
演示视频:优酷视频
本文主要涉及以下部分:
- 内核/用户
RW/RW
调色板framebuffer
共享区域 8bit
颜色深度BMP
格式图片读取与绘制
- 读
BMP
头总是出现问题?不合理的数据? - 为啥读出来的图片颜色怪怪的!!
- 为啥是倒的,还有的运气不好出错了
- 如果是想绘制多个图片在一页上,调色板问题??
- 如果读到一个32位色的图片咋办?
- 读
- 图形化界面数据结构,框架以及接口设计
- 利用图形化接口实现应用程序:
- 日历程序(实时时钟刷新)
- 系统信息获取
- 终端
CGA
模拟器
PART1
framebuffer
在图形库
中,已经将图形模式打开,将显存映射到内存中的一段空间。并进行了简单的测试。
实际上,直接对显存写是很不负责任的行为。很早之前在写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);
}
经过实现kmalloc
与kfree
,已经可以分配这个缓冲区,并直接向缓冲区写入,最后再进行update
#define PIXEL(x, y) *(framebuffer + x + (y * graph.scrnx))
int draw_xx()
{
xxx;
update_screen();
}
canvas (这种思路已经废弃)
从一个单一的应用程序角度来看,应分配一个单独的画布,然后选择在一个位置显示。
typedef struct canvas
{
uint16_t width;
uint16_t height;
uint8_t *data;
} canvas_t;
设计的模式是,与文件系统服务器类似,提供一个图形系统服务器,用于接收从其他的程序发来的请求。请求包括显示的位置,以及canvas
。该服务器将canvas
写入frambuffer并update。其他程序与图形服务器通过IPC
进行通讯。
剩余的事情就可以交给用户空间了。包括对canvas
的处理,更新显示,添加各种元件。之前写的字库也可以不用写在内核了…
首先实现绘制canvas
。
int draw_canvas(uint16_t x, uint16_t y, canvas_t *canvas)
{
int i, j;
int width = (x + canvas->width) > graph.scrnx ? graph.scrnx : (x + canvas->width);
int height = (y + canvas->height) > graph.scrny ? graph.scrny : (y + canvas->height);
cprintf("width %d height %d\n",width,height);
for (j = y; j < height; j++)
for (i = x; i < width; i++)
PIXEL(i, j) = *(canvas->data + (i - x) + (j - y) * canvas->width);
update_screen();
return 0;
}
然后在lib
中新建canvas
的相关方法:
int canvas_init(uint16_t width, uint16_t height, canvas_t *canvas);
int canvas_draw_bg(uint8_t color, canvas_t *canvas);
int canvas_draw_ascii(uint16_t x, uint16_t y, char *str, uint8_t color, canvas_t *canvas);
int canvas_draw_cn(uint16_t x, uint16_t y, char *str, uint8_t color, canvas_t *canvas);
int canvas_draw_rect(uint16_t x, uint16_t y, uint16_t l, uint16_t w, uint8_t color, canvas_t *canvas);
其中只需要将原来的PIXAL
宏换为
#define CANVAS_PIXEL(canvas, x, y) *(canvas->data + x + (y * canvas->width))
测试canvas
canvas_t canvas_test;
canvas_init(300, 200, &canvas_test);
uint8_t testcanvas[60000];
canvas_test.data = (uint8_t *)testcanvas;
canvas_draw_bg(0x22,&canvas_test);
canvas_draw_ascii((uint16_t)2, (uint16_t)2, test_ascii, (uint8_t)0xff, &canvas_test);
canvas_draw_cn((uint16_t)2, (uint16_t)50, test_cn, (uint8_t)0xff, &canvas_test);
draw_canvas(500, 500, &canvas_test);
图像处理的两种设计与遇到的问题
第一种设计与之前描述的一致:
提供一个图像服务器,接收请求,从用户进程传来需要画的画布和显示位置,并在位置上进行绘画。这种方式遇到的问题是画布过大,一页可能装不下。需要
mmap
(还没写)第二种设计是一个
launcher
和application
两个单独的单页面切换制度。这样就是
launcher
提供应用启动界面,application
提供应用界面。
重新回顾了一下内存分配,内核与用户态数据共享的方法后,决定先就第二个思路实现一个简单的用户内核均可见可读写的Framebuffer
。
实现RW/RW
的Framebuffer
分析如何做才能内核用户均可读写
首先分析一个之前做过的pages
,是如何做到用户态可以读,内核态可以写的。
在
mem_init
的时候在在内核空间中分配指定的空间给pages
pages = boot_alloc(sizeof(struct PageInfo) * npages); memset(pages, 0, sizeof(struct PageInfo) * npages);
利用
boot_map_region
将其映射到内核页表中的UPAGES
的位置。boot_map_region(kern_pgdir, UPAGES, PTSIZE, PADDR(pages), PTE_U | PTE_P);
这样内核中依然可以通过
pages
访问页表,而用户程序在entry
的时候通过给pages
变量赋予存储位置.globl pages .set pages, UPAGES
也可以通过
pages
变量进行访问。
预留内存用于framebuffer
再思考如果需要这么一个framebuffer
,我们需要放到哪里。仿造上面的UVPD
,UPAGES
,等,决定就放在接近ULIM
的位置。一个PTSIZE
也远超我们需要的空间,为以后扩展也留下了余量。
/*
* ULIM, MMIOBASE --> +------------------------------+ 0xef800000
* | Cur. Page Table (User R-) | R-/R- PTSIZE
* UVPT ----> +------------------------------+ 0xef400000
* | RO PAGES | R-/R- PTSIZE
* FRAMEBUF ----> +------------------------------+ 0xef000000
* | FRAME BUFFER | RW/RW PTSIZE
* UPAGES ----> +------------------------------+ 0xeec00000
* | RO ENVS | R-/R- PTSIZE
* UTOP,UENVS ------> +-----------------------------