2016-07-07 21:31:09 fridayLL 阅读数 1224
  • 安卓编译与开发、Linux内核及驱动

    安卓编译与开发、Linux内核及驱动视频教程,该课程内容包括一、源码编译:1、常见的ROM种类、谷歌的ROM、第三方的ROM2、区别3、RockChip的ROM、4、编译环境配置、源码下载、编译命令;二、源码开发:源码结构分析、Launcher开发、需求分析、系统级应用;三、内核讲解:内核用途、内核结构、内核职能、内核源码编译、驱动开发;四、内核开发:1、体系架构、目录结构、中断机制、内核进程控制、2、内核库文件、例子简单分析、3、内核调度流程4、内核组件工具 嘉宾介绍:仝利,英文名Burning,CSDN博客讲师,多年主要从事于流媒体,网络通讯,p2p等技术的开发与研究。目前在创业,产品是面向企业会议室和家庭客厅的多媒体通讯盒子的开发。国内还没有相关产品,预计产品会在8月份上市。

    9157 人正在学习 去看看 CSDN讲师
知识要点
一、struct irq_chip、struct irq_desc[]、struct irqaction三者之间的关系
二、Linux内核中中断的初始化流程、中断的注册流程、中断的执行流程
三、多核cpu的中断亲和力和中断负载均衡
四、中断的上半部和下半部

一、struct irq_chip、struct irq_desc[]、struct irqaction三者之间的关
include /linux/irq.h
主要的三个数据结构
struct irq_chip :中断控制器描述符, CPU所对应的一个具体的中断控制器,如早期intel对应的中断控制器为8259a,ioapic_chip。 一个cpu可以有多个irq_chip,即多个中断控制器
struct irq_desc : 中断描述符数组,每一个IRQ对应自己的struct irq_desc对象,共同组成一个
struct irqaction : 中断服务程序描述符,该IRQ对应的一系列中断程序

  





      如图所示为该三个结构体关系,一个中断控制器(irq_chip)对应着一个中断描述符数组(irq_desc),每一个成员都是一个中断号,每一个中断号下面都有具体的中断服务程序(irqaction)链表

1、/*中断描述符*/ ------>一个IRQ对应自己的struct irq_desc对象,多个irq_desc组成irq_desc[ ]数组 
struct irq_desc {/*中断描述符*/

unsigned intirq; /* 该irq_desc具体的中断号 */

irq_flow_handler_thandle_irq;/*该irq线公共中断服务程序*/
struct irq_chip*chip;/*该中断线所属的中断控制器*/
struct msi_desc*msi_desc;
void*handler_data;
void*chip_data;
struct irqaction*action;/*该中断服务程序,区别于公共中断服务程序,这里指向的是中断服务程序的队列头*/
unsigned intstatus; /* 中断线状态*/
unsigned intdepth; /* nested irq disables */
unsigned intwake_depth; /* nested wake enables */
unsigned intirq_count; /* For detecting broken IRQs */    
unsigned longlast_unhandled; /* Aging timer for unhandled count */
unsigned intirqs_unhandled;
const char*name;

} ____cacheline_internodealigned_in_smp;


2、/*中断控制器描述符*/ --->CPU所对应的一个具体的中断控制器,如早期intel对应的中断控制器为8259a etc..
struct irq_desc {
struct irq_chip*chip;/*该中断线所属的中断控制器*/
}

struct irq_chip {
const char*name; /*中断控制器名称*/
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(*mask)(unsigned int irq);
void(*mask_ack)(unsigned int irq);
void(*unmask)(unsigned int irq);
void(*eoi)(unsigned int irq);

void(*end)(unsigned int irq);
int(*set_affinity)(unsigned int irq,
const struct cpumask *dest);
int(*retrigger)(unsigned int irq);
int(*set_type)(unsigned int irq, unsigned int flow_type);
int(*set_wake)(unsigned int irq, unsigned int on);

void(*bus_lock)(unsigned int irq);
void(*bus_sync_unlock)(unsigned int irq);

/* Currently used only by UML, might disappear one day.*/
#ifdef CONFIG_IRQ_RELEASE_METHOD
void(*release)(unsigned int irq, void *dev_id);
#endif
/*
* For compatibility, ->typename is copied into ->name.
* Will disappear.
*/
const char*typename;
};

3、/*中断服务程序描述符*/-------> 一个中断服务程序结构体声明
struct irq_desc {
struct irqaction*action;/*中断服务程序,区别于公共中断服务程序,这里指向的是中断服务程序的队列头*/
}
struct irqaction {
irq_handler_t handler;/*中断服务程序*/
unsigned long flags;/*IRQ 中断处理标志*/
const char *name;/*设备名*/
void *dev_id;
struct irqaction *next;/*指向该IRQ向中断请求队列下一个irqaction对象*/
int irq;
struct proc_dir_entry *dir;
irq_handler_t thread_fn;
struct task_struct *thread;
unsigned long thread_flags;
};

共享同一个IRQ先的多个irqaction对象组成的队列,即所谓的中断请求队列。当该IRQ线上产生中断时,请求队列中的中断服务程序将被依次执行

二、Linux内核中中断的初始化流程、中断的注册流程、中断的执行流程
1、中断子系统的初始化 内核代码linux 2.6.30.4
start_kernel
{
trap_init/* arm 为空函数 */
early_irq_init/* irq_desc[NR_IRQS]数组基本初始化 */
init_IRQ/* irq_desc[NR_IRQS] 中断描述符数组初始化 */
{
    for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;

  init_arch_irq();/* 在setup_arch中init_arch_irq = mdesc->init_irq;*/
--> s3c24xx_init_irq  以s3c24400为例
{
set_irq_chip(irqno, &s3c_irq_chip);/* 根据 irqno 关联对应的irq_desc[irqno]和irq_chip */
set_irq_handler(irqno, handle_edge_irq);/* 设置irq_dest[irqno]公共中断函数 */
set_irq_flags(irqno, IRQF_VALID);/* 设置irq_dest[irqno] flags */
set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);/* 设置irq_dest[irqno]公共中断函数 */
}
}
}
void __init setup_arch(char **cmdline_p)
{
mdesc = setup_machine(machine_arch_type);/*从machine_arch_type 段中获取 machine_desc结构体 */
init_arch_irq = mdesc->init_irq;/* 其中 mdesc结构体在具体的架构中定义 */
}

#define MACHINE_START(_type,_name) \---->  arch/arm/include/asm/mach/arch.h
static const struct machine_desc __mach_desc_##_type\
 __used \
 __attribute__((__section__(".arch.info.init"))) = {\
.nr = MACH_TYPE_##_type,\
.name= _name,
#define MACHINE_END\

};

MACHINE_START(S3C2440, "SMDK2440") -----> mach-smdk2440.c 
/* Maintainer: Ben Dooks <ben@fluff.org> */
.phys_io= S3C2410_PA_UART,
.io_pg_offst= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
.boot_params= S3C2410_SDRAM_PA + 0x100,

.init_irq= s3c24xx_init_irq,
.map_io= smdk2440_map_io,
.init_machine= smdk2440_machine_init,
.timer= &s3c24xx_timer,


2、中断服务的注册 内核代码linux 2.6.30.4
request_irq
-->request_threaded_irq
{
desc = irq_to_desc(irq);/* 根据irq号取出其对应的irq_desc */
__setup_irq(irq, desc, action);/* 将struct irqaction *action 添加到desc->action链表中*/
}

3、内核中中断的执行过程 内核代码linux 2.6.30.4
entry_armv.S 
.long__irq_usr@  0  (USR_26 / USR_32)用户模式下中断
.long__irq_invalid@  1  (FIQ_26 / FIQ_32)
.long__irq_invalid@  2  (IRQ_26 / IRQ_32)
.long__irq_svc@  3  (SVC_26 / SVC_32)内核模式下中断
.long__irq_invalid@  4
.long__irq_invalid@  5
.long__irq_invalid@  6
.long__irq_invalid@  7
.long__irq_invalid@  8
.long__irq_invalid@  9
.long__irq_invalid@  a
.long__irq_invalid@  b
.long__irq_invalid@  c
.long__irq_invalid@  d
.long__irq_invalid@  e
.long__irq_invalid@  f

__irq_svc: entry_armv.S 
irq_handler
ENDPROC(__irq_svc)

.macro irq_handlerentry_armv.S 
bne asm_do_IRQ

asm_do_IRQ ----> irq.c 
--->generic_handle_irq(irq);
----->generic_handle_irq_desc
------->__do_IRQ(irq);
{
struct irq_desc *desc = irq_to_desc(irq);
action_ret = handle_IRQ_event(irq, desc->action);/* 依次执行 irq对应的desc->action的handler服务例程 */
}

handle_IRQ_event(unsigned int irq, struct irqaction *action)/* 依次执行 irq对应的desc->action的handler服务例程 */
{
do {
ret = action->handler(irq, action->dev_id);
action = action->next;
} while (action);
}

又比如hisi芯片的IRQ执行流程

vector_stub irq, IRQ_MODE, 4 ----> entry-armv.S
.long__irq_usr@  0  (USR_26 / USR_32)
.long__irq_invalid@  1  (FIQ_26 / FIQ_32)
.long__irq_invalid@  2  (IRQ_26 / IRQ_32)
.long__irq_svc@  3  (SVC_26 / SVC_32)
.long__irq_invalid@  4
.long__irq_invalid@  5
.long__irq_invalid@  6
.long__irq_invalid@  7
.long__irq_invalid@  8
.long__irq_invalid@  9
.long__irq_invalid@  a
.long__irq_invalid@  b
.long__irq_invalid@  c
.long__irq_invalid@  d
.long__irq_invalid@  e
.long__irq_invalid@  f

__irq_svc: ----> entry-armv.S
irq_handler
ENDPROC(__irq_svc)


.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER      ----> entry-armv.S

ldr r1, =handle_arch_irq/* 在 init_IRQ中赋值, handle_arch_irq*/
mov r0, sp
adr lr, BSYM(9997f)
ldr pc, [r1]
#else
arch_irq_handler_default

#endif

==========handle_arch_irq赋值流程,handle_arch_irq=gic_handle_irq=============

void __init init_IRQ(void)
{
if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
irqchip_init();
else
machine_desc->init_irq();
}

hi3536_gic_init_irq
gic_init_bases
-->set_handle_irq(gic_handle_irq);
{
if (handle_arch_irq)
return;
handle_arch_irq = handle_irq;
}

