理解Linux中断 (1)【转】

转自:http://blog.csdn.net/tommy_wxie/article/details/7425685

版权声明:本文为博主原创文章,未经博主允许不得转载。
一直认为,理解中断是理解内核的开始。中断已经远远超过仅仅为外围设备服务的范畴,它是现代体系结构的重要组成部分。
1、基本输入输出方式
现代体系结构的基本输入输出方式有三种:
(1)程序查询:
CPU周期性询问外部设备是否准备就绪。该方式的明显的缺点就是浪费CPU资源,效率低下。
但是,不要轻易的就认为该方式是一种不好的方式(漂亮的女人不一定好,不漂亮的女人通常很可爱),通常效率低下是由于CPU在大部分时间没事可做造成的,这种轮询方式自有应用它的地方。例如,在网络驱动中,通常接口(Interface)每接收一个报文,就发出一个中断。而对于高速网络,每秒就能接收几千个报文,在这样的负载下,系统性能会受到极大的损害。
为了提高系统性能,内核开发者已经为网络子系统开发了一种可选的基于查询的接口NAPI(代表new API)。当系统拥有一个高流量的高速接口时,系统通常会收集足够多的报文,而不是马上中断CPU。
(2)中断方式
这是现代CPU最常用的与外围设备通信方式。相对于轮询,该方式不用浪费稀缺的CPU资源,所以高效而灵活。中断处理方式的缺点是每传送一个字符都要进行中断,启动中断控制器,还要保留和恢复现场以便能继续原程序的执行,花费的工作量很大,这样如果需要大量数据交换,系统的性能会很低。
(3)DMA方式
通常用于高速设备,设备请求直接访问内存,不用CPU干涉。但是这种方式需要DMA控制器,增加了硬件成本。在进行DMA数据传送之前,DMA控制器会向CPU申请总线控制 权,CPU如果允许,则将控制权交出,因此,在数据交换时,总线控制权由DMA控制器掌握,在传输结束后,DMA控制器将总线控制权交还给CPU。

2、中断概述
2.1、中断向量
X86支持256个中断向量,依次编号为0~255。它们分为两类:
(1)异常,由CPU内部引起的,所以也叫同步中断,不能被CPU屏蔽;它又分为Faults(可更正异常,恢复后重新执行),Traps(返回后执行发生trap指令的后一条指令)和Aborts(无法恢复,系统只能停机);
(2)中断,由外部设备引起的。它又分为可屏蔽中断(INTR)和非可屏蔽中断(NMI)。
Linux对256个中断向量分配如下:
(1)0~31为异常和非屏蔽中断,它实际上被Intel保留。
(2)32~47为可屏蔽中断。
(3)余下的48~255用来标识软中断;Linux只用了其中一个,即128(0x80),用来实现系统调用。当用户程序执行一条int 0x80时,就会陷入内核态,并执行内核函数system_call(),该函数与具体的架构相关。
2.2、可屏蔽中断
X86通过两个级连的8259A中断控制器芯片来管理15个外部中断源,如图所示:

外部设备要使用中断线,首先要申请中断号(IRQ),每条中断线的中断号IRQn对应的中断向量为n+32,IRQ和向量之间的映射可以通过中断控制器商端口来修改。X86下8259A的初始化工作及IRQ与向量的映射是在函数init_8259A()(位于arch/i386/kernel/i8259.c)完成的。
CPU通过INTR引脚来接收8259A发出的中断请求,而且CPU可以通过清除EFLAG的中断标志位(IF)来屏蔽外部中断。当IF=0时,禁止任何外部I/O请求,即关中断(对应指令cli)。另外,中断控制器有一个8位的中断屏蔽寄存器(IMR),每位对应8259A中的一条中断线,如果要禁用某条中断线,相应的位置1即可,要启用,则置0。
IF标志位可以使用指令STI和CLI来设置或清除。并且只有当程序的CPL<=IOPL时才可执行这两条指令,否则将引起一般保护性异常(通常来说,in,ins,out,outs,cli,sti只有在CPL<=IOPL时才能执行,这些指令称为I/O敏感指令)。
以下一些操作也会影响IF标志位:
    (1)PUSHF指令将EFLAGS内容存入堆栈,且可以在那里修改。POPF可将已经修改过的内容写入EFLAGS寄存器。
    (2)任务切换和IRET指令会加载EFLAGS寄存器。因此,可修改IF标志。
    (3)通过中断门处理一个中断时,IF标志位被自动清除,从而禁止可尽屏蔽中断。但是,陷阱门不会复位IF。
2.3、异常及非屏蔽中断
异常就是CPU内部出现的中断,也就是说,在CPU执行特定指令时出现的非法情况。非屏蔽中断就是计算机内部硬件出错时引起的异常情况。从上图可以看出,二者与外部I/O接口没有任何关系。Intel把非屏蔽中断作为异常的一种来处理,因此,后面所提到的异常也包括了非屏蔽中断。在CPU执行一个异常处理程序时,就不再为其他异常或可屏蔽中断请求服务,也就是说,当某个异常被响应后,CPU清除EFLAG的中IF位,禁止任何可屏蔽中断(IF不能禁止异常和非可屏蔽中断)。但如果又有异常产生,则由CPU锁存(CPU具有缓冲异常的能力),待这个异常处理完后,才响应被锁存的异常。我们这里讨论的异常中断向量在0~31之间,不包括系统调用(中断向量为0x80)。

2.4、中断描述符表
2.4.1、中断描述符
在实地址模式中,CPU把内存中从0开始的1K字节作为一个中断向量表。表中的每个表项占四个字节,由两个字节的段地址和两个字节的偏移量组成,这样构成的地址便是相应中断处理程序的入口地址。但是,在保护模式下,由四字节的表项构成的中断向量表显然满足不了要求。这是因为,除了两个字节的段描述符,偏移量必用四字节来表示;要有反映模式切换的信息。因此,在保护模式下,中断向量表中的表项由8个字节组成,中断向量表也改叫做中断描述符表IDT(Interrupt Descriptor Table)。其中的每个表项叫做一个门描述符(gate descriptor),“门”的含义是当中断发生时必须先通过这些门,然后才能进入相应的处理程序。门描述符的一般格式如下:

中断描述符表中可放三类门描述符:
(1)中断门(Interrupt gate)
其类型码为110,它包含一个中断或异常处理程序所在的段选择符和段内偏移。控制权通过中断门进入中断处理程序时,处理器清IF标志,即关中断,以避免嵌套中断的发生。中断门中的DPL(Descriptor Privilege Level)为0,因此,用户态的进程不能访问Intel的中断门。所有的中断处理程序都由中断门激活,并全部限制在内核态。设置中断门的代码如下:
[html] view plain copy print?
//n为中断向量号,addr为中断处理程序地址,位于arch/i386/kernel/traps.c
void set_intr_gate(unsigned int n, void *addr)
{  //type=14,dpl=0,selector=__KERNEL_CS
    _set_gate(idt_table+n,14,0,addr,__KERNEL_CS);
}  

Idt_table为中断描述符表,其定义位于arch/i386/kernel/traps.c中,如下:
[html] view plain copy print?
//中断描述符表
struct desc_struct idt_table[256] __attribute__((__section__(".data.idt"))) = { {0, 0}, };
//描述符结构
struct desc_struct {
    unsigned long a,b;
};
(2)陷阱门(Trap gate)
其类型码为111,与中断门类似,其唯一的区别是,控制权通过陷阱门进入处理程序时维持IF标志位不变,也就是说,不关中断。其设置代码如下:
[html] view plain copy print?
static void __init set_trap_gate(unsigned int n, void *addr)
{
    _set_gate(idt_table+n,15,0,addr,__KERNEL_CS);
}
3)任务门(Task gate)
IDT中的任务门描述符格式与GDT和LDT中的任务门格式相同,含有一个任务TSS段的选择符,该任务用于处理异常或中断,Linux用于处理Double fault。其设置代码如下:
[html] view plain copy print?
static void __init set_task_gate(unsigned int n, unsigned int gdt_entry)
{
    _set_gate(idt_table+n,5,0,0,(gdt_entry<<3));
}
它们各自的格式如下:

