第十天
内存管理中分配内存的函数虽然提供了以字节为单位进行内存分配的函数,但是也可能会造成频繁分配内存和释放内存造成的出现很多不连续的小段未使用的内存空间,这样会把内存清耗殆尽。所以又提供了一次性分配和释放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;
}
假设要申请0x1005字节的内存,现论上我们应该给0x2000字节的内存,也就是向上舍入。按照普通的4舍5入的规则,这里应该需要使用除法和取余,但是这里使用了一个小技巧,把低效的除法变成高效的与运算。
内存管理告一段落,现在要解决之前鼠标移动时与画面叠加时出现的问题了。
为了解决窗口叠加、鼠标叠加的问题我们引入了图层的概念,最上面的小图层用来描绘鼠标,中间描绘窗口,最下面描绘桌面。我们通过移到图层的方未法实现鼠标指针的移动,及窗口的移动。
先定义一个图层结构体
struct SHEET
{
unsigned char *buf;//图像缓存
int bxsize;//水平像素长度
int bysize;//坚直像素长度
int vx0;//水平方向起始点
int vy0;//坚直方向起始点
int col_inv;//颜色值,是否透明
int height;//该图层是第几层
int flags;//标记位,图层是否已被使用
};
把图层定义之后,然后定义操作系统的图层控制结构,在这个结构体中定义操作系统最多处理256个图层。
struct SHTCTL
{
unsigned char *vram;
int xsize, ysize, top;
struct SHEET *sheets[MAX_SHEETS];
struct SHEET sheets0[MAX_SHEETS];
};
定义完相关的数据结构之后,在主程序中创建SHTCTL实例,然后创建背景景图层和鼠标图层,把背景定义为第0层,鼠标为第1层。以后鼠标图层移到的时候,就会刷新全部画面。画面刷新的规则是这样的:先刷新第底下一层,再刷新上面一层,这样每次移动鼠标的时候,背景画面出会刷新,不会消失。
但是这样做虽然逻辑上和功能上都没有问题,主要是效率太低,每次移到鼠标都要刷新全部画面,实际上鼠标移动时候只要刷新鼠标之前的那部分和鼠标新的位置那部分的画面就行了,可以大幅提高效率。方法也很简单,改造刷新函数,调用刷新函数的时候需要传入图层变化位置前的起始地址,和变化后的最后全置就可以了。
看这一章特别要注意的显示器的坐标和窗口座标的问题,很容易弄混,我这一章看了一个星期。
第11天
看书看到这一步经常会对之前学过、理解过的东西又产生疑惑,又要不停得往回翻过来看。在看这一章我运行程序的时候突然看到鼠标是16*16像素的图象,为什么没有把后面的背景挡住,程序里是怎么实现的,之前好像没有怎么注意,然后又重新看了相关的程序。
跟鼠标相关的程序有两部分,主要是sheet.c里,还有graphic.c里面。
void init_mouse_cursor8(char *mouse, char bc)
{
static char cursor[16][16] = {
"**************..",
"*OOOOOOOOOOO*...",
"*OOOOOOOOOO*....",
"*OOOOOOOOO*.....",
"*OOOOOOOO*......",
"*OOOOOOO*.......",
"*OOOOOOO*.......",
"*OOOOOOOO*......",
"*OOOO**OOO*.....",
"*OOO*..*OOO*....",
"*OO*....*OOO*...",
"*O*......*OOO*..",
"**........*OOO*.",
"*..........*OOO*",
"............*OO*",
".............***"
};
int x, y;
for (y = 0; y < 16; y++) {
for (x = 0; x < 16; x++) {
if (cursor[y][x] == '*') {
mouse[y * 16 + x] = COL8_000000;
}
if (cursor[y][x] == 'O') {
mouse[y * 16 + x] = COL8_FFFFFF;
}
if (cursor[y][x] == '.') {
mouse[y * 16 + x] = bc;
}
}
}
return;
}
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;
if (vx0 < 0) { vx0 = 0; }
if (vy0 < 0) { vy0 = 0; }
if (vx1 > ctl->xsize) { vx1 = ctl->xsize; }
if (vy1 > ctl->ysize) { vy1 = ctl->ysize; }
for (h = 0; h <= ctl->top; h++) {
sht = ctl->sheets[h];
buf = sht->buf;
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;
}
每个图层从最低层开始刷新,最后面的桌面层没有设置透明,也就是col_inv设置为-1。将下来的每层,sheet_refreshsub函数总是先在刷新之前先取得缓冲区像素颜色的值,然后把像素的颜色值为事先设置好的透明色对比,如果想等那么这个像素就不会在屏幕上画,那么自色就是透明的。
这一节做了一个计数器,不停得在窗口中写入这符串,然后屏幕就闪烁了。主要是由于sheet_refreshsub效率不高。其实就窗口上的字符在变化,但是刷新的时候从背景桌面开始从下至上全部刷新了一下,我们要修改。
修改sheet_refreshsub函数,使之多接收一个参数h0表示变化的图层中最下面的那一层,然后里面的刷新循环不从0开始,从h0开始。这样做之后虽然性能提高了,但是还是存在一点问题。
鼠标如果放到刷新的地方那么由于窗口会重新刷,鼠标就会看起来一闪一闪的,最好的办法是鼠标所在的区域不要刷新,因为刷新没有用因为反正最后还是被鼠标挡住的。
我们在内存中开辟一块和屏幕一样大的区域,我们称为屏幕地图,每个内存单元做为一个像素点。然后每次在移动图层的时候先往内地图中写入图层编号,然后每次刷新屏幕时候,先查询地图中的内存单元所存的编号是不是和该图层的编号,如果相等就刷新,如果不相等就不刷新。其实这个地图就是屏幕的缓冲区。
第12天
这一章学习使用定时器。使用定时器只需要设置PIT,Programmable Interval Timer,翻译过来就是可编程的间隔型定时器。设置了定时器,可以认定时器每隔多少秒就产生一次中断,电脑中连接PIT的是IRQ0。
IRQ0的中断周期变更
- al=0x34:out 0x43, al;
- al=中断周期的低8位;out 0x40 al;
- al=中断周期的高8位;out 0x40,al;
实际中断产生的频率是主频/设定的数值。主频为1.19318MHZ。因此如果我们把数值设置为11932的话,中断产生的频率也100Hz,也就是每10ms产生一次中断,11932换算成16进制也就是0x2e9c。
学会了使用定时器,我们就可以实现超时功能了。所谓的超时功能就是我们给系统设置一个时间,让系统在我们设置的时间到了的时候通知CPU处理某种任务。比如我们可以这样设置:定时器设器1秒,然后在每次时间到达的时候我们让屏幕上秒钟向前走一秒。
定义超时功能使用的数据结构
struct TIMERCTL {
unsigned int count;//中断发生次数
unsigned int timeout;//设定的时间
struct FIFO8 *fifo;//定时器中断队列
unsigned char data;//数据
};
下面的中断处理程序
void inthandler20(int *esp)
{
io_out8(PIC0_OCW2, 0x60);
timerctl.count++;
if (timerctl.timeout > 0) {
timerctl.timeout--;
if (timerctl.timeout == 0) {
fifo8_put(timerctl.fifo, timerctl.data);
}
}
return;
}
中断处理程序每次在timeout减少到0时就向FIFO发送数据。看到这里我发现我们已经使用了三个队列,分别是鼠标消息队列,键盘消息队列,定时器消息队列。难道每个中断我们都要使用一个消息队列,这样效率不高,又不利于程序维护,以后应该会把消息队列合并的吧。
很多情况下还需要设置 多个定时器。我们的目标是最多可以设立500个计时器,为了提高中断处理程序的效率,我们创建一个数据结构。
struct TIMER {
unsigned int timeout, flags;
struct FIFO8 *fifo;
unsigned char data;
};
struct TIMERCTL {
unsigned int count, next, using;
struct TIMER *timers[MAX_TIMER];
struct TIMER timers0[MAX_TIMER];
};
这里主要说明timers指针变量和timer0。后者是真正的计时器结构体,保存了计时器的相关变量。前者主要是用于计时器超时的时间排序。
看一下中断处理程序
void inthandler20(int *esp)
{
int i, j;
io_out8(PIC0_OCW2, 0x60);
timerctl.count++;
if (timerctl.next > timerctl.count) {
return;
}
for (i = 0; i < timerctl.using; i++) {
if (timerctl.timers[i]->timeout > timerctl.count) {
break;
}
timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC;
fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data);
}
timerctl.using -= i;
for (j = 0; j < timerctl.using; j++) {
timerctl.timers[j] = timerctl.timers[i + j];
}
if (timerctl.using > 0) {
timerctl.next = timerctl.timers[0]->timeout;
} else {
timerctl.next = 0xffffffff;
}
return;
}
这段程序先判断下一个超时的计时器是否超时,没有的话中断程序直接结束。如果下一个计时器超时的话,寻找最后一个超时的计时器,并把最后一个超时的计时器之前的所有计时器关闭,并向队列中发送消息。接下来把timers中的计时器移位,并重新设置next数值。
设置计时器的函数
void timer_settime(struct TIMER *timer, unsigned int timeout)
{
int e, i, j;
timer->timeout = timeout + timerctl.count;
timer->flags = TIMER_FLAGS_USING;
e = io_load_eflags();
io_cli();
for (i = 0; i < timerctl.using; i++) {
if (timerctl.timers[i]->timeout >= timer->timeout) {
break;
}
}
for (j = timerctl.using; j > i; j--) {
timerctl.timers[j] = timerctl.timers[j - 1];
}
timerctl.using++;
timerctl.timers[i] = timer;
timerctl.next = timerctl.timers[0]->timeout;
io_store_eflags(e);
return;
}
在设置计时器之前先关闭中断,然后搜索离将要设置的计时器超时时间最接近的计时器所在位置,然后把这个计时器向后移动,移动之后再插入这个计时器,就这样设置好了,并且timerctl中的计时器还是按超时时间有序排放的。
网友评论