30天自制操作系统-学习笔记 day11~day20

20220327

鼠标显示问题(harib08a)

一开始,对鼠标图层界定范围代码

                    if (mx < 0) {
                        mx = 0;
                    }
                    if (my < 0) {
                        my = 0;
                    }
                    if (mx > binfo->scrnx - 16) {
                        mx = binfo->scrnx - 16;
                    }
                    if (my > binfo->scrny - 16) {
                        my = binfo->scrny - 16;
                    }

其在边缘处可移动的范围如下

image-20220327100442803

而实际上我们需要让其移动范围如下

image-20220327100749757

修改主函数的代码如下

if (mx > binfo->scrnx - 1) {
mx = binfo->scrnx - 1;
}i
f (my > binfo->scrny - 1) {
my = binfo->scrny - 1;
}

在移动到右侧边缘时,产生如下bug

image-20220327101915810

关键处在此

vram[vy * ctl->xsize + vx] = c;

实现画面外的支持(harib08b)

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;
    /* 如果refresh的范围超出了画面则修正 */
    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++) {
        (中略)
    }
    return;
}

在for循环前新增了判断,如果图层超过了底板的范围,则将需要刷新的区域限制到底板范围以内

显示窗口harib08d

创建一个新的图层,用来存储窗口即可

void make_window8(unsigned char *buf, int xsize, int ysize, char *title)
{
    static char closebtn[14][16] = {
        "OOOOOOOOOOOOOOO@",
        "OQQQQQQQQQQQQQ$@",
        "OQQQQQQQQQQQQQ$@",
        "OQQQ@@QQQQ@@QQ$@",
        "OQQQQ@@QQ@@QQQ$@",
        "OQQQQQ@@@@QQQQ$@",
        "OQQQQQQ@@QQQQQ$@",
        "OQQQQQ@@@@QQQQ$@",
        "OQQQQ@@QQ@@QQQ$@",
        "OQQQ@@QQQQ@@QQ$@",
        "OQQQQQQQQQQQQQ$@",
        "OQQQQQQQQQQQQQ$@",
        "O$$$$$$$$$$$$$$@",
        "@@@@@@@@@@@@@@@@"
    };
    int x, y;
    char c;
    boxfill8(buf, xsize, COL8_C6C6C6, 0,         0,         xsize - 1, 0        );
    boxfill8(buf, xsize, COL8_FFFFFF, 1,         1,         xsize - 2, 1        );
    boxfill8(buf, xsize, COL8_C6C6C6, 0,         0,         0,         ysize - 1);
    boxfill8(buf, xsize, COL8_FFFFFF, 1,         1,         1,         ysize - 2);
    boxfill8(buf, xsize, COL8_848484, xsize - 2, 1,         xsize - 2, ysize - 2);
    boxfill8(buf, xsize, COL8_000000, xsize - 1, 0,         xsize - 1, ysize - 1);
    boxfill8(buf, xsize, COL8_C6C6C6, 2,         2,         xsize - 3, ysize - 3);
    boxfill8(buf, xsize, COL8_000084, 3,         3,         xsize - 4, 20       );
    boxfill8(buf, xsize, COL8_848484, 1,         ysize - 2, xsize - 2, ysize - 2);
    boxfill8(buf, xsize, COL8_000000, 0,         ysize - 1, xsize - 1, ysize - 1);
    putfonts8_asc(buf, xsize, 24, 4, COL8_FFFFFF, title);
    for (y = 0; y < 14; y++) {
        for (x = 0; x < 16; x++) {
            c = closebtn[y][x];
            if (c == '@') {
                c = COL8_000000;
            } else if (c == '$') {
                c = COL8_848484;
            } else if (c == 'Q') {
                c = COL8_C6C6C6;
            } else {
                c = COL8_FFFFFF;
            }
            buf[(5 + y) * xsize + (xsize - 21 + x)] = c;
        }
    }
    return;
}

主程序修改

void HariMain(void)
{
(中略)
struct SHEET *sht_back, *sht_mouse, *sht_win; /* 这里! */
unsigned char *buf_back, buf_mouse[256], *buf_win; /* 这里! */
(中略)
init_palette();
shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny);
sht_back = sheet_alloc(shtctl);
sht_mouse = sheet_alloc(shtctl);
sht_win = sheet_alloc(shtctl); /* 这里! */
buf_back = (unsigned char *) memman_alloc_4k(memman, binfo->scrnx * binfo->scrny);
buf_win = (unsigned char *) memman_alloc_4k(memman, 160 * 68); /* 这里! */
sheet_setbuf(sht_back, buf_back, binfo->scrnx, binfo->scrny, -1); /* 没有透明
色 */
sheet_setbuf(sht_mouse, buf_mouse, 16, 16, 99);
sheet_setbuf(sht_win, buf_win, 160, 68, -1); /* 没有透明色 */ /* 这里! */
init_screen8(buf_back, binfo->scrnx, binfo->scrny);
init_mouse_cursor8(buf_mouse, 99);make_window8(buf_win, 160, 68, "window"); /* 这里! */
putfonts8_asc(buf_win, 160, 24, 28, COL8_000000, "Welcome to"); /* 这里! */
putfonts8_asc(buf_win, 160, 24, 44, COL8_000000, " Haribote-OS!"); /* 这里!
*/
sheet_slide(sht_back, 0, 0);
mx = (binfo->scrnx - 16) / 2; /* 为使其处于画面的中央位置, 计算坐标 */
my = (binfo->scrny - 28 - 16) / 2;
sheet_slide(sht_mouse, mx, my);
sheet_slide(sht_win, 80, 72); /* 这里! */
sheet_updown(sht_back, 0);
sheet_updown(sht_win, 1); /* 这里! */
sheet_updown(sht_mouse, 2);
sprintf(s, "(%3d, %3d)", mx, my);
putfonts8_asc(buf_back, binfo->scrnx, 0, 0, COL8_FFFFFF, s);
sprintf(s, "memory %dMB free : %dKB",memtotal / (1024 * 1024), memman_total(memman) / 1024);
putfonts8_asc(buf_back, binfo->scrnx, 0, 32, COL8_FFFFFF, s);
sheet_refresh(sht_back, 0, 0, binfo->scrnx, 48);
(中略)
}

高速计数器harib08f

for (;;) {
    count++; /* 从这里开始 */
    sprintf(s, "%010d", 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); /* 到这里结束 */
    io_cli();
    if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {
        io_sti(); /* 原来是io_stihlt(),现在不做HLT */ 
    } else {
        (中略)
    }
}

但是在实际运行中,会出现闪烁的现象。

为什么会出现这种现象呢? 这是由于在刷新的时候, 总是先刷新refresh
范围内的背景图层, 然后再刷新窗口图层, 所以肯定就会闪烁了。 可是
我们使用Windows的时候就没见过这种闪烁, 因此肯定有什么好的解决
方法。

消除闪烁(1) (harib08g)

窗口图层刷新是因为窗口的内容有变化, 所以要在画面上显示变化后的新内容。 基本上来讲, 可以认为其他图层的内容没有变化(如果其他图层的内容也变了, 那么应该会随后执行该图层的刷新) 。

综上所述, 仅对refresh对象及其以上的图层进行刷新就可以了。

void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int
h0)
{
    (中略)
    for (h = h0; h <= ctl->top; h++) {
    (中略)
}
    return;
}

引入了一个新的参数h0,代表该图层的高度

数字部分的背景闪烁问题是解决了, 可是把鼠标放在上面时, 鼠标又闪
烁起来了

消除闪烁(2) (harib08h)

闪烁现象是由于一会儿描绘一会儿消除造成的。 所以说要想消除闪烁, 就要在刷新窗口时避开鼠标所在的地方
对VRAM进行写入处理。

定义一个跟vram大小相同的map,这块内存用来表示画面上的点是哪个图层的像素, 所以它就相当于是图
层的地图。

image-20220328110633925

struct SHTCTL {
    unsigned char *vram, *map; /* 这里! */
    int xsize, ysize, top;
    struct SHEET *sheets[MAX_SHEETS];
    struct SHEET sheets0[MAX_SHEETS];
};
struct SHTCTL *shtctl_init(struct MEMMAN *memman, unsigned char *vram, int xsize,
int ysize)
{
    struct SHTCTL *ctl;
    int i;
    ctl = (struct SHTCTL *) memman_alloc_4k(memman, sizeof (struct SHTCTL));
    if (ctl == 0) {
        goto err;
    }/
    * 从这里开始 */
    ctl->map = (unsigned char *) memman_alloc_4k(memman, xsize * ysize);
    if (ctl->map == 0) {
        memman_free_4k(memman, (int) ctl, sizeof (struct SHTCTL));
        goto err;
    }/
    * 到这里结束 */
    ctl->vram = vram;
    ctl->xsize = xsize;
    ctl->ysize = ysize;
    ctl->top = -1; /* 没有一张SHEET */
    for (i = 0; i < MAX_SHEETS; i++) {ctl->sheets0[i].flags = 0; /* 未使用标记 */
        ctl->sheets0[i].ctl = ctl; /* 记录所属 */
    }
    err:
    return ctl;
}

记录 图层位置

