Day12

今天引入了定时器这个功能,并且优化了定时器中断的算法

一些感想和问题

加快中断处理

到这一步的时候,已经有了很多个定时器了,但是此时查询是否timeout的方式是for循环查看一遍所有的定时器,并且计时的方式是不断通过0.1s(类似)的中断,来–count。那么。。就从这两个方面来优化中断处理。

  1. 把逐渐减少改为是否到达
    显然,中断时不断–count的操作,会浪费掉很多内存读写的时间。所以修改了timeout的含义,把它理解为“要在什么时候timeout”。之后每次中断,只要查询是否已经到达该timeout时刻即可。
  2. 对到达时间排序,依次查找
    虽然解决了不断–的问题,但是for循环还是太慢了。。刚好不同定时器的到达时刻有先后顺序,我们以此来设定一个next变量,来记住下一个时刻。这样每次中断的时候,先判断一下是否到了next时刻,未到达就直接退出即可。
  3. 继续上文
    但是,对于到达的时刻,我们还是需要for循环来进行查找,这样可能会造成处理突然卡顿的情况。所以,可以把定时器按照到达时刻排序。每次中断的时候,从前往后查找是否已经timeout即可。毕竟在一个已经有序的数组里来判断还是更方便的。

可能的优化?

  1. 过长的主函数
    每有一个缓冲区内容,就需要添加一个else if。。这样下去岂不是会变得很长。。
  2. 防止移位带来的时间浪费
    应该可以用链表来代替有序的数组,对于不断释放定时器来说简直很方便。

以上两点是我可以想到的优化,如果作者到这本书结束还没优化的话。。那可以试试手)

今天份的内容

使用定时器

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

有了之前图层的学习。。这些代码看起来简单多了。。