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

harib04a:
  P126    获取按键编码;
  让程序在按下键盘的键之后,将键值编码显示出来
  修改的是前面编写的鼠标按键的处理键盘中断的函数inthandler21()
  这里笔者介绍了怎样把中断号告诉CPU:
    1、计算0x60+IRQ号码
    2、把结果输出给OCW2寄存器
    3、具体方法:调用io_out8(PIC0_OCW2, 0x60+IRQ);

//int.c节选,修改键盘中断处理函数
void inthandler21(int *esp)
{
  struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
  unsigned char data, s[4];
  io_out8(PIC0_OCW2, 0x61);    /* 通知PIC已经发生了IRQ1中断 */
  data = io_in8(PORT_KEYDAT);  //获取键盘的按键键值,放到DATA中
  sprintf(s, "%02X", data);    //data写到S中;
  boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
  putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
  return;
}

harib04b:
  加快中断处理,上一步获取键盘键值的中断写在了中断处理程序中
  这样如果这时有其他的中断来了就佷尴尬了
  加速原理:利用缓冲区(笔者使用了一个变量)把读到的键值先保存在缓冲区中,需要时,再由HarMain去查看
  注     意:键盘中有的键是一个字节,有的键是两个字节;

//加快中断处理第一步:增加键值缓冲区
struct KEYBUF keybuf{          //键值缓冲区结构体,data:数据位,flag:标志位
  unsigned char data,flag;    };
void inthandler21(int *esp) {
  unsigned char data;
  io_out8(PIC0_OCW2, 0x61);       /* 通知PIC已经发生了IRQ1中断 */
  data = io_in8(PORT_KEYDAT);     //获取键值
  if (keybuf.flag == 0) {         //标志位为0表示缓冲区空,可以放键值
  keybuf.data = data;
  keybuf.flag = 1;
  }
  return;
}
//加快中断处理第二步:修改io_halt的无限循环
for (;;) {
  io_cli();                     //io_cli指令屏蔽中断,因为在后面的处理,防止后面的处理中有中断进来,发生不可预料的结果
  if (keybuf.flag == 0) {
    io_stihlt();              //缓冲区空闲,执行STI和HLT指令;这时,PIC准备好接受中断唤醒CPU
  } else {                     //缓冲区不空闲,将获取的键值输出来,接着标志位置零。
    i = keybuf.data;
    keybuf.flag = 0;
    io_sti();
    sprintf(s, "%02X", i);
    boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
  }
}

harib04c:
  P131    制作FIFO缓冲区;
  思 考:为什么按下和松开都有键值,CTRL键又不一样;
  原 因:我们设定的缓冲区struct KEYBUF keybuf{ unsigned char data,flag};只有一个字节
  下面笔者加大了缓冲区的长度,并用FIFO的栈机制:

struct KEYBUF keybuf{
  unsigned char data[32];     //缓冲区大小增加为32个字节
  int next;                   //指向缓冲区的下一个,因为是字符型数组
  };
//缓冲区数据的程序用FIFO机制做了相应的调整
for (;;) {
  io_cli();                   //io_cli指令屏蔽中断,
  if (keybuf.next == 0) {
    io_stihlt();            //缓冲区空闲,执行STI和HLT指令;
   } else {
    i = keybuf.data[0];
    keybuf.next--;          //不断的从缓冲区读和写FIFO
    for (j = 0; j < keybuf.next; j++) {
      keybuf.data[j] = keybuf.data[j + 1];
     }
    io_sti();
    sprintf(s, "%02X", i);
    boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
  }
}

