《30天自制操作系统》12_day_学习笔记

2023-02-22,,

harib09a:
  定时器:(Timer)每隔一段时间,会向CPU发送一个中断。这样CPU不用记住每一条指令的执行时间。没有定时器很多指令CPU都很难执行。例如HLT指令,这个指令的执行时间不是个固定值,没有定时器,CPU就不能执行这个指令
  -PIT-: 可编程的间隔型定时器(Programmable Interval Timer )通过设定PIT,可以让定时器每隔一定时间就产生一次中断。PIT和PIC都被集成在别的芯片里了,连接着IRQ的0号中断,IRQ0的中断变更周期是通过寄存器AL的值来设定的。

  IRQ0的中断频率  = CPU主屏/AL(中断周期;设定的数值)
  定时器的时间间隔  = 1秒/IRQ0的中断频率

  这里笔者把中断频率设定为100HZ,(1秒钟产生100次中断)根据笔者CPU的主频,计算得到AL为0x2e9c(11932)

  1、初始化定时器Timer(  三次调用OUT()  )

//定时器初始化函数init_pit
//timer.c节选
#define PIT_CTRL 0x0043
#define PIT_CNT0 0x0040
void init_pit(void)
{   //中断周期的变更规则(三次调用OUT()):P222有规则介绍
  //至于规则为什么是这样连续调用三次OUT();笔者介绍说这个是由芯片设定决定的。我们就不深究;知道这样的调用规则就行。
io_out8(PIT_CTRL, 0x34);//第一步:调用OUT(0x43,AL);AL=0x34 此时AL为定值PIT_CTRL(0x34)
io_out8(PIT_CNT0, 0x9c);//第二步:调用OTU(0x40,AL);AL=0x9c 此时AL为中断周期的低八位(0x9c)
io_out8(PIT_CNT0, 0x2e);//第三步:调用OTU(0x40,AL);AL=0x2e 此时AL为中断周期的高八位 (0x2e)
return;
}

  2、在HariMain中调用上面的定时器初始化函数

//bootpack.c节选
void HariMain(void) {
//...............
fifo8_init(&keyfifo, , keybuf);
fifo8_init(&mousefifo, , mousebuf);
init_pit();         //在这里调用定时器初始化函数
io_out8(PIC0_IMR, 0xf8); //PIT和PIC1和键盘中断设置1111-1000
io_out8(PIC1_IMR, 0xef); //PIC1 设置鼠标的中断设置1110-1111
//.............
}

  3、编写IRQ0发生时的中断处理程序

   这里我们先来解释一下io_out8():io_out,表示芯片的端口。例如这里的io_out8(PIC0_OCW2,0x60);8表示这个端口或者寄存器是8位的,参数的第一个表哪个芯片的哪一个寄存器,当然这个寄存器位数要和前面的8位数一样。第二个参数表示要设置的值,位数也要对应。例如:调用io_outM(A_B,N) .表示把芯片A中的B寄存器(这个寄存器的位数为M)的值设置为N 。io_out8(PIC0_OCW2,0x60)表示的意思就是把芯片PIC0的OCW2寄存器(8位)的值设置为0x60。

void inthandler20(int *esp)
{ //把芯片PIC0的OCW2寄存器(8位)的值设置为0x60
io_out8(PIC0_OCW2, 0x60); /* IRQ-00信号接收完了的信息通知给PIC */
return;
}
//naskfunc.nas
//函数_asm_inthandler20
//20中断号,这里把定时器的中断号设置为20

  4、把定时器的中断程序注册到IDT中

void init_gdtidt(void) {
//...................
//20号为定时器的中断
set_gatedesc(idt + 0x20, (int) asm_inthandler20, * , AR_INTGATE32);
set_gatedesc(idt + 0x21, (int) asm_inthandler21, * , AR_INTGATE32);
set_gatedesc(idt + 0x27, (int) asm_inthandler27, * , AR_INTGATE32);
set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, * , AR_INTGATE32);
}

harib09b:
  上面我们已经对定时器做了一些准备工作,下面我们来试一试让他运行起来,我们定义了一个struct TIMERCTL结构体,在结构体中定义了一个计数变量count,在PIT初始化的时候,把count初始化为0,然后在定时器中断程序中,不断对count进行自加,因此定时器每发生一次中断,count都会增加1,这样用来记录定时器中断发生的次数。最后把count显示在我们前面实现的窗口中。