void sheet_refreshmap(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int
h0)
{
    int h, bx, by, vx, vy, bx0, by0, bx1, by1;
    unsigned char *buf, sid, *map = ctl->map;
    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 = h0; h <= ctl->top; h++) {
        sht = ctl->sheets[h];
        sid = sht - ctl->sheets0; /* 将进行了减法计算的地址作为图层号码使用 */
        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;
                    if (buf[by * sht->bxsize + bx] != sht->col_inv) {
                        map[vy * ctl->xsize + vx] = sid;
                    }
                }
            }
    }
    return;
}

这个函数与以前的refreshsub函数基本一样, 只是用色号代替了图层号码而已。

void sheet_refreshsub(struct SHTCTL *ctl, int vx0, int vy0, int vx1, int vy1, int
h0, int h1)
{
    int h, bx, by, vx, vy, bx0, by0, bx1, by1;
    unsigned char *buf, *vram = ctl->vram, *map = ctl->map, sid;
    struct SHEET *sht;
    /* 如果refresh的范围超出了画面则修正*/
    (中略)
    for (h = h0; h <= h1; h++) {
        sht = ctl->sheets[h];
        buf = sht->buf;
        sid = sht - ctl->sheets0;
        /* 利用vx0~vy1, 对bx0~by1进行倒推 */
        (中略)
        for (by = by0; by < by1; by++) {
            vy = sht->vy0 + by;
            for (bx = bx0; bx < bx1; bx++) {
                vx = sht->vx0 + bx;
                if (map[vy * ctl->xsize + vx] == sid) {
                vram[vy * ctl->xsize + vx] = buf[by * sht->bxsize + bx];
                }
            }
        }
    }
    return;
}

以后每次刷新区域时,都先对照一下map中该区域该像素是否为该图层的显示区域。

对调用sheet_refreshsub的函数进行修改

刷新指定图层指定区域,由于图层的上下关系没有改变, 所以不需要重新进行refreshmap的处理。

void sheet_refresh(struct SHEET *sht, int bx0, int by0, int bx1, int by1)
{
    if (sht->height >= 0) { /* 如果正在显示, 则按新图层的信息进行刷新 */
        sheet_refreshsub(sht->ctl, sht->vx0 + bx0, sht->vy0 + by0, sht->vx0 +
        bx1, sht->vy0 + by1,
        sht->height, sht->height);
    }
    return;
}

移动指定图层 ,移动后,部分下级图层会漏出来,部分图层会被隐藏,因此要对图层移动前的区域及图层移动后的区域重新刷新map。最后调用sheet_refreshsub将两部分区域刷新即可。

void sheet_slide(struct SHEET *sht, int vx0, int vy0)
{
    struct SHTCTL *ctl = sht->ctl;
    int old_vx0 = sht->vx0, old_vy0 = sht->vy0;
    sht->vx0 = vx0;
    sht->vy0 = vy0;
    if (sht->height >= 0) { /* 如果正在显示, 则按新图层的信息进行刷新 */
        sheet_refreshmap(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 +sht->bysize, 0);
        sheet_refreshmap(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize,sht->height);
        sheet_refreshsub(ctl, old_vx0, old_vy0, old_vx0 + sht->bxsize, old_vy0 +sht->bysize, 0,sht->height - 1);
        sheet_refreshsub(ctl, vx0, vy0, vx0 + sht->bxsize, vy0 + sht->bysize,sht->height,sht->height);
    }
    return;
}

20220328

使用定时器harib09a

定时器原理

每隔一段时间(比如0.01秒)就发送一个中断信号给CPU,

CPU可以在中断处理程序中记录下中断次数,

其他程序就可以通过获取这个前后的次数来进行时间间隔的判断。

管理定时器

要在电脑中管理定时器, 只需对PIT进行设定就可以了。 PIT是“Programmable Interval Timer”的缩写, 翻译过来就是“可编程的间隔型定时器”。 我们可以通过设定PIT, 让定时器每隔多少秒就产生一次中断。因为在电脑中PIT连接着IRQ(interrupt request, 参考第6章) 的0号, 所以只要设定了PIT就可以设定IRQ0的中断间隔。

IRQ0的中断周期变更:

  1. AL=0x34:OUT(0x43,AL);
  2. AL=中断周期的低8位; OUT(0x40,AL);
  3. AL=中断周期的高8位; OUT(0x40,AL);
  4. 到这里告一段落。
  5. 如果指定中断周期为0, 会被看作是指定为65536。 实际的中断产
    生的频率是单位时间时钟周期数(即主频) /设定的数值。 比如
    设定值如果是1000, 那么中断产生的频率就是1.19318KHz。 设
    定值是10000的话, 中断产生频率就是119.318Hz。 再比如设定值
    是11932的话, 中断产生的频率大约就是100Hz了, 即每10ms发
    生一次中断。

初始化定时器

#define PIT_CTRL 0x0043
#define PIT_CNT0 0x0040
void init_pit(void)
{
    io_out8(PIT_CTRL, 0x34);
    io_out8(PIT_CNT0, 0x9c);
    io_out8(PIT_CNT0, 0x2e);
    return;
}
init_pit(); /* 这里! */
io_out8(PIC0_IMR, 0xf8); /* PIT和PIC1和键盘设置为许可(11111000) */ /* 这里! */
io_out8(PIC1_IMR, 0xef); /* 鼠标设置为许可(11101111) */

IRQ0中断处理程序

void inthandler20(int *esp)
{
io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收完了的信息通知给PIC */
/* 暂时什么也不做 */
return;
}
_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

注册到IDT中

void init_gdtidt(void)
{
(中略)
    /* IDT的设定 */
    set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32); /* 这
    里! */
    set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
    set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
    set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
    return;
}

计量时间harib09b

struct TIMERCTL {
unsigned int count;
};
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; /* 这里! */
    return;
} 
void inthandler20(int *esp)
{
    io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00信号接收完了的信息通知给PIC */
    timerctl.count++; /* 这里! */
    return;
}

每次中断处理都让其+1

也即每1秒它都会自动增加100

void HariMain(void)
{
    (中略)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);
        (中略)
    }
}

超时功能

#include "bootpack.h"

#define PIT_CTRL    0x0043
#define PIT_CNT0    0x0040

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;
    timerctl.timeout = 0;
    return;
}

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

void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data)
{
    int eflags;
    eflags = io_load_eflags();
    io_cli();
    timerctl.timeout = timeout;
    timerctl.fifo = fifo;
    timerctl.data = data;
    io_store_eflags(eflags);
    return;
}

程序若需要延时,就通过settimer来进行设置,fifo和data用于计时器计时结束后和程序交互信息。

定时器在中断处理程序中检查是否有需要延时的程序(根据timeout的值)

验证程序

void HariMain(void)
{
    (中略)
    struct FIFO8 timerfifo;
    char s[40], keybuf[32], mousebuf[128], timerbuf[8];
    (中略)
    fifo8_init(&timerfifo, 8, timerbuf);
    settimer(1000, &timerfifo, 1);
    (中略)
    for (;;) {
            (中略)
            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();
                    putfonts8_asc(buf_back, binfo->scrnx, 0, 64, COL8_FFFFFF,"10[sec]");
                    sheet_refresh(sht_back, 0, 64, 56, 80);
            }
        }
    }
}

多个定时器 hraib09d

定义一个定时器集合数组,

定义 分配、释放定时器函数

中断处理函数中对已分配的定时器进行减一,若减到0,则向fifo中写入

#define PIT_CTRL    0x0043
#define PIT_CNT0    0x0040

struct TIMERCTL timerctl;

#define TIMER_FLAGS_ALLOC        1    /* 定时器已计时完毕标志*/
#define TIMER_FLAGS_USING        2    /* 定时器正在使用标志 */

void init_pit(void)
{
    int i;
    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.timer[i].flags = 0; /* ���g�p */
    }
    return;
}

struct TIMER *timer_alloc(void)
{
    int i;
    for (i = 0; i < MAX_TIMER; i++) {
        if (timerctl.timer[i].flags == 0) {
            timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
            return &timerctl.timer[i];
        }
    }
    return 0; /* 未找到空闲定时器 */
}

void timer_free(struct TIMER *timer)
{
    timer->flags = 0; /* 设置为空闲 */
    return;
}

void timer_init(struct TIMER *timer, struct FIFO8 *fifo, unsigned char data)
{
    timer->fifo = fifo;
    timer->data = data;
    return;
}

void timer_settime(struct TIMER *timer, unsigned int timeout)
{
    timer->timeout = timeout;
    timer->flags = TIMER_FLAGS_USING;
    return;
}

void inthandler20(int *esp)
{
    int i;
    io_out8(PIC0_OCW2, 0x60);    /* 把IRQ-00信号接收结束的信息通知给PIC */
    timerctl.count++;
    for (i = 0; i < MAX_TIMER; i++) {
        if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
            timerctl.timer[i].timeout--;
            if (timerctl.timer[i].timeout == 0) {
                timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
                fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
            }
        }
    }
    return;
}

加快中断处理 harib09e

在timerctl 增加一个next 和count

count 每次中断自增1 ,并和next作比较,若小于next,则计时都没有结束,直接返回。

若到达next,则将超时的进行标记, 遍历timerctl,记录下一个最近的next

在使用计时器时,将计时器的count和 timerctl中的next大小作比较, 保证next为最小计时器的值

加快中断处理(3) (harib09g)

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++) {
        /* timers的定时器都处于动作中, 所以不确认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;
    }
    return;
}

排序

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();
    /* �ǂ��ɓ����΂�������T�� */
    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;
}