harib04d:(这一部分请对照图片看!)
  到这里位置数据移送最多只能有32个字节(明显不够)
  到上面为止,数据移送都是在禁止中断的情况下:
    1、在FOR循环中首先进行的就是中断屏蔽: io_cli();//io_cli指令屏蔽中断;

    2、之后才能进行键盘的数据接收;
  思 考:如果每次读数据都要先屏蔽中断,这样也太。。。。(麻烦)。。怎么办?
  解 决:笔者接下来开发了一个不需要数据移送操作的FIFO缓冲区;大致运用了循环链表的思想:

   

  如上图所示:当写入的位置到达缓冲区末尾,缓冲区开头应该已经开始变空(如果没有变空,说明数据读跟不上数据写,那么只好把部分数据扔掉)。因此,如果下一个数据写入位置到了32以后,就强制性的将其置0;对下一个数据读出位置也做同样的处理,一旦到了32以后,就把它设置从0开始据徐读取数据。这样32字节的缓冲区就能一圈一圈的不断循环(其实就是循环链表;如果看不懂我的解释,请看书本P134内容

//next_r :下一个读的位置
//next_w :下一个写的位置
//   len :缓冲区能记录多少字节的数据
struct KEYBUF {
  unsigned char data[32];
  int next_r, next_w, len;   };

  接下来做的事:修改中断处理程序(void inthandler21(int *esp))和io_halt的无限循环;下面是io_halt修改的部分:

for (;;) {
  io_cli();
  if (keybuf.len == 0) {
    io_stihlt(); //这样每一次屏蔽中断的时间有1/2降低为1/32
  } else {
    i = keybuf.data[keybuf.next_r];
    keybuf.len--;
    keybuf.next_r++;
    if (keybuf.next_r == 32) {
    keybuf.next_r = 0;
    }
    io_sti();
    sprintf(s, "%02X", i);
    boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
  }
}

harib04e:
  我们知道,每次鼠标产生动作,会连续发送3个字节的数据(两个坐标和一个状态信息)
  接下来我们进一步修改缓冲区的内容,让他也能适应我们要做的鼠标的移动(重新定义缓冲区):

//1、缓冲区大小改为可变,不再是32字节了
//2、保存缓冲区的总字节数size
//3、缓冲区空闲的字节数free
//4、缓冲区的地址buf
struct FIFO8 {
  unsigned char *buf;
  int p, q, size, free, flags;
};

  作者其实是相当负责的,接着作者写了几个相关的操作函数以便于后续的调用;这些函数都被封装在FIFO.C中(FIFO.C代码较长,我们折叠起来吧!):

/* FIFO.c */
#include "bootpack.h"
#define FLAGS_OVERRUN        0x0001

void fifo8_init(struct FIFO8 *fifo, int size, unsigned char *buf)
/* FIFO缓冲区的初始化,用来设定FIFO8的结构地址以及有关的各种参数 */
{
    fifo->size = size;
    fifo->buf = buf;
    fifo->free = size; /* 缓冲区大小 */
    fifo->flags = 0;
    fifo->p = 0;       /* 下一个数据写入的位置 */
    fifo->q = 0;       /* 下一个读数据的位置 */
    return;
}

int fifo8_put(struct FIFO8 *fifo, unsigned char data)
/* 向FIFO缓冲区存储一个字节的数据 */
{
    if (fifo->free == 0) {    /* 溢出了 */
        fifo->flags |= FLAGS_OVERRUN;
        return -1;           //返回-1 溢出了
    }
    fifo->buf[fifo->p] = data;
    fifo->p++;
    if (fifo->p == fifo->size) {
        fifo->p = 0;
    }
    fifo->free--;
    return 0;               //返回0,没有溢出
}

int fifo8_get(struct FIFO8 *fifo)
/* 从缓冲区取一个字节的函数 */
{
    int data;
    if (fifo->free == fifo->size) {
        /* 如果缓冲区为空返回-1 */
        return -1;
    }
    data = fifo->buf[fifo->q];
    fifo->q++;
    if (fifo->q == fifo->size) {
        fifo->q = 0;
    }
    fifo->free++;
    return data;
}
int fifo8_status(struct FIFO8 *fifo)
/* 调出缓冲区的状态,报告到底积攒了多少数据 */
{
    return fifo->size - fifo->free;
}

FIFO.C

  接下来做的事,和上面相同:修改中断处理程序(void inthandler21(int *esp));和io_halt的无限循环;

void inthandler21(int *esp)     //修改中断处理程序(void inthandler21(int *esp))
{
  unsigned char data;
  io_out8(PIC0_OCW2, 0x61);    /* PIC打开IRQ-1中断口,告诉CPU */
  data = io_in8(PORT_KEYDAT);
  fifo8_put(&keyfifo, data);  //看见木有,直接调用封装在fifo.c中的函数
  return;
}
for (;;) {                     //MariMain中也做相应调整;io_halt的循环早就没有啦
  io_cli();
  if (fifo8_status(&keyfifo) == 0) {
    io_stihlt();
  } else {
  i = fifo8_get(&keyfifo);
  io_sti();
  sprintf(s, "%02X", i);
  boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
  putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
  }
}

harib04f:
  好了,现在我们又要开始折腾鼠标了!
  还记得前面笔者给鼠标和键盘分配的PIC的中断号是多少吗?(键盘:IRQ01;鼠标:IRQ12
  笔者在书中这一部分首先给我们普及了一下鼠标是如何兴起的。接着普及了鼠标一些操作的一些变化(一句话:以前的鼠标操作和现在不同)
  我们先来看看控制电路是什么情况:
  注   意:鼠标控制器和键盘控制器实际上集成在同一个控制电路中。

void wait_KBC_sendready(void)
{     /* 等待键盘控制电路准备完成 */
    //如果键盘端口就绪PROT_KEYSTA & 处于准备发送数据的状态KEYSTA_SEND_NOTREADY
    //表示键盘控制电路已经准备完毕了。跳出去,返回,不再等待
  for (;;) {   if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) { break; }   }   
  return;
}
void init_keyboard(void) { /* 初始化键盘 */
   wait_KBC_sendready();   
   io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE);
   wait_KBC_sendready();   
   io_out8(PORT_KEYDAT, KBC_MODE);
   return;
}

  接下来向控制器发送指令,激活鼠标:

