Day12
一些感想和问题
加快中断处理
到这一步的时候,已经有了很多个定时器了,但是此时查询是否timeout的方式是for循环查看一遍所有的定时器,并且计时的方式是不断通过0.1s(类似)的中断,来–count。那么。。就从这两个方面来优化中断处理。
- 把逐渐减少改为是否到达
显然,中断时不断–count的操作,会浪费掉很多内存读写的时间。所以修改了timeout的含义,把它理解为“要在什么时候timeout”。之后每次中断,只要查询是否已经到达该timeout时刻即可。 - 对到达时间排序,依次查找
虽然解决了不断–的问题,但是for循环还是太慢了。。刚好不同定时器的到达时刻有先后顺序,我们以此来设定一个next变量,来记住下一个时刻。这样每次中断的时候,先判断一下是否到了next时刻,未到达就直接退出即可。 - 继续上文
但是,对于到达的时刻,我们还是需要for循环来进行查找,这样可能会造成处理突然卡顿的情况。所以,可以把定时器按照到达时刻排序。每次中断的时候,从前往后查找是否已经timeout即可。毕竟在一个已经有序的数组里来判断还是更方便的。
可能的优化?
- 过长的主函数
每有一个缓冲区内容,就需要添加一个else if。。这样下去岂不是会变得很长。。 - 防止移位带来的时间浪费
应该可以用链表来代替有序的数组,对于不断释放定时器来说简直很方便。
以上两点是我可以想到的优化,如果作者到这本书结束还没优化的话。。那可以试试手)
今天份的内容
使用定时器
harib09a
定时器是什么就不用介绍了。我们看一下是如何设定定时器的中断请求的
#define PIT_CTRL 0x0043
#define PIT_CNT0 0x0040
void init_pit(void)
{
io_out8(PIT_CTRL, 0x34); // 指明中断周期需要变更(规定为0x34)
io_out8(PIT_CNT0, 0x9c); // 中断周期低4位
io_out8(PIT_CNT0, 0x2e); // 中断周期高4位
}
所以。。中断周期就是0x2e9c = 11932,cpu主频是1.19318MHz,所以这个值就是10ms产生一次中断。
之后,主函数要调用这个函数,执行中断的初始化。
接下来写中断处理函数
void inthandler20(int *esp)
{
io_out8(PIC0_OCW2, 0x60); // 把IRQ-00信号接收完了的信息通知给PIC
// 暂时什么也不做
}
目前还什么都没有。。
汇编的中断服务程序:
_asm_inthandler20:
PUSH ES
PUSH DS
PUSHAD
MOV EAX,ESP
PUSH EAX
MOV AX,SS
MOV DS,AX
MOV ES,AX
CALL _inthandler20
POP EAX
POPAD
POP DS
POP ES
IRETD
和我们之前见过的一样。。除了CALL的不一样了。
注册这个中断服务程序到IDT中。
set_gatedesc(idt + 0x20, (int)asm_inthandler20, 2 * 8, AR_INTGATE32);
这就是定时器的一些初始化工作了。。接下来要逐步完善一下。
计量时间
harib09b
先进行一个简单的功能。
struct TIMERCTL
{
unsigned int count;
};
extern struct TIMERCTL timerctl;
这是一个。。定时器(控制)的结构体
void init_pit(void)
{
io_out8(PIT_CTRL, 0x34);
io_out8(PIT_CNT0, 0x9c);
io_out8(PIT_CNT0, 0x2e);
timerctl.count = 0;
}
void inthandler20(int *esp)
{
io_out8(PIC0_OCW2, 0x60); // 把IRQ-00信号接收完了的信息通知给PIC
++timerctl.count;
}
每隔0.1s,就会++count
然后修改主函数
for (;;) {
sprintf(s, "%010d", timerctl.count);
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);
}
这样就打印到我们之前那个窗口上了。
超时功能
harib09c
超时功能,就是到达一定时间之后,产生一个中断。其实把定时器产生中断的次数计个数就可以了。
先修改一下struct
struct TIMERCTL
{
unsigned int count;
unsigned int timeout;
struct FIFO8 *fifo;
unsigned char data;
};
fifo是用来超时之后存放数据的地方。然后修改之前的函数
void init_pit(void)
{
io_out8(PIT_CTRL, 0x34);
io_out8(PIT_CNT0, 0x9c);
io_out8(PIT_CNT0, 0x2e);
timerctl.count = 0;
timerctl.timeout = 0;
}
void inthandler20(int *esp)
{
io_out8(PIC0_OCW2, 0x60); // 把IRQ-00信号接收完了的信息通知给PIC
++timerctl.count;
if(timerctl.timeout > 0) // 如果已经设定了超时
{
timerctl.timeout--;
if(timerctl.timeout == 0)
{
fifo8_put(timerctl.fifo, timerctl.data);
}
}
}
void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data)
{
int eflags = io_load_eflags();
io_cli();
timerctl.timeout = timeout;
timerctl.fifo = fifo;
timerctl.data = data;
io_store_eflags(eflags);
}
之后,主函数里就又添加了一个中断分支。。设置缓冲区、设置定时器等等。
设定多个定时器
harib09d
类似图层那种管理方法,定时器也是定时器+定时器管理
#define MAX_TIMER 500
struct TIMER
{
unsigned int timeout, flags;
struct FIFO8 *fifo;
unsigned char data;
};
struct TIMERCTL
{
unsigned int count;
struct TIMER timer[MAX_TIMER];
};
有种图层管理的感觉了,而且根据时刻来排序,也可以把各种函数都做成类似的感觉。。
但是呢,作者还是一步步来的,所以我们到最后一步再放代码吧。。
于是,就有了计时器管理的初始化、分配、回收。
然后就可以制作光标的闪烁过程。。过程略。
加快中断处理(1)
harib09e
把时间段改为时刻,很好的优化想法。。过程略)
加快中断处理(2)
harib09f
其实不算严格意义的排序,只是先去查看最近一个要超时的,有没有超时。但是这样在超时处理中,还是会占用很多时间
加快中断处理(3)
harib09g
排序计时器,这样就很快啦。。
最终版本的数据结构:
#define MAX_TIMER 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];
};
和图层看起来就是很相似。。
最终版本的中断服务程序:
void inthandler20(int *esp)
{
int i, j;
io_out8(PIC0_OCW2, 0x60); // 把IRQ-00信号接收结束的信息通知给PIC
timerctl.count++;
if(timerctl.next > timerctl.count)
{
return;
}
for(i = 0; i < timerctl.using; ++i)
{
// timer的定时器都处于动作中,所以不确认flags
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);
}
// 正好有i个定时器超时了,其余的进行移位
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;
}
}
初始化程序:
void init_pit(void)
{
int i;
io_out8(PIT_CTRL, 0x34);
io_out8(PIT_CNT0, 0x9c);
io_out8(PIT_CNT0, 0x2e);
timerctl.count = 0;
timerctl.next = 0xffffffff; // 最初没有正在运行的定时器
timerctl.using = 0;
for(i = 0; i < MAX_TIMER; ++i)
{
timerctl.timers0[i].flags = 0; // 未使用
}
}
分配和释放:
struct TIMER *timer_alloc(void)
{
int i;
for(i = 0; i < MAX_TIMER; ++i)
{
if(timerctl.timers0[i].flags == 0)
{
timerctl.timers0[i].flags = TIMER_FLAGS_ALLOC;
return &timerctl.timers0[i];
}
}
return 0; // 没找到
}
void timer_free(struct TIMER *timer)
{
timer->flags = 0; // 未使用
}
设定定时器:
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;
}
}
// i号之后全部后移一位
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;
}
有了之前图层的学习。。这些代码看起来简单多了。。