20220331

day13

调整缓冲区harib10b

每个定时器都分配一个缓冲区有点过于浪费,其实只要在超时的情况下FIFO写入不同的数据,就可以正常地分辨出是哪个定时器超时了。

20220401

day13

加快中断处理

在timer_settime和计时器中断处理程序inthandler20中,新计时器的插入处理占用了很长时间,虽然timer_settime不是中断处理程序,但毕竟是在中断禁止期间进行的,所以必须要迅速完成。

其实线性的存储方式(插入慢)并不适合这种模式,我们可以考虑使用链表(插入快)这一数据结构

在TIMER结构体,新增一个TIMER指针变量,它指向当前定时器临近的下一个定时器。

image-20220401102711590

同时我们修改TIMERCTL结构体中的timers数字, 现在我们只需要知道一个当前计时器的位置就可以了,因此将其修改成单个指针

struct TIMERCTL {
    unsigned int count, next, using;
    struct TIMER *t0;
    struct TIMER timers0[MAX_TIMER];
};
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) {
        /* 处于运行状态的定时器只有这一个时 */
        timerctl.timers[0] = timer;
        timer->next = 0; /* 没有下一个 */
        timerctl.next = timer->timeout;
        io_store_eflags(e);
        return;
    }
    t= timerctl.timers[0];//临时存储第一个计时器位置
    if (timer->timeout <= t->timeout) {
        /* 插入最前面的情况下 */
        timerctl.timers[0] = 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; /* s的下一个是timer */
            timer->next = t; /* timer的下一个是t */
            io_store_eflags(e);
            return;
        }
    }
    /* 插入最后面的情况下 */
    s->next = timer;
    timer->next = 0;
    io_store_eflags(e);
    return;
}
void inthandler20(int *esp)
{
    int i;
    struct TIMER *timer;
    io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00接收信号结束的信息通知给PIC */
    timerctl.count++;
    if (timerctl.next > timerctl.count) {
        return;
    }
    timer = timerctl.timers[0]; /* 首先把最前面的地址赋给timer */
    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; /* 下一定时器的地址赋给timer */
    }
    timerctl.using -= i;
    /* 新移位 */
    timerctl.timers[0] = timer;
    /* timerctl.next的设定 */
    if (timerctl.using > 0) {
        timerctl.next = timerctl.timers[0]->timeout;
    } else {
        timerctl.next = 0xffffffff;
    }
    return;
}

使用“哨兵”简化程序(harib10i)

在timer_settime函数有四种可能

  • 运行中的定时器只有一个的情况
  • 插入到最前面的情况
  • 插入到s和t之间的情况
  • 插入到最后面的情况

在进行初始化的时候,将时刻0xffffffff的定时器连到最后一个定时器上。

它一直处于后面,只是一个附带物,这就是 “ 哨兵”

加入哨兵后,有两种情况不再可能出现

  • 处于运行中的定时器只有这1个的情况(因为有哨兵, 所以最少应该有2个)
  • 插入最前面的情况
  • 插入s和t之间的情况
  • 插入最后的情况(哨兵总是在最后)
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; /* 因为只有哨兵, 所以下一个超时时刻就是哨兵的时刻 */
    timerctl.using = 1;
    return;
}
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++;
    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 (timer->timeout <= t->timeout) {
            /* 插入s和t之间的情况 */
            s->next = timer; /* s下一个是timer */
            timer->next = t; /* timer的下一个是t */
            io_store_eflags(e);
            return;
        }
    }
}
void inthandler20(int *esp)
{
    int i;
    struct TIMER *timer;
    io_out8(PIC0_OCW2, 0x60); /* 把IRQ-00接收信号结束的信息通知给PIC */
    timerctl.count++;
    if (timerctl.next > timerctl.count) {
        return;
    }
    timer = timerctl.t0; /* 首先把最前面的地址赋给timer */
    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; /* 将下一定时器的地址代入timer*/
    }
    timerctl.using -= i;
    /* 新移位 */
    timerctl.t0 = timer;
    /* timerctl.next的设定 */ /* 这里! */
    timerctl.next = timerctl.t0->timeout;
    return;
}

在总结一次操作系统的执行流程

首先是映像文件img

img文件总大小为1440KB

在最初的512字节中, 存储着启动区ipl代码 程序在此执行。

我们通过工具将其他文件拷入img时,文件名会存放在0x002600以后的地方,文件的内容会写在0x004200以后的地方 。

haribote.img : ipl10.bin haribote.sys Makefile
    $(EDIMG)   imgin:../z_tools/fdimg0at.tek \
        wbinimg src:ipl10.bin len:512 from:0 to:0 \
        copy from:haribote.sys to:@: \
        imgout:haribote.img

部分标记

在IPL中要将启动区往后的10个柱面的代码(180KB)拷贝入内存中(从0x8000开始到0x34fff )

因为拷入到内存中,基础的地址为0x8000, 且文件在img中起始地址为0x4200,两者之和为0xc200 因此

  1. 在IPL的最后,要跳到haribote.sys ,执行命令JMP 0xc200
  2. 在haribote.sys中,要加入ORG 0xc200命令,防止地址转换出现问题。

然后映像的执行

  1. CPU会读取映像的前512个字节,根据代码将启动区后的10个扇区读入内存0x8000开始的位置
  2. 执行JMP 0xc200进入操作系统主程序

首先执行的是asmhead.nas

  1. 关闭所有中断
  2. 为了让CPU能够访问1MB以上的内存空间, 设定A20GATE
  3. 创建一个临时的GDT, 包含两个段
  4. 将IPL 、bootpack等代码载入指定的内存中
  5. 最后跳转到主程序区(0x280000)继续执行

主程序

首先会初始化GDT IDT 、初始化键盘鼠标、绘制GUI等操作。

进入循环阶段,处理中断产生的信息并绘制图层。

20220406

day 14

提高分辨率

确认VBE是否存在

; 确认VBE是否存在
    MOV AX,0x9000
    MOV ES,AX
    MOV DI,0
    MOV AX,0x4f00
    INT 0x10
    CMP AX,0x004f
    JNE scrn320

通过给ES赋值0x9000 ,DI赋值0 ,AX赋值0x4f00 然后执行INT 0x10,若VBE存在,AX就会变为0x004f

确认VBE版本

MOV AX,[ES:DI+4]
CMP AX,0x0200
JB scrn320 ; if (AX < 0x0200) goto scrn320

获取画面模式的信息

VBEMODE EQU 0x105

MOV CX,VBEMODE
MOV AX,0x4f01
INT 0x10
CMP AX,0x004f
JNE scrn320

此次取得的画面模式信息会写入内存中从ES:DI 开始的256字节中。

确认画面模式信息无误后,即可将信息拷入BOOTINFO中了

; 画面模式的切换
    MOV BX,VBEMODE+0x4000
    MOV AX,0x4f02
    INT 0x10
    MOV BYTE [VMODE],8 ; 记下画面模式(参考C语言)
    MOV AX,[ES:DI+0x12]
    MOV [SCRNX],AX
    MOV AX,[ES:DI+0x14]
    MOV [SCRNY],AX
    MOV EAX,[ES:DI+0x28]
    MOV [VRAM],EAX
    JMP keystatus

键盘输入 harib11f