MACHINE_START(HI3536, "hi3536") ----> arch/arm/mach-hi3536/core.c
.atag_offset  = 0x100,
.map_io       = hi3536_map_io,
.init_early   = hi3536_init_early,
.init_irq     = hi3536_gic_init_irq,
#ifdef CONFIG_HI3536_SYSCNT
.init_time    = arch_timer_init,
#else
.init_time    = hi3536_timer_init,
#endif
.init_machine = hi3536_init,
.smp          = smp_ops(hi3536_smp_ops),
.reserve      = hi3536_reserve,
.restart      = hi3536_restart,
MACHINE_END

====================================================
所以handle_arch_irq=gic_handle_irq

gic_handle_irq
--->handle_IRQ
----->generic_handle_irq
{
      struct irq_desc *desc = irq_to_desc(irq);
      generic_handle_irq_desc(irq, desc);
      {
desc->handle_irq(irq, desc);/*这里根据具体的handle_irq来执行,若irq<32,则使用 handle_percpu_devid_irq,否则使用handle_fasteoi_irq*/
 handle_fasteoi_irq/* 以irq大于32的handle_fasteoi_irq为例 */
{
handle_irq_event(desc);
--->handle_irq_event_percpu/* 依次执行irq_desc上action上的服务例程 */
{
do {
ret = action->handler(irq, action->dev_id);
action = action->next;
} while (action);
}
}
      }
}

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
if (hw < 32) /*中断号小于32 */
irq_set_chip_and_handler(irq, &gic_chip, handle_percpu_devid_irq);/*赋值desc->handle_irq*/
        else  /* 中断号大于32 */
irq_set_chip_and_handler(irq, &gic_chip,handle_fasteoi_irq);/*赋值desc->handle_irq*/

return 0;
}


三、多核cpu的中断亲和力和中断负载均衡
1、处理器间中断
   在多核cpu中,中断可以通过 处理器间中断(Inter-Processor Interrupt) 传递到其他核上.
2、中断亲和力和中断负载均衡
   利用中断亲和可以来做中断的负载均衡,将负载绑定到负载较轻的cpu上,更好的优化性能.


四、中断的上半部和下半部
linux中断上半部(top half) ------> 不可中断
上半部的功能是"登记中断",当一个中断发生时,它进行相应地硬件读写后并把中断例程的下半部挂到该设备的下半部执行 工作队列/tasklet中去
linux中断的下半部(botttom half) ----->  可中断
具体的下半部几种方式
几种下半部方式 : 
1、软中断
2、tasklet
3、工作队列

当前下半部看到使用使用工作队和tasklet比较多


参考资料

Linux 2.6.30.4源码

linux 3.10.y源码

《linux内核修炼之道》






2016-06-04 10:22:24 hash_ydg8ey8 阅读数 467
  • 安卓编译与开发、Linux内核及驱动

    安卓编译与开发、Linux内核及驱动视频教程,该课程内容包括一、源码编译:1、常见的ROM种类、谷歌的ROM、第三方的ROM2、区别3、RockChip的ROM、4、编译环境配置、源码下载、编译命令;二、源码开发:源码结构分析、Launcher开发、需求分析、系统级应用;三、内核讲解:内核用途、内核结构、内核职能、内核源码编译、驱动开发;四、内核开发:1、体系架构、目录结构、中断机制、内核进程控制、2、内核库文件、例子简单分析、3、内核调度流程4、内核组件工具 嘉宾介绍:仝利,英文名Burning,CSDN博客讲师,多年主要从事于流媒体,网络通讯,p2p等技术的开发与研究。目前在创业,产品是面向企业会议室和家庭客厅的多媒体通讯盒子的开发。国内还没有相关产品,预计产品会在8月份上市。

    9157 人正在学习 去看看 CSDN讲师

最近在阅读linux内核源码,把自己的一些理解发上来,一方面看到的朋友可以帮我指正我理解偏差的地方,别一方面也算是做一个简单的总结。

首先调用open_softirq()函数来初始化软件中断处理函数,将软件中断处理函数根据软中断的下标号插入到softirq_vec数组中,实现过程很简单如下:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}



softirq_vec数据有32个元素,对应的是可以有32个软件中断,但实际上linux只是使用了其中的6个软中断,相应的每一个CPU都会有一个对应的32位的掩码__softirq_pending描述挂起的软中断,每一位对应一个软件中断,__soctirq_penging在irq_cpustat_t中定义,如下:

@include/asm/hardirq.h

typedef struct {
	unsigned int	__softirq_pending;
	unsigned long	idle_timestamp;
	unsigned int	__nmi_count;	/* arch dependent */
	unsigned int	__irq_count;	/* arch dependent */
} ____cacheline_aligned irq_cpustat_t;

其中local_softirq_pending()宏用于选择当前CPU所对应的__softirq_penging掩码。相关的宏如下:
@include/linux/irq_cpustat.h

#define local_softirq_pending() \
__IRQ_STAT(smp_processor_id() , __softirq_pending)
 
#define __IRQ_STAT(cpu , member) (irq_stat[cpu].member)

接着调用raise_softirq()函数来激活软中断,函数如下:

@kernel/softirq.c

void raise_softirq(unsigned int nr)
{
	unsigned long flags;
	/* 将eflags寄存器的IF标志保存到flags,并且禁用了本地CPU上的中断 */
	local_irq_save(flags);
	raise_softirq_irqoff(nr);
	/* 根据flags变量的值恢复eflags寄存器的IF标志,重新打开本地CPU上的中断 */
	local_irq_restore(flags);
}

@kernel/softirq.craise_softirq_irqoff()函数必须是在禁止中断的情况下执行的,它首先调用__raise_softirq_irqoff()宏激活软件中断,其实也就是设置当前CPU所对应的__softirq_pending所对应的软中断的位,以表示该软中断已激活。如果当前正处于中断或者软中断当中,那么raise_softirq_irqoff执行结束,否则的话就调用wakeup_softirqd()函数激活ksoftirqd/n内核线程来处理软中断。

inline void raise_softirq_irqoff(unsigned int nr)
{
	__raise_softirq_irqoff(nr);
 
	if (!in_interrupt())
		wakeup_softirqd();
}


__rarse_softirq_irqoff()宏在/include/linux/interput.h中定义:

#defome __raise_softirq_irqoff(nr) \
	do{ or_softirq_pending(1UL << (nr)); } while(0)
 
#define or_softirq_pending(x) (local_softirq_pending() |= (x))


其中local_softirq_pending()宏已经在上面列出来了,我不太明白的是__raise_softirq_irqoff()这个宏为什么要放到一个循环里面,它也只是执行了一次,不过linux既然这样写就一定有它的道理,慢慢再去研究吧。

这样软中断已经激活,其处理函数已经加入到了softirq_sec数组中,并且相应的标志位已经合适地设置,系统会周期性地检查已经挂起的软中断,当在local_softirq_pending()中的某个位检测到挂起的软中断的时候,就会调用do_softirq()函数对软中断进行处理。

do_softirq()函数定义如下:
@ kernel/softirq.h


asmlinkage void do_softirq(void)
{
	__u32 pending;
	unsigned long flags;
 
	if (in_interrupt())
		return;
 
	local_irq_save(flags);
 
	pending = local_softirq_pending();
 
	if (pending)
		__do_softirq();
 
	local_irq_restore(flags);
}


如果在中断上下文中调用了do_softirq函数或者禁用了软件中断,那么in_interrupt函数返回1,这时候do_softirq()不做任何事情,否则,它会把eflags寄存器IF标志保存在flags中并禁用中断,然后将local_softirq_pending()的值保存在pending变量中,检测pending的值,如果pending不为0,则表示pending的某一位被设置,当前CPU就有对应的挂起的软中断需要进行处理,调用__do_softirq()对挂起的软中断进行处理,处理完成之后再根据flags变量的值恢复eflags寄存器并打开中断。

接下来看__do_softirq()函数:

asmlinkage void __do_softirq(void)
{
	struct softirq_action *h;
	__u32 pending;
	int max_restart = MAX_SOFTIRQ_RESTART;
	int cpu;
 
	pending = local_softirq_pending();
 
	/* 增加preempt_count字段中软中断计数器的值,
	 * 以此来禁用软中断,因为软件中断的可延迟函数
	 * 一般是在开中断的情况下执行的,这样就可能在__do_softirq 执行
	 * 的过程中有别一个__do_softirq的实例正在执行
	 * ,这样就会影响可延迟函数的串行化执行*/
	__local_bh_disable((unsigned long)__builtin_return_address(0));
 
restart:
	set_softirq_pending(0);
 
	local_irq_enable();
 
	h = softirq_vec;
 
	do {
		if (pending & 1) {
			int prev_count = preempt_count();
			/* 执行软件中断处理函数 */
			h->action(h);
			if (unlikely(prev_count != preempt_count())) {
				printk(KERN_ERR "huh, entered softirq %td %p"
				       "with preempt_count %08x,"
				       " exited with %08x?\n", h - softirq_vec,
				       h->action, prev_count, preempt_count());
				preempt_count() = prev_count;
			}
		}
		h++;
		pending >>= 1;
	} while (pending);
	/* 禁用本地软中断 */
	local_irq_disable();
 
	pending = local_softirq_pending();
	if (pending && --max_restart)
		goto restart;
 
	if (pending)
		wakeup_softirqd();
 
	_local_bh_enable();
}


内核循环地检测每一个pending的位,以处理当前位挂机的软件中断,为了防止内核在执行__do_softirq()的过程中不停地有新的softirq产生,以导致内核无暇顾及其它,__do_softirq()在循环检测的时候设置了外层循环的最大次数为10次,对pending的每一位检测10次之后如果pending的值仍不为0,则表示当前CPU仍有未处理的挂起的软件中断,这时候__do_softirq不再对它进行处理,而是唤醒内核线程ksoftirqd/n对它进行处理。
在函数开始的时候,先将软件中断的位图__softirq_pending保存在pending局部变量中,然后调用set_softirq_pending(0)以清空软中断位图,从而允许新的软中断的到来。接着调用local_irq_enable()来激活软中断。

2014-04-12 10:38:24 yusiguyuan 阅读数 2388
  • 安卓编译与开发、Linux内核及驱动

    安卓编译与开发、Linux内核及驱动视频教程,该课程内容包括一、源码编译:1、常见的ROM种类、谷歌的ROM、第三方的ROM2、区别3、RockChip的ROM、4、编译环境配置、源码下载、编译命令;二、源码开发:源码结构分析、Launcher开发、需求分析、系统级应用;三、内核讲解:内核用途、内核结构、内核职能、内核源码编译、驱动开发;四、内核开发:1、体系架构、目录结构、中断机制、内核进程控制、2、内核库文件、例子简单分析、3、内核调度流程4、内核组件工具 嘉宾介绍:仝利,英文名Burning,CSDN博客讲师,多年主要从事于流媒体,网络通讯,p2p等技术的开发与研究。目前在创业,产品是面向企业会议室和家庭客厅的多媒体通讯盒子的开发。国内还没有相关产品,预计产品会在8月份上市。

    9157 人正在学习 去看看 CSDN讲师

