第13天 定时器(2)
2020.4.15
1. 简化字符串显示(harib10a)
-
harib09g的bootpack.c有210行,代码中多次出现了:
boxfill8(buf_win, 160, COL8_C6C6C6, 40, 28, 119, 43); putfonts8_asc(buf_win, 160, 40, 28, COL8_000000, s); sheet_refresh(sht_win, 40, 28, 120, 44);
这三个函数的组合。这三个函数完成的工作是:先在特定位置涂上背景色,然后写上字符,最后完成刷新。
-
将这部分归纳到一个函数putfonts8_asc_sht(bootpack.c中):
void putfonts8_asc_sht(struct SHEET *sht, int x, int y, int c, int b, char *s, int l) { boxfill8(sht->buf, sht->bxsize, b, x, y, x + l * 8 - 1, y + 15); putfonts8_asc(sht->buf, sht->bxsize, x, y, c, s); sheet_refresh(sht, x, y, x + l * 8, y + 16); return; }
- sht是要刷新的图层。
- x,y是显示位置的坐标。
- c是字符颜色(color)
- b背景颜色(back color)
- s是字符串(string)
- l是字符串长度(length)
-
改写bootpack.c。(此处改写不再赘述)改写完成后,成功缩短了2行。
2. 重新调整FIFO缓冲区(1)(harib10b)
-
查看HariMain中的代码:
if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) + fifo8_status(&timerfifo2) + fifo8_status(&timerfifo3) == 0) { io_sti(); }
上述代码是3个超时计时器。要是100个那代码可太长了。
-
可以只定义一个FIFO缓冲区,向其中写入不同的数据来区分到底是哪个超时计时器传来的数据。
-
改写HariMain中代码:
fifo8_init(&timerfifo, 8, timerbuf); timer = timer_alloc(); timer_init(timer, &timerfifo, 10); /*timer data写入10*/ timer_settime(timer, 1000); fifo8_init(&timerfifo2, 8, timerbuf2); timer2 = timer_alloc(); timer_init(timer2, &timerfifo2, 3); /*timer2 data写入3*/ timer_settime(timer2, 300); fifo8_init(&timerfifo3, 8, timerbuf3); timer3 = timer_alloc(); timer_init(timer3, &timerfifo3, 1); /*timer3 data=1*/ timer_settime(timer3, 50);
-
继续修改HariMain:
for (;;) { sprintf(s, "%010d", timerctl.count); putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 10); io_cli(); if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) == 0) { io_sti(); } else { if (fifo8_status(&keyfifo) != 0) { …… } else if (fifo8_status(&mousefifo) != 0) { …… } else if (fifo8_status(&timerfifo) != 0) { i = fifo8_get(&timerfifo); /* 从缓冲区读取数据 */ io_sti(); if (i == 10) { /*timer*/ putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7); } else if (i == 3) { /*timer1*/ putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484, "3[sec]", 6); } else { /*timer3*/ if (i != 0) { timer_init(timer3, &timerfifo, 0); /*data = 0*/ boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111); } else { timer_init(timer3, &timerfifo, 1); /*data = 1*/ boxfill8(buf_back, binfo->scrnx, COL8_008484, 8, 96, 15, 111); } timer_settime(timer3, 50); sheet_refresh(sht_back, 8, 96, 16, 112); } } } }
-
make run
正常。同时bootpack.c精简了4行。
3. 测试性能(harib10c~harib10f)
-
我们一直在改善定时器,现在需要知道到底改成啥样子了。之所以如此专注于改良定时器,是因为在今后的开发中,需要经常用到定时器。
-
测试性能的方法:
【完全不显示计数,是为了让CPU全力计数,因为输出数据会导致CPU不能全力计数。】 -
HariMain函数节选:
for (;;) { count++; io_cli(); if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) + fifo8_status(&timerfifo) == 0) { io_sti(); } else { if (fifo8_status(&keyfifo) != 0) { …… } else if (fifo8_status(&mousefifo) != 0) { …… } else if (fifo8_status(&timerfifo) != 0) { i = fifo8_get(&timerfifo); io_sti(); if (i == 10) { putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7); sprintf(s, "%010d", count); /*输出*/ putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 10); } else if (i == 3) { putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484, "3[sec]", 6); count = 0; /* 3秒后count复位,正式开始计数 */ } else { …… } } } }
-
make run
后不要移动鼠标和键盘,让CPU全力计数:
count=103256250. -
多次测试,count值:
103256250 103828731 104334477 103598788 103678979
最大和最小差值为1078227。差值有一百多万。这说明QEMU受到Windows的影响比较大。
-
在VMware上试试:
1047258405 1048709262 1048497937 1048932095 1051931689
最大值和最小值差值为4673284。差值四百多万。这说明VMware受到Windows的影响更大。
-
由于只有一台电脑,无法使用真机运行。但真机误差的确会很小。
-
将harib09d改写成harib10d测试count值。将harib09e改写成harib10e测试count值。将harib09f改写成harib10f测试count值。(这里采用书上的结果)
(harib10c对应harib09g)可以看出count值随着我们的优化而增大,速度就提高。 -
启动3秒后将count置为0的原因:
4. 重新调整FIFO缓冲区(2)(harib10g)
-
测试完性能,将代码改回harib10c。
-
我们将鼠标和键盘也归纳起来,让它们和定时器共用一个FIFO缓冲区。
向FIFO缓冲区写不同的数据,可以区分是哪个定时器、鼠标和键盘。 -
向FIFO写入的数据与中断类型设定:
- 这里的256~511共256(0xff)个。因为每次键盘中断都会产生一字节数据,这一字节数据刚好是0x??。
- 同理,512~767这256个字节也是一样。鼠标变化会产生三次中断,每次中断传送1字节数据。
-
结构体FIFO8只能存放1字节数据,最大255。存放不了256及以上的数字。因此,我们需要将FIFO8改成FIFO32,以存放4字节数据。(只是修改了buf的数据类型,以及将FIFO8改成FIFO32)
-
bootpack.h中FIFO32定义:
struct FIFO32 { int *buf; int p, q, size, free, flags; };
-
fifo.c中修改:
void fifo32_init(struct FIFO32 *fifo, int size, int *buf) /* FIFO缓冲区的初始化 */ { fifo->size = size; fifo->buf = buf; fifo->free = size; /* 空 */ fifo->flags = 0; fifo->p = 0; /* 写入位置 */ fifo->q = 0; /* 读取位置 */ return; } int fifo32_put(struct FIFO32 *fifo, int data) /* 给FIFO发送数据并将数据存储在FIFO中 */ { if (fifo->free == 0) { /* 没有空余空间,溢出 */ fifo->flags |= FLAGS_OVERRUN; return -1; } fifo->buf[fifo->p] = data; fifo->p++; if (fifo->p == fifo->size) { fifo->p = 0; } fifo->free--; return 0; } int fifo32_get(struct FIFO32 *fifo) /* 从FIFO取得一个数据 */ { int data; if (fifo->free == fifo->size) { /* 当缓冲区数据为空的情况下返回-1 */ return -1; } data = fifo->buf[fifo->q]; fifo->q++; if (fifo->q == fifo->size) { fifo->q = 0; } fifo->free++; return data; } int fifo32_status(struct FIFO32 *fifo) /* 返回已经存储了多少数据 */ { return fifo->size - fifo->free; }
- 注意,fifo32_put中data的类型也因此变成了int。
-
修改keyboard.c
struct FIFO32 *keyfifo; int keydata0; void inthandler21(int *esp) { int data; io_out8(PIC0_OCW2, 0x61); data = io_in8(PORT_KEYDAT); fifo32_put(keyfifo, data + keydata0); return; } void init_keyboard(struct FIFO32 *fifo, int data0) { /* 将FIFO缓冲区的信息保存到全局变量中 */ keyfifo = fifo; keydata0 = data0; /* 键盘控制器初始化 */ wait_KBC_sendready(); io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE); wait_KBC_sendready(); io_out8(PORT_KEYDAT, KBC_MODE); return; }
- init_keyboard函数添加了两个参数,参数fifo用于将现在唯一的一个开辟了空间的FIFO缓冲区传进来,data0是我们规定的256。
-
修改mouse.c
struct FIFO32 *mousefifo; int mousedata0; void inthandler2c(int *esp) /* 基于PS/2鼠标的中断 */ { int data; io_out8(PIC1_OCW2, 0x64); io_out8(PIC0_OCW2, 0x62); data = io_in8(PORT_KEYDAT); fifo32_put(mousefifo, data + mousedata0); return; } void enable_mouse(struct FIFO32 *fifo, int data0, struct MOUSE_DEC *mdec) { /* 将FIFO缓冲区的信息保存到全局变量中 */ mousefifo = fifo; mousedata0 = data0; /* 使鼠标有效 */ wait_KBC_sendready(); io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE); wait_KBC_sendready(); io_out8(PORT_KEYDAT, MOUSECMD_ENABLE); /* 顺利的话,ACK(0xfa)会被发送 */ mdec->phase = 0; /* 等待鼠标的0xfa */ return; }
- enable_mouse添加了两个参数fifo和data0。fifo是唯一一个分配了内存的缓冲区,data0是我们规定的512。
-
修改定时器结构体:
struct TIMER { unsigned int timeout, flags; struct FIFO32 *fifo; int data; };
-
修改time.c:
void timer_init(struct TIMER *timer, struct FIFO32 *fifo, int data) { timer->fifo = fifo; timer->data = data; return; } 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; fifo32_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; }
-
修改bootpack.c:
void HariMain(void) { struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO; struct FIFO32 fifo; char s[40]; int fifobuf[128]; …… fifo32_init(&fifo, 128, fifobuf); init_pit(); init_keyboard(&fifo, 256); // 键盘传入256 enable_mouse(&fifo, 512, &mdec); // 鼠标传入512 io_out8(PIC0_IMR, 0xf8); io_out8(PIC1_IMR, 0xef); timer = timer_alloc(); timer_init(timer, &fifo, 10); timer_settime(timer, 1000); timer2 = timer_alloc(); timer_init(timer2, &fifo, 3); timer_settime(timer2, 300); timer3 = timer_alloc(); timer_init(timer3, &fifo, 1); timer_settime(timer3, 50); …… for (;;) { count++; io_cli(); if (fifo32_status(&fifo) == 0) { io_sti(); } else { i = fifo32_get(&fifo); io_sti(); if (256 <= i && i <= 511) { /* 键盘数据 */ sprintf(s, "%02X", i - 256); putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2); } else if (512 <= i && i <= 767) { /* 鼠标数据 */ if (mouse_decode(&mdec, i - 512) != 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'; } putfonts8_asc_sht(sht_back, 32, 16, COL8_FFFFFF, COL8_008484, s, 15); mx += mdec.x; my += mdec.y; if (mx < 0) { mx = 0; } if (my < 0) { my = 0; } if (mx > binfo->scrnx - 1) { mx = binfo->scrnx - 1; } if (my > binfo->scrny - 1) { my = binfo->scrny - 1; } sprintf(s, "(%3d, %3d)", mx, my); putfonts8_asc_sht(sht_back, 0, 0, COL8_FFFFFF, COL8_008484, s, 10); sheet_slide(sht_mouse, mx, my); } } else if (i == 10) { /* 10s定时器 */ putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7); sprintf(s, "%010d", count); putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 10); } else if (i == 3) { /* 3s定时器 */ putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484, "3[sec]", 6); count = 0; /* 复位 */ } else if (i == 1) { /* 光标定时器 */ timer_init(timer3, &fifo, 0); boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, 8, 96, 15, 111); timer_settime(timer3, 50); sheet_refresh(sht_back, 8, 96, 16, 112); } else if (i == 0) { /* 光标定时器 */ timer_init(timer3, &fifo, 1); boxfill8(buf_back, binfo->scrnx, COL8_008484, 8, 96, 15, 111); timer_settime(timer3, 50); sheet_refresh(sht_back, 8, 96, 16, 112); } } } }
- 经过修正,bootpack.c简化了8行。
-
make run
:
- 比harib10c运行结果103256250速度提升了181%!!
- 在真机上运行速度提升130%。
- 原因如下:
5. 加快中断处理(4)(harib10h)
-
现在的问题是,inthandler20和timer_settime中都有移位处理。timer_settime是在中断禁止期间进行的,因此也要尽可能不用移位处理。
-
倘若有多个定时器,使用移位操作是非常浪费时间的。
-
使用链表优化。
-
修改TIMER结构体:
struct TIMER { struct TIMER *next; unsigned int timeout, flags; struct FIFO32 *fifo; int data; };
- 添加新的指针变量next,用于指向下一个即将超时的定时器的地址。
- 注意,结构体TIMERCTL中的next的含义是即将超时的(最紧迫)时刻。
-
定时器们维护一个链表,这个链表是有序的,往这个链表中添加数据时遍历O(n),从链表中取出数据(只取头结点数据,最紧迫)O(1)。
- 添加next指针以后,timers指针数组就不再需要了,因为我们只需要使用timers[0]。timers[0]我们是需要的,因为每次都是从timers[0]中取得下一时刻的定时器。因此,将TIMERCTL中timers[]改成t0即可,每次从t0中取得下一时刻的定时器即可。
- 修改TIMERCTL:
struct TIMERCTL { unsigned int count, next, using; struct TIMER *t0; struct TIMER timers0[MAX_TIMER]; };
-
修改inthandler20:
void inthandler20(int *esp) { int i; struct TIMER *timer; io_out8(PIC0_OCW2, 0x60); /* 通知 */ timerctl.count++; if (timerctl.next > timerctl.count) { return; } timer = timerctl.t0; for (i = 0; i < timerctl.using; i++) { /*防止多个定时器在同一时刻结束*/ /* timers中定时器都是运行的,因此不必确认flags */ if (timer->timeout > timerctl.count) { break; } /* 超时 */ timer->flags = TIMER_FLAGS_ALLOC; fifo32_put(timer->fifo, timer->data); timer = timer->next; } timerctl.using -= i; /* 获取下一时刻的定时器,新移位 */ timerctl.t0 = timer; if (timerctl.using > 0) { timerctl.next = timerctl.t0->timeout; } else { timerctl.next = 0xffffffff; } return; }
-
修改timer_settime:
void timer_settime(struct TIMER *timer, unsigned int timeout) { int e; struct TIMER *t, *s; timer->timeout = timeout + timerctl.count; timer->flags = TIMER_FLAGS_USING; e = io_load_eflags(); io_cli(); timerctl.using++; if (timerctl.using == 1) { /* 处于运行状态的定时器只有1个 */ timerctl.t0 = timer; timer->next = 0; /* 没有下一个 */ timerctl.next = timer->timeout; io_store_eflags(e); return; } t = timerctl.t0; if (timer->timeout <= t->timeout) { /* 插入到最前面的情况 */ timerctl.t0 = timer; timer->next = t; /* 下面是t */ timerctl.next = timer->timeout; io_store_eflags(e); return; } /* 搜寻插入位置 */ for (;;) { s = t; t = t->next; if (t == 0) { break; /* 最后面 */ } if (timer->timeout <= t->timeout) { /* 插入到s和t之间 */ s->next = timer; timer->next = t; io_store_eflags(e); return; } } /* 插入到最后面的情况 */ s->next = timer; timer->next = 0; io_store_eflags(e); return; }
- 自己模拟一下就OK。
-
make run
:
貌似count值变小了?
6. 使用“哨兵”简化程序(harib10i)
-
timer_settime函数有些冗长,分析一下,当向链表中添加一个定时器时,无非就只有以下四种情况:
- 链表为空,也就是说当前插入的定时器是唯一一个在运行的定时器。
- 插入到链表头部
- 插入到两个定时器之间
- 插入到链表尾部
-
规定:设置一个定时器(哨兵),它的超时时刻是0xffffffff。
这个定时器,不需要设定它的fifo(但是其他的属性还是要设定的),因为在到达这个时刻之前,我们已经修改了count值(一年修改一次)。 -
在init_pic函数中加入初始化哨兵的代码(已经删除了using,下面有解释):
void init_pit(void) { int i; struct TIMER *t; io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e); timerctl.count = 0; for (i = 0; i < MAX_TIMER; i++) { timerctl.timers0[i].flags = 0; /* 未使用 */ } t = timer_alloc(); /* 为哨兵分配定时器 */ t->timeout = 0xffffffff; /*哨兵的超时时刻*/ t->flags = TIMER_FLAGS_USING; t->next = 0; /* 只有哨兵一个定时器,末尾 */ timerctl.t0 = t; /* 正在运行的只有哨兵,哨兵是最紧迫的 */ timerctl.next = 0xffffffff; /* 下一时刻是哨兵的超时时刻 */ return; }
-
修改一年调整一次的程序:
-
加入了哨兵,四种情况中只有两种可能发生:
- 链表为空,也就是说当前插入的定时器是唯一一个在运行的定时器。【不可能】
- 插入到链表头部 【可能】
- 插入到两个定时器之间 【可能】
- 插入到链表尾部 【不可能】
-
修改timer_settime(已经删除了using,下面有解释):
void timer_settime(struct TIMER *timer, unsigned int timeout) { int e; struct TIMER *t, *s; timer->timeout = timeout + timerctl.count; timer->flags = TIMER_FLAGS_USING; e = io_load_eflags(); io_cli(); t = timerctl.t0; if (timer->timeout <= t->timeout) { /* 插入到最前面的情况 */ timerctl.t0 = timer; timer->next = t; timerctl.next = timer->timeout; io_store_eflags(e); return; } /* 搜寻插入位置 */ for (;;) { s = t; t = t->next; if (timer->timeout <= t->timeout) { /* 插入s和t之间 */ s->next = timer; timer->next = t; io_store_eflags(e); return; } } }
-
简化inthandler20:
- using对正在运行的定时器计数。不使用timers数组以后,根据using是否为0来决定怎样设定next。现在有了哨兵,所以,using恒大于0。这样一来,using就没啥用了。删掉using吧。
void inthandler20(int *esp) { struct TIMER *timer; io_out8(PIC0_OCW2, 0x60); /* 通知 */ timerctl.count++; if (timerctl.next > timerctl.count) { return; } timer = timerctl.t0; /* 先把最前面的定时器赋值给timer */ for (;;) { /*防止多个定时器在同一时刻*/ /* 不必确认flags */ if (timer->timeout > timerctl.count) { break; } /* 超时 */ timer->flags = TIMER_FLAGS_ALLOC; fifo32_put(timer->fifo, timer->data); timer = timer->next; /* 遍历 */ } /*更新*/ timerctl.t0 = timer; timerctl.next = timer->timeout; return; }
-
make run
:
在QEMU上的确变快了。
7. 5月27号
- 今天早上得知,本科生大四年级毕业设计(论文)的定稿(DDL)日期是2020.5.27。
- 这让我有点小失落和小确幸。
- 小失落是,还有40天,便真正开始结束自己的大学生涯了。
- 小确幸是,自己从3.28开始,到今天是19天,其中还有好几天因为腰痛休息了几天。已经结束了13天了,总进度是261/710=36.8。这还是值得表扬的。
- 同时,还应该看到,还有大部分没有完成。因此,需要加快速度了。
- 革命尚未成功,同志仍需努力啊!
- 2020.4.16 10:48