setup_irq和request_irq(轉)


轉自:blog.163.com/cupidove/blog/static/1005662

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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM