Linux内核系列—8.操作系统开发之时钟中断

外部中断的情况复杂一些,因为需要建立硬件中断与向量号之间的对应关系。外部中断分为不可屏蔽中断(NMI)和可屏蔽中断两种,分别由CPU的两根引脚NMI和INTR来接收。如下图所示:

可屏蔽中断与CPU的关系是通过对可编程中断控制器8259A建立起来的。8259A可以认为它是中断机制中所有外围设备的一个代理。在BIOS初始化它的时候,IRQ0~IRQ7被设置为对应向量号08h~0Fh,在保护模式下向量号08h~0Fh已经被占用了,所以我们不得不重新设置主从8259A。

对8259A的设置并不复杂,通过向相应的端口写入特定的ICW来实现。主8259A对应的端口地址是20h和21h,从8259A对应的端口地址是A0h和A1h。ICW共有4个。初始化过程如下:

1.往端口20h(主片)或A0h(从片)写入ICW1.

2.往端口21h(主片)或A1h(从片)写入ICW2.

3.往端口21h(主片)或A1h(从片)写入ICW3.

4.往端口21h(主片)或A1h(从片)写入ICW4.

这4步的顺序是不能颠倒的。

ICW格式如下

Init8259A:
	mov	al, 011h
	out	020h, al	; 主8259, ICW1.
	call	io_delay

	out	0A0h, al	; 从8259, ICW1.
	call	io_delay

	mov	al, 020h	; IRQ0 对应中断向量 0x20
	out	021h, al	; 主8259, ICW2.
	call	io_delay

	mov	al, 028h	; IRQ8 对应中断向量 0x28
	out	0A1h, al	; 从8259, ICW2.
	call	io_delay

	mov	al, 004h	; IR2 对应从8259
	out	021h, al	; 主8259, ICW3.
	call	io_delay

	mov	al, 002h	; 对应主8259的 IR2
	out	0A1h, al	; 从8259, ICW3.
	call	io_delay

	mov	al, 001h
	out	021h, al	; 主8259, ICW4.
	call	io_delay

	out	0A1h, al	; 从8259, ICW4.
	call	io_delay

	mov	al, 11111110b	; 仅仅开启定时器中断
	;mov	al, 11111111b	; 屏蔽主8259所有中断
	out	021h, al	; 主8259, OCW1.
	call	io_delay

	mov	al, 11111111b	; 屏蔽从8259所有中断
	out	0A1h, al	; 从8259, OCW1.
	call	io_delay

	ret

我们通过对端口21h和A1h的操作屏蔽了所有的外部中断,写入OCW,在以下两种情况下用到它:

1.屏蔽或打开外部中断。

2.发送EOI给8259A以通知它中断处理结束。

若想屏蔽或打开外部中断,只需要往8259A写入OCW1就可以了。OCW1格式如下:

若想屏蔽某一个中断,将对应那一位设成1就可以了。EOI就是当每一次中断处理结束,需要发送一个EOI给8259A,以便继续接收中断。而发送EOI是通过往端口20h或A0h写OCW2来实现的。OCW2的格式如上图所示。

发送EOI给8259A可以由如下代码完成:

mov al, 20h
out 20h或A0h, al

 

这次我们来打开时钟中断(IRQ0),外部可屏蔽中断的发生受到两个因素的影响,只有当IF位为1,并且IMR相应位为0时才会发生。

那么,如果我们想打开时钟中断的话,一方面不仅要设计一个中断处理程序,另一方面还要设置IMR,并且设置IF位。设置IMR可以通过写OCW2来完成,而设置IF可以通过指令sti来完成。

先写一个时钟中断处理程序

_ClockHandler:
ClockHandler	equ	_ClockHandler - $$
	inc	byte [gs:((80 * 0 + 70) * 2)]	; 屏幕第 0 行, 第 70 列。
	mov	al, 20h
	out	20h, al				; 发送 EOI
	iretd

修改初始化8259A的代码,时钟中断不再屏蔽

mov	al, 11111110b	; 仅仅开启定时器中断
	;mov	al, 11111111b	; 屏蔽主8259所有中断
	out	021h, al	; 主8259, OCW1.
	call	io_delay

	mov	al, 11111111b	; 屏蔽从8259所有中断
	out	0A1h, al	; 从8259, OCW1.
	call	io_delay

修改IDT:

%rep 32
		Gate	SelectorCode32, SpuriousHandler,      0, DA_386IGate
%endrep
.020h:		Gate	SelectorCode32,    ClockHandler,      0, DA_386IGate
%rep 95
		Gate	SelectorCode32, SpuriousHandler,      0, DA_386IGate
%endrep
.080h:		Gate	SelectorCode32,  UserIntHandler,      0, DA_386IGate

运行结果:

源码

时间: 2024-08-03 07:17:47

Linux内核系列—8.操作系统开发之时钟中断的相关文章

Linux内核系列—10.操作系统开发之内核HelloWorld

a.我们先来体验一下在Linux下用汇编编程的感觉,见代码 [section .data] ; 数据在此 strHello db "Hello, world!", 0Ah STRLEN equ $ - strHello [section .text] ; 代码在此 global _start ; 我们必须导出 _start 这个入口,以便让链接器识别 _start: mov edx, STRLEN mov ecx, strHello mov ebx, 1 mov eax, 4 ; sy

Linux内核系列—11.操作系统开发之ELF格式

ELF文件的结构如下图所示: ELF文件由4部分组成,分别是ELF头(ELF header).程序头表(Program header table).节(Sections)和节头表(Section header table). 实际上,一个文件中不一定包含全部这些内容,而且它们的位置也未必如上图所示这样安排,只有ELF头的位置是固定的,其余各部分的位置.大小等信息由ELF头中的各项值来决定. ELF header的格式如下代码所示: #define EI_NIDENT 16 typedef str

Linux内核系列—5.操作系统开发之特权级CPL、DPL、RPL

CPL——当前执行的程序或任务的特权级,它被存储在cs和ss的第0位和第1位上. DPL——段或者门的特权级,如果是数据段DPL则规定了可以访问此段的最低特权级 RPL——通过段选择子的第0位和第1位表现出来的.处理器通过检查RPL和CPL来确认一个访问请求是否合法.RPL保证了操作系统不会越俎代庖地代表一个程序去访问一个段. 我们先来展示一下特权级错误访问版本. 先把LABEL_DESC_DATA对应的段描述符的DPL修改为1: LABEL_DESC_DATA: Descriptor 0, D

linux内核系列(一)编译安装Linux内核 2.6.18

1.配置环境 操作系统:CentOS 5.2 下载linux-2.6.18版本的内核,网址:http://www.kernel.org 说明:该编译文档适合2.6.18以上的Linux内核版本,只需所编译的 Linux内核版本不能低于Linux操作系统自身的内核版本,不然会遇到很多问题:   2.开始编译 cp  ./ linux-2.6.18.tar.gz  /usr/src/ tar –zxvf ./linux-2.6.18.tar.gz cd /usr/src/linux-2.6.18 /

linux内核系列(一)内核数据结构之链表

双向链表 传统链表与linu内核链表的区别图: 图一 图二 从上图中看出在传统链表中各种不同链表间没有通用性,因为各个数据域不同,而在linux内核中巧妙将链表结构内嵌到数据域结构中使得不同结构之间能连接起来: 链表的常用操作 内核中链表实现文件路径:include/linux/list.h 链表结构定义 struct list_head {     struct list_head *next, *prev; }; 获取结构入口地址(list_entry) #define list_entry

Linux内核系列—13.c.操作系统开发之进程之中断重入

现在又出现了另外一个的问题,在中断处理过程中是否应该允许下一个中断发生? 让我们修改一下代码,以便让系统可以在时钟中断的处理过程中接受下一个时钟中断.这听起来不是个很好的主意,但是可以借此来做个试验. 首先,因为CPU在响应中断的过程中会自动关闭中断,我们需要人为地打开中断,加入sti指令:然后,为保证中断处理过程足够长,以至于在它完成之前就会有下一个中断产生,我们在中断处理例程中调用一个延迟函数.代码如下: extern delay hwint00: ; Interrupt routine f

Linux内核系列—12.d.操作系统开发之扩充内核 ●

现在把esp.GDT等内容放进内核中,我们现在可以用C语言了,只要能用C,我们就避免用汇编. 下面看切换堆栈和GDT的关键代码: ; 导入函数 extern cstart ; 导入全局变量 extern gdt_ptr [SECTION .bss] StackSpace resb 2 * 1024 StackTop: ; 栈顶 ; 把 esp 从 LOADER 挪到 KERNEL mov esp, StackTop ; 堆栈在 bss 段中 sgdt [gdt_ptr] ; cstart() 中

Linux内核系列—12.a.操作系统开发之从Loader到内核

Loader要做两项工作,我们先来做第一项,把内核加载到内存: 1.加载内核到内存. 2.跳入保护模式. 首先编译无内核时: nasm boot.asm -o boot.bin nasm loader.asm -o loader.bin dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc sudo mount -o loop a.img /mnt/hgfs/ sudo cp loader.bin /mnt/hgfs/ -v sudo umoun

Linux内核系列—12.b.操作系统开发之从Loader跳入保护模式

现在,内核已经被我们加载进内存了,该是跳入保护模式的时候了. 首先是GDT以及对应的选择子,我们只定义三个描述符,分别是一个0~4GB的可执行段.一个0~4GB的可读写段和一个指向显存开始地址的段: ; GDT ; 段基址 段界限, 属性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR|DA_32|DA_LIMIT_4K ;0-4G LABEL_DESC_FLAT_RW: D