//激活鼠标的相关程序,
//这里和上一部中的void init_keyboard(void)初始化键盘很相似,发现了没有?
//没错,就是因为这两个设备的控制器实际上是在同一个控制电路中的
#define KEYCMD_SENDTO_MOUSE    0xd4    //键盘的使能信号
#define MOUSECMD_ENABLE      0xf4     //鼠标的使能信号
void enable_mouse(void)                //鼠标使能函数,想控制器发送激活指令
{   /* 激活鼠标, */
  wait_KBC_sendready();
  io_out8(PORT_KEYCMD, KEYCMD_SENDTO_MOUSE);
  wait_KBC_sendready();
  io_out8(PORT_KEYDAT, MOUSECMD_ENABLE);
  return; /* 激活成功,返回ACK(0xfa) */
}

harib04g:
  P142 从鼠标接收数据;原理和上面的从键盘接受数据相同
  在 harib04e 中,我们已经修改好了缓冲区,让他同时成为鼠标和键盘数据的缓冲(详见harib04e)
  那么我们要做什么事呢?
      没错,和键盘接受数据一样,修改两个东西:鼠标的中断程序(函数):inthandler2c() 和 io_halt的无限循环
  理解了上面键盘的做法,到这里理解起来就很简单了;直接上代码:

//怎么样。鼠标的中断程序和键盘中断程序inthandler21(int *esp)神似,有木有?
void inthandler2c(int *esp)      /* PS/2的鼠标中断 */
{
  unsigned char data;
  io_out8(PIC1_OCW2, 0x64);        /* IRQ-12已经受理完成 */
  io_out8(PIC0_OCW2, 0x62);        /* IRQ-02已经受理完成 */
  data = io_in8(PORT_KEYDAT);
  fifo8_put(&mousefifo, data);
  return;
}
//取数据程序(io_halt的无限循环)
//这里和键盘的取数据程序也神似,不同的是,缓冲区开到了128字节;
//因为鼠标的数据量更大
fifo8_init(&mousefifo, 128, mousebuf){
  if (fifo8_status(&keyfifo) + fifo8_status(&mousefifo) == 0) {
  io_stihlt();
  } else {
    if (fifo8_status(&keyfifo) != 0) {
    i = fifo8_get(&keyfifo);
    io_sti();
    sprintf(s, "%02X", i);
    boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 0, 16, 15, 31);
    putfonts8_asc(binfo->vram, binfo->scrnx, 0, 16, COL8_FFFFFF, s);
  } else if (fifo8_status(&mousefifo) != 0) {
    i = fifo8_get(&mousefifo);
    io_sti();
    sprintf(s, "%02X", i);
    boxfill8(binfo->vram, binfo->scrnx, COL8_008484, 32, 16, 47, 31);
    putfonts8_asc(binfo->vram, binfo->scrnx, 32, 16, COL8_FFFFFF, s);
  }
}
时间: 2024-10-05 10:13:52