static char keytable[0x54] = {
0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '^', 0, 0,'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '@', '[', 0, 0, 'A','S','D', 'F', 'G','H', 'J', 'K', 'L', ';', ':', 0, 0, ']', 'Z', 'X', 'C','V','B', 'N', 'M', ',', '.', '/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+','1','2', '3', '0', '.'};
if (256 <= i && i <= 511) { /* 键盘数据 */
    sprintf(s, "%02X", i - 256);
    putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2);
if (i < 256 + 0x54) {
        if (keytable[i - 256] != 0) {
            s[0] = keytable[i - 256];
            s[1] = 0;
            putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, s, 1);
        }
    }
} else if (512 <= i && i <= 767) { /* 鼠标数据 */

20220407

day15

多任务

当你向CPU发出任务切换的指令时, CPU会先把寄存器中的值全部写入
内存中, 这样做是为了当以后切换回这个程序的时候, 可以从中断的地
方继续运行。 接下来, 为了运行下一个程序, CPU会把所有寄存器中的
值从内存中读取出来(当然, 这个读取的地址和刚刚写入的地址一定是
不同的, 不然就相当于什么都没变嘛) , 这样就完成了一次切换。 我们
前面所说的任务切换所需要的时间, 正是对内存进行写入和读取操作所
消耗的时间。

  1. CPU将寄存器中的值写入内存中
  2. 将另一个任务的寄存器的值从内存中加载

定义任务状态段(task satus segment)

struct TSS32 {
    int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;
    int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;
    int es, cs, ss, ds, fs, gs;
    int ldtr, iomap;
};

TSS共包含26个int成员,总计104字节

第一行保存的不是寄存器数据,而是与任务设置相关的信息,在回字形任务切换的时候这些成员不会被写入(backlink 除外,某些情况下是会被写入的)

第二行成员是32位寄存器,第三行是16位寄存器。 EIP(Extended Instruction Pointer扩展指令指针寄存器) 寄存器用来记录下一条需要执行的指令位于内存中的那个地址的。 每执行一条指令,EIP寄存器中的值就会自动累加,从而保证一直指向下一条指令所在的内存地址。

汇编中的JMP跳转指令,实际上就是通过JMP间接修改EIP的值来达到目的。

第四行 和第一行一样,是存储有关任务设置的部分。

切换任务

JMP指令分两种

  1. 只改写EIP(near模式)
  2. 同时改写EIP和CS(far模式)

如在asmhead.nas的bootpack启动的最后一句

JMP DWORD 2*8:0x0000001b 

这条指令在想EIP存入0x1b同时将CS置为2*8(=16) 。 像这样在JMP目标地址中带冒号(:) 的, 就是far模式的JMP指令。

如果一条JMP指令所指定的目标地址段不是可执行的代码, 而是TSS的
话, CPU就不会执行通常的改写EIP和CS的操作, 而是将这条指令理解
为任务切换。 也就是说, CPU会切换到目标TSS所指定的任务, 说白
了, 就是JMP到一个任务那里去了。

CPU每次执行带有段地址的指令时, 都会去确认一下GDT中的设置, 以
便判断接下来要执行的JMP指令到底是普通的far-JMP, 还是任务切换。
也就是说, 从汇编程序翻译出来的机器语言来看, 普通的far-JMP和任
务切换的far-JMP, 指令本身是没有任何区别的。

代码实践

创建两个TSS ,分别为任务A的TSS 、任务B的TSS

struct TSS32 tss_a, tss_b ;

向他们的LDTR和IOMAP中写入合适的值

tss_a.ldtr = 0;
tss_a.iomap = 0x40000000;
tss_b.ldtr = 0;
tss_b.iomap = 0x40000000;

将这两个任务在GDT中定义

struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
set_segmdesc(gdt + 3, 103, (int) &tss_a, AR_TSS32);
set_segmdesc(gdt + 4, 103, (int) &tss_b, AR_TSS32)

void set_segmdesc(struct SEGMENT_DESCRIPTOR *sd, unsigned int limit, int base, int ar)

段长限制为103字节,基础地址为该函数在内存中的地址。

然后向TR寄存器(task register 任务寄存器 ,作用是让CPU记住当前正在运行哪一个任务)写入 3*8这个值,用于将当前运行的任务定义为GDT的3号。

无法通过C语言进行TR寄存器赋值。需要使用汇编的LTR指令 load_tr(3 * 8);


_load_tr: ; void load_tr(int tr);
LTR [ESP+4] ; tr
RET

要进行任务切换需要使用far模式的JMP指令

_taskswitch4: ; void taskswitch4(void);
    JMP 4*8:0
    RET

jmp far模式过程https://blog.csdn.net/jadeshu/article/details/112074269

tss_b准备

    task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;
    tss_b.eip = (int) &task_b_main;
    tss_b.eflags = 0x00000202; /* IF = 1; */
    tss_b.eax = 0;
    tss_b.ecx = 0;
    tss_b.edx = 0;
    tss_b.ebx = 0;
    tss_b.esp = task_b_esp;
    tss_b.ebp = 0;
    tss_b.esi = 0;
    tss_b.edi = 0;
    tss_b.es = 1 * 8;
    tss_b.cs = 2 * 8;
    tss_b.ss = 1 * 8;
    tss_b.ds = 1 * 8;
    tss_b.fs = 1 * 8;
    tss_b.gs = 1 * 8;

给CS寄存器置为GDT的2号,其他寄存器都置为GDT1号,也就是说使用和bootpack,c相同的地址段。

关于eflags的赋值, 如果把STI后的EFLAGS的值通过io_load_eflags赋给变量的话, 该变量的值就显示为0x00000202, 因此在这里就直接使用了这个值 。

eip中定义切换到该任务后,代码从哪里开始运行。

task_b_esp是为任务B定义的 栈。

int task_b_esp;
task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;

因为栈是向低地址增长,因此ESP存入的应该是栈末尾的值。

设置任务b为HTL

void task_b_main(void)
{
for (;;) { io_hlt(); }
}

主函数中引入任务切换

} else if (i == 10) { /* 10秒计时器*/
putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7);
taskswitch4(); /*这里! */
} else if (i == 3) { /* 3秒计时器 */

运行代码。10秒后会切换到任务b ,整个系统就进入暂停状态。

任务切换进阶

切换到任务B后5秒切换回任务A

void task_b_main(void)
{
    struct FIFO32 fifo;
    struct TIMER *timer;
    int i, fifobuf[128];

    fifo32_init(&fifo, 128, fifobuf);
    timer = timer_alloc();
    timer_init(timer, &fifo, 1);
    timer_settime(timer, 500);

    for (;;) {
        io_cli();
        if (fifo32_status(&fifo) == 0) {
            io_sti();
            io_hlt();
        } else {
            i = fifo32_get(&fifo);
            io_sti();
            if (i == 1) { 
                taskswitch3();
            }
        }
    }
}

在这里所使用的变量名, 比如fifo、 timer等, 和HariMain里面是一样的, 不过别担心, 计算机会把它们当成不同的变量来处理。

_taskswitch3: ; void taskswitch3(void);
JMP 3*8:0
RET

思考一

这里一开始以为回到任务A后,所有程序都会重新执行,实际操作发现会继续执行原来的任务A

https://blog.csdn.net/happyAnger6/article/details/8545351

查找资料发现在任务切换前,CPU会自动将当前任务的上下文保存到当前任务的TSS段中。所以任务A的TSS中的值不需要初始化

思考二

如何标识一个段是否有TSS

set_segmdesc(gdt + 3, 103, (int) &tss_a, AR_TSS32);

我们设置该GDT为一个任务状态段(TSS)

image-20220407151511492

做个简单的多任务harib12c

先定义一个切换任务函数

_farjmp: ; void farjmp(int eip, int cs);
    JMP FAR [ESP+4] ; eip, cs
    RET

JMP FAR 指令的功能是执行far跳转

前面定义的两个函数就可进行修改成如下

taskswitch3(); → farjmp(0, 3 * 8);
taskswitch4(); → farjmp(0, 4 * 8);

void HariMain(void){
    ( 中略)
    timer_ts = timer_alloc();
    timer_init(timer_ts, &fifo, 2);
    timer_settime(timer_ts, 2);
    ( 中略)
    for (;;) {
        io_cli();
        if (fifo32_status(&fifo) == 0) {
            io_stihlt();
        } else {
            i = fifo32_get(&fifo);
            io_sti();
            if (i == 2) {
                farjmp(0, 4 * 8);
                timer_settime(timer_ts, 2);//任务b执行结束跳转回来时会从此处开始执行
            } else if (256 <= i && i <= 511) { /*键盘数据*/
            ( 中略)
            } else if (512 <= i && i <= 767) { /*鼠标数据*/
            ( 中略)
            } else if (i == 10) { /* 10秒计时器*/
                putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484,"10[sec]", 7);
            } else if (i == 3) { /* 3秒计时器*/
                putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484,"3[sec]", 6);
            } else if (i <= 1) { /*光标用计时器*/
            ( 中略)
            }
        }
    }
}
void task_b_main(void)
{
    struct FIFO32 fifo;
    struct TIMER *timer_ts;
    int i, fifobuf[128];

    fifo32_init(&fifo, 128, fifobuf);
    timer_ts = timer_alloc();
    timer_init(timer_ts, &fifo, 1);
    timer_settime(timer_ts, 2);

    for (;;) {
        
        io_cli();
        if (fifo32_status(&fifo) == 0) {
            io_sti();
            io_hlt();
        } else {
            
            i = fifo32_get(&fifo);
            io_sti();
            if (i == 1) { //计时结束
                farjmp(0, 3 * 8);
                timer_settime(timer_ts, 2);//第二次以后切换到任务b都会在这里执行
            }
        }
    }
}

为了更清楚观察,在b中加入计时功能

count++;
sprintf(s, "%10d", count);
putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_008484, s, 10);

但是b并不知道sht_back 的值,我们需要在任务A中将该值写入一个内存中,让B从指定的内存中取出来

本次的HariMain节选
*((int *) 0x0fec) = (int) sht_back;
本次的task_b_main节选
sht_back = (struct SHEET *) *((int *) 0x0fec);

这里用到了许多地址转换

转换解读

sht_back为指针类型,存放的是背景图层所在的地址,首先将其转换成int。

(int *) 0x0fec 将内存的0x0fec转为一个指针变量。

*((int *) 0x0fec)=(int) sht_back 我们将背景图层所在的地址存放在0x0fec内存处。

提高运行速度 harib12e

void task_b_main(struct SHEET *sht_back)
{
    struct FIFO32 fifo;
    struct TIMER *timer_ts, *timer_put;
    int i, fifobuf[128], count = 0;
    char s[12];

    fifo32_init(&fifo, 128, fifobuf);
    timer_ts = timer_alloc();
    timer_init(timer_ts, &fifo, 2);
    timer_settime(timer_ts, 2);
    timer_put = timer_alloc();
    timer_init(timer_put, &fifo, 1);
    timer_settime(timer_put, 1);

    for (;;) {
        count++;
        io_cli();
        if (fifo32_status(&fifo) == 0) {
            io_sti();
        } else {
            i = fifo32_get(&fifo);
            io_sti();
            if (i == 1) {
                sprintf(s, "%11d", count);
                putfonts8_asc_sht(sht_back, 0, 144, COL8_FFFFFF, COL8_008484, s, 11);
                timer_settime(timer_put, 1);
            } else if (i == 2) {
                farjmp(0, 3 * 8);
                timer_settime(timer_ts, 2);
            }
        }
    }
}

新增1个定时器,每隔0.01 秒刷新一次窗口

相较于原本复杂的图层传递方式,这里优化为使用参数传递,将参数传入任务B的栈中即可

task_b_esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
*((int *) (task_b_esp + 4)) = (int) sht_back;

这样一来, 在任务B启动的时候, [ESP+4]这个地址里面就已经存入了
sht_back的值, 因此我们就欺骗了task_b_main, 让它以为自己所接收到
的sht_back是作为一个参数传递过来的。

为什么要- 8

假设分配的初始地址为0x01234000

申请分配64KB,范围为0x01234000到0x01243fff

-8 task_b_esp即为0x01243ff8写入sht_back的地址是
task_b_esp + 4, 即0x01243ffc, 从这个地址向后写入4字节的sht_back
值, 则正好在分配出来的内存范围(0x01234000~0x01243fff) 内完成
操作, 这样就不会出问题了。

task_b_main不能return

return的功能, 说到底其实是返回函数被调用位置的一个JMP指令, 但这个task_b_main并不是由某段程序直接调用的, 因此不能使用return。如果强行return的话, 就会像“执行数据”一样发生问题, 程序无法正常运行。

为了记住现在正在执行的指令所在的内存地址,需要使用EIP寄存器, 那么return的时候要返回的地址又记录在哪里呢?对于记性不好的CPU来说, 肯定会把这个地址保存在某个地方, 没错,它就保存在栈中, 地址是[ESP]

因此, 我们不仅可以利用[ESP+4], 还可以利用[ESP]来欺骗CPU, 其实只要向[ESP]写入一个合适的值, 告诉CPU应该返回到哪个地址,task_b_main中就可以使用return了。

多任务进阶harib12g

真正的多任务,是要做到在程序本身不知道的情况下进行任务切换。


struct TIMER *mt_timer;
int mt_tr;
void mt_init(void)
{
    mt_timer = timer_alloc();
    /*这里没有必要使用timer_init */
    timer_settime(mt_timer, 2);
    mt_tr = 3 * 8;
    return;
} 
void mt_taskswitch(void)
{
    if (mt_tr == 3 * 8) {
        mt_tr = 4 * 8;
    } else {
        mt_tr = 3 * 8;
    }
    timer_settime(mt_timer, 2);
    farjmp(0, mt_tr);
    return;
}

mt_taskswitch 根据当前mt_tr的值计算出下一个mt_tr的值,重置计时器之后进行任务切换

修改time.c

void inthandler20(int *esp)
{
    char ts = 0;
    (中略)
    for (;;) {
    /* timers的计时器全部在工作中, 因此不用确认flags */
        if (timer->timeout > timerctl.count) {
            break;
        }/*超时*/
        timer->flags = TIMER_FLAGS_ALLOC;
        if (timer != mt_timer) {
            fifo32_put(timer->fifo, timer->data);
        } else {
            ts = 1; /* mt_timer超时*/
        }
            timer = timer->next; /*将下一个计时器的地址赋给timer */
        }
            timerctl.t0 = timer;
            timerctl.next = timer->timeout;
        if (ts != 0) {
            mt_taskswitch();
        }
    return;
}

如果产生超时的计时器是mt_timer的话, 不向FIFO写入数据,而是将ts置为1。 最后判断如果ts的值不为0, 就调用mt_taskswitch进行任务切换 。

不能直接在ts=1处 进行 mt_taskswitch(); 因为任务切换的时候即便中断处理还没完成, IF( 中断允许标志) 的值也可能会被重设回1( 因为任务切换的时候会同时切换EFLAGS) 。 这样可不行, 在中断处理还没完成的时候, 可能会产生下一个中断请求, 这会导致程序出错 。

思考1

一开始纠结于多个任务的定时器产生的不同数据万一被另一个任务获取了,不就丢失了吗?

看了一遍源代码才发现每个timer都可以指定不同的FIFO,只要在不同的任务中设置不同的FIFO就行了。

day16

任务管理自动化harib13a

day15实现的多任务,每增加一个任务都需要改写一下代码,尝试将其自动化

#define MAX_TASKS 1000 /*最大任务数量*/
#define TASK_GDT0 3 /*定义从GDT的几号开始分配给TSS */
struct TSS32 {
    int backlink, esp0, ss0, esp1, ss1, esp2, ss2, cr3;
    int eip, eflags, eax, ecx, edx, ebx, esp, ebp, esi, edi;
    int es, cs, ss, ds, fs, gs;
    int ldtr, iomap;
};
struct TASK {
    int sel, flags; /* sel用来存放GDT的编号*/
    struct TSS32 tss;
};
struct TASKCTL {
    int running; /*正在运行的任务数量*/
    int now; /*这个变量用来记录当前正在运行的是哪个任务*/
    struct TASK *tasks[MAX_TASKS];
    struct TASK tasks0[MAX_TASKS];
};

task初始化

struct TASKCTL *taskctl;
struct TIMER *task_timer;

struct TASK *task_init(struct MEMMAN *memman)
{
    int i;
    struct TASK *task;
    struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
    taskctl = (struct TASKCTL *) memman_alloc_4k(memman, sizeof (struct TASKCTL));
    for (i = 0; i < MAX_TASKS; i++) {
        taskctl->tasks0[i].flags = 0;
        taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8;
        set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);
    }
    task = task_alloc();
    task->flags = 2; /*标志正在运行*/
    taskctl->running = 1;
    taskctl->now = 0;
    taskctl->tasks[0] = task;
    load_tr(task->sel);
    task_timer = timer_alloc();
    timer_settime(task_timer, 2);
    return task;
}
  1. 分配内存
  2. 初始化taskctl中所有的TSS
  3. 将当前运行的作为第一个任务
  4. 创建一个0.02秒的定时器


struct TASK *task_alloc(void)
{
    int i;
    struct TASK *task;
    for (i = 0; i < MAX_TASKS; i++) {
        if (taskctl->tasks0[i].flags == 0) {
            task = &taskctl->tasks0[i];
            task->flags = 1; /* 标志已经被使用 */
            task->tss.eflags = 0x00000202; /* IF = 1; 开启中断*/
            task->tss.eax = 0; /* 先设置为0*/
            task->tss.ecx = 0;
            task->tss.edx = 0;
            task->tss.ebx = 0;
            task->tss.ebp = 0;
            task->tss.esi = 0;
            task->tss.edi = 0;
            task->tss.es = 0;
            task->tss.ds = 0;
            task->tss.fs = 0;
            task->tss.gs = 0;
            task->tss.ldtr = 0;
            task->tss.iomap = 0x40000000;
            return task;
        }
    }
    return 0; /* 全部TSS都在使用 */
}

void task_run(struct TASK *task)//将task加入到tasks末尾,使running加1
{
    task->flags = 2; /* 活动中的标志 */
    taskctl->tasks[taskctl->running] = task;
    taskctl->running++;
    return;
}

void task_switch(void)
{
    timer_settime(task_timer, 2);
    if (taskctl->running >= 2) {
        taskctl->now++;
        if (taskctl->now == taskctl->running) {
            taskctl->now = 0;
        }
        farjmp(0, taskctl->tasks[taskctl->now]->sel);
    }
    return;
}

task_switch : 若running为1 表示只有一个当前主任务,不需要进行任务切换,函数直接结束即可。

大于2时,now +1 ,标识进入下一个任务

将timer.c中原本调用mt_taskswitch也相应地修改为调用task_switch

新建一个任务进行测试

void HariMain(void)
{
    (中略)
    struct TASK *task_b;
    (中略)
    task_init(memman);
    task_b = task_alloc();
    task_b->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
    task_b->tss.eip = (int) &task_b_main;
    task_b->tss.es = 1 * 8;
    task_b->tss.cs = 2 * 8;
    task_b->tss.ss = 1 * 8;
    task_b->tss.ds = 1 * 8;
    task_b->tss.fs = 1 * 8;
    task_b->tss.gs = 1 * 8;
    *((int *) (task_b->tss.esp + 4)) = (int) sht_back;
    task_run(task_b);
    (中略)
}

任务休眠 harib13b

harib13a所实现的多任务是为每个任务分配相同的运行时间,在某个任务空闲时间比较多的时候回造成资源的浪费。

在没有键盘输入、没有鼠标操作的时候 任务A明显空闲时间比较多。

解决思路:

  1. 在FIFO结构体中定义一个task变量来绑定当前任务
  2. 若A处于空闲状态,则将A从taskctl->tasks[]中删去
  3. 处理中断时,若向A的FIFO写入数据则表示任务A需要去处理中断了,调用task_run将其重新运行即可。

首先创建task_sleep

void task_sleep(struct TASK *task)
{
    int i;
    char ts = 0;
    if (task->flags == 2) {        /* 如果指定任务处于唤醒状态 */
        if (task == taskctl->tasks[taskctl->now]) {
            ts = 1; /* 让自己休眠的话,稍后需要进行任务的切换 */
        }

        for (i = 0; i < taskctl->running; i++) {
            if (taskctl->tasks[i] == task) {
                /* 找到那个要休眠的任务,其下标为i */
                break;
            }
        }
        taskctl->running--;
        if (i < taskctl->now) {
            taskctl->now--; /* 如果i在now之前,那么移动成员后,当前now需要-1 */
        }
        /* 移动成员 */
        for (; i < taskctl->running; i++) {
            taskctl->tasks[i] = taskctl->tasks[i + 1];
        }
        task->flags = 1; /* 设置为不工作的状态 */
        if (ts != 0) {
            /* 进行任务切换 */
            if (taskctl->now >= taskctl->running) {
                /* 如果now的值出现异常,需要进行修正 */
                taskctl->now = 0;
            }
            farjmp(0, taskctl->tasks[taskctl->now]->sel);
        }
    }
    return;
}

