自制操作系统12:移动鼠标 - 中断机制探秘,捕获键盘中断

本文深入探讨了如何初始化中断描述符表(IDT)和8259A可编程中断控制器(PIC)。通过设置中断屏蔽寄存器(IMR)和初始化控制字(ICW)来管理中断信号。文章还解释了PIC的主从配置,以及中断处理程序的编写,特别是涉及到鼠标和键盘中断。同时,提到了在旧式和现代电脑中不同中断设置的影响。此外,介绍了如何通过BIOS中断调用来设置VGA显卡。
摘要由CSDN通过智能技术生成

参考:

https://www.bilibili.com/video/BV1VJ41157wq?p=12&spm_id_from=pageDriver
https://blog.csdn.net/tyler_download/article/details/52716839
https://weread.qq.com/web/reader/38732220718ff5cf3877215ka6832360236a684eceeee20

我们接着上节继续做鼠标指针的移动。为达到这个目的必须使用中断,而要使用中断,则必须将GDT和IDT正确无误地初始化。
处理中断的器件是两个8259A,又叫PIC,所谓PIC是“programmable interrupt controller”的缩写,意思是“可编程中断控制器”。
PIC是将8个中断信号[插图]集合成一个中断信号的装置。PIC监视着输入管脚的8个中断信号,只要有一个中断信号进来,就将唯一的输出管脚信号变成ON,并通知给CPU。IBM的大叔们想要通过增加PIC来处理更多的中断信号,他们认为电脑会有8个以上的外部设备,所以就把中断信号设计成了15个,并为此增设了2个PIC。
与CPU直接相连的PIC称为主PIC(master PIC),与主PIC相连的PIC称为从PIC(slave PIC)。主PIC负责处理第0到第7号中断信号,从PIC负责处理第8到第15号中断信号。
现在简单介绍一下PIC的寄存器。首先,它们都是8位寄存器。IMR是“interrupt mask register”的缩写,意思是“中断屏蔽寄存器”。8位分别对应8路IRQ信号。如果某一位的值是1,则该位所对应的IRQ信号被屏蔽,PIC就忽视该路信号。这主要是因为,正在对中断设定进行更改时,如果再接受别的中断会引起混乱,为了防止这种情况的发生,就必须屏蔽中断。还有,如果某个IRQ没有连接任何设备的话,静电干扰等也可能会引起反应,导致操作系统混乱,所以也要屏蔽掉这类干扰。

ICW是“initial control word”的缩写,意为“初始化控制数据”。因为这里写着word,所以我们会想,“是不是16位”?不过,只有在电脑的CPU里,word这个词才是16位的意思,在别的设备上,有时指8位,有时也会指32位。PIC不是仅为电脑的CPU而设计的控制芯片,其他种类的CPU也能使用,所以这里word的意思也并不是我们觉得理所当然的16位。

ICW有4个,分别编号为1~4,共有4个字节的数据。ICW1和ICW4与PIC主板配线方式、中断信号的电气特性等有关,所以就不详细说明了。电脑上设定的是上述程序所示的固定值,不会设定其他的值。如果故意改成别的什么值的话,早期的电脑说不定会烧断保险丝,或者器件冒烟[插图];最近的电脑,对这种设定起反应的电路本身被省略了,所以不会有任何反应。

ICW3是有关主—从连接的设定,对主PIC而言,第几号IRQ与从PIC相连,是用8位来设定的。如果把这些位全部设为1,那么主PIC就能驱动8个从PIC(那样的话,最大就可能有64个IRQ),但我们所用的电脑并不是这样的,所以就设定成00000100。另外,对从PIC来说,该从PIC与主PIC的第几号相连,用3位来设定。因为硬件上已经不可能更改了,如果软件上设定不一致的话,只会发生错误,所以只能维持现有设定不变。

因此不同的操作系统可以进行独特设定的就只有ICW2了。这个ICW2,决定了IRQ以哪一号中断通知CPU。“哎?怎么有这种事?刚才不是说中断信号的管脚只有1根吗?”嗯,话是那么说,但PIC还有个挺有意思的小窍门,利用它就可以由PIC来设定中断号了。

大家可能会对此有兴趣,所以再详细介绍一下。中断发生以后,如果CPU可以受理这个中断,CPU就会命令PIC发送2个字节的数据。这2个字节是怎么传送的呢?CPU与PIC用IN或OUT进行数据传送时,有数据信号线连在一起。PIC就是利用这个信号线发送这2个字节数据的。送过来的数据是“0xcd 0x? ? ”这两个字节。由于电路设计的原因,这两个字节的数据在CPU看来,与从内存读进来的程序是完全一样的,所以CPU就把送过来的“0xcd 0x? ? ”作为机器语言执行。这恰恰就是把数据当作程序来执行的情况。这里的0xcd就是调用BIOS时使用的那个INT指令。我们在程序里写的“INT 0x10”,最后就被编译成了“0xcd 0x10”。所以,CPU上了PIC的当,按照PIC所希望的中断号执行了INT指令。

这次是以INT 0x20~0x2f接收中断信号IRQ0~15而设定的。这里大家可能又会有疑问了。“直接用INT 0x00~0x0f就不行吗?这样与IRQ的号码不就一样了吗?为什么非要加上0x20? ”不要着急,先等笔者说完再问嘛。是这样的,INT 0x00~0x1f不能用于IRQ,仅此而已。

之所以不能用,是因为应用程序想要对操作系统干坏事的时候,CPU内部会自动产生INT 0x00~0x1f,如果IRQ与这些号码重复了,CPU就分不清它到底是IRQ,还是CPU的系统保护通知。