写在前面:这篇文章比较宽泛的写了关于中断的一些内容,包括中断的定义,中断的分类,计算机内部硬件产生中断的过程,以及中断的未来展望。但是并没有详细介绍中断处理过程。

什么是中断

Linux 内核需要对连接到计算机上的所有硬件设备进行管理,毫无疑问这是它的份内事。如果要管理这些设备,首先得和它们互相通信才行,一般有两种方案可实现这种功能:

  1. 轮询(polling 让内核定期对设备的状态进行查询,然后做出相应的处理;
  2. 中断(interrupt 让硬件在需要的时候向内核发出信号(变内核主动为硬件主动)。

第一种方案会让内核做不少的无用功,因为轮询总会周期性的重复执行,大量地耗用 CPU 时间,因此效率及其低下,所以一般都是采用第二种方案 。

        对于中断的理解我们先看一个生活中常见的例子:QQ。第一种情况:你正在工作,然后你的好友突然给你发送了一个窗口抖动,打断你正在进行的工作。第二种情况:当然你有时候也会每隔 5 分钟就去检查一下 QQ 看有没有好友找你,虽然这很浪费你的时间。在这里,一次窗口抖动就可以被相当于硬件的中断,而你就相当于 CPU,你的工作就是 CPU 这在执行的进程。而定时查询就被相当于 CPU 的轮询。在这里可以看到:同样作为 CPU 和硬件沟通的方式,中断是硬件主动的方式,较轮询(CPU 主动)更有效些,因为我们都不可能一直无聊到每隔几分钟就去查一遍好友列表。

        CPU 有大量的工作需要处理,更不会做这些大量无用功。当然这只是一般情况下。好了,这里又有了一个问题,每个硬件设备都中断,那么如何区分不同硬件呢?不同设备同时中断如何知道哪个中断是来自硬盘、哪个来自网卡呢?这个很容易,不是每个 QQ 号码都不相同吗?同样的,系统上的每个硬件设备都会被分配一个 IRQ 号,通过这个唯一的 IRQ 号就能区别张三和李四了。

       从物理学的角度看,中断是一种电信号,由硬件设备产生,并直接送入中断控制器(如 8259A)的输入引脚上,然后再由中断控制器向处理器发送相应的信号。处理器一经检测到该信号,便中断自己当前正在处理的工作,转而去处理中断。此后,处理器会通知 OS 已经产生中断。这样,OS 就可以对这个中断进行适当的处理。不同的设备对应的中断不同,而每个中断都通过一个唯一的数字标识,这些值通常被称为中断请求线。  

APIC vs 8259A

       X86计算机的 CPU 为中断只提供了两条外接引脚:NMI 和 INTR。其中 NMI 是不可屏蔽中断,它通常用于电源掉电和物理存储器奇偶校验;INTR是可屏蔽中断,可以通过设置中断屏蔽位来进行中断屏蔽,它主要用于接受外部硬件的中断信号,这些信号由中断控制器传递给 CPU。

常见的中断控制器有两种:

1. 可编程中断控制器8259A

传统的 PIC(Programmable Interrupt Controller)是由两片 8259A 风格的外部芯片以“级联”的方式连接在一起。每个芯片可处理多达 8 个不同的 IRQ。因为从 PIC 的 INT 输出线连接到主 PIC 的 IRQ2 引脚,所以可用 IRQ 线的个数达到 15 个,如图 1 所示。

图 1:8259A 级联原理图
8259A 级联原理图

2. 高级可编程中断控制器(APIC)

8259A 只适合单 CPU 的情况,为了充分挖掘 SMP 体系结构的并行性,能够把中断传递给系统中的每个 CPU 至关重要。基于此理由,Intel 引入了一种名为 I/O 高级可编程控制器的新组件,来替代老式的 8259A 可编程中断控制器。该组件包含两大组成部分:一是“本地 APIC”,主要负责传递中断信号到指定的处理器;举例来说,一台具有三个处理器的机器,则它必须相对的要有三个本地 APIC。另外一个重要的部分是 I/O APIC,主要是收集来自 I/O 装置的 Interrupt 信号且在当那些装置需要中断时发送信号到本地 APIC,系统中最多可拥有 8 个 I/O APIC。

每个本地 APIC 都有 32 位的寄存器,一个内部时钟,一个本地定时设备以及为本地中断保留的两条额外的 IRQ 线 LINT0 和 LINT1。所有本地 APIC 都连接到 I/O APIC,形成一个多级 APIC 系统,如图 2 所示。

图 2:多级I/O APIC系统
多级I/O APIC系统

目前大部分单处理器系统都包含一个 I/O APIC 芯片,可以通过以下两种方式来对这种芯片进行配置:

1) 作为一种标准的 8259A 工作方式。本地 APIC 被禁止,外部 I/O APIC 连接到 CPU,两条 LINT0 和 LINT1 分别连接到 INTR 和 NMI 引脚。

2) 作为一种标准外部 I/O APIC。本地 APIC 被激活,且所有的外部中断都通过 I/O APIC 接收。

辨别一个系统是否正在使用 I/O APIC,可以在命令行输入如下命令:

# cat /proc/interrupts
           CPU0       
  0:      90504    IO-APIC-edge  timer
  1:        131    IO-APIC-edge  i8042
  8:          4    IO-APIC-edge  rtc
  9:          0    IO-APIC-level  acpi
 12:        111    IO-APIC-edge  i8042
 14:       1862    IO-APIC-edge  ide0
 15:         28    IO-APIC-edge  ide1
177:          9    IO-APIC-level  eth0
185:          0    IO-APIC-level  via82cxxx
...

如果输出结果中列出了 IO-APIC,说明您的系统正在使用 APIC。如果看到 XT-PIC,意味着您的系统正在使用 8259A 芯片。

在CPU中集成了APIC,在SMP上,主板上有一个(至少一个,有的主板有多个IO-APIC,用来更好的分发中断信号)全局的APIC,它负责从外设接收中断信号,再分发到CPU上,这个全局的APIC被称作IO-APIC。还有一部分是“本地 APIC”,主要负责传递中断信号到指定的处理器;举例来说,一台具有三个处理器的机器,则它必须相对的要有三个本地 APIC。

中断分类

       中断,广义的来说通常被定义为一个事件,该事件触发改变处理器执行指令的顺序。狭义地来说,针对80x86体系,中断被分为中断和异常,又叫同步中断和异步中断。注意广义的中断和狭义的中断千万不要混淆,以后我的博文中所有所谓的“中断”二字,就是指狭义的中断,即Linux处理80x86异步中断的细节。我们首先必须好好理清一下80x86体系中,

中断可分为同步(synchronous)中断和异步(asynchronous)中断:

1. 同步中断是当指令执行时由 CPU 控制单元产生,之所以称为同步,是因为只有在一条指令执行完毕后 CPU 才会发出中断,而不是发生在代码指令执行期间,比如系统调用。

2. 异步中断是指由其他硬件设备依照 CPU 时钟信号随机产生,即意味着中断能够在指令之间发生,例如键盘中断。

PS:总结为一句话:中断时由硬件产生的异步中断,而异常则是处理器产生的同步中断

根据 Intel 官方资料,同步中断称为异常(exception),异步中断被称为中断(interrupt)。

中断可分为可屏蔽中断(Maskable interrupt)和非屏蔽中断(Nomaskable interrupt)。异常可分为故障(fault)、陷阱(trap)、终止(abort)三类。

从广义上讲,中断可分为四类:中断故障陷阱终止。这些类别之间的异同点请参看 表 1。

表 1:中断类别及其行为
类别 原因 异步/同步 返回行为
中断 来自I/O设备的信号 异步 总是返回到下一条指令
陷阱 有意的异常 同步 总是返回到下一条指令
故障 潜在可恢复的错误 同步 返回到当前指令
终止 不可恢复的错误 同步 不会返回

X86 体系结构的每个中断都被赋予一个唯一的编号或者向量(8 位无符号整数)。非屏蔽中断和异常向量是固定的,而可屏蔽中断向量可以通过对中断控制器的编程来改变。

中断:
1.      可屏蔽中断:当中断被屏蔽,则CPU控制单元就忽略它。这里提一下,所有的IRQ中断都是可屏蔽中断。
2.      非可屏蔽中断:总由CPU辨认并处理。所以,其为非常紧急的硬件故障。

向量中断——由硬件提供中断服务程序入口地址,当中断为向量中断时,直接跳转到预先提供的中断服务程序执行,这种处理方式响应速度快
非向量中断——由软件件提供中断服务程序入口地址。当中断为非向量中断时,无论是什么外部中断源发出的中断,cpu将跳到指定的一段程序执行(称为中断解析程序),在解析程序里,通过判断相应的中断状态寄存器找到对应的中断源,跳转到相应的中断执行程序。有点类似软件中断的处理方式,但是软中断(SWI)与非向量中断不同,它的入口是0x0000,0008。进入软中断后,系统变为管理模式。而非向量中断入口是0x0000,0018。它引导系统进入fiq/irq模式。这种处理方式简单,但是要通过软件查询来判断具体的中断服务程序,所有延迟时间较长.

         向量中断模式用于RESET、NMI、异常处理。当向量中断产生时,控制器直接将PC赋值,如跳到0x0000000d处,而在0x0000000d地址处通常放置ISR服务程序地址LDR PC, =ISR_HANDLER。
        非向量中断模式,有一个寄存器标识位,跳转到统一的函数地址,此函数通过判别寄存器标识位和优先级关系进行中断处理。向量中断模式是当CPU读取位于0x18处的IRQ中断指令的时候,系统自动读取对应于该中断源确定地址上的指令取代0x18处的指令,通过跳转指令系统就直接跳转到对应地址函数中,节省了中断处理时间提高了中断处理速度。例如 ADC 中断的向量地址为0xC0,则在0xC0处放如下代码:ldr PC,=HandlerADC 当ADC中断产生的时候系统会自动跳转到HandlerADC函数中处理中断。
      非向量中断模式处理方式是一种传统的中断处理方法,当系统产生中断的时候,系统将INTPND寄存器中对应标志位置位,然后跳转到位于0x18处的统一中断函数中;该函数通过读取INTPND寄存器中对应标志位来判断中断源,并根据优先级关系再跳到对应中断源的处理代码中处理中断。

Linux 2.6 中断处理原理简介

中断描述符表(Interrupt Descriptor Table,IDT)是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中存放的是相应的中断或异常处理程序的入口地址。内核在允许中断发生前,也就是在系统初始化时,必须把 IDT 表的初始化地址装载到 idtr 寄存器中,初始化表中的每一项。