当FIFO中写入数据的时候需要将绑定的任务进行唤醒

struct FIFO32 {
    int *buf;
    int p, q, size, free, flags;
    struct TASK *task;
};

初始化时,将当前任务作为参数进行传递,

void fifo32_init(struct FIFO32 *fifo, int size, int *buf, struct TASK *task)
/* FIFO缓冲区初始化*/
{
fifo->size = size;
fifo->buf = buf;
fifo->free = size; /*剩余空间*/
fifo->flags = 0;
fifo->p = 0; /*写入位置*/
fifo->q = 0; /*读取位置*/
fifo->task = task; /*有数据写入时需要唤醒的任务*/ /*这里! */
return;
}

写入数据时,对任务状态进行检测

int fifo32_put(struct FIFO32 *fifo, int data)
/*向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--;
    if (fifo->task != 0) { /*从这里开始*/
        if (fifo->task->flags != 2) { /*如果任务处于休眠状态*/
            task_run(fifo->task); /*将任务唤醒*/
        }
    } /*到这里结束*/
    return 0;
}

改写主函数

void HariMain(void)
{
    struct TASK *task_a, *task_b;
    ( 中略)
    fifo32_init(&fifo, 128, fifobuf, 0);
    ( 中略)
    task_a = task_init(memman);
    fifo.task = task_a;
    ( 中略)
    for (;;) {
        io_cli();
        if (fifo32_status(&fifo) == 0) {
            task_sleep(task_a);
            io_sti();
        } else {
            ( 中略)
        }
    }
} 
void task_b_main(struct SHEET *sht_back)
{
    ( 中略)
    fifo32_init(&fifo, 128, fifobuf, 0);
    ( 中略)
}

最开始的fifo32_init中指定任务的参数, 我们用0代替了。 因为我们在任务A中应用了休眠, 也就需要使用让FIFO来唤醒的功能, 不过在这个时间点上多任务的初始化还没有完成, 因此无法指定任务, 只能先在这里用0代替, 也就是禁用自动唤醒功能。

随后, 在task_init中会返回自己的构造地址, 我们将这个地址存入fifo.task。

task_b_main不需要让FIFO唤醒, 因此任务参数指定为0。

在此情况下,只有当键盘/鼠标输入数据的时候,才会唤醒taskA进行处理

其他情况会将绝大部分资源都分配给B进行任务处理。

增加窗口的数量

  1. 分配三个图层,在图层中创建一个窗口
  2. 创建三个task,都指向task_b_main
  3. 向taskb的栈内传递图层地址
  4. 调整窗口位置、优先级

设定任务的优先级(harib13d)

资源分配优先

即使引入了休眠机制,同时运行的多任务都分配相同的时间,需要高CPU资源的任务会运行缓慢。

在此之前的任务切换间隔都固定在0.02秒,我们可以为每一个任务在0.01~0.1秒的范围内设定不同的任务切换间隔。

task中新增priority成员

struct TASK {
    int sel, flags; /* sel代表GDT编号 */
    int priority; /*这里! */
    struct TSS32 tss;
};

改写mtask.c ,初始任务运行时间都为0.02秒

struct TASK *task_init(struct MEMMAN *memman)
{
    (中略)
    task = task_alloc();
    task->flags = 2; /*活动中标志*/
    task->priority = 2; /* 0.02秒 */
    taskctl->running = 1;
    taskctl->now = 0;
    taskctl->tasks[0] = task;
    load_tr(task->sel);
    task_timer = timer_alloc();
    timer_settime(task_timer, task->priority);
    return task;
}

在上一个任务结束后,调用task_switch时,将下一个任务的priority作为定时器延时

void task_switch(void)
{
    struct TASK *task;
    taskctl->now++;
    if (taskctl->now == taskctl->running) {
        taskctl->now = 0;
    }
    task = taskctl->tasks[taskctl->now];
    timer_settime(task_timer, task->priority);
    if (taskctl->running >= 2) {
        farjmp(0, task->sel);
    }
    return;
}

注: 当只有一个任务是,执行farjmp跳转当前任务会导致程序运行乱套,因此在执行farjmp前要判断当前任务不少于2个

最后,在task run 中设置优先级

void task_run(struct TASK *task, int priority)
{
    if (priority > 0) {
        task->priority = priority;
    }
    if (task->flags != 2) {
        task->flags = 2; /* ���쒆�}�[�N */
        taskctl->tasks[taskctl->running] = task;
        taskctl->running++;
    }
    return;
}

image-20220408110845209

20220408

设定任务优先级2(harib13e)

​ 在实际运行过程中 ,鼠标已经出现明显的卡顿了,这是因为其他任务的运行造成A运行速度变慢,从而导致了上述情形。

​ 我们需要设计一种架构,使得即便高优先级的任务同时运行也能区分哪一个更加优先。

image-20220408112707405

这种架构的工作原理是, 最上层的LEVEL 0中只要存在哪怕一个任务,则完全忽略LEVEL 1和LEVEL 2中的任务, 只在LEVEL 0的任务中进行任务切换。 当LEVEL 0中的任务全部休眠, 或者全部降到下层LEVEL,也就是当LEVEL 0中没有任何任务的时候, 接下来开始轮到LEVEL 1中的任务进行任务切换。 当LEVEL 0和LEVEL 1中都没有任务时, 那就该轮到LEVEL 2出场了 。

对于每个LEVEL设置最多允许创建100个任务,总共10个LEVEL

#define MAX_TASKS_LV 100
#define MAX_TASKLEVELS 10
struct TASK {
    int sel, flags; /* se1用来存放GDT的编号*/
    int level, priority;
    struct TSS32 tss;
};
struct TASKLEVEL {
    int running; /*正在运行的任务数量*/
    int now; /*这个变量用来记录当前正在运行的是哪个任务*/
    struct TASK *tasks[MAX_TASKS_LV];
};
struct TASKCTL {
    int now_lv; /*现在活动中的LEVEL */
    char lv_change; /*在下次任务切换时是否需要改变LEVEL */
    struct TASKLEVEL level[MAX_TASKLEVELS];
    struct TASK tasks0[MAX_TASKS];
};

TASKLEVEL 存储当前LEVEL的任务

task_now函数,用来返回现在活动中的任务的内存地址

struct TASK *task_now(void)
{
    struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];
    return tl->tasks[tl->now];
}

task_add函数,用来向struct TASKLEVEL中添加一个任务

void task_add(struct TASK *task)
{
    struct TASKLEVEL *tl = &taskctl->level[task->level];
    tl->tasks[tl->running] = task;
    tl->running++;
    task->flags = 2; /*活动中*/
    return;
}

在task中有自己的level变量用来标识应该存放在哪个TASKLEVEL中

task_remove函数, 用来从struct TASKLEVEL中删除一个任务。

void task_remove(struct TASK *task)
{
    int i;
    struct TASKLEVEL *tl = &taskctl->level[task->level];

    /* 寻找task所在位置 */
    for (i = 0; i < tl->running; i++) {
        if (tl->tasks[i] == task) {
            /*记录下该任务的下标 */
            break;
        }
    }

    tl->running--;
    if (i < tl->now) {
        tl->now--; /* 需要移动成员 */
    }
    if (tl->now >= tl->running) {
        /*如果now的值出现异常, 则进行修正*/
        tl->now = 0;
    }
    task->flags = 1; /* 休眠 */

    /* 移动 */
    for (; i < tl->running; i++) {
        tl->tasks[i] = tl->tasks[i + 1];
    }

    return;
}

task_switchsub函数, 用来在任务切换时决定接下来切换到哪个LEVEL。

void task_switchsub(void)
{
    int i;
    /*寻找最上层的LEVEL */
    for (i = 0; i < MAX_TASKLEVELS; i++) {
        if (taskctl->level[i].running > 0) {
            break; /*找到了*/
        }
    }
    taskctl->now_lv = i;
    taskctl->lv_change = 0;
    return;
}

切换LEVEL后将lv_change计为0,除非 调用task_run引入了新的任务或调用task_sleep结束了任务,此时才需要进行重新LEVEL切换。

task_init 最开始的任务将其放置在LEVEL 0 中

struct TASK *task_init(struct MEMMAN *memman)
{
    (中略)
    for (i = 0; i < MAX_TASKLEVELS; i++) {
        taskctl->level[i].running = 0;
        taskctl->level[i].now = 0;
    }
    task = task_alloc();
    task->flags = 2; /*活动中标志*/
    task->priority = 2; /* 0.02秒*/
    task->level = 0; /*最高LEVEL */
    task_add(task);
    task_switchsub(); /* LEVEL 设置*/
    load_tr(task->sel);
    task_timer = timer_alloc();
    timer_settime(task_timer, task->priority);
    return task;
}

task_run 让其可以在参数中指定LEVEL

