Day13

今天的主要工作是精简代码、优化代码。

一些感想与问题

合并缓冲区

果然。。昨天提到的判断多个timer太占地方,这里就开始优化了。

  1. timer共用一个缓冲区
    当定时器超时的时候,定时器只会发送一个1bit的timeout信息。我们可以把它们压缩到一起,这样n个定时器只会占用logn位。
  2. 所有中断共用一个缓冲区
    我们把所有中断产生的向缓冲区存储的信息,分段放入缓冲区中,这个时候8位缓冲区就不那么够用了。改成32位。
    0-255:定时器
    256-511:键盘输入
    512-767:鼠标输入
    把不同区间分给不同的中断,检查是否有缓冲区信息的时候,检查对应区间就可以了。
    虽然会增加稍许加减法之类的开销,但是比起逐个检查缓冲区来说,这样的开销还是值得的。

链表代替数组

昨天提到的移位太麻烦了,果然,作者就用链表了。
在链表最后,作者为了防止太多的特判情况,加了一个哨兵timmer(该定时器虽然是在运行中,但是永远不会到达)。

测试性能

因为我们有了定时器,就可以测试一段代码的执行效率了。
例如,测试昨天的中断效率是否有改进,就可以在3s的时候开始++count,10s的时候停止count。通过count的多少就可以反映出来程序的运行效率了(count反映了空闲时间)。
至于为什么要3s才开始计时,是因为之前可能会有各种东西的初始化。所以预留3s再计时,可以防止初始化带来的影响。

统一类型的重要性

看到改FIFO的时候,把所以地方的char改成了int,这时候就明白了

typedef unsigned int size_t;

的重要性。
所以为什么windows会把所有东西都typedef了一遍。。

没有用define或者const做到统一修改

观察到在设定偏移的时候,作者在初始化(比如键盘的初始化)的时候写了一遍256,然后在中断判断的时候是

if(256 <= i && i <= 511)

如果能把各种区间提前define好,这样以后修改会方便一些。

今天的代码。。emmm大多是优化。。

简化字符串显示

harib10a

对于打印字符串这个函数,需要背景色+字符串+刷新连着三个函数,所以我们把它们放一起用。

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

重新调整FIFO缓冲区(1)

harib10b

把timer对应上不同的标号,放到缓冲区中。这样就不需要好多个timerfifo了。

测试性能

harib10c
harib10d
harib10e
harib10f

测试性能的方法之前已经说了,于是把昨天不断优化的版本放到这里,确实会有一些略微的优化。

重新调整FIFO缓冲区(2)

harib10g

把char改成了int,于是fifo、各类中断、定义、主函数都需要修改一下。
当所有中断共用一个缓冲区后,系统的执行效率也得到了显著的提升(因为只有一个缓冲区,不会进行过多的查询)。

加快中断处理(4)

harib10h

因为担心移位操作,所以用链表来代替数组好了。

#define MAX_TIMER 500
struct TIMER
{
    struct TIMER *next_timer;
    unsigned int timeout, flags;
    struct FIFO32 *fifo;
    int data;
};

struct TIMERCTL
{
    unsigned int count, next_time;
    struct TIMER *t0; // 其实是链表的头指针
    struct TIMER timers0[MAX_TIMER];
};

因为这里还没有用到哨兵,所以先不放其它代码。。

使用“哨兵”简化程序

harib10i

用了哨兵程序,可以简化判断逻辑
有时候我们为了再简化逻辑,可能会把头指针变成一个头结点。但是这里不太适合用头结点,因为头结点的timeout会显得比较逻辑奇怪。

void init_pit()
{
    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_timer = 0; // 末尾
    timerctl.t0 = t; // 因为现在只有哨兵,所以他就在最前面
    timerctl.next_time = 0xffffffff; // 因为只有哨兵,所以下一个超时的时刻就是哨兵的时刻
}
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_timer = t; // 下面是设定t
        timerctl.next_time = timer->timeout;
        io_store_eflags(e);
        return;
    }
    // 搜寻插入位置
    for(;;)
    {
        s = t;
        t = t->next_timer;
        if(timer->timeout <= t->timeout)
        {
            // 插入s和t之间的情况
            s->next_timer = timer; // s下一个是timer
            timer->next_timer = t; // timer的下一个是t
            io_store_eflags(e);
            return;
        }
    }
}
void inthandler20(int *esp)
{
    struct TIMER *timer;
    io_out8(PIC0_OCW2, 0x60); // 把IRQ-00接收信号结束的信息通知给PIC
    timerctl.count++;
    if(timerctl.next_time > timerctl.count)
    {
        return;
    }
    timer = timerctl.t0; // 首先把最前面的地址赋给timer
    for(;;)
    {
        // 因为timer的定时器都处于运行状态,所以不确认flags
        if(timer->timeout > timerctl.count)
        {
            break;
        }
        // 超时
        timer->flags = TIMER_FLAGS_ALLOC;
        fifo32_put(timer->fifo, timer->data);
        timer = timer->next_timer; // 将下一定时器的地址代入timer
    }
    timerctl.t0 = timer;
    timerctl.next_time = timer->timeout;
    return;
}

这样就算是一个比较好的优化效果了吧。。