【读书笔记-《30天自制操作系统》-9】Day10

本篇主要解决两个问题。一是扩充上一篇内存管理部分的功能,实现一种按4KB大小分配内存的函数;二是解决鼠标显示中鼠标覆盖任务栏的问题。
在这里插入图片描述

1. 以4KB大小为单位分配内存的函数

如果每次分配内存都需要按字节去分配,效率还是比较低的,可用内存也会变得越来越零碎。这里作者引入一种以4KB大小为单位进行内存分配的函数。

首先要根据应用程序所需的内存大小,进行4KB向上取整,也就是不足4KB的部分用4KB补足。相应的代码如下:

unsigned int memman_alloc_4k(struct MEMMAN *man, unsigned int size)
{
	unsigned int a;
	size = (size + 0xfff) & 0xfffff000;
	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;
}

可以看出对于4KB(0x1000)的向上取整,采用的方法是(size + 0xfff) & 0xfffff000
用十进制来举例子就很简单,比如以10为单位向上舍入,123舍入后就是130,这个很好理解。

2. 图层的叠加处理

引入了以4KB为单位进行内存分配的函数之后,本篇的重点是完成叠加处理。从鼠标和任务栏的叠加扩展开去,扩展到多个不同图层的叠加显示。这部分确实比较复杂,如果没明白的话,还是需要认认真真地多看几遍。

2.1 图层概念的引入

为了讲解叠加处理,需要引入图层的概念。这里作者引入的图层概念就是一张图片,除了有图案的地方,背景色是透明的。而多个窗口叠加在一起,可能是多个大小不同的图层的叠放。最上面的小图层用来描绘鼠标,下面依次是叠加在一起的不同图层,而最下面则是桌面背景。另外还需要实现窗口的移动。

首先考虑描述一个图层信息的数据结构:

struct SHEET {
	unsigned char *buf;
	int bxsize, bysize, vx0, vy0, col_inv, height, flags;
};

这个结构体中,buf用来存放图层上的图案内容。bxsize,bysize用于描述图层的大小,vx0和vy0用来描述图层在整个显示画面中的位置,col_inv表示图层的透明色色号,height表示图层的高度,flags用于存放有关图层设置的信息。
这样一个图层就可以通过这个结构体进行描述了。

当然,只有一个图层,我们无法实现叠加处理,还需要一个管理多个图层信息的结构体:

#define MAX_SHEETS		256
struct SHTCTL {
	unsigned char *vram;
	int xsize, ysize, top;
	struct SHEET *sheets[MAX_SHEETS];
	struct SHEET sheets0[MAX_SHEETS];
};

SHTCTL这个结构体用于管理图层。其中MAX_SHEETS是最大图层数,设置为256。结构体中,vram表示显存地址,xsize与ysize表示画面的大小,初始化时就从BOOTINFO中获取到并存起来。top用来表示最上面图层的高度。sheets0是一个结构体数组,用于保存图层的信息;而sheets是一个结构体指针数组。sheets0中存放的图层信息是乱序的,而通过sheets结构体指针数组将经过升序处理后的图层信息保存起来。

2.2 图层叠加处理

具备了这些数据结构之后,我们开始编写程序。首先需要对图层管理的数据结构进行初始化:

struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize, int ysize)
{
	struct SHTCTL *ctl;
	int i;
	ctl = (struct SHTCTL *) memman_alloc_4k(memman, sizeof (struct SHTCTL));
	if (ctl == 0) {
		goto err;
	}
	ctl->vram = vram;
	ctl->xsize = xsize;
	ctl->ysize = ysize;
	ctl->top = -1; /* 当前画面中没有显示的图层 */
	for (i = 0; i < MAX_SHEETS; i++) {
		ctl->sheets0[i].flags = 0; /* 图层标记为未使用 */
	}
err:
	return ctl;
}

这段程序的内容比较清楚。首先是通过内存分配函数给图层控制结构体分配足够的内存,通过sizeof(struct SHTCTL)来由编译器计算所需的内存大小。这个内存大小超过了4KB,所以用前面的内存分配函数来分配内存很合适。接下来将显存地址、画面大小等信息存放在结构体中。将top设置为-1,表示当前没有显示的图层;将sheets0数组中所有元素的flags成员置为0,表示当前图层都没有使用。

初始化之后,是分配空闲图层的函数:

#define SHEET_USE		1

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->flags = SHEET_USE; /* 将图层标记为使用 */
			sht->height = -1; /* 表示当前图层隐藏 */
			return sht;
		}
	}
	return 0;	/* 走到这里说明所有图层都处于使用状态,没有空闲图层 */
}

从图层管理结构体的sheets0数组中寻找未使用的图层,标记为使用,将height先设置为-1表示隐藏。

获取空闲图层之后,就可以根据实际情况对图层的参数进行设置,这也没什么难点。

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;
}

下面是叠加显示中最关键的设置图层高度的函数,也是最复杂的:

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指针数组对图层进行重新排序 */
	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_refresh(ctl); /* 根据更新后的图层信息,重新绘制画面 */
	} else if (old < height) {	/* 图层的新位置比原来的位置高 */
		if (old >= 0) {
			/* 图层原高度大于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_refresh(ctl); /* 根据更新后的图层信息,重新绘制画面 */
	}
	return;
}

这一段代码比较复杂。简单地理解,这段代码做的其实就是从一堆按顺序堆放的图层中抽出一张,改变其高度,再放回堆中,重新进行排序的过程。如果当前图层向下移动,那么处在该图层新老位置之间的图层要依次向上移动,填补该图层留出的空位,而总高度不变;如果该图层由显示变为隐藏,那么原来处在该图层上面的图层要依次向下移动,填补该图层留出的空位,这样总高度由于该图层的隐藏而减少了。
同理,如果图层向上移动,那么原来处于该图层新老位置之间的图层要依次向下移动,填补该图层留出的空位,而总高度不变;如果该图层由隐藏变为显示,那么原来处在该图层与最顶层之间的图层要依次向上移动,给该图层腾出空间,总高度由于该图层的显示而增加了。
如此解释,这段程序是否更好懂一些了呢?
在这里插入图片描述