当处于实模式下时,IDT 被初始化并由 BIOS 程序所使用。然而,一旦 Linux 开始接管,IDT 就被移到 ARM 的另一个区域,并进行第二次初始化,因为 Linux 不使用任何 BIOS 程序,而使用自己专门的中断服务程序(例程)(interrupt service routine,ISR)。中断和异常处理程序很像常规的 C 函数

有三个主要的数据结构包含了与 IRQ 相关的所有信息:hw_interrupt_typeirq_desc_tirqaction,图3 解释了它们之间是如何关联的。

图 3:IRQ 结构之间的关系
IRQ结构之间的关系

在 X86 系统中,对于 8259A 和 I/O APIC 这两种不同类型的中断控制器,hw_interrupt_type 结构体被赋予不同的值,


 
 

在中断初始化阶段,调用 hw_interrupt_type 类型的变量初始化 irq_desc_t 结构中的handle 成员。在早期的系统中使用级联的8259A,所以将用i8259A_irq_type 来进行初始化,而对于SMP系统来说,要么以ioapic_edge_type,或以ioapic_level_type 来初始化handle 变量。

对于每一个外设,要么以静态(声明为 static 类型的全局变量)或动态(调用 request_irq 函数)的方式向 Linux 内核注册中断处理程序。不管以何种方式注册,都会声明或分配一块irqaction 结构(其中handler 指向中断服务程序),然后调用setup_irq() 函数,将irq_desc_tirqaction 联系起来。

当中断发生时,通过中断描述符表 IDT 获取中断服务程序入口地址,对于 32≤ i ≤255(i≠128) 之间的中断向量,将会执行push $i-256,jmp common_interrupt 指令。随之将调用do_IRQ() 函数,以中断向量为irq_desc[] 结构的下标,获取action 的指针,然后调用handler 所指向的中断服务程序。

从以上描述,我们不难看出整个中断的流程,如图 4 所示:

图 4:X86中断流
X86中断流

 

中断线程化

中断线程化是实现Linux实时性的一个重要步骤,在Linux标准内核中,中断时最高优先级的执行单元,不管内核代码当时处理什么,只要有中断事件,系统将立即响应改事件并执行相应的中断处理代码,除非当时中断禁用。因此,如果系统有严重的网络或I/O负载,中断将非常频繁,实时任务将很难有机会运行,也就是说毫无实时性可言。

  • 应用背景

Linux实时性的要求,中断线程化之后,中断将作为内核线程运行而且赋予不同的实时优先级,实时任务可以有比中断线程更高的优先级,这样,实时任务就可以作为最高优先级的执行单元来运行,即使在严重负载下仍有实时性保证。

中断线程化的另一个重要原因是spinlock被mutex取代。中断处理代码中大量地使用了spinlock,当spinlock被mutex取代之后,中断处理代码就有可能因为得不到锁而需要被挂到等待队列上,但是只有可调度的进程才可以这么做,如果中断处理代码仍然使用原来的spinlock,则spinlock取代mutex的努力将大打折扣,因此为了满足这一要求,中断必须被线程化,包括IRQ和softirq。

  • 实现原理

图8

现在的线程化是作为内核R-T Patch实现的,并不是主流,是一个实时补丁。我们简单看一下其整个实现流程。[4][5]

中断初始化:对于向量表的初始化在系统引导时就已经开始了。通过调用setupidt将向量表中的每一项都初始化为默认的中断服务例程ignoreint。这个默认的例程只是打印中断向量号,因此需要进一步初始化;在调用startkernel()函数进行内核初始化时,将调用initIRQ()函数对中断进行第二次初始化,将IRQi对应的IDT表项设成interrupti,这部分已经运行于保护模式下。在startkernel结尾处调用我们的线程初始化函数inithardirqs,该函数为每个IRQ创建一个内核线程。最高实时优先级为50,依次类推直到25,因此任何IRQ 线程的最低实时优先级为25。

中断处理过程:如果某个中断号状态位中的 IRQNODELAY 被置位,那么该中断不能被线程化。在中断处理阶段,两者之间的异同点主要体现在:两者相同的部分是当发生中断时,CPU 将调用 doIRQ() 函数来处理相应的中断,doIRQ() 在做了必要的相关处理之后调用 _doIRQ()。两者最大的不同点体现在doIRQ() 函数中,在该函数中,将判断该中断是否已经被线程化(如果中断描述符的状态字段不包含 IRQNODELAY 标志,则说明该中断被线程化了),对于没有线程化的中断,将直接调用 handleIRQ_event() 函数来处理。

对于已经线程化的情况,调用 wakeupprocess() 函数唤醒中断处理线程,并开始运行,内核线程将调用 dohardirq() 来处理相应的中断,该函数将判断是否有中断需要被处理,如果有就调用 handleIRQevent() 来处理。handleIRQ_event() 将直接调用相应的中断处理函数来完成中断处理。

  • 线程化之后,我们还需要思考的问题

一旦中断线程化之后,中断将会运行在进程上下文中,而之前中断上下文所有的约束在这里将不复存在。我们也可以动态地对线程化的中断设置优先等级。那么,对于这种中断世界“革命性”的改变,我们又该有哪些思考:

还需要下半部吗?

中断线程化后,中断可以运行与进程上下文,没有了中断上下文的束缚。那么我们还需要下半部吗,因为在下半部能做的事情,现在都可以通过线程化中断实现了。作为我个人来看,并不认为这时候就可以草率的淘汰下半部。首先,中断线程化还需完善,还需标准化。现在只是作为一个补丁提供在内核中,还需考验。其次,中断线程化并不是万能的,我们很多事情是中断线程化不能实现的,后面会谈到。再者,下半部作为辅助中断处理程序来完成推后执行的工作,在一些场合下仍然具有不可替代的作用。

  • 和CPU亲和力相结合

同中断亲和力一样,CPU也有亲和力,所谓CPU亲和力就是通过设置 CPU 亲和力(CPU affinity),将一个或多个进程绑定到一个或多个处理器上运行。一旦中断线程化后,中断就可以用进程(或线程)的观点来看待,这就为我们把中断线程化和CPU亲和力相结合提供可能。对于线程化的中断,我们可以通过设置CPU亲和力来限制线程迁移和优化处理器Cache使用率。当然可能的应用还有很多,这也是研究的方向。线程化不一定永远好

并不是所有的中断都可以被线程化,比如时钟中断,主要用来维护系统时间以及定时器等,其中定时器是操作系统的脉搏,一旦被线程化,就有可能被挂起,这样后果将不堪设想,所以不应当被线程化。类似的还有串行端口等。所以即使现在我们能够线程化我们的中断并不意味着我们都应该线程化我们的中断。



2018-08-23 23:09:24 weixin_42092278 阅读数 7147
  • 安卓编译与开发、Linux内核及驱动

    安卓编译与开发、Linux内核及驱动视频教程,该课程内容包括一、源码编译:1、常见的ROM种类、谷歌的ROM、第三方的ROM2、区别3、RockChip的ROM、4、编译环境配置、源码下载、编译命令;二、源码开发:源码结构分析、Launcher开发、需求分析、系统级应用;三、内核讲解:内核用途、内核结构、内核职能、内核源码编译、驱动开发;四、内核开发:1、体系架构、目录结构、中断机制、内核进程控制、2、内核库文件、例子简单分析、3、内核调度流程4、内核组件工具 嘉宾介绍:仝利,英文名Burning,CSDN博客讲师,多年主要从事于流媒体,网络通讯,p2p等技术的开发与研究。目前在创业,产品是面向企业会议室和家庭客厅的多媒体通讯盒子的开发。国内还没有相关产品,预计产品会在8月份上市。

    9157 人正在学习 去看看 CSDN讲师
日期 内核版本 架构 作者 内容
2018-8-23 Linux-2.6.32

X86

Bystander Linux内核中断
  • 一、中断概述

中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。

1.1中断类型

同步中断由CPU本身产生,又称为内部中断。这里同步是指中断请求信号与代码指令之间的同步执行,在一条指令执行完毕后,CPU才能进行中断,不能在执行期间。所以也称为异常(exception)。

异步中断是由外部硬件设备产生,又称为外部中断,与同步中断相反,异步中断可在任何时间产生,包括指令执行期间,所以也被称为中断(interrupt)。

异常又可分为可屏蔽中断(Maskable interrupt)和非屏蔽中断(Nomaskable interrupt)。而中断可分为故障(fault)、陷阱(trap)、终止(abort)三类。

从广义上讲,中断又可分为四类:中断故障陷阱终止。这些类别之间的异同点请参考 表 1。

表 1:中断类别及其行为

类别

原因

异步/同步

返回行为

中断

来自I/O设备的信号

异步

总是返回到下一条指令

陷阱

有意的异常

同步

总是返回到下一条指令

故障

潜在可恢复的错误

同步

返回到当前指令

终止

不可恢复的错误

同步

不会返回

 

有些参考资料中按照中断来源进行分类,如下图所示(个人建议不采用这种方式):

                                                                     图1-1

1.2区分中断号与中断向

I/O设备把中断信号发送给中断控制器(8259A)时与之相关联的是一个中断号,当中断控制器把中断信号发送给CPU时与之关联的是一个中断向量。换个角度分析就是中断号是从中断控制器层面划分,中断向量是从CPU层面划分,所以中断号与中断向量之间存在一对一映射关系。在Intel X86中最大支持256种中断,从0到255开始编号,这个8位的编号就是中断向量。其中将0到31保留用于异常处理和不可屏蔽中断。

  • 二、中断数据处理结构

Linux内核中处理中断主要有三个数据结构,irq_desc,irq_chip和irqaction。

在\include\linux\ irq.h中定义了

1)irq_desc用于描述IRQ线的属性与状态,被称为中断描述符。

/**

 * struct irq_desc - interrupt descriptor

 * @irq:           interrupt number for this descriptor

 * @timer_rand_state:    pointer to timer rand state struct

 * @kstat_irqs:        irq stats per cpu

 * @irq_2_iommu:  iommu with this irq

 * @handle_irq:             highlevel irq-events handler [if NULL, __do_IRQ()]

 * @chip:         low level interrupt hardware access

 * @msi_desc:        MSI descriptor

 * @handler_data:  per-IRQ data for the irq_chip methods

 * @chip_data:        platform-specific per-chip private data for the chip

 *                    methods, to allow shared chip implementations

 * @action:             the irq action chain

 * @status:             status information

 * @depth:             disable-depth, for nested irq_disable() calls

 * @wake_depth:           enable depth, for multiple set_irq_wake() callers

 * @irq_count:        stats field to detect stalled irqs

 * @last_unhandled:      aging timer for unhandled count

 * @irqs_unhandled:      stats field for spurious unhandled interrupts

 * @lock:         locking for SMP

 * @affinity:            IRQ affinity on SMP

 * @node:       node index useful for balancing

 * @pending_mask: pending rebalanced interrupts

 * @threads_active: number of irqaction threads currently running

 * @wait_for_threads:    wait queue for sync_irq to wait for threaded handlers

 * @dir:           /proc/irq/ procfs entry

 * @name:             flow handler name for /proc/interrupts output

 */

