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_学习笔记的更多相关文章

  1. 《30天自制操作系统》学习笔记--Mac下工具的使用

    现在来介绍官网上下的工具怎么用首先是官网地址,书上有个注释上有:hrb.osask.jp 翻译成中文大概是这个样子滴. 上面有两个文件可以下载,一个是工具,一个是工具的源代码,很好的学习资料 下面把工 ...

  2. 《30天自制操作系统》学习笔记--Mac环境搭建

    弄了三天了,终于弄好了,先说结果,就是作者在网站上放了os x的工具(hrb.osask.jp,也有linux下的工具,可以自己去下载),也就是说我白忙活了三天... 再说一下这几天都干啥了,主要是想 ...

  3. 《30天自制操作系统》学习笔记--番外篇之Mac环境下的工具介绍

    这几天又有点不务正业了,书也没看,一直在搞这个破环境,尝试各种做法,网上各种垃圾信息,浪费了很多时间,说的基本都是废话,不过还是找到了一些,赶紧写下来,不然这个过几天又忘了 首先是环境,我用的是Max ...

  4. 《30天自制操作系统》读书笔记(5) GDT&IDT

    梳理项目结构 项目做到现在, 前头的好多东西都忘了, 还是通过Makefile重新理解一下整个项目是如何编译的: 现在我们拥有这么9个文件: ipl10.nas    InitialProgramLo ...

  5. 《30天自制操作系统》读书笔记(3) 引入C语言

    这一次的学习相当曲折, 主要是因为粗心, Makefile里面的错误导致了文件生成出现各种奇奇怪怪的问题, 弄得心力交瘁, 因此制作过程还是尽量按着作者的路子来吧. 作者提供的源码的注释在中文系统下是 ...

  6. 《30天自制操作系统》读书笔记(2)hello, world

    让系统跑起来 要写一个操作系统,我们首先要有一个储存系统的介质,原版书似乎是06年出版的,可惜那时候没有电脑,没想到作者用的还是软盘,现在的电脑谁有软驱?不得已我使用一张128M的SD卡来代替,而事实 ...

  7. 30天自制操作系统第九天学习笔记(u盘软盘双启动版本)

    暑假学习小日本的那本书:30天自制操作系统 qq交流群:122358078    ,更多学习中的问题.资料,群里分享 environment:开发环境:ubuntu 第九天的课程已学完,确实有点不想写 ...

  8. 从你的u盘启动:30天自制操作系统第四天u盘启动学习笔记

    暑假学习小日本的那本书:30天自制操作系统 qq交流群:122358078    ,更多学习中的问题.资料,群里分享 developing environment:ubuntu 关于u盘启动自己做的操 ...

  9. 30天自制操作系统第八天学习笔记(u盘软盘双启动版本)

    暑假学习小日本的那本书:30天自制操作系统 qq交流群:122358078    ,更多学习中的问题.资料,群里分享 environment:开发环境:ubuntu 第八天的学习思考: 关于鼠标是怎么 ...

  10. 《30天自制操作系统》笔记(03)——使用Vmware

    <30天自制操作系统>笔记(03)——使用Vmware 进度回顾 在上一篇,实现了用IPL加载OS程序到内存,然后JMP到OS程序这一功能:并且总结出下一步的OS开发结构.但是遇到了真机测 ...

随机推荐

  1. [WP8.1UI控件编程]SemanticZoom控件实现分组列表

    11.1.5 SemanticZoom实现分组列表 SemanticZoom控件可以让用户实现一种更加高级的列表,这种列表可以对列表的项目进行分组,同时这个SemanticZoom控件会提供两个具有相 ...

  2. 4分钟apache自带ab压力测试工具使用: 2015.10.4

    2015.10.44分钟apache自带ab压力测试工具使用:win8.1 wampserver2.5 -Apache-2.4.9-Mysql-5.6.17-php5.5.12-64b 可以参考一下部 ...

  3. 【POJ】2891 Strange Way to Express Integers

    http://poj.org/problem?id=2891 题意:求最小的$x$使得$x \equiv r_i \pmod{ a_i }$. #include <cstdio> #inc ...

  4. [BZOJ2803][Poi2012]Prefixuffix

    2803: [Poi2012]Prefixuffix Time Limit: 10 Sec  Memory Limit: 64 MBSubmit: 219  Solved: 95[Submit][St ...

  5. xml学习

    一,数据类型 xmlChar  对char的基本代替,是一个UTF-8编码字符串中的一个字节.如果你的数据使用了其他编码,在使用libxml函数前就必须转换为UTF-8. xmlDoc和xmlDocP ...

  6. SolrCloud-5.2.1 集群部署及测试

    一. 说明 Solr5内置了Jetty服务,所以不用安装部署到Tomcat了,网上部署Tomcat的资料太泛滥了. 部署前的准备工作: 1. 将各主机IP配置为静态IP(保证各主机可以正常通信,为避免 ...

  7. Working with HTTP

    A WebClient façade class for simple download/upload operations via HTTP or FTP WebRequest and WebRes ...

  8. Odoo中的Javascript单元测试

    前端页面利用QUnit进行单元测试,本文参考官方文档:https://www.odoo.com/documentation/8.0/reference/javascript.html 访问/web/t ...

  9. MySQL中引号的问题

    原文: http://blog.csdn.net/wisgood/article/details/6317543 mysql中一个字符串,既可以用两个单引号表示,也可以用两个双引号表示. 比如字符串 ...

  10. CSS3初学篇章_6(自定义动画)

    自定义动画 由于有一部分低版本的浏览器并不支持的问题,所以这个样式要多做兼容,各大浏览器兼容前缀如下: 前缀 浏览器  -webkit  chrome和safari  -moz  firefox  - ...