Linux中断完全分析

学习本文可以对linux中断有全面而深刻的认识。本文对Linux中断所涉及的需求、管理机制、中断实现、中断接口(上半部和下半部)、驱动使用进行完全分析。

一、中断需求

软件是需求驱动的,软件的出现是为了解决需求和问题的。先知道需求,那理解代码就是为了验证已知的问题;不知道需求,那理解代码就是揣摩设计者的目的。两者相比,前者自然效率跟高。

中断是为了解决异步的需求。紧急的事情或者更高优先级的事情需要先做,就代表异步。例如,手机需要时刻优先响应用户按键或者触摸这个事件,否则用户体验就变差。信号是软件上的异步机制,而中断则是硬件上的异步机制。对于中断控制器(SOC)的设计原理请参考《软件和硬件都是对生活的高度抽象---论中断控制(ARM体系编程)》一文。

二、Linux中断需求

上述中断需求对所有实时设备均适用,不管是高级处理器(如ARM体系手机)还是低端控制器(如51体系)。但是对于不同的体系和不同的多任务环境上,中断的需求也要进一步细分。如不同的CPU体系在内存上的表现也会不同的,高级处理器所配置的内存比较大,M级以上,而低端处理器配置的内存比较小,都是K级别。内存的大小也决定了软件代码量和复杂度。Linux操作系统适用于高级处理器和大内存,能够处理大量多任务并发执行。在多进程多线程环境下,中断的需求也变得复杂,因此Linux的中断处理也相对复杂。我们一一来分析Linux中断的需求来自哪里,以及Linux是如何应对的。

我们设想有以下场景:假设是90年代初有某个公司就只有一台电话机(这个场景过时了,一时没想到更好的J),公司只有总经理A和主管B和员工C三个人。我们很容易想到,三者做的事情的重要性是总经理到主管再到员工。在使用电话机对外交流的时候,应该优先总经理,再到主管,最后才是员工。好,三个人常态时是在埋头工作,需求电话沟通代表了异步紧急的事情。我们逐一分析遇到的问题,并考虑怎么应对解决:

1.先是主管B遇到紧急事情需要打断手头的工作去电话,而总经理A在B打电话期间遇到紧急的事情也要打电话。如果主管B一直拿着电话煲粥,那总经理肯定很不爽,因为总经理的事情紧急又更重要。所以合理的处理方式是,总经理有权利抢占电话来先处理紧急的事情。这就是在软件上要允许中断嵌套,让更高优先级的中断能够得到的及时的响应。

2.主管B在打电话的时候,如果员工C遇到紧急的事情也要打电话,他自然是不能抢来打的。这样就引发一个这样的问题,如果B一直占着电话不放,这样合理吗?主管B跟客户肯定是先把紧急的事情(设为B1)说完了,然后为了维护客户关系,就跟客户吹吹水什么的(设为B2)。员工C要打电话处理紧急的事情应该要比B2更加重要吧。假设C和对方沟通完紧急的事情(设为C1)后,也跟客户吹水(设为C2)。那从公平的角度来讲,C1要比B2重要。怎么处理这种情况呢?Linux中断使用上半部和下半部机制。即将每个中断服务分成两个阶段,上半部处理硬件相关和非常紧急的操作(如读硬件数据),而下半部处理没有那么紧急的操作(将硬件数据放到队列等);而且规定,低优先级中断的上半部可以抢占高优先级中断的下半部。即主管B的紧急处理分为B1和B2两个部分,员工C的紧急处理分为C1和C2两个部分。C1可以抢占B2进行。中断上半部和下半部机制是为了让低优先级中断也得到执行的机会。

3. 如果C在一段时间需要跟很多个客户紧急沟通,然后跟每个客户都要吹吹水,即由C发起很多的C1和C2,那主管B想跟C说个事交代一下都找不到机会啊,如果是周一早上例会,C这么干,那这个例会肯定开不成了。例会是主管正常的工作流程(非紧急,如果紧急就是B要打电话了),那B应该等待C和客户交流紧急的事情即C1,也可以容忍C跟一两个客户吹水即C2,但不可以一直等C跟每个客户都吹水的,C怎么都要歇一会跟主管碰碰面吧,等主管B
布置好任务再继续吹水也不迟。Linux中断怎么处理这种情况?Linux中断在处理完所有中断的上半部后会继续处理一部分下半部,但是每次规定最多处理N个下半部。如果还剩下没有处理的下半部,那就交给一个叫做ksoftirqd的后台控制线程,然后退出中断并进行任务调度,这样高优先级的进程就可以得到执行的机会。Ksoftirqd也能通过正常的调度得到执行的机会,将剩下的下半部执行完。Ksoftirqd是一个有较高优先级的内核线程,它能够比较快得到执行机会,因此整个中断的服务也能较快完成。当然,如果Ksoftirqd能够有执行的机会,也代表中断产生很频繁,可以考虑从系统层面进行优化。

Linux常规的下半部实现机制有两种,一种是tasklet,其即由上述Ksoftirqd执行;另一种是工作队列workqueue,其由内核线程eventX来执行。两者的区别在于,tasklet对应的下半部不可以睡眠,而workqueue对应的下半部不可以睡眠。

另外,Linux从2.6开始也支持中断线程化,其实它也是中断下半部的一种实现方式,即创建中断线程来执行下半部,而中断线程是有优先级,因此更高优先级的任务进程就可以得到执行的机会。

本文只对tasklet下半部机制进行描述。Workqueue和中断线程化不做讨论。

4. 总结:

1)执行顺序:高优先级中断的上半部->低优先级的上半部->下半部(高低优先级中断)->高优先级进程->低优先级进程。其中,下半部过多时会转移到ksoftirqd内核线程执行。

2)中断嵌套是为了让高优先级的中断得到及时响应;中断上下半部机制是为了低优先级的中断得到及时响应;ksoftirqd内核线程的调度是为了让高优先级的任务进程得到执行的机会。

三、中断管理机制

以下基于Linux2.6内核和ARM(s5pv210)体系进行分析,其中ARM(s5pv210)体系中断的硬件编程请参考《软件和硬件都是对生活的高度抽象---论中断控制(ARM体系编程)》一文。本文侧重描述Linux中断的管理。

但是我们注意一点,就是基于ARM体系的SOC中断控制器一般都会有几个二级中断源合并(或电路)为一条一级中断线,然后所有的一级中断线合并(或电路)之后再接入CPU的irq信号线。s5pv210由VIC0、VIC1、VIC2、VIC3对应的四组寄存器来管理一级中断源,例如,UART0中断对应VIC1寄存器组的第10个bit;但是UART0有接收中断,也有发送中断,它们属于二级中断源。

1.   数据结构

--include/linux/irq.h

struct irq_desc是中断管理的核心数据结构,由它来连接硬件中断控制器和对应中断的服务函数。

struct irq_desc {

//中断号,指的是一级中断号,如UART0即32+10=42.

unsigned int         irq;

//对应中断控制器操作,如mask,enable操作等

struct irq_chip       *chip;

//一级公共中断服务函数,如接收和发送中断都有共同的清pending的操作。

irq_flow_handler_t handle_irq;

//具体的二级中断服务函数连成链表

structirqaction     *action;

}

--kernel/irq/handle.c

//定义irq_desc全局数组,元素就是128个。

structirq_desc irq_desc[NR_IRQS];

--include/linux/irq.h

