自制操作系统日志——第十天
今天的主要任务就是继续优化内存管理部分,然后进一步的做一下叠加处理,主要就了解图层的设置,整个程序利用图层的整体过程。
一、内存管理优化
由于之前的内存管理程序中,都是以1字节为单位进行管理的,这就有可能导致出现过多的不连续的小段的内存空间地址,也会重复多次的内存进行分配和释放,不利于我们系统的运行稳定性和利用率等等。
因此,下面我们将以0x1000(4kb)为单位进行内存的分配于与管理。如果分配的不足4kb则向上取整,直接默认为4kb。。对了,再此之前,我们先将内存管理的代码写入到memory.c当中,以便进行分割管理:
在memory.c中增加以下内容:
unsigned int memman_alloc_4k(struct MEMMAN *man, unsigned int size)
{
unsigned int a;
size = (size + 0xfff) & 0xfffff000;//向上取整,确保为是以4k为单位的,不够4k的按4k算
a = memman_alloc(man, size);
return a;
}
int memman_free_4k(struct MEMMAN *man, unsigned int addr, unsigned int size)
{
int i;
size = (size+0xfff) & 0xfffff000;
i = memman_free(man, addr, size);
return i;
}
size = (size + 0xfff) & 0xfffff000
⇒ 在二进制中若以0x1000为单位对0x12345678进行向上舍入,则就变成:0x12346000。 至于这里为什么使用size + 0xfff 这里我们用十进制说明以下:
假如我们有456元,以100为单位进行向上舍入,则按上述的理解我们应该进行(456+99)= 555 ==> 500 。 z因此这个公式的意思也就差不多是这样子。至于后面的&操作就是相对于将555中后两个55舍去的意思。。
二、叠加处理
所谓的叠加处理,我们主要思想就是利用图层技术进行的。我们按高度顺序画面从下往上显示处所有的图层。(最高层的画面一般都会显现出来,下面的图层是可能会被高图层盖住的,就像纸张的叠加一样)
我们可以把图层视为一张纸,而我们画面就是纸上我们描绘的东西。也就是说最上层的纸往往是可以看见的,越下面的图层越可能被覆盖住。
以下我们来按照这个图层的概念来详细解释以下代码的逻辑:
设定图层的结构体:
#define MAX_SHEETS 256 //能管理的最大数
struct SHEET
{
unsigned char *buf;//记录图层上所描绘图画的地址
int bxsize, bysize, vx0, vy0, col_inv, height, flags;
//图层整体大小用bxsize、bysize表示;vx0, vy0 图层在vram画面上位置的相对坐标
//col_inv 表颜色的透明度; height 表图层高度;flag记录图层各种设定信息
};
struct SHTCTL//图层管理
{
unsigned char *vram;//vram x y 代表VRAM的地址和画面大小
int xsize, ysize, top;//top最上面图层的高度
struct SHEET *sheets[MAX_SHEETS];//记忆地址变量领域,由于shets0之中的图层比较混乱,因此这里将图层能照高度升序进行写入
struct SHEET sheets0[MAX_SHEETS];//存放准备好的256个图层信息
};
sheet.c:
//初始化图层管理表的地址
struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize, int ysize)
{
struct SHTCTL *ctl;
int i;
//返回一个空闲的addr地址,用sizeof计算该变量的地址空间大小,然后进行分配对应的内存空间大小
ctl = (struct SHTCTL *) memman_alloc_4k(memman, sizeof (struct SHTCTL));
if(ctl == 0)
{
goto err;
}
ctl->vram = vram; //导入vram的地址和分辨率
ctl->xsize = xsize;
ctl->ysize = ysize;
ctl->top = -1;//此时,一个SHEET都没有,也就是说没有图层在最上面
for(i = 0; i < MAX_SHEETS; i++)
{
ctl->sheets0[i].flags = 0;//让所有的图层都标记未使用
}
err:
return ctl;
}
给管理表初始化,并分配一段空闲的内存空间,将vram显卡的数据导入其中。
sheet.c:
//取得新生成的未使用的图层
struct SHEET *sheet_alloc(struct SHTCTL *ctl)
{
struct SHEET *sht; //建立一个新的图层,以便进行后续的返回
int i;
for(i = 0; i < MAX_SHEETS; i++) //开始循环,直到找到可用的图层为止
{
if(ctl->sheets0[i].flags == 0)
{
sht = &ctl->sheets0[i];//将该可用图层的地址传入给sht
sht->flags = SHEET_USE; //标记为正在使用
sht->height = -1; //隐藏,先将高度设为-1,置于底层
return sht;
}
}
return 0; //所有的sheet都在使用当中
}
分配一个可用的图层,并先将图层数据写入sheets0中,sheets0仅仅是用于存储图层的数据,我们后续显示在屏幕当中的是利用按高度拍好顺序的sheets中的图层数据。
sheet.c:
//设定图层的缓冲区大小和透明色的函数
void sheet_setbuf(struct SHEET *sht, unsigned char *buf, int xsize, int ysize, int col_inv)
{//将图层和图层在画面中的大小以及
sht->buf = buf;//记录该图层所描绘图形的地址
sht->bxsize = xsize;//图层整体大小
sht->bysize = ysize;
sht->col_inv = col_inv;//给予透明色号
return;
}
这里主要是设定每一层的图层的整体大小,以及透明度的设定!!除此之外,还将个图层的缓冲区地址传入其中。
sheet.c:
//指定了vx0~vy1 的刷新范围
void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1)
{
int h, bx, by, vx, vy, bx0, by0, bx1, by1;
unsigned char *buf, c, *vram = ctl->vram;
struct SHEET *sht;//创建一个临时图层
for( h = 0; h <= ctl->top; h++)//从下往上,所有图层都描绘一遍
{
sht = ctl->sheets[h];//记录该层的图层信息
buf = sht->buf;//记录图层上描绘图画的地址
//使用vx0~vy1,对比bx0~by1进行倒推
bx0 = vx0 - sht->vx0;
by0 = vy0 - sht->vy0;
bx1 = vx1 - sht->vx0;
by1 = vy1 - sht->vy0;
if(bx0 < 0) {bx0 = 0;} //这里的bx0、1都是相对距离,即相对于sht->vx0,也就是刷新的部分=图层在画面上的的起始地址+相对于起始地址的距离
if(by0 < 0) {by0 = 0;}
if(bx1 > sht->bxsize) {bx1 = sht->bxsize;}
if(by1 > sht->bysize) {by1 = sht->bysize;}
for(by = by0; by < by1; by++)
{
vy =sht->vy0 + by;
for (bx = bx0; bx < bx1; bx++)
{
vx = sht->vx0 + bx;//vy0 vx0代表图层在VRAM上画面的相对地址,用这个来让图层进行移动
c = buf[by * sht->bxsize + bx];//记录原图层对应像素点上的颜色信息
if (c != sht->col_inv) //如果透明了就不往里写,例如:鼠标图层中是会有一部分不用往里面写入颜色的,即init_mouse中的bc值,如果读取的c值和图层中的col值一样就不写入,默认为前一个图层的颜色
{
vram[vy * ctl->xsize + vx] = c;//写入vram中
}
}
}
}
}
这里是指定图层在移动后,或者位置改变后进行刷新的部分。利用仅刷新指定范围内的数据,来减少程序的运行的时间(因为这样子可以不用再将所有的像素全部刷新一遍了)。
至于图中的bx by的那四个if判断的语句,是为了便于在两个图层有重叠部分时,仅仅只刷新上方图层的重叠部分的!!!
如图所示,如果需要刷新第i个图层时(此时绿色框的作为i-1的图层已经刷新完毕了),由于刷新的区间为vx0~vy1这部分之间,因此至于图中的蓝色部分是重叠的需要刷新的。
因此,我们首先使用bx0 = vx0 - sht->vx0 ; 由于我们前面所作的坐标是以向下为y轴正方向,以向右为x轴正方向的。因此,这里bx0 一定会为负数。由于我们的bx0主要是作为刷新时的相对于图层框起始地址的偏移地址所使用。因此,若我们设置为0,则说明我们不需要在加上相对位置了,因此直接在下面的计算中当 for (bx = bx0; bx < bx1; bx++) vx = sht->vx0 + bx;开始时,就可以直接从sht->vx0处开始,即重叠部分的左上角处开始。
同理,这里的bx1 = vx1 - sht->vx0 ,也就是说,我们的bx实际上就是从sht->vx0 处开始刷新直到vx1 - sht->vx0 处,也就是蓝色填充框的右下角处。这样子我们就可以直接指定范围进行更新了,极大的减少了对其他无关像素的刷新操作。
对于 if(bx1 > sht->bxsize) {bx1 = sht->bxsize;} 这两个的判断,主要用于其他重叠的情况,比如在右上角重叠时。 开始刷新的部分就应该为sht->vx0 + (vx0 - sht->vx0) 【这里是因为vx0 - sht->vx0 >0 不需要改变】,而 bx1则直接就为sht->bxize , 也就是设定好了右下角的部分。
进一步继续修改,设定高度,以及更新sheets
sheet.c:
//设定底板的高度
void sheet_updown(struct SHTCTL *ctl, struct SHEET *sht, int height)
{
int h, old = sht->height; //存储之前的高度信息
//如果指定的高度过高或者过低,则进行修正
if(height > ctl->top+1)
{
height = ctl->top+1;
}
if(height < -1)
{
height = -1;
}
sht->height = height; //设定高度
//进行sheets[]的重新排列,也就是按高度顺序写入sheets中
if(old > height)
{//比以前低
if(height >= 0) //把中间的往上提
{
for(h = old; h > height; h--)
{
ctl->sheets[h] = ctl->sheets[h-1];
ctl->sheets[h]->height = h;
}
ctl->sheets[height] = sht;
}else{// 隐藏
if(ctl->top > old)//把上面的降下来
{
for(h = old; h < ctl->top; h++)
{
ctl->sheets[h] = ctl->sheets[h+1];
ctl->sheets[h]->height = h;
}
}
ctl->top--; //由于中间图层减少一个,所以最上面的图层高度下降
}
sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize); //按新的图层信息重新绘制画面
}else if(old < height)//比以前高
{
if(old > 0)
{
//把中间的拉下去
for(h = old; h < height; h++)
{
ctl->sheets[h] = ctl->sheets[h+1];
ctl->sheets[h]->height = h;
}
ctl->sheets[height] = sht;
}else{//由隐藏状态转化为显示状态
//将已在上面的提上来
for(h = ctl->top; h >= height ; h--)
{
ctl->sheets[h+1] = ctl->sheets[h];
ctl->sheets[h+1]->height =h+1;
}
ctl->sheets[height] = sht;
ctl->top++;//由于已显示的图层增加一个,所以最上面的图层高度增加
}
sheet_refreshsub(ctl, sht->vx0, sht->vy0, sht->vx0 + sht->bxsize, sht->vy0 + sht->bysize);//按新图层重新绘制画面
}
return;
}
//从下往上,将透明以外的所有像素都复制到vram中
void sheet_refresh(struct SHTCTL *ctl, struct SHEET *sht, int bx0, int by0, int bx1, int by1)
{
if (sht->height >= 0) { /* 如果正在显示,则按新图层进行刷新 */
sheet_refreshsub(ctl, sht->vx0 + bx0, sht->vy0 + by0, sht->vx0 + bx1, sht->vy0 + by1);
}
return;
}
//进行上下左右移动
void sheet_slide(struct SHTCTL *ctl, struct SHEET *sht, int vx0, int vy0)
{
int old_vx0 = sht->vx0, old_vy0 = sht->vy0;
sht->vx0 = vx0;
sht->vy0 = vy0;
if(sht->height >= 0)//如果正在显示//按图层信息刷新画面
{
/*由于鼠标图层的地址已经改变了,因此以下两条语句:(因为sub这个是只刷新指定范围内的)
如果只执行第一句的话,那么新鼠标无法显示(执行完后,因为鼠标图层改变了,不再源像位置了,因此图层只会刷新原先地方存在的图层的)
如果只执行第二句的话,那么可能会显示不完整
*/
sheet_refreshsub(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 + sht->bysize);
sheet_refreshsub(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize);
}
return;
}
//释放已使用的图层
void sheet_free(struct SHTCTL *ctl, struct SHEET *sht)
{
if(sht->height >= 0)
{
sheet_updown(ctl, sht, -1);//处于显示状态,先设定隐藏
}
sht->flags = 0; //为使用标志
return;
}
然后,让我们来看看主函数的部分:
struct SHTCTL *shtctl;//用于管理的
struct SHEET *sht_back, *sht_mouse;//准备了两个图层
unsigned char *buf_back, buf_mouse[256];//缓冲区,用于在其中描绘需要的图形。binfo->vram = buf_back
init_gdtidt();//初始化gdt、idt表
init_pic(); //初始化pic控制器
io_sti();
fifo8_init(&keyfifo, 32, keybuf);
fifo8_init(&mousefifo, 128, mousebuf);
io_out8(PIC0_IMR, 0xf9); /* 开放PIC1和键盘中断(11111001),键盘是IRQ1 */
io_out8(PIC1_IMR, 0xef); /* 开放鼠标中断(11101111) ,鼠标是IRQ12*/
init_keyboard();
enable_mouse(&mdec);
memtotal = memtest(0x00400000, 0xbfffffff); //算出内存地址
memman_init(memman);
memman_free(memman, 0x00001000, 0x0009e000); /* 0x00001000 - 0x0009efff */
memman_free(memman, 0x00400000, memtotal - 0x00400000);
init_palette();
shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny);//传入内存管理表是因为要分配地址给图层
sht_back = sheet_alloc(shtctl);//分配给背景一个图层,背景图层位于最底层,sheets0[0]
sht_mouse = sheet_alloc(shtctl);//sheets0[1]
buf_back = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny);//分配给back一个与vram大小一样的地址
sheet_setbuf(sht_back, buf_back,binfo->scrnx, binfo->scrny, -1);//没有透明色,由于我们已经给图层管理表的初始地址设定在了vram当中,因此任何图层都会在vram空间里
sheet_setbuf(sht_mouse, buf_mouse, 16, 16, 99);//鼠标像素阵16x16,给予最不透明度
init_screen8(buf_back, binfo->scrnx, binfo->scrny);//往back地址中填入颜色
init_mouse_cursor8(buf_mouse, 99); //背景色99号,往mouse中填入颜色和透明度
sheet_slide(shtctl, sht_back, 0, 0);//将背景页面不移动,直接覆盖整个画面
mx = (binfo->scrnx - 16) / 2; /* 计算画面的中心坐标*/
my = (binfo->scrny - 28 - 16) / 2;
sheet_slide(shtctl, sht_mouse, mx, my);//移动鼠标到画面中心
sheet_updown(shtctl, sht_back, 0);//背景图的高度设置为0,并且将sheets按高度顺序写入
sheet_updown(shtctl, sht_mouse, 1);//鼠标图层高度设置为1
sprintf(s, "(%3d, %3d)", mx, my);
putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s);
sprintf(s, "memory %dMB free : %dKB",
memtotal / (1024 * 1024), memman_total(memman) / 1024);
putfonts8_asc(buf_back, binfo->scrnx, 0, 32, COL8_FFFFFF, s);
sheet_refresh(shtctl, sht_back, 0, 0, binfo->scrnx, 48);
for(;;)
{
io_cli(); //IF=0
if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0)
{
io_stihlt();
}else
{
if (fifo8_status(&keyfifo) != 0)
{
i = fifo8_get(&keyfifo);
io_sti();
sprintf(s,"%02x",i);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
sheet_refresh(shtctl, sht_back, 0, 16, 16, 32);
}else if(fifo8_status(&mousefifo) != 0)
{
i = fifo8_get(&mousefifo);
io_sti();
if(mouse_decode(&mdec,i) != 0)
{
//凑齐三字节了进行输出!!
sprintf(s, "[lcr %4d %4d]", mdec.x, mdec.y);
if((mdec.btn & 0x01) != 0)
{
s[1] = 'L';
}
if((mdec.btn & 0x02) != 0)
{
s[3] = 'R';
}
if((mdec.btn & 0x04) != 0)
{
s[3] = 'C';
}
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 32+15*8 - 1, 31);
putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
sheet_refresh(shtctl, sht_back, 32, 16, 32 + 15 * 8, 32);
//让鼠标动起来
mx += mdec.x;
my += mdec.y;
if(mx < 0)
{
mx =0;
}
if(my < 0)
{
my = 0;
}
if(mx > binfo->scrnx - 16)
{
mx = binfo->scrnx - 16;
}
if(my > binfo->scrny - 16)
{
my = binfo->scrny - 16;
}
sprintf(s,"(%3d,%3d)", mx, my);
boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 0, 79, 15);//隐藏坐标
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);//显示坐标
sheet_refresh(shtctl, sht_back, 0, 0, 80, 16);
sheet_slide(shtctl, sht_mouse, mx, my);//包含sheet_slide与sheet_refresh
}
}
}
}
}
然后,make run一下:
总结
以上就是关于图层叠加的部分,这一张部分可以多看看代码,理解一下图层叠加这个有意思的东西。