struct irq_desc{

  unsigned int          irq;

  struct timer_rand_state *timer_rand_state;

  unsigned int            *kstat_irqs;

#ifdef CONFIG_INTR_REMAP

  struct irq_2_iommu      *irq_2_iommu;

#endif

  irq_flow_handler_t handle_irq;

  struct irq_chip              *chip;

  struct msi_desc            *msi_desc;

  void               *handler_data;

  void               *chip_data;

  struct irqaction      *action;   /* IRQ action list */

  unsigned int          status;            /* IRQ status */


  unsigned int          depth;            /* nested irq disables */

  unsigned int          wake_depth;  /* nested wake enables */

  unsigned int          irq_count;      /* For detecting broken IRQs */

  unsigned long              last_unhandled;     /* Aging timer for unhandled count */

  unsigned int          irqs_unhandled;

  spinlock_t             lock;

#ifdef CONFIG_SMP

  cpumask_var_t             affinity;

  unsigned int          node;

#ifdef CONFIG_GENERIC_PENDING_IRQ

  cpumask_var_t             pending_mask;

#endif

#endif

  atomic_t         threads_active;

  wait_queue_head_t       wait_for_threads;

#ifdef CONFIG_PROC_FS

  struct proc_dir_entry    *dir;

#endif

  const char             *name;

}

2)irq_chip用于描述不同类型的中断控制器。

/**

 * struct irq_chip - hardware interrupt chip descriptor

 *

 * @name:             name for /proc/interrupts

 * @startup:           start up the interrupt (defaults to ->enable if NULL)

 * @shutdown:              shut down the interrupt (defaults to ->disable if NULL)

 * @enable:            enable the interrupt (defaults to chip->unmask if NULL)

 * @disable:           disable the interrupt (defaults to chip->mask if NULL)

 * @ack:          start of a new interrupt

 * @mask:              mask an interrupt source

 * @mask_ack:       ack and mask an interrupt source

 * @unmask:          unmask an interrupt source

 * @eoi:          end of interrupt - chip level

 * @end:         end of interrupt - flow level

 * @set_affinity:      set the CPU affinity on SMP machines

 * @retrigger:         resend an IRQ to the CPU

 * @set_type:          set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ

 * @set_wake:        enable/disable power-management wake-on of an IRQ

 *

 * @bus_lock:         function to lock access to slow bus (i2c) chips

 * @bus_sync_unlock:    function to sync and unlock slow bus (i2c) chips

 *

 * @release:           release function solely used by UML

 * @typename:              obsoleted by name, kept as migration helper

 */

struct irq_chip {

  const char      *name;

  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        (*mask)(unsigned int irq);

  void        (*mask_ack)(unsigned int irq);

  void        (*unmask)(unsigned int irq);

  void        (*eoi)(unsigned int irq);


  void        (*end)(unsigned int irq);

  int          (*set_affinity)(unsigned int irq,

                              const struct cpumask *dest);

  int          (*retrigger)(unsigned int irq);

  int          (*set_type)(unsigned int irq, unsigned int flow_type);

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


  void        (*bus_lock)(unsigned int irq);

  void        (*bus_sync_unlock)(unsigned int irq);


  /* Currently used only by UML, might disappear one day.*/

#ifdef CONFIG_IRQ_RELEASE_METHOD

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

#endif

  /*

   * For compatibility, ->typename is copied into ->name.

   * Will disappear.

   */

  const char      *typename;

}

在\include\linux\ interrupt.h中定义了 irqaction用来描述特定设备所产生的中断描述符。

/**

 * struct irqaction - per interrupt action descriptor

 * @handler:   interrupt handler function

 * @flags: flags (see IRQF_* above)

 * @name:      name of the device

 * @dev_id:     cookie to identify the device

 * @next:  pointer to the next irqaction for shared interrupts

 * @irq:    interrupt number

 * @dir:    pointer to the proc/irq/NN/name entry

 * @thread_fn: interupt handler function for threaded interrupts

 * @thread:     thread pointer for threaded interrupts

 * @thread_flags:    flags related to @thread

 */

struct irqaction {

  irq_handler_t handler;

  unsigned long flags;

  const char *name;

  void *dev_id;

  struct irqaction *next;

  int irq;

  struct proc_dir_entry *dir;

  irq_handler_t thread_fn;

  struct task_struct *thread;

  unsigned long thread_flags;

};
  • 三、Linux中断机制

Linux中断机制由三部分组成:

  1. 中断子系统初始化:内核自身初始化过程中对中断处理机制初始化,例如中断的数据结构以及中断请求等。
  2. 中断或异常处理:中断整体处理过程。
  3. 中断API:为设备驱动提供API,例如注册,释放和激活等。

3.1中断子系统初始化

3.1.1中断描述符表(IDT)初始化

中断描述符表初始化需要经过两个过程:

  1. 第一个过程在内核引导过程。由两个步骤组成,首先给分配IDT分配2KB空间(256中断向量,每个向量由8bit组成)并初始化;然后把IDT起始地址存储到IDTR寄存器中。
  2. 第二个过程内核在初始化自身的start_kernal函数中使用trap_init初始化系统保留中断向量,使用init_IRQ完成其余中断向量初始化。

3.1.2中断请求队列初始化

init_IRQ调用pre_intr_init_hook,进而最终调用init_ISA_irqs初始化中断控制器以及每个IRQ线的中断请求队列。

3.2中断或异常处理

中断处理过程:设备产生中断,并通过中断线将中断信号送往中断控制器,如果中断没有被屏蔽则会到达CPU的INTR引脚,CPU立即停止当前工作,根据获得中断向量号从IDT中找出门描述符,并执行相关中断程序。

异常处理过程:异常是由CPU内部发生所以不会通过中断控制器,CPU直接根据中断向量号从IDT中找出门描述符,并执行相关中断程序。

                                                                                      图3-1

中断控制器处理主要有5个步骤:1.中断请求 2.中断相应 3.优先级比较 4.提交中断向量 5.中断结束。这里不再赘述5个步骤的具体流程。

CPU处理流程主要有6个步骤:1.确定中断或异常的中断向量 2.通过IDTR寄存器找到IDT 3.特权检查 4.特权级发生变化,进行堆栈切换 5.如果是异常将异常代码压入堆栈,如果是中断则关闭可屏蔽中断 6.进入中断或异常服务程序执行。这里不再赘述6个步骤的具体流程。

3.3中断API

内核提供的API主要用于驱动的开发。

注册IRQ:

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);

释放IRQ:

void free_irq(unsigned int, void *);

注:IRQ线资源非常宝贵,我们在使用时必须先注册,不使用时必须释放IRQ资源。

激活当前CPU中断:

local_irq_enable();

禁止当前CPU中断:

local_irq_disable();

激活指定中断线:

void enable_irq(unsigned int irq);

禁止指定中断线:

void disable_irq(unsigned int irq);

禁止指定中断线:

void disable_irq_nosync(unsigned int irq);

注:此函数调用irq_chip中disable禁止指定中断线,所以不会保证中断线上执行的中断服务程序已经退出。

3.4中断机制划分

 由于中断会打断内核中进程的正常调度运行,所以要求中断服务程序尽可能的短小精悍;但是在实际系统中,当中断到来时,要完成工作往往进行大量的耗时处理。因此期望让中断处理程序运行得快,并想让它完成的工作量多,这两个目标相互制约,诞生——顶/底半部机制。

  中断处理程序是顶半部——接受中断,它就立即开始执行,但只有做严格时限的工作。能够被允许稍后完成的工作会推迟到底半部去,此后,在合适的时机,底半部会被开终端执行。顶半部简单快速,执行时禁止一些或者全部中断。

 底半部稍后执行,而且执行期间可以响应所有的中断。这种设计可以使系统处于中断屏蔽状态的时间尽可能的短,以此来提高系统的响应能力。顶半部只有中断处理程序机制,而底半部的实现有软中断,tasklet和工作队列实现。

                                             图3-2 注:登记中断,将底半部处理程序挂到该设备的低半部执行队列中。

3.4.1顶/底半部划分原则:

 1) 如果一个任务对时间非常敏感,将其放在顶半部中执行;

 2) 如果一个任务和硬件有关,将其放在顶半部中执行;

 3) 如果一个任务要保证不被其他中断打断,将其放在顶半部中执行;

 4) 其他所有任务,考虑放置在底半部执行。

 

3.4.2底半部实现机制

                                                                                            图3-3

软中断:

软中断作为下半部机制的代表,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,软中断执行中断处理程序留给它去完成的剩余任务,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:

a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。

b)可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。

内核中定义了几种软中断的用途:

enum

{
   HI_SOFTIRQ=0,

   TIMER_SOFTIRQ,

   NET_TX_SOFTIRQ,

   NET_RX_SOFTIRQ,

   BLOCK_SOFTIRQ,

   BLOCK_IOPOLL_SOFTIRQ,

   TASKLET_SOFTIRQ,

   SCHED_SOFTIRQ,

   HRTIMER_SOFTIRQ,

   RCU_SOFTIRQ,  /* Preferable RCU should always be the last softirq */

   NR_SOFTIRQS

};

 

Tasklet

  tasklet是通过软中断实现的,所以它本身也是软中断。

  软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。

  为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。

  Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。Tasklet作为一种新机制,显然可以承担更多的优点。正好这时候SMP越来越火了,因此又在tasklet中加入了SMP机制,保证同种中断只能在一个cpu上执行。在软中断时代,显然没有这种考虑。因此同一种软中断可以在两个cpu上同时执行,很可能造成冲突。

  总结下tasklet的优点:

  (1)无类型数量限制;

  (2)效率高,无需循环查表;

  (3)支持SMP机制;

  它的特性如下:

  1)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。

  2)多个不同类型的tasklet可以并行在多个CPU上。

3)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。

工作队列:

上面我们介绍的可延迟函数运行在中断上下文中,于是导致了一些问题,说明它们不可挂起,也就是说软中断不能睡眠、不能阻塞,原因是由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列,它能够在不同的进程间切换,以完成不同的工作。