鼠标是IRQ12,键盘是IRQ1,所以我们编写了用于INT 0x2c和INT 0x21的中断处理程序(handler),即中断发生时所要调用的程序。

中断处理完成之后,不能执行“return; ”(=RET指令),而是必须执行IRETD指令,真不好办。而且,这个指令还不能用C语言写。所以,还得借助汇编语言的力量修改。

还有一个不怎么常见的指令PUSHAD,它相当于:push eax,ecx,edx,ebx,esp,ebp,esi,edi,反过来,POPAD指令相当于按以上相反的顺序,把它们全都POP出来。

开始做实验:os-kernel-interrupt
write_vga_desktop.c
boot.asm
kernel.asm
初始化:
LABEL_IDT:
%rep  255
    Gate  SelectorCode32, SpuriousHandler,0, DA_386IGate
%endrep

IdtLen  equ $ - LABEL_IDT
IdtPtr  dw  IdtLen - 1
        dd  0
LABEL_BEGIN:
     ......
     mov   al, 0x13  -- VGA显卡
     mov   ah, 0
     int   0x10 -- 这个调用是做什么?
     
     call  init8259A
初始化8259
init8259A:
 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, 002h --003h
     out  021h, al
     call io_delay

     out  0A1h, al
     call io_delay

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

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

     ret

io_delay:
     nop
     nop
     nop
     nop
     ret
中断描述符
struct GATE_DESCRIPTOR {
short offset_low;
short selector;
char dw_count;
char attribute;
short offset_high;
};
pm.inc
;Gate selecotor, offset, DCount, Attr
%macro Gate 4
  dw  (%2 & 0FFFFh)
  dw  %1
  dw  (%3 & 1Fh) | ((%4 << 8) & 0FF00h)
  dw  ((%2>>16) & 0FFFFh)
%endmacro


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

  [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  $
     
_SpuriousHandler:
SpuriousHandler  equ _SpuriousHandler - $$
call intHandlerFromC
iretd          

write_vga_desktop.c
改为全局静态变量
static struct BOOTINFO bootInfo;
增加中断处理函数
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();
}

然后就是老套路,编译反编译,制作启动盘
gcc -m32 -fno-pie -s -c -o write_vga_desktop.o write_vga_desktop.c
../objconv-new/objconv/objconv -fnasm write_vga_desktop.o -o write_vga_desktop.asm
delete:
global
extern
section
endbr32 
kernel.asm
%include "write_vga_desktop.asm"
nasm -o kernel.bat kernel.asm

制作磁盘工具修改一下,从1 sector开始
    public void makeFllopy()   {
        writeFileToFloppy("kernel.bat", false, 1, 2); //改为1,1
        floppyDisk.makeFloppy("system.img");
    }

boot.asm change 20
Load file boot.bat to floppy with cylinder: 0 and sector:1
Load file kernel.bat to floppy with cylinder: 1 and sector:1
Load file kernel.bat to floppy with cylinder: 1 and sector:2
Load file kernel.bat to floppy with cylinder: 1 and sector:3
Load file kernel.bat to floppy with cylinder: 1 and sector:4
Load file kernel.bat to floppy with cylinder: 1 and sector:5
Load file kernel.bat to floppy with cylinder: 1 and sector:6
Load file kernel.bat to floppy with cylinder: 1 and sector:7
Load file kernel.bat to floppy with cylinder: 1 and sector:8
Load file kernel.bat to floppy with cylinder: 1 and sector:9
Load file kernel.bat to floppy with cylinder: 1 and sector:10
Load file kernel.bat to floppy with cylinder: 1 and sector:11
Load file kernel.bat to floppy with cylinder: 1 and sector:12
Load file kernel.bat to floppy with cylinder: 1 and sector:13
Load file kernel.bat to floppy with cylinder: 1 and sector:14
Load file kernel.bat to floppy with cylinder: 1 and sector:15
Load file kernel.bat to floppy with cylinder: 1 and sector:16
Load file kernel.bat to floppy with cylinder: 1 and sector:17
Load file kernel.bat to floppy with cylinder: 1 and sector:18
Load file kernel.bat to floppy with cylinder: 2 and sector:1
Load file kernel.bat to floppy with cylinder: 2 and sector:2
运行virtualbox出错,boot.asm写错了
    mov          CH, 1        ;CH 用来存储柱面号
    mov          DH, 0        ;DH 用来存储磁头号
    mov          CL, 1        ;CL 用来存储扇区号 -- 原来是2,也要修改为1

可以显示汉字了,但还是出错。
还是写盘有问题,修改为按柱面(磁道)循环读盘
readFloppy:
    cmp  byte [load_count], 0
    je   beginLoad
    
    mov          CH, 1        ;CH 用来存储柱面号
    mov          DH, 0        ;DH 用来存储磁头号
    mov          CL, 1        ;CL 用来存储扇区号

    mov          AH, 0x02      ;  AH = 02 表示要做的是读盘操作
    mov          AL,  18        ; AL 表示要练习读取几个扇区
    mov          DL, 0         ;驱动器编号,一般我们只有一个软盘驱动器,所以写死   
                               ;为0
    INT          0x13          ;调用BIOS中断实现磁盘读取功能
    
    inc  CH
    dec  byte [load_count]
   
    JC           fin
    jmp          readFloppy
可以显示,但打键盘报错了
仔细核对,竟然漏掉了加载中断表
     xor   eax, eax
     mov   ax,  ds
     shl   eax, 4
     add   eax, LABEL_IDT
     mov   dword [IdtPtr + 2], eax
     lidt  [IdtPtr]
增加后就正常了:

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值