Linux 内核提供了两个注册中断处理函数的接口:setup_irq和request_irq。这两个函数都定义在kernel/irq/manage.c里。
/*
* Internal function to
register an irqaction - typically used to
* allocate special interrupts
that are part of the architecture.
*/
int setup_irq(unsigned int irq, struct irqaction
*new);
/*
* request_irq - allocate an interrupt
line
* This call allocates interrupt resources and
enables the
* interrupt line and IRQ handling.
*/
int request_irq(unsigned int irq,
irqreturn_t
(*handler)(int, void *, struct pt_regs *),
unsigned long
irqflags, const char *devname, void *dev_id)
这两个函数有什么样的区别呢?
先看看setup_irq
Setup_irq通常用在系统时钟(GP Timer)驱动里,注册系统时钟驱动的中断处理函数。
下面举个列子, 如s3c2410
timer驱动:
/* arch/arm/mach-s3c2410/time.c */
static struct irqaction s3c2410_timer_irq =
{
.name = "S3C2410 Timer Tick",
.flags = IRQF_DISABLED |
IRQF_TIMER,
.handler = s3c2410_timer_interrupt,
};
static void __init s3c2410_timer_init (void)
{
s3c2410_timer_setup();
setup_irq(IRQ_TIMER4,
&s3c2410_timer_irq);
}
struct sys_timer s3c24xx_timer = {
.init = s3c2410_timer_init,
.offset = s3c2410_gettimeoffset,
.resume = s3c2410_timer_setup
};
struct sys_timer s3c24xx_timer = {
.init = s3c2410_timer_init,
.offset = s3c2410_gettimeoffset,
.resume = s3c2410_timer_setup
};
可以看到,setup_irq的使用流程很简单。首先定义s3c2410
timer驱动的irqaction结构体,该结构体用于描述timer中断的基本属性包括中断名、类别以及该中断handler等。然后通过setup_irq函数将timer的irqaction注册进内核。其中,IRQ_TIMER4为s3c2410
timer的中断号。
再看看request_irq
request_irq源码如下:
/* kernel/irq/manage.c */
int request_irq(unsigned int irq,
irqreturn_t
(*handler)(int, void *, struct pt_regs *),
unsigned long
irqflags, const char *devname, void *dev_id)
{
struct irqaction
*action;
int
retval;
#ifdef CONFIG_LOCKDEP
/*
* Lockdep wants atomic interrupt
handlers:
*/
irqflags |=
SA_INTERRUPT;
#endif
/*
* Sanity-check: shared interrupts must pass in
a real dev-ID,
* otherwise we'll have trouble later trying to
figure out
* which interrupt is which (messes up the
interrupt freeing
* logic etc).
*/
if ((irqflags &
IRQF_SHARED) && !dev_id) /* 使用共享中断但没有提供非NULL的dev_id则返回错误 */
return
-EINVAL;
if (irq >=
NR_IRQS) /*
中断号超出最大值 */
return
-EINVAL;
if (irq_desc[irq].status
& IRQ_NOREQUEST) /* 该中断号已被使用并且未共享 */
return
-EINVAL;
if
(!handler)
return
-EINVAL;
action =
kmalloc(sizeof(struct irqaction), GFP_ATOMIC); /*
动态创建一个irqaction */
if
(!action)
return
-ENOMEM;
/* 下面几行是根据request_irq 传进来的参数对irqaction结构体赋值 */
action->handler =
handler;
action->flags =
irqflags;
cpus_clear(action->mask);
action->name =
devname;
action->next =
NULL;
action->dev_id =
dev_id;
select_smp_affinity(irq);
retval = setup_irq(irq,
action); /*
调用setup_irq注册该中断的irqaction结构体 */
if
(retval)
kfree(action);
return
retval;
}
由上可以看出,request_irq的大致流程为先对申请的中断线进行安全检测,然后根据request_irq传进来的参数,动态创建该中断对应的irqaction结构体,最后通过setup_irq函数将该irqaction注册进内核适当的位置。
这两个函数的使用流程搞清楚了,那么两者之间的联系也就清楚了:
1)
Request_irq的注册过程包含setup_irq,最终是调用setup_irq。
2)
Request_irq比setup_irq多一套错误检测机制,即kmalloc前面3行if语句。
而Setup_irq通常是直接注册irqaction,并没针对相应中断线进行错误检测,如该irq 线是否已经被占用等。因此setup_irq通常只用在特定的中断线上,如System timer。除系统时钟驱动外,大部份驱动还是通过request_irq注册中断。
这里有个小问题:
既然Request_irq实际上就是包含了setup_irq的注册过程,那系统时钟驱动(GP Timer Driver)中断可以用request_irq来注册吗?
做个小试验, 将s3c2410
timer驱动的setup_irq那行去掉,改为用request_irq注册。
修改后代码如下:
static void __init s3c2410_timer_init (void)
{
s3c2410_timer_setup();
//setup_irq(IRQ_TIMER4,
&s3c2410_timer_irq);
request_irq(IRQ_TIMER4,
s3c2410_timer_interrupt,
IRQF_DISABLED |
IRQF_TIMER, "S3C2410 Timer Tick",
NULL);
}
编译运行。
结果:内核挂掉
为什么呢?很明显,系统时钟驱动中断不能用request_irq注册,大致搜了一下源码也发现,看到其他平台相关的时钟驱动中断部分都是用的setup_irq注册的。
我们来分析一下原因。
看看request_irq和setup_irq 还有哪些细节不一样?
仔细观察后注意到request_irq内有这么一行代码:
action = kmalloc(sizeof(struct irqaction),
GFP_ATOMIC);
作用为动态创建一个irqaction。
Kmalloc实际上也是使用的slab机制进行分配的。源码如下:
/* include/linux/slab.h */
static inline void *kmalloc(size_t size, gfp_t
flags)
{
if
(__builtin_constant_p(size)) {
int i =
0;
#define CACHE(x) /
if (size <= x)
/
goto
found; /
else
/
i++;
#include "kmalloc_sizes.h"
#undef CACHE
{
extern
void __you_cannot_kmalloc_that_much(void);
__you_cannot_kmalloc_that_much();
}
found:
return kmem_cache_alloc((flags & GFP_DMA)
?
malloc_sizes[i].cs_dmacachep :
malloc_sizes[i].cs_cachep, flags);
}
return __kmalloc(size,
flags);
}
使用slab机制分配内存必须先对slab进行初始化,包括mem_init和kmem_cache_init。
看看kernel的初始化流程:
/* init/main.c */
asmlinkage void __init start_kernel(void)
{
……
time_init();
……
vfs_caches_init_early();
cpuset_init_early();
mem_init(); ß------ initializes the
memory data structures
kmem_cache_init(); ß---- set up the general
caches
……
}
Time_init函数在mem_init和kmem_cache_init之前被调用,而time_init会调用体系结构相关部分系统时钟驱动的初始化函数。拿s3c2410的例子来说,time_init最终会调用s3c2410_timer_init函数,进行s3c2410时钟驱动的初始化和注册中断处理函数。
具体过程如下:
time_init函数定义在arch/arm/kernel/time.c内:
void __init time_init(void)
{
#ifndef CONFIG_GENERIC_TIME
if
(system_timer->offset == NULL)
system_timer->offset = dummy_gettimeoffset;
#endif
system_timer->init(); ß-这行实际执行的就是s3c2410_timer_init
#ifdef CONFIG_NO_IDLE_HZ
if
(system_timer->dyn_tick)
system_timer->dyn_tick->lock =
SPIN_LOCK_UNLOCKED;
#endif
}
system_timer在setup_arch(arch/arm/kernel/setup.c)内通过map_desc机制被初始化为s3c24xx_timer.
如上面s3c2410时钟驱动代码所示,s3c24xx_timer的init成员即指向s3c2410_timer_init函数。
现在我们搞清楚了,我们大概的估计是系统时钟驱动(GP Timer Driver)的中断处理函数不能用request_irq注册是因为request_irq内会调用kmalloc动态分配内存创建timer的irqaction结构体。而kmalloc也是使用的slab内存分配机制,使用kmalloc前必须先对kernel的slab以及mem data structure进行初始化。而这部分初始化工作是在系统时钟驱动初始化之后才进行的,所以造成kmalloc失败,从而造成系统时钟驱动的中断未注册成功,进而内核挂掉。
分享到:
相关推荐
/sbin/set_irq_affinity eth1 可以进行中断绑定指定的cpu,提高网卡收包效率 把下面“eth1” 修改成对应的网卡名称 irq=$(cat /proc/interrupts | grep eth1 | cut -d':' -f 1); echo $irq for i in $irq ; do sudo...
STM32的USART_Irq串口中断方式(直接操作寄存器)。内有详细的注释和文档。很好的学习资料。
LPC213X系列串口通信程序,使用了中断编写
用于本文 网卡CPU绑核文章的脚本使用,用来绑定CPU到制定CPU核上,使用配合。
Provides a framework for enqueueing and running callbacks from hardirq context.
Linux内核
单片机向量IRQ中断。对于初学者是非常好的学习资料
17-STM32F429_nRF24L01P_TRANSMITTER_IRQ.7z
17-STM32F429_nRF24L01P_RECEIVER_IRQ.7z
int WINAPI sio_cnt_irq(int port, VOID (CALLBACK *func)(int port), int count); int WINAPI sio_modem_irq(int port, VOID (CALLBACK *func)(int port)); int WINAPI sio_break_irq(int port, VOID (CALLBACK *...
Atmel AT91 AIC (Advanced Interrupt Controller) driver for Linux v2.13.6.
声卡驱动和使用教程(添加对OC引导的_IRQ修复方案)
VBS to list all IRQ current Windows
EM6819单片机中断方式范例,RIDE C816编译器
2104IRQ中断C语言编程
学习飞思卡尔单片机的外部中断的使用,了解中断程序的书写方式。
此文档是在linux环境下,网卡绑定的文档,希望能给大家帮助
ZLG的smart2400开发板的外部中断例程
drm irq.c IRQ and vblank support for Linux v2.13.6.
重新分配网卡cpu,防止部分cpu被打满,导致网络传输数据有丢失。