/* timer.c */
//计数结构体定义
struct TIMERCTL { unsigned int count; };
struct TIMERCTL timerctl;
void init_pit(void)
{
//..初始化count为 0 .............
timerctl.count = ;
return;
} void inthandler20(int *esp)
{
//定时器每次发生中断会调用这个函数
//这是计数变量count会 +1 。
timerctl.count++;
return;
}
  //在HariMain中把数值显示出来:HariMain节选
void HariMain(void)
{
  for (;;) {
    //先把值写到字符串s中
    sprintf(s, "%010d", timerctl.count);
    //初始化窗口图层的缓存buf_win
    boxfill8(buf_win, , COL8_C6C6C6, , , , );
    //把字符串写到窗口图层缓存buf_win中
    putfonts8_asc(buf_win, , , , COL8_000000, s);
    //刷新并显示窗口图层sht_win
    sheet_refresh(sht_win, , , , );
    //.......
    }
}

harib09c:
  超  时:笔者举了一个很形象的例子来说明什么是超时:“喂,OS小弟,10秒钟之后,通知我一声,我要干什么干什么”我们就把这样的功能叫做超时(定时吧)。下面我们来实现这个功能。

  1、扩展struct TIMERCTL记录超时有关的信息

/* timer.c */
struct TIMERCTL {
unsigned int count;//定时器中断计数器
unsigned int timeout; //记录离超时还有多长时间
struct FIFO8 *fifo;//使用FIFO缓冲区来通知
unsigned char data;
};

   2、修改PIT初始化和中断处理函数

void init_pit(void)
{ //.........修改pit初始化函数
//将count和timeout都初始化为0
timerctl.count = ;
timerctl.timeout = ;
return;
}
void inthandler20(int *esp)
{ //.........修改第20号中断
if (timerctl.timeout > ) { /* 如果已经设定了超时 */
timerctl.timeout--;//没发生一次中断,记录离超时的时间timeout减1
if (timerctl.timeout == ) { //如果记录的时间已经没有了,说明已经到了定时的时间
fifo8_put(timerctl.fifo, timerctl.data);//时间已经到了,通过FIFO缓冲区通知CPU
}
}
return;
}
//定时函数:进行超时设定
void settimer(unsigned int timeout, struct FIFO8 *fifo, unsigned char data)
{
int eflags;
//先禁止中断,避免IRQ0中断没有结束前进来中断发生混乱
eflags = io_load_eflags();
io_cli();
//接着进行超时设置。
timerctl.timeout = timeout;
timerctl.fifo = fifo;
timerctl.data = data;
//最后恢复中断状态
io_store_eflags(eflags);
return;
}

   3、在HariMain中调用定时函数settimer()

    //在定时器的中断函数发生1000次中断后,向timerFIFO中写入”1“,而timerFIFO接收到数据,就会砸屏幕上显示”10[sec]“
settimer(, &timerfifo, );

harib09d:
  上一步我们实现了超时的功能。在操作系统中,超时功能的使用非常方便也非常多。下面我们来根据超时设定多个定时器
  1、修改结构体struct TIMERCTL

#define MAX_TIMER        500
   //定时器结构体
struct TIMER {
//timeout:定时器的中断次数
//flag:记录各个定时器的状态
unsigned int timeout, flags;
struct FIFO8 *fifo;
unsigned char data;
};
struct TIMERCTL {
unsigned int count;
struct TIMER timer[MAX_TIMER];
};

  2、修改timer.c中的相关函数

