知识要点
一、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 int
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;
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
*/
.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_handler
entry_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内核修炼之道》