此外,在Linux中还有系统门(System gate),用于处理用户态下的异常overflow,bound以及系统调用int 0x80;以及系统中断门(system interrupt gate),用来处理int3,这样汇编指令int3就能在用户态下调用。
[html] view plain copy print?
static void __init set_system_gate(unsigned int n, void *addr)
{
    _set_gate(idt_table+n,15,3,addr,__KERNEL_CS);
}
//设置系统调用门描述符,在trap.c中被trap_init()调用
set_system_gate(SYSCALL_VECTOR,&system_call);  

//设置系统中断门
static inline void set_system_intr_gate(unsigned int n, void *addr)
{
    _set_gate(idt_table+n, 14, 3, addr, __KERNEL_CS);
}  

//位于arch/i386/kernel/traps.c
void __init trap_init(void)
{
set_trap_gate(0,÷_error);
    set_intr_gate(1,&debug);
    set_intr_gate(2,&nmi);
    //系统中断门
    set_system_intr_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_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);
#ifdef CONFIG_X86_MCE
    set_trap_gate(18,&machine_check);
#endif
    set_trap_gate(19,&simd_coprocessor_error);  

    set_system_gate(SYSCALL_VECTOR,&system_call);
}
2.4.2、中断描述表初始化
中断描述表的最终初始化是init/main.c中的start_kernel()中完成的
[html] view plain copy print?
asmlinkage void __init start_kernel(void)
{
//陷阱门初始化
    trap_init();
    //中断门初始化
    init_IRQ();
    //软中断初始化
    softirq_init();
}
中断门的设置是在init_IRQ()中完成的,如下:
[html] view plain copy print?
//位于arch/i386/kernel/i8259.c
void __init init_IRQ(void)
{
    //调用init_ISA_irqs
    pre_intr_init_hook();
     //设置中断门
    for (i = 0; i < (NR_VECTORS - FIRST_EXTERNAL_VECTOR); i++) {
        int vector = FIRST_EXTERNAL_VECTOR + i;
        if (i >= NR_IRQS)
            break;
        //跳过系统调用的向量
        if (vector != SYSCALL_VECTOR)
            set_intr_gate(vector, interrupt[i]);
    }
}  

 

时间: 2024-10-06 22:44:25

理解Linux中断 (1)【转】的相关文章

理解Linux中断 (3)【转】

转自:http://blog.csdn.net/tommy_wxie/article/details/7425712 版权声明:本文为博主原创文章,未经博主允许不得转载. 4.下半部 在中断处理过程中,不能睡眠.另外,它运行的时候,会把当前中断线在所有处理器上都屏蔽(在ack中完成屏蔽):更糟糕的情况是,如果一个处理程序是SA_INTERRUPT类型,它执行的时候会禁上所有本地中断(通过cli指令完成),所以,中断处理应该尽可能快的完成.所以Linux把中断处理分为上半部和下半部. 上半部由中断

理解Linux中断 (2)【转】

转自:http://blog.csdn.net/tommy_wxie/article/details/7425692 版权声明:本文为博主原创文章,未经博主允许不得转载. 3.内核的中断处理 3.1.中断处理入口 由上节可知,中断向量的对应的处理程序位于interrupt数组中,下面来看看interrupt: [html] view plain copy print? 341 .data #数据段 342 ENTRY(interrupt) 343 .text 344 345 vector=0 3

深入理解Linux内存寻址的分段机制