#define TIMER_FLAGS_ALLOC        1    /* 表示定时器已配置 */
#define TIMER_FLAGS_USING 2 /* 定时器运行中 */
void init_pit(void)
{ //....................
for (i = ; i < MAX_TIMER; i++) {
  //初始化TIMERCTL结构体将所有定时器标志位置0,表示未使用
timerctl.timer[i].flags = ;
}//...................
}
struct TIMER *timer_alloc(void)
{ //...
if (timerctl.timer[i].flags == ) {
  //定时器已分配,将FLAG由未使用改为已配置
timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
return &timerctl.timer[i];
}//...
}
void timer_free(struct TIMER *timer)
{
timer->flags = ;          /* 定时器释放后,将flag置0 */
} void timer_settime(struct TIMER *timer, unsigned int timeout)
{ //...
timer->flags = TIMER_FLAGS_USING;//设置后,flag置运行中
} void inthandler20(int *esp)
{ //...
if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
timerctl.timer[i].timeout--;                    //定时器运行中,每次20号中断一次,timeout-1
if (timerctl.timer[i].timeout == ) {               //timeout没有了
timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;        //解除运行状态,
fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);//通知CPU想FIFO中写入data
}
}
}

  3、HariMain中设置3s和10s两个定时器

  if (fifo8_status(&timerfifo) != ) {     //设置10s定时器
    i = fifo8_get(&timerfifo);         /* 首先获得第一个FIFO缓冲区的地址 */
    io_sti();                  //IDT/PIC开始向CPU发送中断信号
    //显示出来
    putfonts8_asc(buf_back, binfo->scrnx, , , COL8_FFFFFF, "10[sec]");
    sheet_refresh(sht_back, , , , );
  } else if (fifo8_status(&timerfifo2) != ) {//设置3s定时器
     i = fifo8_get(&timerfifo2);
    io_sti();
    putfonts8_asc(buf_back, binfo->scrnx, , , COL8_FFFFFF, "3[sec]");
    sheet_refresh(sht_back, , , , );
  } else if (fifo8_status(&timerfifo3) != ) { //模拟光标
    i = fifo8_get(&timerfifo3);         //获得第三个FIFO缓冲区的地址
    io_sti();
    if (i != ) {                 //i!=0 表示FIFO地址获取成功。
      timer_init(timer3, &timerfifo3, );   /* 初始化定时器timer3,flag=0,表示没使用 */
      boxfill8(buf_back, binfo->scrnx, COL8_FFFFFF, , , , );
    } else {
      timer_init(timer3, &timerfifo3, );   /* 初始化timer3,flag=1表示该定时器一杯分配 */
      boxfill8(buf_back, binfo->scrnx, COL8_008484, , , , );
    }
    timer_settime(timer3, );          //设置定时的时间,50个中断一次闪烁
    sheet_refresh(sht_back, , , , );
  }

harib09e:
  加快中断处理(01):我们知道,中断在CPU中基本是时刻都在发生的,如果按照上面的中断速度,CPU基本什么都做不了。接下来我们加快定时器中断处理的速度。在上面程序中,为了计时,我们在每一次中断发生时,让定时器的timeout减1。这样会增加中断函数执行的时间。修改timeout的含义,表示予定时间,接下来用count和timeout比较就知道是否达到定时的时间了。

void inthandler20(int *esp)
{ //...
if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {
//当予定时间小于计数count的时间时,就把数据写到FIFO中。
//避免了timeout--的操作
if (timerctl.timer[i].timeout <= timerctl.count) {
timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
}
}//....
}
void timer_settime(struct TIMER *timer, unsigned int timeout)
{ //因为定时器启动时,不能确定count的值,所以予定时间设定为count+定时时间
timer->timeout = timeout + timerctl.count;
//此时定时器启动了,FLAG设置定时器正在使用
timer->flags = TIMER_FLAGS_USING;
return;
}
//时刻调整:cout从系统启动,到0xffff_ffff的时间为497天。
//每年多一点的时间启动一次。需要重新将count的值置为0

harib09f:
  加快中断处理(02):我们发现中断执行if(i<MAX_TIMER)的次数太多了。而且大多数是不必要的。追加一个变量timerctl.next记住下一个时刻的值,这样不用每次都做无用的判断。

struct TIMERCTL {            //next记录下一个时刻的值
unsigned int count, next;
struct TIMER timer[MAX_TIMER];
};
                     //把中断函数修改一下,把使用到了next的地方都修改一下