structirq_chip代表中断控制器的硬件控制。为什么要将硬件的控制操作作为函数指针然后连接到irq_desc,而不是直接在中断服务中进行?这是因为中断服务属于驱动的范畴,如果驱动直接对硬件寄存器编程,那就会导致驱动的可移植性变差,应该将平台硬件相关的操作进行分离,在linux启动的时候做好初始化,并注册到系统中,那驱动需要进行控制时就可以通过获取到irq_chip数据结构,并调用里面的函数来实现控制。这样讲驱动移植到不同的平台,只需要修改BSP板级初始化的过程,而不需要修改驱动。这与将外围设备连接的GPIO作为资源并附属到设备数据结构,而驱动通过资源接口来获取GPIO是一样的道理。Linux是尽可能地考虑高度可移植性的系统,绝对是面向对象设计编程的典范。

structirq_chip {

const char *name;

void          (*enable)(unsignedint irq);

void          (*disable)(unsignedint irq);

void          (*mask)(unsignedint irq);

void          (*unmask)(unsignedint irq);

int       (*set_wake)(unsigned int irq, unsignedint on);

….

}

--include/linux/irq.h

typedef  void (*irq_flow_handler_t)(unsigned int irq, structirq_desc *desc);

irq_flow_handler_t   handle_irq代表一级中断公共服务操作。

--include/linux/interrupt.h

structirqaction {

//typedef irqreturn_t(*irq_handler_t)(int, void *);二级中断服务接口

irq_handler_t handler;

//触发中断方式,如边沿或者电平触发

unsigned long flags;

const char *name;

//一般是中断设备的数据结构

void *dev_id;

//二级中断链接点

struct irqaction *next;

//中断号

int irq;

//proc映象路径

struct proc_dir_entry *dir;

//中断线程化,我们不讨论这点,设为NULL。

irq_handler_t thread_fn;

struct task_struct *thread;

unsigned long thread_flags;

};

2.   Irq_desc的初始化

struct irq_descirq_desc[NR_IRQS] = {

[0 ... NR_IRQS-1] = {

.status = IRQ_DISABLED,//初始状态为disable

.chip = &no_irq_chip, //未对应实际硬件

.handle_irq = handle_bad_irq,

}

};

Linux启动的第二部分会调用:

Start_kernel

1)early_irq_init

---desc = irq_desc;

---count= ARRAY_SIZE(irq_desc);

---for(i = 0; i < count; i++) {

---desc[i].irq= i;//中断号设置

----------…

---}

arch_early_irq_init()

2)init_IRQ

-----for (irq = 0; irq < NR_IRQS; irq++)

----------irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

-----init_arch_irq()

//即s5pv210_init_irq—arch/arm/mach-s5pv210/smdk210.c

----------s5p_init_irq(vic,ARRAY_SIZE(vic));

---------------vic_init(VA_VIC(irq),VIC_BASE(irq), vic[irq], 0);

--------------------vic_disable(base);//默认不使能中断

--------------------vic_set_irq_sources

-------------------------//初始化各个irq_desc的irq_chip

-------------------------set_irq_chip(irq,&vic_chip);

-------------------------//默认一级中断和相关寄存器。

-------------------------set_irq_handler(irq,handle_level_irq);

3.   各个irq_desc对应硬件irq_chip的初始化

set_irq_chip(irq,&vic_chip);其中vic_chip在\arch\arm\common\vic.c

static struct irq_chipvic_chip = {

.name       ="VIC",

.ack           =vic_ack_irq,

.mask        =vic_mask_irq,//屏蔽中断

.unmask          =vic_unmask_irq,

.retrigger   = vic_retrigger_irq,

.set_wake  =vic_set_wake,//唤醒

};

Irq_chip中的接口均是以中断号为参数,这样可以找到对应的VIC寄存器,实现屏蔽等功能。

4.   总结申请中断之前的中断相关数据结构

中断注册前,即在linux启动之后,irq_desc数组的每个元素(即一级中断)部分数据结构和对应的硬件控制寄存器irq_chip均已经初始化。但是irq_desc的action,即具体中断服务是空的,而且状态是disable的。

5.   中断申请注册接口

int request_irq(unsigned int irq,irq_handler_t handler, unsigned long flags,

const char *name, void *dev);

1)   irq是一级中断号,但是我们不应该按照SOC的SPEC直接填入一级中断号,而应该按照

arch\arm\plat-s5p\include\plat\irqs.h中通过宏定义设置的中断号,它通过一级级的宏展开,最终也可以得到一级中断号,但是通过宏定义的中断号,编程更加明了。如:

#defineIRQ_S5P_UART_RX0   (IRQ_S5P_UART_BASE0 +UART_IRQ_RXD)。

用IRQ_S5P_UART_RX0即代表UART0的接收中断。

2)handler中断处理函数

3)flags触发方式

4)name名称,会出现在/proc/interrupt/目录中。

5)dev一般填入中断对应设备的数据结构。

6.   中断注册之后的映象

注册后也能在/proc/interrupts/目录中看到以request_irq的参数name为名称的目录,该目录有几个属性文件,记录中断发生的次数等等。

可以看出,irq_desc是连接硬件相关的chip和对应的软件处理action。chip的初始化在linux启动过程即完成初始化,而action在request_irq接口中完成填充。

四、中断处理

中断是异常的一种,异常还包括缺页、syscall等等。对于一般低端单片机来说,中断向量表是异步信号发生时的直接入口,而对于cortex A8高级CPU来说,异常向量表是异步信号发生时的直接入口。假如发生irq中断,那么PC会转到异常向量表中的irq偏移处取出指令并执行,即该处是irq中断的总入口。而S5PV210是基于cortex A8核心,中断控制器是SOC级别控制模块,是三星进行系统设计的,而cortex A8是ARM公司提供。中断控制器管理最多不超过128个一级中断源,其同样对应一份中断向量。具体选择哪个向量地址来执行,是由irq中断总入口根据当前的irq
pending位进行分发调用。

异常向量地址由异常向量基址和偏移组成,如irq中断偏移0x18. 当发生异常时,cortex A8的PC跳转到异常向量地址的基址是可以配置的。其由协处理器CP15来设置。默认的基址是0x0000 0000,而linux启动的过程中设置为0xffff 0000。

Linux中断以下内容学习请关注博主的微信公众号---嵌入式企鹅圈,博主为上市公司资深嵌入式架构师和高级培训师,通过嵌入式企鹅圈公众号分享嵌入式和Linux相关的原创技术文章,敬请关注。谢谢!

1.异常向量表的初始化

2.中断服务和分发

五、中断下半部tasklet

以上描述的中断处理均是指中断的上半部,下半部由上半部创建和调度。

1.   接口

2.   中断调度

3.   Ksoftirq执行

六、典型的Linux中断编程设计

七、总结

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-25 08:06:41

Linux中断完全分析的相关文章

linux中断源码分析 - 中断发生(三)

本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 回顾 上篇文章linux中断源码分析 - 初始化(二)已经描述了中断描述符表和中断描述符数组的初始化,由于在初始化期间系统关闭了中断(通过设置CPU的EFLAGS寄存器的IF标志位为0),当整个中断和异常的初始化完成后,系统会开启中断(设置CPU的EFLAGS寄存器的IF标志位为1),此时整个系统的中断已经开始可以使用了.本篇文章我们具体研究一次典型中断发生时的运行流程. 中断产生 我们需要先明确一下,中断控

linux串口驱动分析

linux串口驱动分析 硬件资源及描写叙述 s3c2440A 通用异步接收器和发送器(UART)提供了三个独立的异步串行 I/O(SIO)port,每一个port都能够在中断模式或 DMA 模式下操作.UART 使用系统时钟能够支持最高 115.2Kbps 的波特率.每一个 UART 通道对于接收器和发送器包含了 2 个 64 位的 FIFO. 寄存器 名称 地址 在linux中的描写叙述 (2410 和 2440 处理器对内存地址映射关系同样) UART 线性控制寄存器(ULCONn) ULC