工作队列能运行在进程上下文,它将工作给一个内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。我们用结构体workqueue_struct表示工作者线程,工作者线程是用内核线程实现的。而工作者线程是如何执行被推后的工作——有这样一个链表,它由结构体work_struct组成,而这个work_struct则描述了一个工作,一旦这个工作被执行完,相应的work_struct对象就从链表上移去,当链表上不再有对象时,工作者线程就会继续休眠。因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。

如何选择下半部机制:

  1. 软中断和tasklet运行在中断上下文,工作队列运行在进程上下文。如果需要休眠则选择工作队列,否则选择tasklet;如果对性能要求较高则选择软中断。
  2. 从易用性考虑,首选工作队列,然后tasklet,最后是软中断,因为软中断需要静态创建。
  3. 从代码安全考虑,如果对底半部代码保护不够安全,则选择tasklet,因为相对软中断,tasklet对锁要求低,上面也简述它们工作方式以及运用场景。

四、多处理器系统中断相关概念

4.1处理器间中断

 在多处理器系统中,操作系统需要在多个处理器中协调操作,所以需要处理器中断(Inter-Processor-Interrupt,IPI)实现,IPI是一种特殊硬件中断,由CPU送出,其他CPU接收,处理CPU之间通信和同步操作。以下是x86中SMP定义的IPI,中断向量号用十六进制表示:

SPURIOUS_APIC_VECTOR		0xff
ERROR_APIC_VECTOR		0xfe
RESCHEDULE_VECTOR		0xfd
CALL_FUNCTION_VECTOR		0xfc
CALL_FUNCTION_SINGLE_VECTOR	0xfb
THERMAL_APIC_VECTOR		0xfa
THRESHOLD_APIC_VECTOR		0xf9
REBOOT_VECTOR			0xf8
INVALIDATE_TLB_VECTOR_END	0xf7
INVALIDATE_TLB_VECTOR_START	0xf0

 

4.2中断亲和力

     将一个或多个中断服务程序绑定到特定的CPU上处理,这就是中断亲和力(SMP IRQ affinity)。我们可以使用中断亲和力来均衡各个CPU的负载,提高系统处理能力。

以上便是本人对Linux中断的理解,若有纰漏欢迎指正!

2014-04-14 19:24:44 yusiguyuan 阅读数 2960
  • 安卓编译与开发、Linux内核及驱动

    安卓编译与开发、Linux内核及驱动视频教程,该课程内容包括一、源码编译:1、常见的ROM种类、谷歌的ROM、第三方的ROM2、区别3、RockChip的ROM、4、编译环境配置、源码下载、编译命令;二、源码开发:源码结构分析、Launcher开发、需求分析、系统级应用;三、内核讲解:内核用途、内核结构、内核职能、内核源码编译、驱动开发;四、内核开发:1、体系架构、目录结构、中断机制、内核进程控制、2、内核库文件、例子简单分析、3、内核调度流程4、内核组件工具 嘉宾介绍:仝利,英文名Burning,CSDN博客讲师,多年主要从事于流媒体,网络通讯,p2p等技术的开发与研究。目前在创业,产品是面向企业会议室和家庭客厅的多媒体通讯盒子的开发。国内还没有相关产品,预计产品会在8月份上市。

    9157 人正在学习 去看看 CSDN讲师

Linux中断内核编程

前言

在前面分析了中断的基本原理后,就可以写一个内核中断程序来体验以下,也可以借此程序继续深入来了解内核中断的执行过程

一.内核中断程序

我们还是来看一看成程序:

在看程序之前,要熟悉如何进行模块编程,和了解module_pararm()的用法。如果不熟悉的话请大家看,module_param()的学习和Linux内核模块编程,在此不作解释。

1.程序interrupt.c

  1. /* 
  2.  2 *file name :interrupt.c 
  3.  3 *atuthor   : john  
  4.  4 */  
  5.  5 #include<linux/init.h>  
  6.  6 #include<linux/module.h>  
  7.  7 #include<linux/kernel.h>  
  8.  8 #include<linux/interrupt.h>  
  9.  9   
  10. 10 MODULE_LICENSE("GPL");  
  11. 11 static int irq;  
  12. 12 char *interface;  
  13. 13 static irqreturn_t myirq_handler(int irq,void *dev);  
  14. 14   
  15. 15 static int __init myirq_init(void)  
  16. 16 {  
  17. 17         printk("the module is working!/n");  
  18. 18         printk("the irq is ready for working!/n");  
  19. 19         if(request_irq(irq,myirq_handler,IRQF_SHARED,interface,&irq)){  
  20. 20         printk(KERN_ERR "%s interrrupt can't register %d IRQ /n",interface,irq);  
  21. 21         return -EIO;  
  22. 22         }  
  23. 23         printk("%s request %d IRQ/n",interface,irq);  
  24. 24         return 0;  
  25. 25 }  
  26. 26 static irqreturn_t myirq_handler(int irq,void *dev)  
  27. 27 {  
  28. 28         printk("%d IRQ is working/n",irq);  
  29. 29         return IRQ_NONE;  
  30. 30 }  
  31. 31 static void  __exit myirq_exit(void)  
  32. 32 {  
  33. 33         printk("the module is leaving!/n");  
  34. 34         printk("the irq is bye bye!/n");  
  35. 35         free_irq(irq,&irq);  
  36. 36         printk("%s interrupt free %d IRQ/n",interface,irq);  
  37. 37   
  38. 38 }  
  39. 39 module_init(myirq_init);  
  40. 0 module_exit(myirq_exit);  
  41. 41 module_param(interface,charp,0644);  
  42. 42 module_param(irq,int,0644);  
  43. 43   
 

2.Makefile的编写

  1.  1 obj-m:=tiger.o  
  2.  2   
  3.  3 CURRENT_PATH:=$(shell pwd)  
  4.  4 VERSION_NUM:=$(shell uname -r)  
  5.  5 LINUX_PATH:=/usr/src/linux-headers-$(VERSION_NUM)  
  6.  6   
  7.  7   
  8.  8 all :  
  9.  9         make -C $(LINUX_PATH) M=$(CURRENT_PATH) modules  
  10. 10 clean:  
  11. 11         make -C $(LINUX_PATH) M=$(CURRENT_PATH) clean  
 

(程序的调试,加载和运行,在此不进行说明)

3.首先我们来分析下内核加载模块

在内核加载模块中最重要的的action就是注册中断处理程序。很明显,这一动作是通过request_irq()函数来完成的。

int request_irq(unsigned int irq,  irq_handler_t handler,unsigned long flags, const char *devname, void *dev_id)

A.先来分析形参:

第一个参数irq: 表示要分配的中断号。对于一些设备(系统时钟或键盘)它的值是预先固定的,而对于大多数设备来说,这个值是动态确定的。

第二个参数handler:表示要挂入到中断请求对列中的中断服务例程,这个中断服务函数的原型是static irqreturn_t handler(int , void *);

中断处理程序的前缀为static,因为它从来不会被别的文件中的代码直接调用。

第三个参数flags:为标志位。可以取IRQF_DISABLED、IRQF_SHARED和IRQF_SAMPLE_RANDOM之一。在本实例程序中取 IRQF_SHARED,该标志表示多个中断处理程序共享irq中断线。一般某个中断线上的中断服务程序在执行时会屏蔽请求该线的其他中断,如果取 IRQF_DISABLED标志,则在执行该中断服务程序时会屏蔽所有其他的中断。取IRQF_SAMPLE_RANDOM则表示设备可以被看做是事件随见的发生源。

以下是官方解释:

  1. /*  
  2. * These flags used only by the kernel as part of the  
  3. * irq handling routines.  
  4.  
  5. * IRQF_DISABLED - keep irqs disabled when calling the action handler  
  6. * IRQF_SAMPLE_RANDOM - irq is used to feed the random generator  
  7. * IRQF_SHARED - allow sharing the irq among several devices  
  8. * IRQF_PROBE_SHARED - set by callers when they expect sharing mismatches to occur  
  9. * IRQF_TIMER - Flag to mark this interrupt as timer interrupt  
  10. * IRQF_PERCPU - Interrupt is per cpu  
  11. * IRQF_NOBALANCING - Flag to exclude this interrupt from irq balancing  
  12. * IRQF_IRQPOLL - Interrupt is used for polling (only the interrupt that is  
  13. *                registered first in an shared interrupt is considered for  
  14. *                performance reasons)  
  15. */   
  16. #define IRQF_DISABLED           0x00000020   
  17. #define IRQF_SAMPLE_RANDOM       0x00000040   
  18. #define IRQF_SHARED             0x00000080   
  19. #define IRQF_PROBE_SHARED       0x00000100   
  20. #define IRQF_TIMER               0x00000200   
  21. #define IRQF_PERCPU             0x00000400   
  22. #define IRQF_NOBALANCING         0x00000800   
  23. #define IRQF_IRQPOLL             0x00001000    

第四个参数devname:是请求中断的设备的名称。当你加载模块成功后可以在/proc/interrupts中查看到具体设备的名称,与此同时也可以看到这个设备对应的中断号以及请求次数。

第五个参数dev_id:为一个指针型变量。注意该参数为void型,也就是说通过强制转换可以转换为任意类型。dev_id主要用于共享中断线,对每个注册的中断处理程序来说,( Dev_id must be globally unique. Normally the address of the  device data structure is used as the cookie.)dev_id参数必须唯一(指向任一设备结构的指针就可以满足此要求,选择设备结构因为它是唯一的,而且中断处理程序可能会用到它)如果无需共享中断线,则将该参数赋值为NULL。

B:函数返回值

requset_irq()函数成功执行后返回0。如果返回非0值,就表示错误发生。此时,指定的中断处理程序不会被注册。

这里面有几个疑问:

为什么要注册中断函数

共享中断线的概念,参数dev_id的作用是什么

看一个图进行说明:


1>由图可知:有16个中断线。要使用中断线,就要进行中断线的申请 ,也常把申请一条中断线称为申请一个中断号,这就 与request_irq()函数中的第一个形参irq有关系

2>Linux有256个中断向量,而外部中中断向量只有16个(32~47)。由于硬件上的限制,很多外部设备不得不共享中断线。

(例如:一些PC机所用的网卡和图形卡可以把它们分配到一条中断线上)

让每个中断源独自占用一条中断线是不实现的。

3>共享中断线的话虽然解决了中断资源的问题,但是,此时引出了另一个问题(任何事物都有其两面性),此时仅仅用中断描述符并不能提供中断产生的所有信息。为了解决这个问题,内核必须对中断线给出近一步的描述,所以在Linux设计中,为每个中断请求IRQ设置了一个专用队列(中断请求队列)
4>中断服例程序和中断处理程序的区别:
a.中断服务例程(interrupt service routine):

