




已阅读5页,还剩36页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第五章 Linux内核的中断机制 (By 詹荣开,NUDT) Copyright © 2003 by 詹荣开 E-mail: Linux-2.4.0 Version 1.0.0,2003-2-14 摘要:本文主要从内核实现的角度分析了Linux 2.4.0内核的设备中断流程。本文是为那些想要了解Linux I/O子系统的读者和Linux驱动程序开发人员而写的。 关键词:Linux、中断、设备驱动程序 申明:这份文档是按照自由软件开放源代码的精神发布的,任何人可以免费获得、使用和重新发布,但是你没有限制别人重新发布你发布内容的权利。发布本文的目的是希望它能对读者有用,但没有任何担保,甚至没有适合特定目的的隐含的担保。更详细的情况请参阅GNU通用公共许可证(GPL),以及GNU自由文档协议(GFDL)。 你应该已经和文档一起收到一份GNU通用公共许可证(GPL)的副本。如果还没有,写信给: The Free Software Foundation, Inc., 675 Mass Ave, Cambridge,MA02139, USA 欢迎各位指出文档中的错误与疑问。 51 I386的中断与异常 中断通常被分为“同步中断”和异步中断两大类。同步中断是指当指令执行时由CPU控制单元产生的中断,之所以称为“同步中断”是因为只有在一条指令中止执行后CPU才会发出这类中断信号。而异步中断则是指由其他硬件设备依照CPU时钟随机产生的中断信号。 在Intel 80x86 CPU手册中,同步中断和异步中断也被分别称为“异常(Exception)”和“中断(Interrupt)”。Intel又详细地把中断和异常细分为以下几类: (1)中断 1. 可屏蔽中断(Maskable Interrupt):这类中断被送到CPU的INTR引脚,通过清除eflag寄存器的IF标志可以关闭中断。 2. 不可屏蔽中断(Nonmaskable Interrupt):被送到CPU的NMI引脚,通常只有几个危急的事件,如:硬件故障等,才产生不可屏蔽中断信号。寄存器eflag中的IF标志对这类中断不起作用。 (2)异常 1. 处理器探测异常(Processordetected exception):当CPU执行一条指令时所探测到的一个反常条件所产生的异常。依据CPU控制单元产生异常时保存在内核态堆栈eip寄存器的值,这类异常又可以细分为三种: n 故障(Fault):保存在eip中的值是引起故障的指令地址,因此但异常处理程序结束后,会重新执行那条指令。“缺页故障”是这类异常的一个常见例子。 n 陷阱(Trap):保存在eip中的值是一个指令地址,但该指令在引起陷阱的指令地址之后。只有当没有必要重新执行已执行过的指令时,才会触发trap,其主要用途是调试程序。 n 异常中止(Abort):当发生了一个严重的错误,致使CPU控制单元除了麻烦而不能在eip寄存器中保存有意义的值。异常中止通常是由硬件故障或系统表中无效的值引起的。由CPU控制单元发生的这个中断是一种紧急信号,用来把CPU的执行路径切换到异常中止的处理程序,而相应的ISR通常除了迫使受到影响的进程中止外,别无选择。 2. 编程异常(Programmed Exception):通常也称为“软中断(software interrupt)”,是由编程者发出中断请求指令时发生的中断,如:int指令和int3指令。当into(检查溢出)和bound(检查地址越界)指令检查的条件不为真时,也引起编程异常。CPU控制单元把编程异常当作Trap来处理,这类异常有两个典型的用途:一、执行系统调用;二、给调试程序通报一个特定条件。 511 中断向量 每个中断和异常都可以用一个0255之间的无符号整数来标识,Intel称之为“中断向量(Interrupt Vector)”。通常,不可屏蔽中断和异常的中断向量是固定的,而可屏蔽中断的中断向量则可以对中断控制器进行编程来改变。I386 CPU的256个中断向量是这样分配的: 1. 从031这一共32个向量用于异常和不可屏蔽中断。 2. 从3247这一共16个向量用于可屏蔽中断,分别对应于主、从8259A中断控制器的IRQ输入线。 3. 剩余的48255用于标识软中断。 Linux全部使用了047之间的向量。但对于48255之间的软中断向量,Linux只使用了其中的一个,即用于实现系统调用的中断向量128(0x80)。当用户态下的进程执行一条int 0x80汇编指令时,CPU切换到内核态,以服务于系统调用。 Linux在头文件include/asm-i386/hw_irq.h中定义了宏FIRST_EXTERNAL_VECTOR来表示第一个外设中断(即8259A的IRQ0)所对应的中断向量,此外还定义了SYSCALL_VECTOR来表示用于系统调用的中断向量。如下所示: /* * IDT vectors usable for external interrupt sources start * at 0x20: */ #define FIRST_EXTERNAL_VECTOR 0x20 #define SYSCALL_VECTOR 0x80 512 I386的IDT i386 CPU的IDT表一共有256项,分别对应每一个中断向量。每一个表项就是一个中断描述符,用以描述相对应的中断向量,中断向量就是该描述符在IDT中的索引,每一个中断描述符的大小都是8个字节。根据INTEL的术语,中断描述符也称为“门(Gate)”。 中断描述符有下列4种类型: (1)任务们(Task Gate):包含了一个进程的TSS段选择符。每当中断信号发生时,它被用来取代当前进程的那个TSS段选择符。Linux并没有使用任务们。任务们的格式如下: (2)中断门(Interrupt Gate):中断门中包含了一个段选择符和一个中断处理程序的段内偏移。注意,当I386 CPU穿越一个中断门进入相应的中断处理程序时,它会清除eflag寄存器中的IF标志,从而屏蔽接下来可能发生的可屏蔽中断。 (3)陷阱门(Trap Gate):与中断门类似,不同之处在于CPU通过陷阱门转入中断处理程序时不会清除IF标志。 (4)调用门(Call Gate):Linux并没有使用调用门。 这三种门的格式如图52所示。 513 中断控制器8259A 我们都知道,PC机中都使用两个级联的8359A PIC(Programmable Interrupt Controller,可编程中断控制器,简称PIC)来管理来自系统外设的中断信号。每个8259A PIC提供8根IRQ(Interrupt ReQuest,中断请求,简称IRQ)输入线。在级联方式中,Master 8259A PIC(第一个PIC)的中断信号输入线IR2用于级联Slave 8259A PIC(第二个PIC)的INT引脚,因此两个8259A一共可以提供15根可用的IRQ输入线。如下图所示: 图53 主、从8259A中断控制器的级联 5131 8259A PIC的基本原理 8259A PIC芯片的基本逻辑块图如下所示: “中断屏蔽寄存器”(Interrupt Mask Register,简称IMR)用于屏蔽8259A的中断信号输入,每一位对应一个输入。当IMR中的biti(0i7)位被置1时,相对应的中断信号输入线IRi上的中断信号将被8259A所屏蔽,也即IRi被禁止。 当外设产生中断信号时(由低到高的跳变信号,80x86系统中的8259A是边缘触发的,Edge Triggered),中断信号被输入到“中断请求寄存器”(Interrupt Request Register,简称IRR),并同时看看IMR中的相应位是否已被设置。如果没有被设置,则IRR中的相应位被设置为1,表示外设产生一个中断请求,等待CPU服务。 然后,8259A的优先级仲裁部分从IRR中选出一个优先级最高中断请求。优先级仲裁之后,8259A就通过其INT引脚向CPU发出中断信号,以通知CPU有外设请求中断服务。CPU在其当前指令执行完后就通过他的INTA引脚给8259A发出中断应答信号,以告诉8259A,CPU已经检测到有中断信号产生。 8259A在收到CPU的INTA信号后,将优先级最高的那个中断请求在ISR寄存器(InService Register,简称ISR)中对应的bit置1,表示该中断请求已得到CPU的服务,同时IRR寄存器中的相应位被清零重置。 然后,CPU再向8259A发出一个INTA脉冲信号,8259A在收到CPU的第二个INTA信号后,将中断请求对应的中断向量放到数据总线上,以供CPU读取。CPU读到中断向量后,就可以装入执行相应的中断处理程序。 如果8259A工作在AEOI(Auto End Of Interrupt,简称AEOI)模式下,则当他收到CPU的第二个INTA信号时,它就自动重置ISR寄存器中的相应位。否则,ISR寄存器中的相应位就一直保持为1,直到8259A显示地收到来自于CPU的EOI命令。 5132 8259A的I/O端口 Master 8259A的IO端口地址位0x20和0x21,Slave 8259A的IO端口地址为0xA0和0xA1。对这些IO端口进行读写操作时的功能如下表所示: I/O Port Addrss Read/Write Function Port A(0x20/0xA0) W Initialization Command Word1(ICW1) W Operation Command Word2(OCW2) W Operation Command Word3(OCW3) R Interrupt Request Register(IRR) R In-Service Register(ISR) Port B(0x21/0xA1) W Initialization Command Word2(ICW2) W Initialization Command Word3(ICW3) W Initialization Command Word4(ICW4) W Operation Command Word1(OCW1) R Interrupt Mask Register(IMR) 表51 8259A的I/O端口地址列表 5133 初始化8259A 8259A PIC的初始化是通过向其写一系列“初始化命令字”(Initialization Command Word,简称ICW)来实现的。其中,ICW1必须写到Port A(0x20/0xA0)中,而ICW2、ICW3和ICW4则必须写到Port B(0x21/0xA1)中。此外,主、从8259A必须分别进行初始化。 ICW1的格式如下图55所示: ICW2的格式如下图56所示: 在MCS80/85模式下,A15A8指定中断向量地址;而在80x86模式下,T7T3用于指定中断向量地址,而bit2:0则可被设置为0。 ICW3(Master Device)的格式如下: Si(0i7)为1则表示相应的IRi上级联了一个Slave 8259A PIC。 ICW3(Slave Device)的格式如下: Bit7:3总为0,而ID2、ID1、ID0(即bit2:0)用于标识Slave 8259A连接在Master 8259A的哪一根IRQ线上。例如:010就表示Slave 8259A是连接在Master 8259A的IR2上。 ICW4的格式如下: 5134 控制8259A 可以向Port A或Port B写入“控制命令字”(Control Command Word,简称OCW)来控制8259A PIC。有三种类型的OCW,其中OCW1只能被写入到Port B中,OCW2和OCW3只能被写到Port A中。 OCW1(Interrupt Mask Register)的格式如下: M7M0就是IRQ7IRQ0各自对应的屏蔽位。IMR寄存器可以通过向Port B写入OCW1来设置,它的当前值也可以通过读取Port B来得到。 OCW2的格式如下: OCW3的格式如下: 52 Linux对IDT的初始化 521 定义IDT的数据结构 Linux在include/asm-i386Desc.h头文件中定义了数据结构desc_struct,用来描述i386 CPU中的各种描述符(均为8字节),如下: struct desc_struct unsigned long a,b; ; 基于上述结构,Linux在arch/i386/kernel/traps.c文件中定义了数组idt_table256,来表示中断描述符表IDT,其定义如下: /* * The IDT has to be page-aligned to simplify the Pentium * F0 0F bug workaround. We have a special link segment * for this. */ struct desc_struct idt_table256 _attribute_(_section_(.data.idt) = 0, 0, ; 522 对门的操作函数 Linux在arch/i386/kernel/traps.c文件中定义了宏_set_gate(),用来设置一个描述符(即门)的具体值。由于Linux内核代码均在段选择子_KERNEL_CS所指向的内核段中,因此门中的Segment Selector字段总是等于_KERNEL_CS。宏_set_gate()有四个参数:(1)gate_addr:描述符desc_struct结构类型的指针,指定待操作的描述符,通常指向数组idt_table中的某个项。(2)type:描述符类型,对应于门格式中的Type字段。(3)dpl:该描述符的权限级别;(4)addr:中断处理程序入口地址的段内偏移量,由于内核段的起始地址总是为0,因此中断处理程序在内核段中的段内偏移量也就是中断处理程序的入口地址(即核心虚地址)。 宏_set_gate()的源码如下: #define _set_gate(gate_addr,type,dpl,addr) do int _d0, _d1; _asm_ _volatile_ (movw %dx,%axnt movw %4,%dxnt movl %eax,%0nt movl %edx,%1 :=m (*(long *) (gate_addr), =m (*(1+(long *) (gate_addr), =&a (_d0), =&d (_d1) :i (short) (0x8000+(dpl13)+(type8), 3 (char *) (addr),2 (_KERNEL_CS 16); while (0) 由于不同的门其Type字段和DPL字段是固定的,因此Linux又在宏_set_gate()的基础上为不同类型的门分别封装了专用的操作宏,它们同样也在traps.c文件中: (1)中断门的操作宏set_intr_gate(),其源码如下: void set_intr_gate(unsigned int n, void *addr) _set_gate(idt_table+n,14,0,addr); Type=1110(14),dpl=0(ring 0) 其中,参数n是中断向量(被用作IDT表的索引)。参数addr是中断处理程序的入口地址。 (2)陷阱门的操作宏set_trap_gate()。其源码如下: static void _init set_trap_gate(unsigned int n, void *addr) _set_gate(idt_table+n,15,0,addr); Type=1111(15),dpl=0(ring 0) (3)系统门的操作宏set_system_gate()。Linux扩展了INTEL的术语,它将可编程异常所对应的中断描述符称为“系统门”。系统门的DPL字段为3(用户态),因此使得用户进程可以在用户态下(I386运行在ring 3级别)通过int指令或其它指令穿越系统门而进入内核态。 static void _init set_system_gate(unsigned int n, void *addr) _set_gate(idt_table+n,15,3,addr); Type=1111(15),dpl=3(ring 3) (4)调用门的操作宏set_call_gate(),其源码如下: static void _init set_call_gate(void *a, void *addr) _set_gate(a,12,3,addr); Type=1100(12),dpl=3(ring 3) 从上述这四个专用的宏操作实现也可以看出,Linux并没有使用i386 CPU的调用门和任务门,而是仅仅使用了中断门和陷阱门(Linux又将它细分为陷阱门和系统门)两种中断描述符。 523 对IDT表的初始化设置 Linux内核在初始阶段完成了对也是虚存管理机制的初始化后,便调用trap_init()函数和init_IRQ()函数对i386 CPU中断机制的核心IDT进行初始化设置。如下: asmlinkage void _init start_kernel(void) trap_init(); init_IRQ(); 其中,函数trap_init()用来对除外设中断(3247)以外的所有处理器保留的中断向量进行初始化。而init_IRQ()函数则用来初始化对应于主、从8259A中断控制器的可屏蔽外设中断3247。 函数trap_init()定义在arch/i386/kernel/traps.c文件中,其源码如下: void _init trap_init(void) #ifdef CONFIG_EISA if (isa_readl(0x0FFFD9) = E+(I8)+(S16)+(A24) EISA_bus = 1; #endif set_trap_gate(0,÷_error); set_trap_gate(1,&debug); set_intr_gate(2,&nmi); set_system_gate(3,&int3); /* int3-5 can be called from all */ set_system_gate(4,&overflow); set_system_gate(5,&bounds); set_trap_gate(6,&invalid_op); set_trap_gate(7,&device_not_available); set_trap_gate(8,&double_fault); 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_trap_gate(14,&page_fault); set_trap_gate(15,&spurious_interrupt_bug); set_trap_gate(16,&coprocessor_error); set_trap_gate(17,&alignment_check); set_trap_gate(18,&machine_check); set_trap_gate(19,&simd_coprocessor_error); set_system_gate(SYSCALL_VECTOR,&system_call); /* * default LDT is a single-entry callgate to lcall7 for iBCS * and a callgate to lcall27 for Solaris/x86 binaries */ set_call_gate(&default_ldt0,lcall7); set_call_gate(&default_ldt4,lcall27); /* * Should be a barrier for any external CPU state. */ cpu_init(); #ifdef CONFIG_X86_VISWS_APIC superio_init(); lithium_init(); cobalt_init(); #endif 从上述代码可以看出,trap_init()函数的核心就是做两件事:(1)设置IDT的前20个表项,这是因为在031这32个CPU保留的异常中断向量中,Intel仅定义了前20个(019)中断向量,而中断向量2031这12个中断向量则保留待以后扩展。(2)设置中断向量SYSCALL_VECTOR(0x80),以用于系统调用的实现。 函数init_IRQ()实现在arch/i386/kernel/i8259.c文件中。它负责初始化IDT表中的后224个中断描述符即中断向量32256所对应的中断描述符(除了用于syscall的中断向量0x80)。其源码如下: void _init init_IRQ(void) int i; #ifndef CONFIG_X86_VISWS_APIC init_ISA_irqs(); #else init_VISWS_APIC_irqs(); #endif /* * Cover the whole vector space, no vector can escape * us. (some of these will be overridden and become * special SMP interrupts) */ for (i = 0; i 8 , 0x40); /* MSB */ #ifndef CONFIG_VISWS setup_irq(2, &irq2); #endif /* * External FPU? Set up irq13 if so, for * original braindamaged IBM FERR coupling. */ if (boot_cpu_data.hard_math & !cpu_has_fpu) setup_irq(13, &irq13); 该函数主要执行以下几个步骤: 1. 在没有配置80x86 APIC的情况下,调用init_ISA_irqs()函数来初始化中断向量32256这后224个中断向量所对应的IRQ描述符。否则就调用init_VISWS_APIC_irqs()来完成这一点。PC体系结构中通常都没有配置APIC,因此后面将详细分析init_ISA_irqs()函数。 2. 接下来,用一个简单的for循环来初始化32256这后224个中断向量(除了用于syscall之外的0x80中断向量)在IDT中对应的描述符。位于32256之间的中断向量i所对应的中断处理程序入口地址由数组元素interrupti32。后面将会详细介绍这个数组interrupt224一个被内核用来保存中断处理程序入口地址的数组。 3. 初始化系统时钟。 4. 如果没有定义CONFIG_VISWS配置选项,则调用setup_irq()函数将8259A中断控制器的IRQ2设置为用于级联。 5. 如果使用了FPU,则将IRQ13分配用于数学协处理器的错误报告中断。所以调用setup_irq()函数将IRQ13设置为用于FPU。 接下来将讨论内核对中断处理程序的构建,也即数组interrupt224的构建。 53内核对中断服务程序的构建 由上一节对init_IRQ()函数的分析我们知道,函数指针数组interrupt中定义了中断向量32256所对应的中断服务程序的入口地址。本节我们就来分析一下Linux内核是如何巧妙地为这224个中断向量构建ISR的。 函数指针数组interrupt定义在arch/i386kernel/i8259.c文件中: #define IRQ(x,y) IRQ#x#y#_interrupt #define IRQLIST_16(x) IRQ(x,0), IRQ(x,1), IRQ(x,2), IRQ(x,3), IRQ(x,4), IRQ(x,5), IRQ(x,6), IRQ(x,7), IRQ(x,8), IRQ(x,9), IRQ(x,a), IRQ(x,b), IRQ(x,c), IRQ(x,d), IRQ(x,e), IRQ(x,f) void (*interruptNR_IRQS)(void) = IRQLIST_16(0x0), #ifdef CONFIG_X86_IO_APIC IRQLIST_16(0x1), IRQLIST_16(0x2), IRQLIST_16(0x3), IRQLIST_16(0x4), IRQLIST_16(0x5), IRQLIST_16(0x6), IRQLIST_16(0x7), IRQLIST_16(0x8), IRQLIST_16(0x9), IRQLIST_16(0xa), IRQLIST_16(0xb), IRQLIST_16(0xc), IRQLIST_16(0xd) #endif ; #undef IRQ #undef IRQLIST_16 从上述定义可以看出,在没有定义CONFIG_X86_IO_APIC配置选项的单CPU体系结构中,interrupt数组中只有前16个数组元素中包含有有效的指针(也即对应于主、从8259A中断控制器的中断请求)。 先看宏IRQ()的定义。我们知道GCC预编译符号的作用就是将字符串连接在一起。因此经过GCC的预编译处理后,宏IRQ(x,y)实际上就是符号IRQxy_interrupt。 在来看宏IRQLIST_16()。它的作用主要是为了避免重复的文字输入。因此但在interrupt数组的初始化中以参数0x0来调用宏IRQLIST_16()时,我们所得到的就是16个宏定义:IRQ(0x0,0) 、IRQ(0x0,f),将IRQ()宏继续展开,我们就可知道interrupt函数指针数组的前16项的值为:IRQ0x00_interrupt、IRQ0x0f_interrupt。而后208个数组元素则或者都是NULL指针(在没有APIC的情况下),或者分别是IRQ0x10_interruptIRQ0xdf_interrupt。 现在我们已经清楚地了解了interrupt224数组的定义以及它的初始值。很自然地我们会想到,函数IRQ0x00_interrupt到IRQ0xdf_interrupt这224个函数又是在哪定义的呢?请看i8259.c文件中另外几行宏定义与宏引用: #define BI(x,y) BUILD_IRQ(x#y) #define BUILD_16_IRQS(x) BI(x,0) BI(x,1) BI(x,2) BI(x,3) BI(x,4) BI(x,5) BI(x,6) BI(x,7) BI(x,8) BI(x,9) BI(x,a) BI(x,b) BI(x,c) BI(x,d) BI(x,e) BI(x,f) /* * ISA PIC or low IO-APIC triggered (INTA-cycle or APIC) interrupts: * (these are usually mapped to vectors 0x20-0x2f) */ BUILD_16_IRQS(0x0) 显然,以参数0x0来引用BUILD_16_IRQ()宏在经过gcc预处理后,将展开成:BUILD_IRQ(0x00)、BUILD_IRQ(0x01)、BUILD_IRQ(0x0f)等共16个宏定义的引用。而宏BUILD_IRQ()则是定义在include/asm-i386/hw_irq.h头文件中定义的: #define BUILD_IRQ(nr) asmlinkage void IRQ_NAME(nr); _asm_( n_ALIGN_STRn SYMBOL_NAME_STR(IRQ) #nr _interrupt:nt pushl $#nr-256nt jmp common_interrupt); 上述代码中的IRQ_NAME()宏也是定义在include/asm-i386/hw_irq.h中: #define IRQ_NAME2(nr) nr#_interrupt(void) #define IRQ_NAME(nr) IRQ_NAME2(IRQ#nr) 所以,宏引用IRQ_NAME(nr)被展开后也就是一个类似于IRQ0x01_interrupt(void)这样的函数名。 因此,从BUILD_IRQ(0x00)到BUILD_IRQ(0x0f)这一共16个宏引用经过gcc预处理后,将展开成为一系列如下样式的代码: amslinkage void IRQ0x01_interrupt(void); _asm_( “n” “IRQ0x01_interrupt:nt” “pushl $0x01-256 nt” “jmp common_interrupt”); 可以看出,Linux内核正是通过gcc的预处理功能巧妙地定义了从IRQ0x00_interrupt()到IRQ0x0f_interrupt()这16个中断处理函数。 下面在来看看中断处理函数IRQ0x00_interrupt()到IRQ0x0f_interrupt()本身的流程。这16个中断处理函数的执行过程都是一样的,它主要完成两件事:(1)将立即数(IRQ号256)这样一个负数压入内核堆栈,以供中断处理流程中后面的函数获取中断号。比如对于中断向量0x20,它对应于注8259A中断控制器的IRQ0,因此其中断服务程序的入口地址应该是interrupt0,也即函数IRQ0x00_interrupt(),该函数所做的第一件事情就是把负数256压入内核堆栈中。这里之所以用复数来表示中断号,是因为正数已被用于标识0x80中断中的系统调用号。(2)所有的中断服务函数所做的第二件事情都相同即跳转到一个公共的的程序common_interrupt中,并由该公共程序继续对中断请求进行服务。 531 公共的中断服务程序common_interrupt 在源文件arch/i386/kernel/i8259.c中一开始就引用了宏BUILD_COMMON_IRQ(),其作用就是构建面向所有中断的公共服务程序common_interrup,如下所示: 36: BUILD_COMMON_IRQ() 宏BUILD_COMMON_IRQ()定义在头文件include/asm-i386/hw_irq.h中,如下所示: #define BUILD_COMMON_IRQ() asmlinkage void call_do_IRQ(void); _asm_( n _ALIGN_STRn common_interrupt:nt SAVE_ALL pushl $ret_from_intrnt SYMBOL_NAME_STR(call_do_IRQ):nt jmp SYMBOL_NAME_STR(do_IRQ); 上述代码经过展开后,就成为如下汇编代码: common_interrupt: SAVE_ALL call do_IRQ jmp ret_from_intr 可以看出,公共服务程序common_interrupt主要做三件事:(1)首先,调用宏SAVE_ALL来保存CPU的执行现场;(2)然后,调用总控函数do_IRQ()对中断请求进行真正的服务;(3)当从do_IRQ()返回后,跳到函数ret_from_intr,进入中断返回操作。 用于保存现场的SAVE_ALL宏定义在arch/i386/kernel/entry.S文件中,如下所示: #define SAVE_ALL cld; pushl %es; pushl %ds; pushl %eax; pushl %ebp; pushl %edi; pushl %esi; pushl %edx; pushl %ecx; pushl %ebx; movl $(_KERNEL_DS),%edx; movl %edx,%ds; movl %edx,%es; 说明几点:(1)用户态堆栈SS寄存器和ESP寄存器是在切换到内核态堆栈被压入的;(2)CPU在进入中断服务程序之前已经把EFLAGS寄存器和返回地址压入堆栈中;(3)段寄存器DS和ES被更改为内核态的段选择符_KERNEL_DS。因此,在执行SAVE_ALL宏之后内核态堆栈的内容应为如下图所示: 而Linux也根据上图中的关系在arch/i386/kernel/entry.S文件中定义了一些常数来表示个寄存器的内容相对于当前内核堆栈指针的偏移: EBX = 0x00 ECX = 0x04 EDX = 0x08 ESI = 0x0C EDI = 0x10 EBP = 0x14 EAX = 0x18 DS = 0x1C ES = 0x20 ORIG_EAX = 0x24 EIP = 0x28 CS = 0x2C EFLAGS = 0x30 OLDESP = 0x34 OLDSS = 0x38 真正对中断请求进行服务的do_IRQ()函数和中断返回函数ret_from_intr()将在下面介绍。在分析总控函数do_IRQ()之前,先来讨论一下中断请求描述符和中断服务队列。 54 中断请求描述符和中断服务队列 我们都知道,在256个中断向量中,i386 CPU保留了031这前32个中断向量用于CPU异常,而剩余的224个中断向量则是可用于外设中断或软中断的可使用中断向量。由于不同体系结构的CPU所保留的中断向量不同,因此剩余的可使用中断向量数目也不同。所以,Linux定义了宏NR_IRQS来表示这个值。对于i386而言,该宏定义在include/asm-i386/irq.h头文件中: define TIME_IRQ 0 for i386,主8259A的IRQ0用于时钟中断 define NR_IRQS 224 541 对中断控制器的抽象描述 在剩余的224个可用中断向量中,各中断向量所对应的中断类型也是不同的。比如对于PC机,中断向量0x200x2f则来自于主、从8259A PIC,其余中断向量则属于软中断(Linux仅仅使用了其中的0x80)。因此有必要对这些不同类型的中断向量进行区分。 另外,从中断控制器的角度看,尽管不同平台使用不同的PIC,但几乎所有的PIC都由相同的基本功能和工作方式。因此为了获得更好的跨平台兼容性,Linux对中断控制器进行了抽象描述。定义在头文件include/linux/irq.h头文件中的数据结构hw_interrupt_type描述了一个标准的中断控制器,如下所示: /* * Interrupt controller descriptor. This is all we need * to describe about the low-level hardware. */ struct hw_interrupt_type const char * typename; unsigned int (*startup)(unsigned int irq); void (*shutdown)(unsigned int irq); void (*enable)(unsigned int irq); void (*disable)(unsigned int irq); void (*ack)(unsigned int irq); void (*end)(unsigned int irq); void (*set_affinity)(unsigned int irq, unsigned long mask); ; typedef struct hw_interrupt_type hw_irq_controller; 在此基础上,Linux又在arch/i386/kernel/i8259.c文件中定义了全局变量i82559_irq_type,以用于所有来自主、从8259A中断控制器的中断请求(对应的中断向量为0x200x2f),如下: static struct hw_interrupt_type i8259A_irq_type = XT-PIC, startup_8259A_irq, shutdown_8259A_irq, enable_8259A_irq, disable_8259A_irq, mask_and_ack_8259A, end_8259A_irq, NULL ; 而对于0x300xff子间的中断向量,Linux也在arch/i386/kernel/irq.c文件中定义了全局变量no_irq_type一个虚拟的中断控制器,以表示这些中断向量的中断请求并不是来自于任何硬件PIC的中断,而是由软件编程指令产生的软中断。如下所示: /* startup is the same as enable, shutdo
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年文化旅游特色小镇商业场地租赁服务协议
- 2025年城市绿化带养护管理服务合同协议
- 2025年度跨境电商供应链金融保证人合同
- 2025年度高品质智能家居系统安装与施工合同
- 2025年实木家具制造企业专用包装材料供应合同
- 2025年度外贸电商平台货物采购居间代理服务合同
- 地球与人类自然课件教学
- 非营业性汽车租赁协议范本
- 2025年高品质古建筑彩绘修复与维护工程合同
- 2025年跨区域合作项目保密条款合同(2025版)
- 化工设备基础知识培训课件
- 产科危急重症早期识别中国专家共识(2025年版)
- 福建福州工会招聘工会社会工作者笔试真题2024
- 医疗生产安全知识培训课件
- 化学品使用安全知识培训课件
- 中国丝绸课件
- 2025年事业单位工勤技能-河北-河北保安员二级(技师)历年参考题库含答案解析(5卷套题【单选100题】)
- 2025至2030全球及中国互联网安全审核行业运营态势与投资前景调查研究报告
- 2025版跨境电商代运营战略合作框架协议
- 文化资本价值评估框架-洞察及研究
- 2025年江苏省苏豪控股集团有限公司人员招聘笔试备考试题及一套答案详解
评论
0/150
提交评论