MacOS环境-手写操作系统-11-建立中断机制 原创

建立中断机制

1.简介

上一节,我们绘制了鼠标图案,遗憾的是,鼠标箭头是死的,动不了,要想让鼠标移动,我们需要为内核建立中断机制

当我们移动鼠标时,鼠标会给CPU发送信号,CPU接收到信号后,终止当前的运算,执行内核给定的代码以处理鼠标发送的信号

中断信号的发送机制

每一个8259A控制器有8根中断信号线 总共可以接入15个外设硬件

一般情况下 鼠标接入的是从8259A所对应的IRQ4这根信号线

鼠标发送信号时 先通过管线IRQ4将信号传递到从8259A

然后通过管线IRQ2传递到主8259A

最后信号再传递给CPU 键盘产生的中断通过主8259A的IRQ1管线向CPU发送信号

既然有硬件,那就需要对其初始化后才能使用,对硬件的控制我们前面已经说过,需要通过端口发送命令来完成,要配置这两个控制器,我们需要对指定端口发送1字节的数据,这个一字节(8 bit)的数据,我们称之为ICW(initialization control word).主8259A对应的端口地址是20h,21h, 从8259A对应的端口是A0h和A1h. 对端口发送数据时,顺序是定死的,不能违背:
1. 往端口20h(主片)或A0h(从片)发送ICW1
2. 往端口21h(主片)或A1h(从片)发送ICW2
3. 往端口21h(主片)或A1h(从片)发送ICW3
4. 往端口20h(主片)或A0h(从片)发送ICW4

(这里略过一些概念的东西·· 毕竟比较晦涩难懂 别睡着了)

2.代码控制

下面我们通过代码配置两个中断控制器:

(1)先向主8259A发生ICW1:

mov al, 011h
out 020h, al

011h 对应的二进制是00010001 对应ICW1的说明

由于ICW1[0]=1表示需要发送ICW4, ICW1[1] = 0 说明有级联8259A(我们买来的电脑都是级联的)
ICW1[2] =0 表示用8字节来设置中断向量号

ICW1[3]=0表示中断形式是边沿触发 ICW[4]必须设置为1

ICW[5,6,7]必须是0

(那是相当的晦涩了·· 看个大概就好!后面慢慢懂吧 都是硬件的部分 我不是很关心)