在这里插入图片描述排序之后,还需要从下到上依次描绘出各个图层的图案,最终最上层的图案会显示出来。这里就要用到sheet_refresh函数:

void sheet_refresh(struct SHTCTL *ctl)
{
	int h, bx, by, vx, vy;
	unsigned char *buf, c, *vram = ctl->vram;
	struct SHEET *sht;
	for (h = 0; h <= ctl->top; h++) {
		sht = ctl->sheets[h];
		buf = sht->buf;
		for (by = 0; by < sht->bysize; by++) {
			vy = sht->vy0 + by;
			for (bx = 0; bx < sht->bxsize; bx++) {
				vx = sht->vx0 + bx;
				c = buf[by * sht->bxsize + bx];
				if (c != sht->col_inv) {
					vram[vy * ctl->xsize + vx] = c;
				}
			}
		}
	}
	return;
}

从程序中可以看出,这个函数其实是将buf中不是透明色的像素点写入显存,这样自下而上层层写入下来,最终留下的就是最顶层的图案和下面未被覆盖的部分了。

叠加显示最重要的部分基本上就说完了,此外还有一些附加的函数,比如左右移动窗口的函数,释放图层的函数:

void sheet_slide(struct SHTCTL *ctl, struct SHEET *sht, int vx0, int vy0)
{
	sht->vx0 = vx0;
	sht->vy0 = vy0;
	if (sht->height >= 0) { /* 当前图层正在显示 */
		sheet_refresh(ctl); /* 根据新的图层信息重新描绘图案 */
	}
	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];

init_palette();
	shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny);
	sht_back  = sheet_alloc(shtctl);
	sht_mouse = sheet_alloc(shtctl);
	buf_back  = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny);
	sheet_setbuf(sht_back, buf_back, binfo->scrnx, binfo->scrny, -1); /* 背景图层,没有透明色 */
	sheet_setbuf(sht_mouse, buf_mouse, 16, 16, 99); /* 鼠标图层,设置透明色色号为99 */
	init_screen8(buf_back, binfo->scrnx, binfo->scrny);
	init_mouse_cursor8(buf_mouse, 99);
	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);
	sheet_updown(shtctl, sht_mouse, 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);

	for (;;) {
		io_cli();
		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(buf_back, binfo->scrnx, COL8_008484,  0, 16, 15, 31);
				putfonts8_asc(buf_back, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
				sheet_refresh(shtctl);
			} 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[2] = 'C';
					}
					boxfill8(buf_back, binfo->scrnx, COL8_008484, 32, 16, 32 + 15 * 8 - 1, 31);
					putfonts8_asc(buf_back, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
					/* 移动鼠标 */
					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(buf_back, binfo->scrnx, COL8_008484, 0, 0, 79, 15); /* 清除原坐标显示 */
					putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s); /* 显示新坐标 */
					sheet_slide(shtctl, sht_mouse, mx, my); /* 包含了sheet_refresh */
				}
			}
		}
	}

主程序中当前还只有两个图层,一个是背景图层,另一个是鼠标图层。这样运行程序,移动鼠标时,就不会出现遮挡任务栏的情况了。
在这里插入图片描述

2.3 加快图层叠加处理速度

但是当前还存在着显示比较慢的问题,叠加处理的速度还可以进一步提升。

当前的鼠标图案是1616=256个像素,其实移动一次只要重新绘制256个像素点就可以了。但当前的实现是每次移动都会将整个画面共320200=64000个像素点重新描绘一遍,从这一点出发就可以提高速度:

void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1)
{
	int h, bx, by, vx, vy;
	unsigned char *buf, c, *vram = ctl->vram;
	struct SHEET *sht;
	for (h = 0; h <= ctl->top; h++) {
		sht = ctl->sheets[h];
		buf = sht->buf;
		for (by = 0; by < sht->bysize; by++) {
			vy = sht->vy0 + by;
			for (bx = 0; bx < sht->bxsize; bx++) {
				vx = sht->vx0 + bx;
				if (vx0 <= vx && vx < vx1 && vy0 <= vy && vy < vy1) {
					c = buf[by * sht->bxsize + bx];
					if (c != sht->col_inv) {
						vram[vy * ctl->xsize + vx] = c;
					}
				}
			}
		}
	}
	return;
}

可以看出sheet_refreshsub函数中通过vx0-vy1指定了范围,这样就可以只在鼠标的256个像素范围内重新描绘,速度得到提升。
sheet_slide函数修改如下:

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) { /* 如果当前图层正在显示,按照新的图层信息重新描绘画面 */
		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;
}

这里做的是记住移动前的显示位置,再设置新的位置,只需要将原位置的显示消除,在新位置描绘即可。

虽然不用将整个画面都描绘一遍,但还是通过if判断了画面内的所有位置。在描绘之前就限制好描绘的范围,减少不必要的if判断,可以进一步提高速度:

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; }
		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;
				c = buf[by * sht->bxsize + bx];
				if (c != sht->col_inv) {
					vram[vy * ctl->xsize + vx] = c;
				}
			}
		}
	}
	return;
}

本篇从建立图层的数据结构出发,建立了图层动作的程序描述,并通过鼠标的实例进行实现。这部分从原理到程序内容都不少,看似简单实则又并不简单,自己就反复读了好几遍才理解。下一篇的制作窗口仍然与显示相关,敬请期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值