void task_run(struct TASK *task, int level, int priority)
{
    if (level < 0) {
        level = task->level; /*不改变LEVEL */
    }
    if (priority > 0) {
        task->priority = priority;
    } 
    if (task->flags == 2 && task->level != level) { /*改变活动中的LEVEL */
        task_remove(task); /*这里执行之后flag的值会变为1, 于是下面的if语句块也会被执行*/
    }
    if (task->flags != 2) {
        /*从休眠状态唤醒的情形*/
        task->level = level;
        task_add(task);
    }
    taskctl->lv_change = 1; /*下次任务切换时检查LEVEL */
    return;
}

在此之前, task_run中下一个要切换到的任务是固定不变的, 不过现在情况就不同了。 例如, 如果用task_run启动了一个比现在活动中的任务LEVEL更高的任务, 那么在下次任务切换时, 就必须无条件地切换到该LEVEL中的该任务去 。

此外, 如果当前活动中的任务LEVEL被下调, 那么此时就必须将其他LEVEL的任务放在优先的位置(同样以上图来说的话, 比如当LEVEL 0的任务被降级到LEVEL 2时, 任务切换的目标就需要从LEVEL 0变为LEVEL 1)

综上所述, 我们需要在下次任务切换时先检查LEVEL, 因此将
lv_change置为1

tasksleep

void task_sleep(struct TASK *task)
{
    struct TASK *now_task;
    if (task->flags == 2) {
        /*如果处于活动状态*/
        now_task = task_now();
        task_remove(task); /*执行此语句的话flags将变为1 */
        if (task == now_task) {
            /*如果是让自己休眠, 则需要进行任务切换*/
            task_switchsub();
            now_task = task_now(); /*在设定后获取当前任务的值*/
            farjmp(0, now_task->sel);
        }
    }
    return;
}

task_switch 除了当lv_change不为0时的处理意外,其余几乎没有变化

void task_switch(void)
{
    struct TASKLEVEL *tl = &taskctl->level[taskctl->now_lv];
    struct TASK *new_task, *now_task = tl->tasks[tl->now];
    tl->now++;
    if (tl->now == tl->running) {
        tl->now = 0;
    }
    if (taskctl->lv_change != 0) {//不为0时,需要进行任务等级的切换。
        task_switchsub();
        tl = &taskctl->level[taskctl->now_lv];
    }
    new_task = tl->tasks[tl->now];
    timer_settime(task_timer, new_task->priority);
    if (new_task != now_task) {
        farjmp(0, new_task->sel);
    }
    return;
}
void HariMain(void)
{
    (中略)
    init_palette();
    shtctl = shtctl_init(memman, binfo->vram, binfo->scrnx, binfo->scrny);
    task_a = task_init(memman);
    fifo.task = task_a;
    task_run(task_a, 1, 0); /*这里! */
    (中略)
    /* sht_win_b */
    for (i = 0; i < 3; i++) {
    (中略)
    task_run(task_b[i], 2, i + 1); /*这里! */}
    ( 中略)
}

day17 命令窗口

闲置任务 harib14a

在前文中任务A下面的LEVEL有任务B0~B2,但若只有任务A且不进行任何操作的时候,A会进入休眠状态,程序会因为找不到其他任务而导致运行的异常。

如果“所有LEVEL中都没有任务”就会出问题, 那我们只要避免这种情况发生不就可以了吗

我们可以创建一个哨兵任务,将其放置在最下层的LEVEL中,该任务只进行hlt()

void task_idle(void)
{
    for (;;) {
           io_hlt();
    }
}

在初始化的时候,将这个闲置任务放在最下层LEVEL中就可以了

struct TASK *task_init(struct MEMMAN *memman)
{
    struct TASK *task, *idle;
    (中略)
    idle = task_alloc();
    idle->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024;
    idle->tss.eip = (int) &task_idle;
    idle->tss.es = 1 * 8;
    idle->tss.cs = 2 * 8;
    idle->tss.ss = 1 * 8;
    idle->tss.ds = 1 * 8;
    idle->tss.fs = 1 * 8;
    idle->tss.gs = 1 * 8;
    task_run(idle, MAX_TASKLEVELS - 1, 1);
    return task;
}

创建命令行窗口(harib14b)

  1. 创建一个图层
  2. 创建一个任务,初始化、绑定函数
  3. 将图层作为参数传递给该任务·
void HariMain(void)
{
    (中略)
    /* sht_cons */
    sht_cons = sheet_alloc(shtctl);
    buf_cons = (unsigned char *) memman_alloc_4k(memman, 256 * 165);
    sheet_setbuf(sht_cons, buf_cons, 256, 165, -1); /*无透明色*/
    make_window8(buf_cons, 256, 165, "console", 0);
    make_textbox8(sht_cons, 8, 28, 240, 128, COL8_000000);
    task_cons = task_alloc();
    task_cons->tss.esp = memman_alloc_4k(memman, 64 * 1024) + 64 * 1024 - 8;
    task_cons->tss.eip = (int) &console_task;
    task_cons->tss.es = 1 * 8;
    task_cons->tss.cs = 2 * 8;
    task_cons->tss.ss = 1 * 8;
    task_cons->tss.ds = 1 * 8;
    task_cons->tss.fs = 1 * 8;
    task_cons->tss.gs = 1 * 8;
    *((int *) (task_cons->tss.esp + 4)) = (int) sht_cons;
    task_run(task_cons, 2, 2); /* level=2, priority=2 */
    (中略)sheet_slide(sht_back, 0, 0);
    sheet_slide(sht_cons, 32, 4);
    sheet_slide(sht_win, 64, 56);
    sheet_slide(sht_mouse, mx, my);
    sheet_updown(sht_back, 0);
    sheet_updown(sht_cons, 1);
    sheet_updown(sht_win, 2);
    sheet_updown(sht_mouse, 3);
    ( 中略)
} 
void console_task(struct SHEET *sheet)
{
    struct FIFO32 fifo;
    struct TIMER *timer;
    struct TASK *task = task_now();
    int i, fifobuf[128], cursor_x = 8, cursor_c = COL8_000000;
    fifo32_init(&fifo, 128, fifobuf, task);
    timer = timer_alloc();
    timer_init(timer, &fifo, 1);
    timer_settime(timer, 50);
    for (;;) {
        io_cli();
        if (fifo32_status(&fifo) == 0) {
            task_sleep(task);
            io_sti();
        } else {
            i = fifo32_get(&fifo);
            io_sti();
            if (i <= 1) { /*光标用定时器*/
                if (i != 0) {
                    timer_init(timer, &fifo, 0); /*下次置0 */
                    cursor_c = COL8_FFFFFF;
                } else {
                    timer_init(timer, &fifo, 1); /*下次置1 */
                    cursor_c = COL8_000000;
                }
                timer_settime(timer, 50);
                boxfill8(sheet->buf, sheet->bxsize, cursor_c, cursor_x, 28,
                cursor_x + 7, 43);
                sheet_refresh(sheet, cursor_x, 28, cursor_x + 8, 44);
            }
        }
    }
}

切换输入窗口 harib14c

首先将make_window中描绘窗口标题栏的代码和描绘窗口剩余部分的代码区分开

void make_window8(unsigned char *buf, int xsize, int ysize, char *title, char act)
{
    boxfill8(buf, xsize, COL8_C6C6C6, 0,         0,         xsize - 1, 0        );
    boxfill8(buf, xsize, COL8_FFFFFF, 1,         1,         xsize - 2, 1        );
    boxfill8(buf, xsize, COL8_C6C6C6, 0,         0,         0,         ysize - 1);
    boxfill8(buf, xsize, COL8_FFFFFF, 1,         1,         1,         ysize - 2);
    boxfill8(buf, xsize, COL8_848484, xsize - 2, 1,         xsize - 2, ysize - 2);
    boxfill8(buf, xsize, COL8_000000, xsize - 1, 0,         xsize - 1, ysize - 1);
    boxfill8(buf, xsize, COL8_C6C6C6, 2,         2,         xsize - 3, ysize - 3);
    boxfill8(buf, xsize, COL8_848484, 1,         ysize - 2, xsize - 2, ysize - 2);
    boxfill8(buf, xsize, COL8_000000, 0,         ysize - 1, xsize - 1, ysize - 1);
    make_wtitle8(buf, xsize, title, act);
    return;
}

void make_wtitle8(unsigned char *buf, int xsize, char *title, char act)
{
    static char closebtn[14][16] = {
        "OOOOOOOOOOOOOOO@",
        "OQQQQQQQQQQQQQ$@",
        "OQQQQQQQQQQQQQ$@",
        "OQQQ@@QQQQ@@QQ$@",
        "OQQQQ@@QQ@@QQQ$@",
        "OQQQQQ@@@@QQQQ$@",
        "OQQQQQQ@@QQQQQ$@",
        "OQQQQQ@@@@QQQQ$@",
        "OQQQQ@@QQ@@QQQ$@",
        "OQQQ@@QQQQ@@QQ$@",
        "OQQQQQQQQQQQQQ$@",
        "OQQQQQQQQQQQQQ$@",
        "O$$$$$$$$$$$$$$@",
        "@@@@@@@@@@@@@@@@"
    };
    int x, y;
    char c, tc, tbc;
    if (act != 0) {
        tc = COL8_FFFFFF;
        tbc = COL8_000084;
    } else {
        tc = COL8_C6C6C6;
        tbc = COL8_848484;
    }
    boxfill8(buf, xsize, tbc, 3, 3, xsize - 4, 20);
    putfonts8_asc(buf, xsize, 24, 4, tc, title);
    for (y = 0; y < 14; y++) {
        for (x = 0; x < 16; x++) {
            c = closebtn[y][x];
            if (c == '@') {
                c = COL8_000000;
            } else if (c == '$') {
                c = COL8_848484;
            } else if (c == 'Q') {
                c = COL8_C6C6C6;
            } else {
                c = COL8_FFFFFF;
            }
            buf[(5 + y) * xsize + (xsize - 21 + x)] = c;
        }
    }
    return;
}

