操作系统真象还原实验记录之实验十一:实现中断处理(二)

书p335 7.6.2 改进中断处理程序,并调快时钟

1.实验代码第一次修改

对应
书p335 7.6.2 改进中断处理程序

这次是上一次实验的修改
在cpu获得中断向量号后,会调用中断处理程序,上次实验中断处理程序kernel.s是汇编写的,这次对kernel.s进行修改,在中断处理程序里调用了c语言写的处理程序来执行中断处理

1.1 interrupt.c

#include "interrupt.h"
#include "stdint.h"
#include "global.h"
#include "io.h"
#include "print.h"#define PIC_M_CTRL 0x20        // 这里用的可编程中断控制器是8259A,主片的控制端口是0x20
#define PIC_M_DATA 0x21        // 主片的数据端口是0x21
#define PIC_S_CTRL 0xa0        // 从片的控制端口是0xa0
#define PIC_S_DATA 0xa1        // 从片的数据端口是0xa1#define IDT_DESC_CNT 0x21      // 目前总共支持的中断数33#define EFLAGS_IF   0x00000200       // eflags寄存器中的if位为1
#define GET_EFLAGS(EFLAG_VAR) asm volatile("pushfl; popl %0" : "=g" (EFLAG_VAR))/*中断门描述符结构体*/
struct gate_desc {uint16_t    func_offset_low_word;uint16_t    selector;uint8_t     dcount;   //此项为双字计数字段,是门描述符中的第4字节。此项固定值,不用考虑uint8_t     attribute;uint16_t    func_offset_high_word;
};// 静态函数声明,非必须
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function);
static struct gate_desc idt[IDT_DESC_CNT];   // idt是中断描述符表,本质上就是个中断门描述符数组char* intr_name[IDT_DESC_CNT];          // 用于保存异常的名字/********    定义中断处理程序数组    ********* 在kernel.S中定义的intrXXentry只是中断处理程序的入口,* 最终调用的是ide_table中的处理程序*/
intr_handler idt_table[IDT_DESC_CNT];/********************************************/
extern intr_handler intr_entry_table[IDT_DESC_CNT];     // 声明引用定义在kernel.S中的中断处理函数入口数组/* 初始化可编程中断控制器8259A */
static void pic_init(void) {/* 初始化主片 */outb (PIC_M_CTRL, 0x11);   // ICW1: 边沿触发,级联8259, 需要ICW4.outb (PIC_M_DATA, 0x20);   // ICW2: 起始中断向量号为0x20,也就是IR[0-7] 为 0x20 ~ 0x27.outb (PIC_M_DATA, 0x04);   // ICW3: IR2接从片. outb (PIC_M_DATA, 0x01);   // ICW4: 8086模式, 正常EOI/* 初始化从片 */outb (PIC_S_CTRL, 0x11);    // ICW1: 边沿触发,级联8259, 需要ICW4.outb (PIC_S_DATA, 0x28);    // ICW2: 起始中断向量号为0x28,也就是IR[8-15] 为 0x28 ~ 0x2F.outb (PIC_S_DATA, 0x02);    // ICW3: 设置从片连接到主片的IR2引脚outb (PIC_S_DATA, 0x01);    // ICW4: 8086模式, 正常EOI/* IRQ2用于级联从片,必须打开,否则无法响应从片上的中断主片上打开的中断有IRQ0的时钟,IRQ1的键盘和级联从片的IRQ2,其它全部关闭 */outb (PIC_M_DATA, 0xfe);  只打开时钟中断,其余均屏蔽/* 打开从片上的IRQ14,此引脚接收硬盘控制器的中断 */outb (PIC_S_DATA, 0xff);put_str("   pic_init done\n");
}/* 创建中断门描述符 */
static void make_idt_desc(struct gate_desc* p_gdesc, uint8_t attr, intr_handler function) { p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF;p_gdesc->selector = SELECTOR_K_CODE;p_gdesc->dcount = 0;p_gdesc->attribute = attr;p_gdesc->func_offset_high_word = ((uint32_t)function & 0xFFFF0000) >> 16;
}/*初始化中断描述符表*/
static void idt_desc_init(void) {int i, lastindex = IDT_DESC_CNT - 1;for (i = 0; i < IDT_DESC_CNT; i++) {make_idt_desc(&idt[i], IDT_DESC_ATTR_DPL0, intr_entry_table[i]); }
/* 单独处理系统调用,系统调用对应的中断门dpl为3,* 中断处理程序为单独的syscall_handler */put_str("   idt_desc_init done\n");
}/* 通用的中断处理函数,一般用在异常出现时的处理 */
static void general_intr_handler(uint8_t vec_nr) {if (vec_nr == 0x27 || vec_nr == 0x2f) {   // 0x2f是从片8259A上的最后一个irq引脚,保留return;     //IRQ7和IRQ15会产生伪中断(spurious interrupt),无须处理。}put_str("int vector : 0x");put_int(vec_nr);put_char('\n');
}/* 完成一般中断处理函数注册及异常名称注册 */
static void exception_init(void) {              // 完成一般中断处理函数注册及异常名称注册int i;for (i = 0; i < IDT_DESC_CNT; i++) {/* idt_table数组中的函数是在进入中断后根据中断向量号调用的,* 见kernel/kernel.S的call [idt_table + %1*4] */idt_table[i] = general_intr_handler;           // 默认为general_intr_handler。// 以后会由register_handler来注册具体处理函数。intr_name[i] = "unknown";                    // 先统一赋值为unknown }intr_name[0] = "#DE Divide Error";intr_name[1] = "#DB Debug Exception";intr_name[2] = "NMI Interrupt";intr_name[3] = "#BP Breakpoint Exception";intr_name[4] = "#OF Overflow Exception";intr_name[5] = "#BR BOUND Range Exceeded Exception";intr_name[6] = "#UD Invalid Opcode Exception";intr_name[7] = "#NM Device Not Available Exception";intr_name[8] = "#DF Double Fault Exception";intr_name[9] = "Coprocessor Segment Overrun";intr_name[10] = "#TS Invalid TSS Exception";intr_name[11] = "#NP Segment Not Present";intr_name[12] = "#SS Stack Fault Exception";intr_name[13] = "#GP General Protection Exception";intr_name[14] = "#PF Page-Fault Exception";// intr_name[15] 第15项是intel保留项,未使用intr_name[16] = "#MF x87 FPU Floating-Point Error";intr_name[17] = "#AC Alignment Check Exception";intr_name[18] = "#MC Machine-Check Exception";intr_name[19] = "#XF SIMD Floating-Point Exception";}/*完成有关中断的所有初始化工作*/
void idt_init() {put_str("idt_init start\n");idt_desc_init();    // 初始化中断描述符表exception_init();       // 异常名初始化并注册通常的中断处理函数pic_init();        // 初始化8259A/* 加载idt */uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16));asm volatile("lidt %0" : : "m" (idt_operand));put_str("idt_init done\n");
}

增加了如下

char* intr_name[IDT_DESC_CNT];            // 用于保存异常的名字intr_handler idt_table[IDT_DESC_CNT];static void general_intr_handler(uint8_t vec_nr)static void exception_init(void)

我们写的33个中断处理程序,前20个的中断向量号是异常,由cpu产生
第21个到第32个中断向量号需保留,所以名字赋值成unknown。
所以两块8259A芯片产生的15个中断向量号从0x20到0x2f
其中IRQ7为并口1,IRQ15是保留,伪中断经常用这两个号,这两个接口无法通过IMR屏蔽寄存器来屏蔽,所以需要软件单独处理
伪中断是一种硬件中断,比如中断线路上的电气信号异常等。

本次实验只打开了IRQ0,所以由8259A芯片产生的中断向量号只可能是0x20,0x27、0x2f。
CPU还可能产生0~19号中断向量号(第15号除外)

上述本次实验可能产生中断向量号,都会进入中断处理程序,在中断处理程序调用general_intr_handler,打印自己的中断向量号,0x27、0x2f的伪中断除外。

1.2 kernel.s

[bits 32]
%define ERROR_CODE nop       ; 若在相关的异常中cpu已经自动压入了错误码,为保持栈中格式统一,这里不做操作.
%define ZERO push 0      ; 若在相关的异常中cpu没有压入错误码,为了统一栈中格式,就手工压入一个0extern put_str;
extern idt_table;section .data
global intr_entry_table
intr_entry_table:%macro VECTOR 2
section .text
intr%1entry:         ; 每个中断处理程序都要压入中断向量号,所以一个中断类型一个中断处理程序,自己知道自己的中断向量号是多少%2               ; 中断若有错误码会压在eip后面
; 以下是保存上下文环境push dspush espush fspush gspushad           ; PUSHAD指令压入32位寄存器,其入栈顺序是: EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI; 如果是从片上进入的中断,除了往从片上发送EOI外,还要往主片上发送EOI mov al,0x20                   ; 中断结束命令EOIout 0xa0,al                   ; 向从片发送out 0x20,al                   ; 向主片发送push %1             ; 不管idt_table中的目标程序是否需要参数,都一律压入中断向量号,调试时很方便call [idt_table + %1*4]       ; 调用idt_table中的C版本中断处理函数jmp intr_exitsection .datadd    intr%1entry   ; 存储各个中断入口程序的地址,形成intr_entry_table数组
%endmacrosection .text
global intr_exit
intr_exit:
; 以下是恢复上下文环境add esp, 4             ; 跳过中断号popadpop gspop fspop espop dsadd esp, 4              ; 跳过error_codeiretdVECTOR 0x00,ZERO
VECTOR 0x01,ZERO
VECTOR 0x02,ZERO
VECTOR 0x03,ZERO
VECTOR 0x04,ZERO
VECTOR 0x05,ZERO
VECTOR 0x06,ZERO
VECTOR 0x07,ZERO
VECTOR 0x08,ERROR_CODE
VECTOR 0x09,ZERO
VECTOR 0x0a,ERROR_CODE
VECTOR 0x0b,ERROR_CODE
VECTOR 0x0c,ZERO
VECTOR 0x0d,ERROR_CODE
VECTOR 0x0e,ERROR_CODE
VECTOR 0x0f,ZERO
VECTOR 0x10,ZERO
VECTOR 0x11,ERROR_CODE
VECTOR 0x12,ZERO
VECTOR 0x13,ZERO
VECTOR 0x14,ZERO
VECTOR 0x15,ZERO
VECTOR 0x16,ZERO
VECTOR 0x17,ZERO
VECTOR 0x18,ERROR_CODE
VECTOR 0x19,ZERO
VECTOR 0x1a,ERROR_CODE
VECTOR 0x1b,ERROR_CODE
VECTOR 0x1c,ZERO
VECTOR 0x1d,ERROR_CODE
VECTOR 0x1e,ERROR_CODE
VECTOR 0x1f,ZERO
VECTOR 0x20,ZERO    ;时钟中断对应的入口

1.3 效果

2.加快时钟

2.1 timer.c


#include "io.h"
#include "print.h"
#include "interrupt.h"#define IRQ0_FREQUENCY     100
#define INPUT_FREQUENCY    1193180
#define COUNTER0_VALUE     INPUT_FREQUENCY / IRQ0_FREQUENCY
#define CONTRER0_PORT      0x40
#define COUNTER0_NO    0
#define COUNTER_MODE       2
#define READ_WRITE_LATCH   3
#define PIT_CONTROL_PORT   0x43/* 把操作的计数器counter_no、读写锁属性rwl、计数器模式counter_mode写入模式控制寄存器并赋予初始值counter_value */
static void frequency_set(uint8_t counter_port, \uint8_t counter_no, \uint8_t rwl, \uint8_t counter_mode, \uint16_t counter_value) {
/* 往控制字寄存器端口0x43中写入控制字 */outb(PIT_CONTROL_PORT, (uint8_t)(counter_no << 6 | rwl << 4 | counter_mode << 1));
/* 先写入counter_value的低8位 */outb(counter_port, (uint8_t)counter_value);
/* 再写入counter_value的高8位 */outb(counter_port, (uint8_t)counter_value >> 8);
}/* 初始化PIT8253 */
void timer_init() {put_str("timer_init start\n");/* 设置8253的定时周期,也就是发中断的周期 */frequency_set(CONTRER0_PORT, COUNTER0_NO, READ_WRITE_LATCH, COUNTER_MODE, COUNTER0_VALUE);put_str("timer_init done\n");
}

2.2 timer.h

#ifndef __DEVICE_TIME_H
#define __DEVICE_TIME_H
#include "stdint.h"
void timer_init(void);
void mtime_sleep(uint32_t m_seconds);
#endif

2.3 init.c

#include "init.h"
#include "print.h"
#include "interrupt.h"
#include "../device/timer.h"/*负责初始化所有模块 */
void init_all() {put_str("init_all\n");idt_init();         // 初始化中断timer_init();}

2.4运行

1.编译timer.c

gcc -m32 -I lib/kernel -c -o build/timer.o device/timer.c

2.链接

ld -m elf_i386  -Ttext 0xc0001500 -e main -o build/kernel.bin build/main.o build/init.o build/interrupt.o build/print.o build/kernel.o build/timer.o

3.刻入

dd if=/home/Seven/bochs2.68/bin/build/kernel.bin of=/home/Seven/bochs2.68/bin/Seven.img bs=512 count=200 seek=9 conv=notrunc

4.运行

./bochs -f bochsrc.disk

5.结果

很奇怪,中断向量号变成了0x0D,这个是GP异常
我记得当时不要time.c,就不会报GP异常
要了time.c就会报,以后再来处理。先过一遍~~

相当于调用的是

VECTOR 0x0d,ERROR_CODE

这个宏

总结一下整个流程:
1.时钟中断信号从8259A芯片IR0引脚进入,pic_init函数设置起始中断向量号为0x20,IR0引脚接受信号。
2.cpu接收到0x20的中断向量号,查询IDTR寄存器获得的idt首地址,随即找到0x20对应的中断门描述符,从而获得中断程序入口地址。idt_desc_init函数里的make_idt_desc函数写好了idt的所有中断门描述符,idt_init赋值了idtr,kernel.s里的intr_entry_table数组记录了33个中断程序入口地址。
3.cpu保存现场后,执行中断处理程序,相当于执行kernel.s定义的宏VECTOR 0x20,ZERO,这个宏里的代码执行了call [idt_table + %14],调用了c程序。相当于中断处理程序核心功能是c写的。
4. exception_init函数把general_intr_handler这个c函数地址赋值给了idt_table[i]数组,所以call [idt_table + %1
4]就是调用c函数general_intr_handler,上一句push %1 就是c函数general_intr_handler所需参数中断向量号。

操作系统真象还原实验记录之实验十一:实现中断处理(二)相关推荐

  1. 《操作系统真象还原》从零开始自制操作系统 全流程记录

    文章目录 前引 章节博客链接 实现源码链接 前引 这本<操作系统真象还原>里面一共有十五个章节 大约760页 这些系列博客也是我在做完哈工大操作系统Lab之后 觉得还是有些朦朦胧胧 毅然决 ...

  2. 操作系统真象还原实验记录之实验十二:实现ASSERT

    操作系统真象还原实验记录之实验十二:实现ASSERT,通过makefile完成编译 对应书P367 第8.2节 1.相关基础知识 见书 2.实验代码 完成了开关中断函数.实现assert断言函数用于调 ...

  3. 操作系统真象还原实验记录之实验七:加载内核

    操作系统真象还原实验记录之实验七:加载内核 对应书P207 1.相关基础知识总结 1.1 elf格式 1.1.1 c程序如何转化成elf格式 写好main.c的源程序 //main.c int mai ...

  4. 操作系统真象还原实验记录之实验六:内存分页

    操作系统真象还原实验记录之实验五:内存分页 对应书P199页 5.2 1.相关基础知识总结 页目录 页目录项 页表 页表项 物理页 虚拟地址 物理地址 概念略 页目录项及页表项 低12位都是属性.高2 ...

  5. 操作系统真象还原实验记录之实验一:第一次编写mbr

    操作系统真象还原之实验一:第一次编写mbr 对应书中第2.3节:让mbr飞一会 第58页 1.相关基础知识提炼总结 1.1电脑开机前与后 在电脑未开机前,BIOS就被事先写入到内存的F0000~FFF ...

  6. 操作系统真象还原实验记录之实验二十三:硬盘分区,并编写硬盘驱动程序

    操作系统真象还原实验记录之实验二十三:编写硬盘驱动程序 1.硬盘分区 1.1 创建Seven80.img硬盘 ./bximage -mode=create -imgmode=flat -hd=80 - ...

  7. 操作系统真象还原实验记录之实验三十四:实现管道

    操作系统真象还原实验记录之实验三十四:实现管道 1.管道相关知识总结 先说我们操作系统的管道实现: 上述图中,管道缓冲区就是一页内存,这一页内存被我们当成了环形缓冲区结构, 当这页管道被创建出来后,全 ...

  8. 《操作系统真象还原》1-3章 学习记录

    文章目录 前言 一.开始实验前的一些基本问题解答? section的含义? vstart的含义? $ 和 $$区别? 实模式的特点? CPU如何和硬盘进行交互? CPU和IO设备交互方式? 程序载入内 ...

  9. 操作系统真象还原第一章

    开一个新坑,最终目标是按照<操作系统真象还原>这本书实现一个操作系统. 在读每一章的过程中都会按照书中的步骤配环境.写代码.做实验,完成每章后都产出一篇博客. 写博客的主要目的是鞭策自己不 ...

最新文章

  1. Nature官方劝退读博:全球七成博士对前途迷茫,36%自认有过心理疾病
  2. python实现洗牌算法_洗牌算法及 random 中 shuffle 方法和 sample 方法浅析
  3. 少年郎,你需要封装好的ViewPager工具类
  4. [新闻]Ubuntu7.04于4月19日全球同步发布
  5. Boost:以协程的方式实现带有单个默认值的echo服务器的实例
  6. 单片机float数发给上位机_上位机倒计时器
  7. python 百度ocr安装_Python调用百度OCR实现图片文字识别的示例代码
  8. 程序包管理器控制台 Add-Migration 用法
  9. ORACLE坏块检查
  10. QA:Visual C# 2008 compiler could not be created.
  11. leftjoin多了性能下降_SBR胶乳改性剂用量对乳化沥青及微表处性能影响
  12. SLAM算法 - 3D激光匹配算法
  13. 树莓派搭建DLNA客户端,使用gmediarender,DLAN render。
  14. 8路USB继电器模块 windows Linux使用
  15. Android实现仿有道云笔记头像背景(模糊效果)
  16. C#生成含数字字母的随机字符串
  17. java语言可以编程无人机么_java可以作为第一门编程语言学习吗
  18. Dubbo的使用和原理
  19. c语言随机生成5到10,C语言生成随机数
  20. 人工智能写作_智能写作软件_文案策划写作_写作机器人服务商|Giiso智搜

热门文章

  1. 中职双师型教师计算机培训总结,双师型教师计算机培训心得体会.doc
  2. 签入/签出是什么意思?
  3. TypeScript 中slice(-1)是什么意思?
  4. AS 项目目录组成架构
  5. JZOJ ???? Or
  6. Linux---基本指令
  7. AMBER分子动力学简例
  8. 产品经理的工作职责是什么,需要具备什么能力?
  9. K8S (kubernetes)
  10. Iphone 6 plus 刷机