《30天自制操作系统》07_day_学习笔记的相关文章

《30天自制操作系统》学习笔记-第1天

为了加深对操作系统的理解,我决定照着<30天自制操作系统>这本书实践一下.项目的github链接是https://github.com/YatesXu/YatesOSASK/ 关于十六进制编辑器 第一个问题就是书中给的十六进制编辑器是日文的,在我的电脑上打开之后是一片乱码,于是我比较之后选用了这个十六进制编辑器wxMEdit,链接是https://wxmedit.github.io/. 另外,visual studio也可以,但是express版不能用,所以还是选用免费开源的软件吧(笑) (伪

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

这一次的学习相当曲折, 主要是因为粗心, Makefile里面的错误导致了文件生成出现各种奇奇怪怪的问题, 弄得心力交瘁, 因此制作过程还是尽量按着作者的路子来吧. 作者提供的源码的注释在中文系统下是乱码, 而且代码的分隔用了两个Tab, 在这里要处理一下: :%s/;.*//g 删除所有的注释; :%s/\t\t/\t 把两个Tab替换为一个Tab; 要让作者的nas文件和asm文件拥有相同的语法规则, 在_vimrc文件的最后一行添加 au BufNewFile,BufRead *.nas

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

让系统跑起来 要写一个操作系统,我们首先要有一个储存系统的介质,原版书似乎是06年出版的,可惜那时候没有电脑,没想到作者用的还是软盘,现在的电脑谁有软驱?不得已我使用一张128M的SD卡来代替,而事实上你用的是U盘还是软盘对我们的操作系统没有影响,缺点是你的U盘刷入系统后容量只能是1440 MB,即当年流行的3.5英寸软盘的大小,当然不用担心,再格式化一次(用DiskGeniu),就可以恢复. 我做事情的话,总是怕自己的努力的结果白费了,害怕辛辛苦苦看完这本书但是发现做出来的东西现在根本没法用,

《30天自制操作系统》读书笔记(4) 绘图

暑假果然是滋生懒散的温床. (╯‵□′)╯︵┻━┻ 好久不动都忘记之前做到哪里了, 上次好像做到了C语言的引入, 这一节所做的东西都相当轻松, 将会绘制出操作系统的基本界面. 绘图的原理 按照书中所说, 将值写入到显存中就能在屏幕上显示相应的像素, 在asmhead.nas 中有这一段: 1 CYLS EQU 0x0ff0 ; 设定启动区 2 LEDS EQU 0x0ff1 3 VMODE EQU 0x0ff2 ; 关于颜色数目的信息,颜色的位数 4 SCRNX EQU 0x0ff4 ; 分辨率