Linux Bluetooth内核分析之HCI部分

关于HCI规范相关内容,请看<Bluetooth HCI介绍> 首先我们要了解,在Linux上实现的是HCI的Host部分 1. 相关数据结构 1.1 hci_dev 在Linux中hci_dev用来表示一个HCI Host(对应于一个Control) 成员 作用 char name[8] 蓝牙名称 __u8 bus HCI总线类型,有HCI_USB,HCI_PCCARD,HCI_UART,HCI_PCI等 __u8 dev_type HCI Controller类型,有HCI_BREDR,H

linux中断流程详解

异常体系比较复杂,但是linux已经准备了很多的函数和框架,但是因为中断是和具体的开发板相关,所以中断需要我们自己来处理一些方面,但是这也是很少的一部分,很多公用的处理函数内核已经实现,linux内核搭建了一个非常容易扩充的中断处理体系. 中 断系统结构涉及的方面很多,而且分布在很多的函数中,这里我主要理清一些结构和流程顺序已经在哪些函数中实现,我不知道其他人怎么样?但是我自己一开始怎 是找不到linux内核是怎么把GPIO设置成中断的,我找了很久都找不到,还有我们很多的设置,初始化等等东西好像

linux用户进程分析

       经过实验3的介绍,我们需要来点实在的,所以将我们理解的流程用于linux系统的分析.换句话说,通过类比的方式去进行描述与理解linux相关的部分.本节的内容很详实,而且也分析了很久,所以长话短说,静静的去感受与理解linux内核代码的实现.当然,我们实验的系统代码很简单而且直接,但是linux内核经过20多年的发展,更有成千上万的开发者共同维护,所以对于代码的书写会更加精练,对于基础相对薄弱的程序员去理解有一些障碍,但是反过来说,更有利于我们语言知识的提高. 如下的介绍方法是对

linux系统瓶颈分析(精)

linux系统瓶颈分析(精) (2013-09-17 14:22:00)   分类: linux服务器瓶颈分析 1.0 性能监控介绍 性能优化就是找到系统处理中的瓶颈以及去除这些的过程,多数管理员相信看一些相关的"cook book"就 可以实现性能优化,通常通过对内核的一些配置是可以简单的解决问题,但并不适合每个环境,性能优化其实 是对OS 各子系统达到一种平衡的定义,这些子系统包括了: CPU Memory IO Network 这些子系统之间关系是相互彼此依赖的,任何一个高负载都

(转)linux中断 软中断

在分析linux内核的中断,软中断时,先应该明确这样一个派生关系: irq ==> softirq ==> tasklet ==> bottom half ==> task queue------------------------|==> timer 中断是最初的原动力.分时系统依赖于时钟中断来定时重新调度可以运行的程序.外设通过中断来通知cpu处理相关的任务.中断处理程序是内核中一段特殊的,独立的,可运行实体.这个实体,某种程度上,是和进程或线程类似的. 由于中断需要快速

Linux中断 - High level irq event handler

一.前言 当外设触发一次中断后,一个大概的处理过程是: 1.具体CPU architecture相关的模块会进行现场保护,然后调用machine driver对应的中断处理handler 2.machine driver对应的中断处理handler中会根据硬件的信息获取HW interrupt ID,并且通过irq domain模块翻译成IRQ number 3.调用该IRQ number对应的high level irq event handler,在这个high level的handler中

Linux中断 - 驱动申请中断API

一.前言 本文主要的议题是作为一个普通的驱动工程师,在撰写自己负责的驱动的时候,如何向Linux Kernel中的中断子系统注册中断处理函数?为了理解注册中断的接口,必须了解一些中断线程化(threaded interrupt handler)的基础知识,这些在第二章描述.第三章主要描述了驱动申请 interrupt line接口API request_threaded_irq的规格.第四章是进入request_threaded_irq的实现细节,分析整个代码的执行过程. 二.和中断相关的lin