setup_irq和request_irq


请尊重原创版权,转载注明出处。

Linux 内核提供了两个注册中断处理函数的接口:setup_irq和request_irq。这两个函数都定义在kernel/irq/manage.c里。 这两个函数有什么样的区别呢?

先看看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失败,从而造成系统时钟驱动的中断未注册 成功,进而内核挂掉。

上描述了Request_irq和setup_irq的区别,这里我们来看看,在设备驱动中该怎么使用request_irq和setup_irq。

通过看request_irq和setup_irq的 实现我们可以知道, request_irq的action是通过kmalloc分配得到的,而 free_irq内部调用了__free_irq,__free_irq实际上是将对应的irqaction从action chain(是该中断的action列表,存放在链表中)中去掉并返回此irqaction的指针,然后用kfree去free掉它。这说 明,request_irq申请的中断应该使用free_irq去free掉。

但对于setup_irq,第二个参数irqaction很有可能不是kmalloc得到,因此在使用setup_irq设置一个irqaction后,通常不能用free_irq去free它,否则会出现访问NULL pointer的oops。与setup_irq对应的应该是remove_irq(内部也调用了__free_irq,但是没有去free它的返回值)。

再 来看看,free_irq的第二个参数是dev_id,该参数用于判断要free掉的到底是该中断 action chain中的哪个 irqaction(通过遍历,逐个比较),如果遍历到chain末尾还找不到匹配的irqaction(使得action->dev_id == dev_id)那么就会报Trying to free already-free IRQ的错。

上述结论可以通过查看kernel/irq/manage.c中的具体实现验证。

另外,disable_irq / enable_irq用于除能和使能某个中断,但对于共享中断要特别注意,一次disable_irq 作用是使得整个中断线上的中断action都被disable掉(实际上是将中断向量irq_desc的status成员置位IRQ_DISABLED, 这将影响该中断向量的整个action chain)

文档信息
--------------
* 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
* [转载: ](blog.163.com/cupidove/blog/static/1005662)