(下面很长一部分都是这些 刚兴趣的可以去看看:https://blog.csdn.net/tyler_download/article/details/52716839)

如果你和我一样不敢兴趣 那就先和我一起往下走吧!

综合以上 我们得到了如下的代码:

init8259A:
     mov  al, 011h
     out  02h, al
     call io_delay
  
     out 0A0h, al
     call io_delay

     mov al, 020h
     out 021h, al
     call io_delay

     mov  al, 028h
     out  0A1h, al
     call io_delay

     mov  al, 004h
     out  021h, al
     call io_delay

     mov  al, 002h
     out  0A1h, al
     call io_delay

     mov  al, 003h
     out  021h, al
     call io_delay

     out  0A1h, al
     call io_delay

     mov  al, 11111101b ;允许键盘中断
     out  21h, al
     call io_delay

     mov  al, 11111111b
     out  0A1h, al
     call io_delay

     ret

上边的这段代码 就是汇编部分 调用硬件中断的代码

那二话不说 汇编有了 我们去上C语言:

前面我们处理了硬件如何发送信号的问题

接下来 我们来看看 CPU接收到信号后 如何执行内核指定的代码

要执行相应代码 CPU必须知道代码所在的内存位置 这个信息是通过中断描述符表来实现的

中断描述符的数据结构:

struct GATE_DESCRIPTOR {
  short offset_low;
  short selector;
  char dw_count;
  char attribute;
  short offset_high;
};

中断描述符跟前面说到的全局描述符类似 也是用于描述内存性质的

只不过它专门用于描述可执行代码所在的内存

offset_low 和 offset_high 合在一起作为中断函数在代码执行段中的偏移

selector 用来指向全局描述符表中的某个描述符 中断函数的代码就处于该描述符所指向的段中

dw_count设置为0 attribute设置为08Eh

如何在内核中加载中断描述符表:

;Gate selecotor, offset, DCount, Attr
%macro Gate 4
  dw  (%2 & 0FFFFh)
  dw  %1
  dw  (%3 & 1Fh) | ((%4 << 8) & 0FF00h)
  dw  ((%2>>16) & 0FFFFh)
%endmacro

上面汇编代码中 %2对应的是4字节的地址偏移

把地址偏移的低2字节放到中断门的前两字节

接下来的一字节是宏定义的第一个参数 是中断代码所在的代码段的全局描述符

第三行设置中断描述符的属性 当前写死为08Eh

最后一行设置中断代码偏移的高二字节

在内核代码里 当全局描述符表加载到CPU后 就是我们加载中断描述符表的时机了

首先初始化一个中断描述符:

LABEL_IDT:
%rep  255
    Gate  SelectorCode32, SpuriousHandler,0, DA_386IGate
%endrep

IdtLen  equ $ - LABEL_IDT
IdtPtr  dw  IdtLen - 1
        dd  0

上面代码中 通过指令%rep 255 重复定义255个中断描述符

这么说来 CPU其实可以支持255种中断 其中两个8259A芯片的15个中断信号就包含在255个中断中 SpuriousHandler是中断代码的入口 我们把255个中断的处理代码都设置成SpuriousHandler 也就是无论哪个中断发生,都调用这个函数来处理:

     xor   eax, eax
     mov   ax,  ds
     shl   eax, 4
     add   eax, LABEL_IDT
     mov   dword [IdtPtr + 2], eax
     lidt  [IdtPtr]

上面代码跟以前我们加载全局描述符表是一样的 由于加载全局描述符时我们使用指令cli关闭了中断功能

因此我们需要回复中断功能 CPU才能相应来自8259A芯片的信号:

  [SECTION .s32]
     [BITS  32]
     LABEL_SEG_CODE32:
     ;initialize stack for c code
     mov  ax, SelectorStack
     mov  ss, ax
     mov  esp, TopOfStack

     mov  ax, SelectorVram
     mov  ds,  ax

     mov  ax, SelectorVideo
     mov  gs, ax

     sti
     %include "write_vga_desktop.asm"

     jmp  $

上面的代码通过运行指令sti 恢复中断功能

最后再看看SpuriousHandler的实现

_SpuriousHandler:
SpuriousHandler  equ _SpuriousHandler - $$
call intHandlerFromC
iretd

当点击键盘 引发中断时 _SpuriousHandler的代码被调用 它又调用了C模块实现的函数intHandlerFromC

我们看看C语言怎么实现intHandlerFromC的

void intHandlerFromC(char* esp) {
    char*vram = bootInfo.vgaRam;
    int xsize = bootInfo.screenX, ysize = bootInfo.screenY;
    boxfill8(vram, xsize, COL8_000000, 0,0,32*8 -1, 15);
    showString(vram, xsize, 0, 0, COL8_FFFFFF, "PS/2 keyboard"); 
    for (;;) {
        io_hlt();
    }
    show_char();
}

(虽然晦涩难懂 但是还要看一看的!不可直接略过的!)

3.编译运行

下面又到了编译的时候了···

编译C文件

i386-elf-gcc -m32 -fno-asynchronous-unwind-tables -s -c -o write_vga_desktop.o write_vga_desktop.c

反汇编o文件

./objconv -fnasm write_vga_desktop.o write_vga_desktop.asm

删除无用部分

修改kernel

%include "write_vga_desktop.asm"

修改boot(直接放大一点)直接读了20个扇区 肯定够用了

    mov          AL,  20        ; AL 表示要练习读取几个扇区

编译boot

nasm -o boot.bat boot.asm

编译kernel

nasm -o kernel.bat kernel.asm

运行java 生成system.img

在这里插入图片描述

敲击键盘后

在这里插入图片描述

posted @   武子康  阅读(0)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示