linux中断处理之初始化.docx
- 文档编号:23023514
- 上传时间:2023-04-30
- 格式:DOCX
- 页数:24
- 大小:186.26KB
linux中断处理之初始化.docx
《linux中断处理之初始化.docx》由会员分享,可在线阅读,更多相关《linux中断处理之初始化.docx(24页珍藏版)》请在冰豆网上搜索。
linux中断处理之初始化
linux中断处理之初始化
发布者:
chinaitlab 日期:
2010-09-0300:
00:
00 浏览次数:
0 (共有_条评论)查看评论|我要评论
一:
引言
在Intel的文档中,把中断分为两种.一种是异常,也叫同步同断.一种称之为中断,也叫异常中断.
同步中断指的是由CPU控制单元产生,之所以称之为同步,是因为只有一条指令执行完毕后才会发出中断.例如除法运算中,除数为零的时候,就会产生一个异常
异步中断是由外部设备按照CPU的时钟随机产生的.例如,网卡检测到一个数据到来就会产生一个中断.
二:
x86的中断处理过程
由于中断是开着的,所以当执行完一条指令后,cs和eip这对寄存器中已经包含了下一条将要执行的指令的逻辑地址。
在处理那条指令之前,控制单元会检查在运行前一条指令时是否发生了一个中断或异常。
如果发生了一个中断和异常,那么控制单元执行下列操作:
1.确定与中断或异常关联的向量i(0≤i≤255)
2.读由idtr寄存器指向的IDT表中的第i项。
3.从gdtr寄存器获得GDT的基地址,并在GDT中查找,以读取IDT表项中的选择符标识的段描述符。
这个描述符指定中断或异常处理程序所在的段的基地址。
4.确信中断是由授权的(中断)发生源发出的。
首先将当前特权级CPL(存放在cs寄存器的低两位)与段描述符(存放在GDT中)的描述符特权级DPL比较。
如果CPL小于DPL,就产生一个“通常保护”异常,因为中断处理程序的特权级不能低于引起中断的程序的特权。
对于编程异常,则做进一步的安全检查:
比较CPL与处于IDT中的门描述符的DPL,如果DPL小于CPL,就产生一个“通常保护”异常,这最后
一个检查可以避免用户应用程序访问特殊的陷阱门和中断门。
5.检查是否发生了特权级的变化,也就是说,CPL是否不同于所选择的段描述符的DPL。
如果是,控制单元必须开始使用与新的特权级相关的栈,通过执行以下步骤来保证这一点:
A.读tr寄存器,以访问运行进程的TSS段。
B.用与新特权级相关的栈段和栈指针的正确值装载ss和esp寄存器。
这些值可以在TSS中找到。
C.在新的栈中保存ss和esp以前的值,这些值定义了与旧特权级相关的栈的逻辑地址。
6.如果故障已发生,用引起异常的指令地址装载cs和eip寄存器,从而使得这条指令能再次被执行。
7.在栈中保存eflag、cs和eip的内容。
8.如果异常产生了一个硬件出错码,则将它保存在栈中。
9.装载cs和eip寄存器,其值分别是IDT表中第i项门描述符的段选择符和偏移量字段。
这些值给出了中断或者异常处理程序的第一条指令的逻辑地址。
控制单元所执行的最后一步就是跳转到中断或异常处理程序。
换句话说,处理完中断信号后,控制单元所执行的指令就是被选中处理程序的第一条指令。
上面的处理过程的描述摘自<<深入理解linux内核>>,其中有几点值得注意的地方:
1:
通过门后,只能提高运行级别.就像上面所述的“当前特权级CPL(存放在cs寄存器的低两位)与段描述符(存放在GDT中)的描述符特权级DPL比较。
如果CPL小于DPL,就产生一个“通常保护”异常”.在中断处理中,通常把IDT中的相应段选择符设为__KERNEL_CS.即最高的运行级别
2:
上面C所述:
“在新的栈中保存ss和esp以前的值,这些值定义了与旧特权级相关的栈的逻辑地址”,那ss,esp以前的值是如何找到的呢?
应该是从TSS中.在中断发生的时候,如果检测到运行级别发生了改了,将寄存器SS,ESP中的值保存进TSS的相应级别位置.再加载新的SS,ESP的值,然后从TSS中取出旧的SS,ESP值,再压栈.
3:
堆栈的改变,如下图所示:
从上图中我们可以看到,硬件自动保存的硬件环境是非常少,要在中断后恢复到以前的环境,还需要保存更多的寄存器值,这是由操作系统完成的.这我们在以后的代码分析中可以看到
中断和异常被处理完毕后,相应的处理程序必须产生一条iret指令,把控制权转交给被中断的进程,这将迫使控制单元:
1.用保存在栈中的值装载cs、eip和eflag寄存器。
如果一个硬件出错码曾被压入栈中,并且在eip内容的上面,那么,执行iret指令前必须先弹出这个硬件出错码。
2.检查处理程序的CPL是否等于cs中的低两位的值。
如果是,iret终止返回;否则,转入下一步。
3.从栈中转载ss和esp寄存器,因此,返回到与旧特权级相关的栈。
4.检查ds、es、fs及gs段寄存器的内容,如果其中一个寄存器包含的选择符是一个段描述符,并且其DPL值小于CPL,那么,清相关的段寄存器。
控制单元这么做是为了禁止用户态的程序利用内核以前所用的段寄存器。
如果不清除这些寄存器的话,恶意的用户程序就会利用他们来访问内核地址空间。
注意到4:
举例说明一下.如果通过系统调用进入内核态.然后将DS,ES的值赋为__KERNEL_DS(在2.4的内核里),处理完后(调用iret后),恢复CS,EIP的值,此时CS的CPL是3.因为DS,ES被设为了__KERNEL_DS,所以其DPL是0,所以要将DS,ES中的值清除.在2.6内核中,发生中断或异常后,将DS,ES的值设为了__USER_DS,避免了上述的清除过程,提高了效率.
三:
重要的数据结构
在深入源代码之前,先把所用到的数据结构分析如下:
Irq_desc[]定义如下:
externirq_desc_tirq_desc[NR_IRQS]
typedefstructirq_desc{
unsignedintstatus; /*IRQ的状态;IRQ是否被禁止了,有关IRQ的设备当前是否正被自动检测*/
hw_irq_controller*handler;/*指向一个中断控制器的指针*/
c*action;/*挂在IRQ上的中断处理程序*/
unsignedintdepth; /*为0:
该IRQ被启用,如果为一个正数,表示被禁用*/
unsignedintirq_count; /* 该IRQ发生的中断的次数*/
unsignedintirqs_unhandled; /*该IRQ线上没有被处理的IRQ总数*/
spinlock_tlock;
}____cacheline_alignedirq_desc_t;
Hw_irq_controller定义如下:
structhw_interrupt_type{
constchar*typename; /*中断控制器的名字*/
unsignedint(*startup)(unsignedintirq); /*允许从IRQ线产生中断*/
void(*shutdown)(unsignedintirq); /*禁止从IRQ线产生中断*/
void(*enable)(unsignedintirq);/*enable与disable函数在8259A中与上述的startupshutdown函数相同*/
void(*disable)(unsignedintirq);
void(*ack)(unsignedintirq); /*在IRQ线上产生一个应答*/
void(*end)(unsignedintirq); /*在IRQ处理程序终止时被调用*/
void(*set_affinity)(unsignedintirq,cpumask_tdest); /*在SMP系统中,设置IRQ处理的亲和力*/
}
typedefstructhw_interrupt_type hw_irq_controller;
structirqaction定义如下:
structirqaction{
//中断处理例程
irqreturn_t(*handler)(int,void*,structpt_regs*);
//flags:
//SA_INTERRUPT:
中断嵌套
//SA_SAMPLE_RANDOM:
这个中断源于物理随机性
//SA_SHIRQ:
中断线共享
unsignedlongflags;
//在x86平台无用
cpumask_tmask;
//产生中断的硬件名字
constchar*name;
//设备ID,一般由厂商指定
void*dev_id;
//下一个irqaction.共享的时候,通常一根中断线对应很多硬件设备的中断处理例程
structirqaction*next;
}
可以用下图来表示上述数据结构的关系:
四:
idt在保护模式下的初始化
有关实模式下的初始化,以后再做专题分析.详情请关注本站更新.
在init/main.c中:
asmlinkagevoid__initstart_kernel(void)
{
……
//设定系统规定的异常/中断
trap_init();
//设置外部IRQ中断
init_IRQ();
……
}
在start_kernel中,调用trap_init()来设置系统规定的异常与中断,调用init_IRQ()来设置外部中断.
void__inittrap_init(void)
{
……
set_trap_gate(0,÷_error);
set_intr_gate(1,&debug);
set_intr_gate(2,&nmi);
set_system_intr_gate(3,&int3);/*int3-5canbecalledfromall*/
set_system_gate(4,&overflow);
set_system_gate(5,&bounds);
set_trap_gate(6,&invalid_op);
set_trap_gate(7,&device_not_available);
set_task_gate(8,GDT_ENTRY_DOUBLEFAULT_TSS);
set_trap_gate(9,&coprocessor_segment_overrun);
set_trap_gate(10,&invalid_TSS);
set_trap_gate(11,&segment_not_present);
set_trap_gate(12,&stack_segment);
set_trap_gate(13,&general_protection);
set_intr_gate(14,&page_fault);
set_trap_gate(15,&spurious_interrupt_bug);
set_trap_gate(16,&coprocessor_error);
set_trap_gate(17,&alignment_check);
#ifdefCONFIG_X86_MCE
set_trap_gate(18,&machine_check);
#endif
set_trap_gate(19,&simd_coprocessor_error);
set_system_gate(SYSCALL_VECTOR,&system_call); //系统调用
……
}
如上所示,设置了0~19的中断/异常处理程序,这些都是intel所规定的,除些之后设置了系统调用入口(用户空间的intSYSCALL_VECTOR)
那,set_trap_gate()/set_intr_gate()/set_system_gata()都有一些什么样的区别呢?
继续看代码:
voidset_intr_gate(unsignedintn,void*addr)
{
_set_gate(idt_table+n,14,0,addr,__KERNEL_CS);
}
staticinlinevoidset_system_intr_gate(unsignedintn,void*addr)
{
_set_gate(idt_table+n,14,3,addr,__KERNEL_CS);
}
void__initset_trap_gate(unsignedintn,void*addr)
{
_set_gate(idt_table+n,15,0,addr,__KERNEL_CS);
}
void__initset_system_gate(unsignedintn,void*addr)
{
_set_gate(idt_table+n,15,3,addr,__KERNEL_CS);
}
都是通过统一的接口_set_gate().在i386中,这段代码是用嵌入式汇编完成的,如下所示:
#define_set_gate(gate_addr,type,dpl,addr,seg)\
do{\
int__d0,__d1;\
__asm____volatile__("movw%%dx,%%ax\n\t"\
"movw%4,%%dx\n\t"\
"movl%%eax,%0\n\t"\
"movl%%edx,%1"\
:
"=m"(*((long*)(gate_addr))),\
"=m"(*(1+(long*)(gate_addr))),"=&a"(__d0),"=&d"(__d1)\
:
"i"((short)(0x8000+(dpl<<13)+(type<<8))),\
"3"((char*)(addr)),"2"((seg)<<16));\
}while(0)
我们看可以看:
_set_gate(gate_addr,type,dpl,addr,seg)中:
Gate_addr:
相应IDT项的地址.type:
设置IDT项的TYPE字段,15表示系统门,14表示中断门.dpl:
IDT项对应的DPL值,addr:
中断处理程序的地址,seg:
IDT中对应项的段选择符
由此可以看出,陷阱门与中断门被锁定在内核态(DPL为0),系统门可以从用户态进入.那既然陷阱门与中断门又有什么区别呢?
唯一的区别是,通过陷阱门不会改变FLAGES中的中断标志,但是中断门就会改变,即会屏弊中断
接下来看IRQ中断的设置:
void__initinit_IRQ(void)
{
inti;
//8259初始化
pre_intr_init_hook();
for(i=0;i<(NR_VECTORS-FIRST_EXTERNAL_VECTOR);i++){
//FIRST_EXTERNAL_VECTOR:
第一个可用号,前面部份均为系统保留
intvector=FIRST_EXTERNAL_VECTOR+i;
if(i>=NR_IRQS)
break;
//跳过系统调用号
if(vector!
=SYSCALL_VECTOR)
set_intr_gate(vector,interrupt[i]);
}
……
……
}
在深入这段代码之前,我们先看下x86的中断的硬件处理机制.x86中断处理系统一般采用两个8259A芯片级连的方式.每个8259A有8根中断信号线,从片有一根信号线连接至了主片,所以,总共可以处理15个IRQ信号.如下图所示:
来看下具体的代码:
pre_intr_init_hook()-àinit_ISA_irqs()
void__initinit_ISA_irqs(void)
{
inti;
#ifdefCONFIG_X86_LOCAL_APIC
init_bsp_APIC();
#endif
//初始化8259A芯片
init_8259A(0);
//初始化irq_desc[]数组
for(i=0;i //状态: 禁用 irq_desc[i].status=IRQ_DISABLED; //初始化为NULL.表示无中断处理函数,系统初始化完成之后,可以调用request_irq()注册中断处理函数 irq_desc[i].action=NULL; //depth值为1,表示当前IRQ线被禁用 irq_desc[i].depth=1; //只使用了15根(两块8259A级联) if(i<16){ //将irq_desc[i].handler: 设置为8259A的中断控制器处理 irq_desc[i].handler=&i8259A_irq_type; }else{ //其它的在x86平台被设为no_irq_type.表示无中断控制器 irq_desc[i].handler=&no_irq_type; } } } 在这个函数里,初始化了irq_desc[]数组.随后,调用了set_intr_gate(vector,interrupt[i])为第n条中断线设置的中断处理函数为interrupt[n-FIRST_EXTERNAL_VECTOR]. Interrupt[]数组在哪里定义的呢? 接下来往下看: ENTRY(interrupt) .previous vector=0 ENTRY(irq_entries_start) .reptNR_IRQS ALIGN 1: pushl$vector-256 jmpcommon_interrupt .data .long1b .previous vector=vector+1 .endr 相当于,interrupt[i]执行下列操作: Pushl$i-256 //中断号取负再压栈 Jmpcommon_interrupt //跳转至一段公共的处理函数 至此,保护模式下的中断子系统初始化完成.接下来分析linux如何响应中断 五: 中断入口分析 1: IRQ入口分析 如上所述.将中断号取负压栈之后,会跳转一段公共的处理函数,跟进这段函数: common_interrupt: SAVE_ALL calldo_IRQ #调用相应的中断处理函数 jmpret_from_intr #从中断返回 SAVE_ALL定义如下: #defineSAVE_ALL\ __SAVE_ALL; \ __SWITCH_KERNELSPACE;#在没有定义CONFIG_X86_HIGH_ENTRY的情况下,此宏是一个空宏 __SAVE_ALL定义如下: #define__SAVE_ALL\ cld;\ pushl%es;\ pushl%ds;\ pushl%eax;\ pushl%ebp;\ pushl%edi;\ pushl%esi;\ pushl%edx;\ pushl%ecx;\ pushl%ebx;\ movl$(__USER_DS),%edx;\ movl%edx,%ds;\ movl%edx,%es; 相当于把中断发生时,硬件没有保存的寄存器压栈保存下来.把DS.ES设为了__USER_DS是有一定原因的,参考上节所述. 经过SAVE_ALL后.堆栈内容如下所示: 这个图是从<<中断处理源码情景分析>>一文中摘出来的.事实上图中的用户堆栈指针在IRQ处理中是不可能存在的,因为中断处理对应IDT项的PL值为0,所以,不可能是从用户空间发生的 2: 异常处理入口分析 异常处理程序也有很多相同的操作,以16号向量对应的中断处理程序为例: set_trap_gate(16,&coprocessor_error); ENTRY(coprocessor_error) pushl$0 #把0入栈.如果异常没有产生一个硬件出错码,就把0入栈 pushl$do_coprocessor_error #相应的异常处理程序 jmperror_code #跳转到error_code error_code定义如下: error_code: pushl%ds pushl%eax xorl%eax,%eax //EAX中的值变为零 pushl%ebp pushl%edi pushl%esi pushl%edx decl%eax #eax=-1 pushl%ecx pushl%ebx cld movl%es,%ecx movlORIG_EAX(%esp),%esi #gettheerrorcode movlES(%esp),%edi #getthefunctionaddress movl%eax,ORIG_EAX(%esp) movl%ecx,ES(%esp) pushl%esi #pushtheerrorcode movl$(__USER_DS),%edx movl%edx,%ds movl%edx,%es /*clobbersedx,ebxandebp*/ __SWITCH_KERNELSPACE leal4(%esp),%edx #preparept_regs pushl%edx #pushpt_regs call*%edi addl$8,%esp jmpret_f
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- linux 中断 处理 初始化