参考:
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]
增加后就正常了: