分类: LINUX
2008-04-24 11:17:24
今天看了0.11核的关于硬件中断处理的基本原理,下面作一下总结:
一、I386中断处理原理
I386体系结构CPU中有两种中断,硬中断和软中断,硬中断是外部硬件产生的,软中断是程序中的某条指令或者程序对标志寄存器中某个标志的设置而产生的,与硬件电路无关。无论是硬件中断和软件中断都有各自的中断对应的处理程序,这些处理程序分布在内核中。那么系统是怎么根据不同的中断找到对应的处理过程的呢?当发生一个中断时候,如果是硬件中断,则CPU会再执行两个周期从对应的硬件中取到中断号,如果是软中断,则直接从指令中取得中断号,CPU再根据中断号在中断向量表中找到对应的中断处理程序的入口,并调用处理。在调用这些处理程序之前,会保护一些寄存器的值,这分为两种情况处理:
1)未发生特权级改变。这种情况下,当前进程是内核进程,由于中断处理程序也在内核中,所以程序的转移不会引起特权级变化。这个时候,CPU会将当前进程的EFLAGS、CS、IP压入堆栈,处理程序结束的时候,iret指令会从堆栈中恢复这些寄存器值。
2)特权级发生变化。这种情况下,当前进程是用户进程,由于中断处理程序在内核中,程序需要转移到内核的代码段和数据段,这个时候就会发生特权级的转变:从用户态专项核心态。这个时候要在将当前进程的EFLAGS、CS、IP压入堆栈之前,把原进程的SS、ESP也压入堆栈,因为,特权级的转变会引起堆栈的切换。处理程序结束的时候,iret指令会从堆栈中恢复这些寄存器的值。
二、Linux中断处理原理
CPU只是提供了发生中断时候,具体是怎么找到中断处理程序的而中断向量表的设置和中断处理过程需要操作系统提供。在Linux中,中断处理程序的描述如入口,也就是上面说的中断向量是中断门和陷阱门的形式,对应的中断向量表叫做中断描述符表IDT。
1)IDT的设置
IDT的设置是在main.c中初始化中调用trap_init()函数完成的,trap_init()在trap.c中定义,主要是调用set_trap_gate和set_system_gate填充中断号对应在IDT中的中断门和陷阱门。这两个函数的具体定义和实现在system.c中。
2)中断处理程序
Linux中的中断处理程序主要包括两个文件,asm.s和trap.c.asm.s用于实现大部分硬件异常所引起的中断的汇编处理过程。而trap.c则实现了asm.s的中断处理过程中调用的C函数。还有几个中断处理程序的实现在system_call.s和page.s中。
A、asm.s
该程序的主要处理方式是调用trap.c中对应的C函数,显示出错位置和出错码,然后退出中断。其中的处理分为两种情况,带出错号的中断处理和不带出错号的处理。
对于不带出错号的处理过程是这样的,在各自的不带中断号的处理过程中,将对应的C函数指针入栈,然后跳到no_error_code处,no_error_code做到工作是:
交换eax和栈顶值,目的是保存eax的值同时将函数指针值放入eax;
保护原进程的通用寄存器:将ebx,ecx,edx,edi,esi,ebp,ds,es,fs入栈;
将错误码入栈(均为0);
指向中断返回地址eip的栈指针esp的值入栈;
重新设置ds、es、fs。使其指向内核段。
调用C函数;
退栈,恢复各个寄存器的值,最后iret。
带中断号的处理过程与不带中断号的处理过程的不同有三,一个地方是在各自带中断号的处理层过程中,跳到error_code而不是no_error_code。再一个地方是开始的地方要做两次交换,一次是错误码和eax交换,一次使函数地址和ebx的交换。另一个地方是错误码入栈的是对应实际的错误码,而不是0。
B、trap.c
此程序包括一些asm.s中调用的C函数的定义和实现,并显示错误位置和错误码。还有一个就是IDT的初始化:trap_init()的定义和实现。在0.11核中,很多函数中基本上都是空的。