《30天自制操作系统》读书笔记(1)读前感

做一个自己的操作系统, 在我看来一直是不可以思议的,而且奇妙的,像是吉他手亲手打造一把自己的吉他? 似乎这个比喻不太恰当, 但是,感觉是一样的. <30天自制操作系统> 为日本的川和秀实先生所著, 有人说他是 "<XX天学会XXX>之类的书中为数不多的几本好书之一." 这本书的优点非常明显,通俗生趣,甚至于有点啰嗦:而且作者无私地提供了源代码而且允许你以任何方式使用,也提供了编译的所有工具,所有东西都是"开箱即用",不容易出问题. 作为后生我

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

梳理项目结构 项目做到现在, 前头的好多东西都忘了, 还是通过Makefile重新理解一下整个项目是如何编译的: 现在我们拥有这么9个文件: ipl10.nas    InitialProgramLoader, 占用了软盘的第一个扇区并符合启动盘的规范, 默认被载入地址是0x7c00 到 0x7e00, 负责将10个柱面读入到0x8200到0x34fff (10个柱面共10*2*18 = 360 个扇区但是第一个没有被读入); asmhead.nas     包含一些暂时未知的设定; naskf

多定时器处理1(30天自制操作系统--读书笔记)

自认为写过很多MCU程序,但总是回头想想,我所了解的MCU编程思想大体有两种,其中具体的想法我得再找时间写下来. 总想总结出一个可扩展的,易移植的写法,但能力还没到这个层次.但<30天自制操作系统>这本书确实给我了一个思路,就像我已经写过的两篇读书笔记. 将两个独立的内容--FIFO和内存动态管理做到高度模块化,尤其是其中数据结构模型的设计更是我学习的好例子. 今天要学习的设计内容是多定时器处理.原书对这部分的处理讲的很详细,由浅入深,看得我由衷佩服作者,也可能是因为我水平低,稍稍看出点门道来

单字节的FIFO缓存(30天自制操作系统--读书笔记)

从今天起,写一些读书笔记.最近几个月都在看<30天自制操作系统这本书>,书虽说看的是电子书,但可以花钱买的正版书,既然花费了金钱,就总得有些收获. 任何人都不能总是固步自封,想要进步就得学习别人的知识,对于程序员而言,最简单的方法即是学习别人的代码. 今天的标题是“单字节的FIFO缓存”,其实就是做一个FIFO,看名字就知道了.也就4个函数和1个相关结构体,这样的小代码在嵌入式系统中很常用,也会很好用. 1.相关数据结构体 struct FIFO8 { unsigned char *buf;

《30天自制操作系统》笔记(12)——多任务入门

<30天自制操作系统>笔记(12)——多任务入门 进度回顾 上一篇介绍了设置显示器高分辨率的方法.本篇讲一下操作系统实现多任务的方法. 什么是多任务 对程序员来说,也许这是废话,不过还是说清楚比较好. 多任务就是让电脑同时运行多个程序(如一边写代码一边听音乐一边下载电影). 电脑的CPU只有固定有限的那么一个或几个,不可能真的同时运行多个程序.所以就用近似的方式,让多个程序轮换着运行.当轮换速度够快(0.01秒),给人的感觉就是"同时"运行了. 多任务之不实用版 我们首先从

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

<30天自制操作系统>笔记(03)--使用Vmware 进度回顾 在上一篇,实现了用IPL加载OS程序到内存,然后JMP到OS程序这一功能:并且总结出下一步的OS开发结构.但是遇到了真机测试和U盘启动的一些问题.本篇就来解决之. 遇到的问题 物理机测试 简单来说,把软盘(U盘)做成启动盘后,自然想要用来启动物理机器.毕竟这才是真正的测试.(用QEMU总没多大的成就感)但物理机测试的麻烦在于太慢了,每次都要关掉Windows,重启,测试,然后再重启Windows.而且还没办法截图. 而用Vmwa