一.前言 最近在学习Linux内核,读到<深入理解Linux内核>的内存寻址一章.原本以为自己对分段分页机制已经理解了,结果发现其实是一知半解.于是,查找了很多资料,最终理顺了内存寻址的知识.现在把我的理解记录下来,希望对内核学习者有一定帮助,也希望大家指出错误之处. 二.分段到底是怎么回事 相信学过操作系统课程的人都知道分段分页,但是奇怪的是书上基本没提分段分页是怎么产生的,这就导致我们知其然不知其所以然.下面我们先扒一下分段机制产生的历史. 实模式的诞生(16位处理器及寻址) 在8086处

LINUX中断学习笔记【转】

转自:http://blog.chinaunix.net/uid-14825809-id-2381330.html 1.中断的注册与释放: 在 , 实现中断注册接口: int request_irq(unsigned int irq,irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long flags, const char *dev_name,void *dev_id); void free_irq(unsigne

linux中断--中断嵌套&amp;amp;中断请求丢失

  关于中断嵌套: 在linux内核里,如果驱动在申请注册中断的时候没有特别的指定,do_irq在做中断响应的时候,是开启中断的,如果在驱动的中断处理函数正在执行的过程中,出现同一设备的中断或者不同设备的中断,这时候新的中断会被立即处理,还是被pending,等当前中断处理完成后,再做处理. 在2.4和2.6内核里,关于这一块是否有什么不同. 一般申请中断的时候都允许开中断,即不使用SA_INTERRUPT标志.如果允许共享则加上 SA_SHIRQ,如果可以为内核熵池提供熵值(譬如你写的驱动是i

linux中断--内核中断编程

Linux中断内核编程 前言 在前面分析了中断的基本原理后,就可以写一个内核中断程序来体验以下,也可以借此程序继续深入来了解内核中断的执行过程 一.内核中断程序: 我们还是来看一看成程序: 在看程序之前,要熟悉如何进行模块编程,和了解module_pararm()的用法.如果不熟悉的话请大家看,module_param()的学习和Linux内核模块编程,在此不作解释. 1.程序interrupt.c [c-sharp] view plaincopy 1 /* 2 *file name :inte

深入理解Linux内存管理机制(一)

深入理解Linux内存管理机制(一)通过本文,您即可以: 1. 存储器硬件结构: 2.分段以及对应的组织方式: 3.分页以及对应的组织方式. 注1:本文以Linux内核2.6.32.59本版为例,其对应的代码可以在http://www.kernel.org/pub/linux/kernel/v2.6/longterm/v2.6.32/linux-2.6.32.59.tar.bz2找到. 注2:本文所有的英文专有名词都是我随便翻译的,请对照英文原文进行理解. 注3:推荐使用Source Insig

深入理解linux互斥锁(mutex)

                                      深入理解linux互斥锁(mutex)     锁机制,可以说是linux整个系统的精髓所在,linux内核都是围绕着同步在运转.在多进程和多线程编程中,锁起着极其重要的作用.我这里说的是互斥锁,其实是泛指linux中所有的锁机制.我在这里不讲如果创建锁,关于锁的创建,网上代码很多,我在这里就不多说了.我要谈一谈一个让所有刚刚接触锁机制的程序员都很困惑的问题:如何使用以及锁机制在程序中是如何运作的.     为什么要使用

深入理解linux内核之(二)进程

                                      深入理解linux内核之(二)进程       程序是静态的,进程是正在执行的程序的一个实例,一个程序可以由多个进程组成.进程是资源分配的实体.在进程被创建出来之后,该子进程几乎和父进程一样.子进程复制了父进程的地址空间,从fork()之后的第一条指令开始执行,和父进程有同样的程序可执行代码(exec调用除外).尽管子进程和父进程具有同样的程序执行代码,但是子进程拥有自己的stack和heap,因此,子进程对数据的修改对