Linux中,15条中断线对应15个中断处理程序,依次命名是IRQ0x00_interrupt(),IRQ0x01_interrupt().....IRQ0X1f_interrupt().

中断处理程序相当于某个中断向量的总处理程序。
eg:IRQ0X05_interupt()是5号中断(向量为37)的总处理程序。

b.中断服务例程是针对一个具体设备的中断。
5>.注册中断服务例程:
在IDT表完成初始化时,每个中断服务队列还为空。此时即使打开中断且某个外设的中断真的发生了,也得不到实际的服务。因为CPU虽然通过中断门进入了某个中断向量的总处理程序。但是,具体的中断服务例程还没有挂入中断请求队列。所以,在设备驱动程序的初始化阶段,必须通过request_irq()函数将响应的中断服务例程挂入中断请求队列,也就是进行注册。

6>分析一下中断服务程序,即request_irq()函数中第二个参数所对应的函数

static irqreturn_t myirq_handler(int irq,void *dev_id)
{
 
         printk("ISR is Working/n");
         return IRQ_HANDLED;

}

中断服务例程的形参:

a.int irq :中断号。
b.void *dev_id :与request_irq()的参数dev_id一致,可以根据这个设备id号得到相应设备的数据结构,进而得到相应设备的信息和相关数据。
c.返回值:中断程序的返回值是一个特殊类型 rqreturn_t。但是中断程序的返回值却只有两个值IRQ_NONE和IRQ_HANDLED。
IRQ_NONE:中断程序接收到中断信号后发现这并不是注册时指定的中断原发出的中断信号。
IRQ_HANDLED:接收到了准确的中断信号,并且作了相应正确的处理。

PS:当一个中断到达,通过IRQ进入确定中断向量,由中断向量找到中断描述表(也就是各种门描述符的信息,这里使用中断门),由中断门描述符中的信息就可以知道中断处理程序的入口地址,进入中断处理程序后就需要执行中断服务例程,由于一个中断处理程序对应多个中断服务例程,就可以通过IRQ_NONE/IRQ_HANDLED来判断是否执行这个中断服务例程

一般中断处理程序要做什么service,主要取决于产生的设备和该设备为什么要发送中断。

John哥说明:

1.当一个给定的中断处理程序正在执行时,这条中断线上的其它中断都会被屏蔽。but,所有其他中断线上的中断都是打开的。因此这些不同中断线上的其他中断都能被处理。

PS:这个问题也就引发了中断嵌套和中断请求丢失的问题,在后续的文章中会有介绍

2.request_irq()函数可能会睡眠,所以,不能在中断上下文或其它不允许阻塞的代码中调用该函数。

4.在深入分析request_irq()函数之前,先来看几个重要的数据结构。

A.irqaction的数据结构(用irqaction结构体来描述一个具体的中断服务例程)

  1. 113struct irqaction {  
  2. 114        irq_handler_t handler;  
  3. 115        unsigned long flags;  
  4. 116        const char *name;  
  5. 117        void *dev_id;  
  6. 118        struct irqaction *next;  
  7. 119        int irq;  
  8. 120        struct proc_dir_entry *dir;  
  9. 121        irq_handler_t thread_fn;  
  10. 122        struct task_struct *thread;  
  11. 123        unsigned long thread_flags;  
  12. 124};  
  13. 125  

1>handler:指向具体的一个中断服务例程。

2>flags:表示中断标志位,对应于request_irq()函数中所传递的第三个参数,可取IRQF_DISABLED、IRQF_SAMPLE_RANDOM和IRQF_SHARED其中之一。

3>name:请求中断的设备名称,对应request_irq()函数中所传递的第四个参数

4>dev_id:共享中断时有用。对应于request_irq()函数中所传递的第五个参数,可取任意值,但必须唯一能够代表发出中断请求的设备,通常取描述该设备的结构体。

5>strct irqaction *next:指向irqaction描述符的下一个元素。用一条链表将共享同一条中断线上的中断服务例程链接起来。

6>irq:所申请的中断号

7>dir:指向proc/irq/NN/name entry

8>thread_fn:指向具体的一个线程化的中断。

9>thread:指向线程中断的指针。

10>thread_flags:线程中断的标志。

PS:平常在有的解释中,很随意的说注册中断处理程序,其实我觉得,对于用户来说,特别是I/O中断,注册都是中断服务例程,也就是上面的这个结构体)

B.irq_desc的数据结构体

每个中断向量都有它自己的irq_desc 描述符。即用irq_desc来描述中断向量。所有的这些中断描述符组织在一起就形成了irq_desc irq_desc[NR_IRQS]数组

  1. 175struct irq_desc {  
  2. 176        unsigned int            irq;  
  3. 177        struct timer_rand_state *timer_rand_state;  
  4. 178        unsigned int            *kstat_irqs;  
  5. 179#ifdef CONFIG_INTR_REMAP  
  6. 180        struct irq_2_iommu      *irq_2_iommu;  
  7. 181#endif  
  8. 182        irq_flow_handler_t      handle_irq;  
  9. 183        struct irq_chip         *chip;  
  10. 184        struct msi_desc         *msi_desc;  
  11. 185        void                    *handler_data;  
  12. 186        void                    *chip_data;  
  13. 187        struct irqaction        *action;        /* IRQ action list */  
  14. 188        unsigned int            status;         /* IRQ status */  
  15. 189  
  16. 190        unsigned int            depth;          /* nested irq disables */  
  17. 191        unsigned int            wake_depth;     /* nested wake enables */  
  18. 192        unsigned int            irq_count;      /* For detecting broken IRQs */  
  19. 193        unsigned long           last_unhandled; /* Aging timer for unhandled count */  
  20. 194        unsigned int            irqs_unhandled;  
  21. 195        raw_spinlock_t          lock;  
  22. 196#ifdef CONFIG_SMP  
  23. 197        cpumask_var_t           affinity;  
  24. 198        const struct cpumask    *affinity_hint;  
  25. 199        unsigned int            node;  
  26. 200#ifdef CONFIG_GENERIC_PENDING_IRQ  
  27. 201        cpumask_var_t           pending_mask;  
  28. 202#endif  
  29. 203#endif  
  30. 204        atomic_t                threads_active;  
  31. 205        wait_queue_head_t       wait_for_threads;  
  32. 206#ifdef CONFIG_PROC_FS  
  33. 207        struct proc_dir_entry   *dir;  
  34. 208#endif  
  35. 209        const char              *name;  
  36. 210} ____cacheline_internodealigned_in_smp;  
  37. 211  
  38. 212extern void arch_init_copy_chip_data(struct irq_desc *old_desc,  
  39. 213                                        struct irq_desc *desc, int node);  
  40. 214extern void arch_free_chip_data(struct irq_desc *old_desc, struct irq_desc *desc);  
  41. 215  
  42. 216#ifndef CONFIG_SPARSE_IRQ  
  43. 217extern struct irq_desc irq_desc[NR_IRQS];  
 

1>irq:表示这个描述符所对应的中断号。

2>handle_irq:指向该IRQ线的公共服务程序(即该IRQ所对应的中断处理程序。

3>chip:它是一个struct irq_chip类型的指针,是中断控制器的描述符 。在2.6以前的版本中它是hw_irq_controller。
4>handler_data:是handler_irq的参数。
5>chip_data:是指向irq_chip的指针。
6>atcion:一个struct irqaction类型的指针,它指向一个单链表。该链表是由该中断线上所有中断服务例程链接起来的。
7>status:表示中断线当前的状态。
8>depth:中断线被激活时,值为0;当值为正数时,表示被禁止的次数。
9>irq_count:表示该中断线上发生中断的次数
10>irqs_unhandled:该IRQ线上未处理中断发生的次数
11>name:申请中断设备的名字。

C.struct irq_chip结构体:

struct irq_chip是一个中断控制器的描述符。Linux支持N种可编程中断控制器PIC(中断控制器),通常不同的体系结构就有一套自己的中断处理方式。内核为了统一的处理中断,提供了底层的中断处理抽象接口,对于每个平台都需要实现底层的接口函数。这样对于上层的中断通用处理程序就无需任何改动。

struct irq_chip的具体代码如下:

  1. 111struct irq_chip {  
  2. 112        const char      *name;  
  3. 113        unsigned int    (*startup)(unsigned int irq);  
  4. 114        void            (*shutdown)(unsigned int irq);  
  5. 115        void            (*enable)(unsigned int irq);  
  6. 116        void            (*disable)(unsigned int irq);  
  7. 117  
  8. 118        void            (*ack)(unsigned int irq);  
  9. 119        void            (*mask)(unsigned int irq);  
  10. 120        void            (*mask_ack)(unsigned int irq);  
  11. 121        void            (*unmask)(unsigned int irq);  
  12. 122        void            (*eoi)(unsigned int irq);  
  13. 123  
  14. 124        void            (*end)(unsigned int irq);  
  15. 125        int             (*set_affinity)(unsigned int irq,  
  16. 126                                        const struct cpumask *dest);  
  17. 127        int             (*retrigger)(unsigned int irq);  
  18. 128        int             (*set_type)(unsigned int irq, unsigned int flow_type);  
  19. 129        int             (*set_wake)(unsigned int irq, unsigned int on);  
  20. 130  
  21. 131        void            (*bus_lock)(unsigned int irq);  
  22. 132        void            (*bus_sync_unlock)(unsigned int irq);  
  23. 133  
  24. 134        /* Currently used only by UML, might disappear one day.*/  
  25. 135#ifdef CONFIG_IRQ_RELEASE_METHOD  
  26. 136        void            (*release)(unsigned int irq, void *dev_id);  
  27. 137#endif  
  28. 138        /* 
  29. 139         * For compatibility, ->typename is copied into ->name. 
  30. 140         * Will disappear. 
  31. 141         */  
  32. 142        const char      *typename;  
  33. 143};  
  34. 144  

name:中断控制器的名字;
Startup:启动中断线;
Shutdown:关闭中断线;
Enable:允许中断;
Disable:禁止中断;

分析了struct irq_desc,struct irq_chip和irqaction的数据结构之后我们来看看他们之间的关系

现在深入分析request_irq()内部是如何实现的。

  1. 135request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,  
  2. 136            const char *name, void *dev)  
  3. 137{  
  4. 138        return request_threaded_irq(irq, handler, NULL, flags, name, dev);  
  5. 139}  
  6. 140  

可以看到request_irq()函数里面有封装了request_threaded_irq(irq, handler, NULL, flags, name, dev)函数。

