三十天自制操作系统(4)

第十天

内存管理中分配内存的函数虽然提供了以字节为单位进行内存分配的函数,但是也可能会造成频繁分配内存和释放内存造成的出现很多不连续的小段未使用的内存空间,这样会把内存清耗殆尽。所以又提供了一次性分配和释放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的中断周期变更

  1. al=0x34:out 0x43, al;
  2. al=中断周期的低8位;out 0x40 al;
  3. 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中的计时器还是按超时时间有序排放的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,639评论 18 139
  • 第13天 在这之前的程序中每次新设置一个定时器都要创造一个队列与之对应,这样效率低而且导致操作系统主程序中逻辑复杂...
    whatcanhumando阅读 1,240评论 0 1
  • 第24天 我们已经可以让应用程序显示窗口了,如果一个应用程序显示了N个窗口,我们也应该让各个窗口可以现切换。实现鼠...
    whatcanhumando阅读 755评论 0 5
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,473评论 6 30
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,105评论 5 13