void inthandler20(int *esp)
{
int i;
io_out8(PIC0_OCW2, 0x60);   /* IRQ-00信号接收结束的信息通知给PIC */
timerctl.count++;
if (timerctl.next > timerctl.count) {
return;             /* 还不到下一个时刻,结束 */
}
timerctl.next = 0xffffffff;
for (i = ; i < MAX_TIMER; i++) {
if (timerctl.timer[i].flags == TIMER_FLAGS_USING) {//定时器正在运行
if (timerctl.timer[i].timeout <= timerctl.count) {
            /* 定时时间已经到了,写FIFO */
timerctl.timer[i].flags = TIMER_FLAGS_ALLOC;
fifo8_put(timerctl.timer[i].fifo, timerctl.timer[i].data);
} else {
            /* 定时时间还没有到,修改NEXT的值往后 */
if (timerctl.next > timerctl.timer[i].timeout) {
timerctl.next = timerctl.timer[i].timeout;
}
}
}
}
return;
}
void init_pit(void)
{ //...
timerctl.next = 0xffffffff;    /* 刚初始化,没有在运行的定时器 */
for (i = ; i < MAX_TIMER; i++) {
timerctl.timer[i].flags = ; /* 没有使用 */
}//...
}
void timer_settime(struct TIMER *timer, unsigned int timeout)
{ //...
if (timerctl.next > timer->timeout) {
/* 此时刚启动定时器,初始化next在timeout的下一个时刻 */
timerctl.next = timer->timeout;
}//...
}

harib09g:
  加速中断处理(03):到达next和没有到达next时刻的定时器处理的时间差别很大。我们进一步改进:定义timers[]用来存放按照顺序排列的定时器的地址(和之前描绘图层的处理顺序很相似)

    struct TIMERCTL {
unsigned int count, next, using;//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++;//每一次中断,计数器count++
if (timerctl.next > timerctl.count) {
return;//还不到下一个时刻,结束
}
for (i = ; i < timerctl.using; i++) {
/* 这里直接用using。只对使用中的定时器进行定时器地址排序 */
//timeout>count,没有超时
if (timerctl.timers[i]->timeout > timerctl.count) {
break;
}
/* 超时了,先设置标志位已分配 */
timerctl.timers[i]->flags = TIMER_FLAGS_ALLOC;
//写data到FIFO中
fifo8_put(timerctl.timers[i]->fifo, timerctl.timers[i]->data);
}
/* 上面循环超时执行后到这里来了
这是i保存的是超时寄存器的个数 */
timerctl.using -= i; //超时了i个定时器,活动中的定时器减少i个
for (j = ; j < timerctl.using; j++) {     //对于每一个活动中的定时器
//这是活动中的定时器已经减少了i个,需要对活动中的定时器重新排序,地址重新赋值给timers[]
timerctl.timers[j] = timerctl.timers[i + j];
}
if (timerctl.using > ) {            //活动中的定时器不为0
//将next指向下一个时刻
timerctl.next = timerctl.timers[]->timeout;
} else {
//否则,没有活动中的定时器
timerctl.next = 0xffffffff;
}
return;
} void init_pit(void)
{ //...定时器初始化函数,此时刚刚初始化
timerctl.next = 0xffffffff;           /* 没有活动中的定时器 */
timerctl.using = ;
for (i = ; i < MAX_TIMER; i++) {
timerctl.timers0[i].flags = ;       /* 刚初始化,flag都为0 */
}
return;
}
void timer_settime(struct TIMER *timer, unsigned int timeout)
{ //...设置定时器,(相当于创建了一个对象的一个实例)
for (i = ; i < timerctl.using; i++) {
//这里从排序好的定时器的地址开始找找到最后一个定时器地址的位置i
if (timerctl.timers[i]->timeout >= timer->timeout) {
break;
}
}
for (j = timerctl.using; j > i; j--) {
//在地址i的后面,把新的(实例)定时器的地址放到i的后面
timerctl.timers[j] = timerctl.timers[j - ];
}
timerctl.using++;                //有了新的定时器,活动定时器数量增加一个
timerctl.timers[i] = timer;          //赋值,定时的时间
timerctl.next = timerctl.timers[]->timeout;//新的活动定时器来了,next指向下一个时刻
io_store_eflags(e);               //恢复中断。在前面部分是先禁止了所有的中断的,io_load_eflags()
return;
}

《30天自制操作系统》12_day_学习笔记的相关教程结束。

《《30天自制操作系统》12_day_学习笔记.doc》

下载本文的Word格式文档,以方便收藏与打印。