先看一下官方的解释

  1. 1006/** 
  2. 1007 *      request_threaded_irq - allocate an interrupt line 
  3. 1008 *      @irq: Interrupt line to allocate 
  4. 1009 *      @handler: Function to be called when the IRQ occurs. 
  5. 1010 *                Primary handler for threaded interrupts 
  6. 1011 *                If NULL and thread_fn != NULL the default 
  7. 1012 *                primary handler is installed 
  8. 1013 *      @thread_fn: Function called from the irq handler thread 
  9. 1014 *                  If NULL, no irq thread is created 
  10. 1015 *      @irqflags: Interrupt type flags 
  11. 1016 *      @devname: An ascii name for the claiming device 
  12. 1017 *      @dev_id: A cookie passed back to the handler function 
  13. 1018 * 
  14. 1019 *      This call allocates interrupt resources and enables the 
  15. 1020 *      interrupt line and IRQ handling. From the point this 
  16. 1021 *      call is made your handler function may be invoked. Since 
  17. 1022 *      your handler function must clear any interrupt the board 
  18. 1023 *      raises, you must take care both to initialise your hardware 
  19. 1024 *      and to set up the interrupt handler in the right order. 
  20. 1025 * 
  21. 1026 *      If you want to set up a threaded irq handler for your device 
  22. 1027 *      then you need to supply @handler and @thread_fn. @handler ist 
  23. 1028 *      still called in hard interrupt context and has to check 
  24. 1029 *      whether the interrupt originates from the device. If yes it 
  25. 1030 *      needs to disable the interrupt on the device and return 
  26. 1031 *      IRQ_WAKE_THREAD which will wake up the handler thread and run 
  27. 1032 *      @thread_fn. This split handler design is necessary to support 
  28. 1033 *      shared interrupts. 
  29. 1034 * 
  30. 1035 *      Dev_id must be globally unique. Normally the address of the 
  31. 1036 *      device data structure is used as the cookie. Since the handler 
  32. 1037 *      receives this value it makes sense to use it. 
  33. 1038 * 
  34. 1039 *      If your interrupt is shared you must pass a non NULL dev_id 
  35. 1040 *      as this is required when freeing the interrupt. 
  36. 1041 * 
  37. 1042 *      Flags: 
  38. 1043 * 
  39. 1044 *      IRQF_SHARED             Interrupt is shared 
  40. 1045 *      IRQF_SAMPLE_RANDOM      The interrupt can be used for entropy 
  41. 1046 *      IRQF_TRIGGER_*          Specify active edge(s) or level 
  42. 1047 * 
  43. 1048 */  

5.首先分析request_threaded_irq()函数中的各个形参
1>:irq:表示申请的中断号。
2>:handler:表示中断服务例程
3.> thread_fn:中断线程化,此处传递的是NULL。NULL表示没有中断线程化。
此参数是最新版本中才出现的。为什么要提出中断线程化?
在 Linux 中,中断具有最高的优先级。不论在任何时刻,只要产生中断事件,内核将立即执行相应的中断
处理程序,等到所有挂起的中断和软中断处理完毕后才能执行正常的任务,因此有可能造成实时任务得不
到及时的处理。中断线程化之后,中断将作为内核线程运行而且被赋予不同的实时优先级,实时任务可以
有比中断线程更高的优先级。这样,具有最高优先级的实时任务就能得到优先处理,即使在严重负载下仍
有实时性保证。but,并不是所有的中断都可以被线程化,比如时钟中断,主要用来维护系统时间以及定时器
等,其中定时器是操作系统的脉搏,一旦被线程化,就有可能被挂起,这样后果将不堪设想,所以不应当
被线程化。

4>.irqflags:表示中断标志位。
5>.devname:表示请求中断的设备的名称。

6>.dev_id:对应于request_irq()函数中所传递的第五个参数,可取任意值,但必须唯一能够代表发出中断请求的设备,通常取描述该设备的结构体。共享中断时所用。

现在继续迭代深入request_threaded_irq()内部是如何实现的。

  1. 1049int request_threaded_irq(unsigned int irq, irq_handler_t handler,  
  2. 1050                         irq_handler_t thread_fn, unsigned long irqflags,  
  3. 1051                         const char *devname, void *dev_id)  
  4. 1052{  
  5. 1053        struct irqaction *action;  
  6. 1054        struct irq_desc *desc;  
  7. 1055        int retval;  
  8. 1056  
  9. 1057        /* 
  10. 1058         * Sanity-check: shared interrupts must pass in a real dev-ID, 
  11. 1059         * otherwise we'll have trouble later trying to figure out 
  12. 1060         * which interrupt is which (messes up the interrupt freeing 
  13. 1061         * logic etc). 
  14. 1062         */  
  15. 1063        if ((irqflags & IRQF_SHARED) && !dev_id)  
  16. 1064                return -EINVAL;  
  17. 1065  
  18. 1066        desc = irq_to_desc(irq);  
  19. 1067        if (!desc)  
  20. 1068                return -EINVAL;  
  21. 1069  
  22. 1070        if (desc->status & IRQ_NOREQUEST)  
  23. 1071                return -EINVAL;  
  24. 1072  
  25. 1073        if (!handler) {  
  26. 1074                if (!thread_fn)  
  27. 1075                        return -EINVAL;  
  28. 1076                handler = irq_default_primary_handler;  
  29. 1077        }  
  30. 1078  
  31. 1079        action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);  
  32. 1080        if (!action)  
  33. 1081                return -ENOMEM;  
  34. 1082  
  35. 1083        action->handler = handler;  
  36. 1084        action->thread_fn = thread_fn;  
  37. 1085        action->flags = irqflags;  
  38. 1086        action->name = devname;  
  39. 1087        action->dev_id = dev_id;  
  40. 1088  
  41. 1089        chip_bus_lock(irq, desc);  
  42. 1090        retval = __setup_irq(irq, desc, action);  
  43. 1091        chip_bus_sync_unlock(irq, desc);  
  44. 1092  
  45. 1093        if (retval)  
  46. 1094                kfree(action);  
  47. 1095  
  48. 1096#ifdef CONFIG_DEBUG_SHIRQ  
  49. 1097        if (!retval && (irqflags & IRQF_SHARED)) {  
  50. 1098                /* 
  51. 1099                 * It's a shared IRQ -- the driver ought to be prepared for it 
  52. 1100                 * to happen immediately, so let's make sure.... 
  53. 1101                 * We disable the irq to make sure that a 'real' IRQ doesn't 
  54. 1102                 * run in parallel with our fake. 
  55. 1103                 */  
  56. 1104                unsigned long flags;  
  57. 1105  
  58. 1106                disable_irq(irq);  
  59. 1107                local_irq_save(flags);  
  60. 1108  
  61. 1109                handler(irq, dev_id);  
  62. 1110  
  63. 1111                local_irq_restore(flags);  
  64. 1112                enable_irq(irq);  
  65. 1113        }  
  66. 1114#endif  
  67. 1115        return retval;  
  68. 1116}  

程序的第一行和第二行分别定义了:

(1) struct irqaction *action;

(2)2struct irq_desc *desc;

两个指针action和desc,它们分别指向了结构体irqaction和 irq_desc。

(3)    if ((irqflags & IRQF_SHARED) && !dev_id)
              return -EINVAL;

作用是:判断中断标志位,如果是共享中断的话就必须要有一个唯一的dev_id,否则返回一个错误。

(4)      desc = irq_to_desc(irq);

irq_to_desc(irq):根据中断号irq在 irq_desc[NR_IRQS]数组中返回一个具体的irq_desc。即根据irq找到它的中断处理程序。

(5)    if (!desc)

        return -EINVAL;

当返回一个空值时返回一个错误。说明申请中断号失败。

(6)if (desc->status & IRQ_NOREQUEST)
               return -EINVAL;

判断中断线的状态,若为IRQ_NOREQUEST时(IRQ_NOREQUEST表示 IRQ 不能被申请)

(7)        if (!handler) {
                        if (!thread_fn)
                        return -EINVAL;
               handler = irq_default_primary_handler;
              }

判断中断服务例程是否为空,如果handler为空,则判断线程中断服务例程,若线程中断服务例程也为空,则返回一个错误值。否则中断服务例程指向:rq_default_primary_handler。

(8)

1079        action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
1080        if (!action)
1081                return -ENOMEM;
1082
1083        action->handler = handler;
1084        action->thread_fn = thread_fn;
1085        action->flags = irqflags;
1086        action->name = devname;
1087        action->dev_id = dev_id;

从1079~1087:根据requst_irq()函数中传递的参数生成一个irqaction.

1097        if (!retval && (irqflags & IRQF_SHARED)) {
1098                /*
1099                 * It's a shared IRQ -- the driver ought to be prepared for it
1100                 * to happen immediately, so let's make sure....
1101                 * We disable the irq to make sure that a 'real' IRQ doesn't
1102                 * run in parallel with our fake.
1103                 */
1104                unsigned long flags;
1105
1106                disable_irq(irq);
1107                local_irq_save(flags);
1108
1109                handler(irq, dev_id);
1110
1111                local_irq_restore(flags);
1112                enable_irq(irq);
1113        }

1097~1113:如果为共享中断的话,在执行中断服务例程之前,要先把这条中断线上的中断屏蔽,让后在执行,执行完之后打开中断。

6.有注册中断服务函数,那必然有相应的释放中断函数。

可以调用void free_irq(unsigned int irq, void *dev_id)来释放我们申请的中断线。

函数形参:

1>unsigned int riq:表示申请的中断号与request_irq()函数中的第一个形参对应。

2>void *dev_id:与request_irq()函数中的最后一个形参含义和用法相同,在此不再说明。

函数功能:

如果指定的中断线不是共享的,那么,该函数删除处理程序的同时将禁用这条中断线。如果中断线是共享的,则仅删除dev_id所对应的处理程序,而这条中断线本省只有在删除了最后一个处理程序时才会被禁止。

切记:This function must not be called from interrupt context

freee_irq()函数不能在中断上下文中被调用。

3>深入分析下free_irq()函数内部是如何实现的

  1.  993void free_irq(unsigned int irq, void *dev_id)  
  2.  994{  
  3.  995        struct irq_desc *desc = irq_to_desc(irq);  
  4.  996  
  5.  997        if (!desc)  
  6.  998                return;  
  7.  999  
  8. 1000        chip_bus_lock(irq, desc);  
  9. 1001        kfree(__free_irq(irq, dev_id));  
  10. 1002        chip_bus_sync_unlock(irq, desc);  
  11. 1003}  

可以看到free_irq()函数了封装了_free_irq(irq,dev_id)函数。

free_irq()调用_free_irq()把每一个具体的中断服务例程()释放。

转载:http://blog.csdn.net/tigerjibo/article/details/6069516

添加了自己的理解认识

Linux 内核之中断

阅读数 1990

没有更多推荐了,返回首页