在主函数中,定义一个变量key_to, 若键盘输入为tab键,首先将key_to 设置为0或1,

然后将两个窗口的颜色进行刷新

实现字符输入 harib14d

要想向不同的任务输入字符、我们必须知道其FIFO的位置,上文中将FIFO定义在任务B指向的函数中,这样显然是不行的,我们需要将FIFO定义在TASK中,这样,主函数(任务A)就能从TASK中获取到目标的FIFO,并向其中写入数据,当切换到目标任务时,只要处理FIFO中的数据即可。

struct TASK {
    int sel, flags; /* sel代表GDT编号 */
    int level, priority;
    struct FIFO32 fifo; /*这里! */
    struct TSS32 tss;
};

主函数主要修改如下

                    if (key_to == 0) {    //等于0 代表输出到A
                        if (cursor_x < 128) {
                            
                            s[0] = keytable[i - 256];
                            s[1] = 0;
                            putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1);
                            cursor_x += 8;
                        }
                    } else {    /*否则输出到B命令窗口 */
                        fifo32_put(&task_cons->fifo, keytable[i - 256] + 256);
                    }
                }
                if (i == 256 + 0x0e) {    /* 退格键 */
                    if (key_to == 0) {    /* 发送给任务A */
                        if (cursor_x > 8) {
                            /*用空白擦除光标后将光标前移一位*/
                            putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1);
                            cursor_x -= 8;
                        }
                    } else {    /* 发送给命令窗口 */
                        fifo32_put(&task_cons->fifo, 8 + 256);
                    }

任务B也需要进行修改

            if (256 <= i && i <= 511) { /* 通过任务A */
                if (i == 8 + 256) {
                    /* 退格键 */
                    if (cursor_x > 16) {
                        /* 用空白擦除光标后将光标前移一位 */
                        putfonts8_asc_sht(sheet, cursor_x, 28, COL8_FFFFFF, COL8_000000, " ", 1);
                        cursor_x -= 8;
                    }
                } else {
                    /* 一般字符*/
                    if (cursor_x < 240) {
                        /* 显示一个字符之后将光标后移一位*/
                        s[0] = i - 256;
                        s[1] = 0;
                        putfonts8_asc_sht(sheet, cursor_x, 28, COL8_FFFFFF, COL8_000000, s, 1);
                        cursor_x += 8;
                    }
                }
            }

符号输入 harib14e 大写字母与小写字母(harib14f)

符号

左Shift按下 0x2a

左shift抬起 0xaa

右Shift按下 0x36

右Shift抬起 0xb6

我们准备一个key_shift变量, 当左Shift按下时置为1, 右Shift按下时置为2, 两个都不按时置为0, 两个都按下(有人会这么干吗? ) 的时候就置为3。

当key_shift为0时, 我们用keytable0[]将按键编码转换为字符编码, 而当key_shift不为0时, 则使用keytable1[]进行转换。

if (key_shift == 0) {
    s[0] = keytable0[i - 256];
} else {
    s[0] = keytable1[i - 256];
}
if (i == 256 + 0x2a) { /*左Shift ON */
    key_shift |= 1; //01
}if (i == 256 + 0x36) { /*右Shift ON */
    key_shift |= 2;//10
}if (i == 256 + 0xaa) { /*左Shift OFF */
    key_shift &= ~1;
}if (i == 256 + 0xb6) { /*右Shift OFF */
    key_shift &= ~2;
}

大小写

CapsLock为OFF & Shift键为OFF → 小写英文字母
CapsLock为OFF & Shift键为ON → 大写英文字母
CapsLock为ON & Shift键为OFF → 大写英文字母
CapsLock为ON & Shift键为ON → 小写英文字母

我们已经知道如何获取Shift键的状态, 但是CapsLock的状态要如何获取呢? BIOS知道CapsLock的状态, 可现在我们处于32位模式, 没办法向BIOS查询。

在asmhead.nas中, 我们已经从BIOS获取到了键盘状态, 就保存在binfo—>leds中

binfo->leds的第4位→ ScrollLock状态
binfo->leds的第5位 → NumLock状态
binfo->leds的第6位 → CapsLock状态

int key_to = 0, key_shift = 0, key_leds = (binfo->leds >> 4) & 7; /*这里! *

if ('A' <= s[0] && s[0] <= 'Z') { /*当输入字符为英文字母时*/
    if (((key_leds & 4) == 0 && key_shift == 0) ||((key_leds & 4) != 0 && key_shift != 0)) {
        s[0] += 0x20; /*将大写字母转换为小写字母*/
    }
/*到此结束*/ 
}

20220409

对各种锁定键的支持harib14g

三个按键对应的编码

0x3a: CapsLock
0x45: NumLock
0x46: ScrollLock

我们只要收到该编码就将bifo->leds 对应位置修改即可。 但如何让键盘上的指示灯进行相应的状态切换呢?

对于NumLock和CapsLock等LED的控制, 可采用下面的方法向键盘发送指令和数据

  1. 读取状态寄存器, 等待bit 1的值变为0。
  2. 向数据输出(0060) 写入要发送的1个字节数据。等待键盘返回1个字节的信息, 这和等待键盘输入所采用的方法相同(用IRQ等待或者用轮询状态寄存器bit 1的值直到其变为0都可以) 。
  3. 返回的信息如果为0xfa, 表明1个字节的数据已成功发送给键盘。 如为0xfe则表明发送失败, 需要返回第1步重新发送。

要控制LED的状态, 需要按上述方法执行两次, 向键盘发送EDxx数据。 其中, xx的bit 0代表ScrollLock, bit 1代表NumLock, bit 2代表CapsLock(0表示熄灭, 1表示点亮) 。 bit 3~7为保留位, 置0即可。

代码实现思路

  1. 创建一个keycmd的FIFO缓冲区, 用来管理由任务A向键盘控制器发送数据的顺讯的,如果有数据要发送到键盘控制器会先在这个缓冲区中累计下来。
    因为要等待键盘控制器的回应,因此定义一个keycmd_wait,当键盘响应成功时将其设置为-1

  2. 当输入指定按键时,将其存放在keycmd中

                    if (i == 256 + 0x3a) {    /* CapsLock */
                        key_leds ^= 4;
                        fifo32_put(&keycmd, KEYCMD_LED);
                        fifo32_put(&keycmd, key_leds);
                    }
                    if (i == 256 + 0x45) {    /* NumLock */
                        key_leds ^= 2;
                        fifo32_put(&keycmd, KEYCMD_LED);
                        fifo32_put(&keycmd, key_leds);
                    }
                    if (i == 256 + 0x46) {    /* ScrollLock */
                        key_leds ^= 1;
                        fifo32_put(&keycmd, KEYCMD_LED);
                        fifo32_put(&keycmd, key_leds);
                    }
    
  3. 若keycmd中有数据,且keycmd_wait为-1 ,将缓冲区的值存入keycmd_wait中并将其写入0x0060端口

            if (fifo32_status(&keycmd) > 0 && keycmd_wait < 0) {
    
                keycmd_wait = fifo32_get(&keycmd);
                wait_KBC_sendready();
                io_out8(PORT_KEYDAT, keycmd_wait);
            }
    
  4. 键盘响应成功时,将key_wait设置为-1 进行下一次数据写入,不成功时重新发送keycmd_wait

                    if (i == 256 + 0xfa) {    
                        keycmd_wait = -1;
                    }
                    if (i == 256 + 0xfe) {    
                        wait_KBC_sendready();
                        io_out8(PORT_KEYDAT, keycmd_wait);
                    }
    

day18 dir命令

控制光标闪烁

只有可以接受键盘输入的窗口才有光标闪烁

对于任务A ,定义一个变量cursor_c 在按下TAB键时,若切换至其他窗口,则将其设置为-1

if (key_to == 0) {
    key_to = 1;
    make_wtitle8(buf_win,  sht_win->bxsize,  "task_a",  0);
    make_wtitle8(buf_cons, sht_cons->bxsize, "console", 1);
    cursor_c = -1;
    boxfill8(sht_win->buf, sht_win->bxsize, COL8_FFFFFF, cursor_x, 28, cursor_x + 7, 43);
} else {
    key_to = 0;
    make_wtitle8(buf_win,  sht_win->bxsize,  "task_a",  1);
    make_wtitle8(buf_cons, sht_cons->bxsize, "console", 0);
    cursor_c = COL8_000000; 
}

在重新绘制光标时,若cursor_c的值为-1,则光标的颜色不会切换,这样就完成了暂停的效果。

应该怎样由HariMain(任务A) 向console_task(命令行窗口) 传递信息, 告诉它“不需让光标闪烁”或者“需要让光标闪烁”呢? 像传递按键编码一样, 我们可以使用FIFO来实现。

—-


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1944270374@qq.com