分享

linux操作系统之中断

 昵称11935121 2018-03-06

一、与中断相关的知识

1、中断

所谓中断是指CPU在执行程序的过程中,出现了某些突发事件急待处理,CPU必须暂停执行当前的程序,转去处理突发事件,处理完毕后CPU又返回程序被中断的位置并继续执行。

2、中断的分类

1)根据中断来源分为:内部中断和外部中断。内部中断来源于CPU内部(软中断指令、溢出、语法错误等),外部中断来自CPU外部,由设备提出请求。

2)根据是否可被屏蔽分为:可屏蔽中断和不可屏蔽中断(NMI),被屏蔽的中断将不会得到响应。

3)根据中断入口跳转方法分为:向量中断和非向量中断。向量中断为不同的中断分配不同的中断号,非向量中断多个中断共享一个中断号,在软件中判断具体是哪个中断(非向量中断由软件提供中断服务程序入口地址)。

3、中断向量寻址:

硬件提供可供256个服务程序中断进入的入口,即中断向量;

中断向量在保护模式下的实现机制是中断描述符表IDT,IDT的位置由idtr确定,idtr是个48位的寄存器,高32位是IDT的基址,低16位为IDT的界限;

IDT中包含256个中断描述符,对应256个中断向量;每个中断描述符8位,其结构如图

linux操作系统之中断

异常处理机制:

Intel保留0-31号中断向量用来处理异常事件:当产生一个异常时,处理机就会自动把控制转移到相应的处理程序的入口,异常的处理程序由操作系统提供

4、中断进入过程

当中断是由低特权级转到高特权级(即当前特权级CPL>DPL)时,将进行堆栈的转移;内层堆栈的选择由当前tss的相应字段确定,而且内层堆栈将依次被压入如下数据:外层SS,外层ESP,EFLAGS,外层CS,外层EIP; 中断返回过程为一逆过程;

二、中断处理过程

Linux中断分为两个半部:上半部(tophalf)下半部(bottom half)。上半部的功能是'登记中断',当一个中断发生时,它进行相应地硬件读写后就把中断例程的下半部挂到该设备的下半部执行队列中去。因此,上半部执行的速度就会很快,可以服务更多的中断请求。但是,仅有'登记中断'是远远不够的,因为中断的发生是一个随机事件。因此,Linux引入了一个下半部,来完成中断事件的绝大多数使命。下半部和上半部最大的不同是下半部是可中断的,而上半部是不可中断的,下半部几乎做了中断处理程序所有的事情,而且可以被新的中断打断!下半部则相对来说并不是非常紧急的,通常还是比较耗时的,因此由系统自行安排运行时机,不在中断服务上下文中执行。

中断向量表初始化

用三个函数来进行这些表项的初始化,分别为set_trap_gate()、set_system_gate()set_intr_gate(),在文件中定义如下

陷阱门 static void __init set_trap_gate(unsigned int n, void *addr) { _set_gate(idt_table+n,15,0,addr,__KERNEL_CS); } 系统门 static void __init set_system_gate(unsigned int n, void *addr) { _set_gate(idt_table+n,15,3,addr,__KERNEL_CS); } 中断门 void set_intr_gate(unsigned int n, void *addr) { _set_gate(idt_table+n,14,0,addr,__KERNEL_CS); }

中断请求初始化

trap_init()是为特定的中断源初始化中断向量表项的,而init_IRQ()是为外设的通用中断门设置表项的,不过还没完成,通用中断的服务程序为空,所以需要进一步的初始化,即中断请求队列的初始化。由于通用中断是让多个中断源共用的,而且允许这种共用的结构在系统运行过程中动态地变化,所以在IDT的初始化阶段只是为每个中断向量(表项)准备下一个“中断请求队列”,从而形成一个中断请求队列数组。

申请和释放中断

在Linux设备驱动中,使用中断的设备需要申请和释放相对应的中断,

a. 申请IRQ

typedef irqreturn_t (*irq_handler_t)(int irq, void *dev_id);

b.注册中断服务程序

真正的中断服务要到具体设备的初始化程序将其中断服务程序通过request_irq()向系统“登记”,挂入某个中断请求队列以后才会发生,该函数如下

int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *),

unsigned long irq_flags, const char * devname, void *dev_id)

在分配并设置了一个irqaction数据结构action后,便调用setup_irq(),将其链入相应的中断请求队列中,函数如下

int setup_irq(unsigned int irq, struct irqaction *new)

c. 注销中断服务程序

卸载驱动程序时,需要注销相应的中断处理服务程序,并释放中断线调用的函数如下

void free_irq(unsigned int irq, void *dev_id);

三、下半部

下半部的任务就是执行与中断处理密切相关但中断处理程序本身不执行的工作。在理想情况下,最好是中断处理程序将所有工作都交给下半部执行,因为我们希望给在中断处理程序中完成的工作越少越好,我们期望中断处理程序能够尽快地返回

下半部可以通过多种机制实现:工作队列、软中断、tasklet等。

tasklet是利用软中断实现的一种下半部机制。选择到底用软中断还是tasklet其实很简单:通常应该用tasklet,它与软中断本质上很相似,行为表现也相近,但是接口更简单,锁保护也要求低

工作队列(work queue)是另外一种将工作退后执行的形式,它和我们前面讨论的形式不同。工作队列把工作推后,交给一个内核线程去执行---这个下半部分总是会在进程上下文中执行。这样,通过工作队列执行的代码能占尽进程上下文的所有优势。最重要的是工作队列允许重新调度和睡眠。

通常在工作队列和软中断/tasklet中做出选择很容易,如果要推后执行的任务需要睡眠,那么就选择工作队列。如果推后执行的任务不需要睡眠,那么就选择软中断或tasklet。

如果需要用一个可以重新调度的实体来执行下半部处理,应该选择使用工作队列,他是唯一能在进程上下文中运行的下半部机制,也只有它可以睡眠,这意味着需要获得大量内存是、需要获取信号量时、需要执行阻塞I/O操作时,它会非常有用

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多