linux 中斷管理(一)


#一、中斷作用

Linux 內核需要對連接到計算機上的所有硬件設備進行管理。如果要管理這些設備,首先得和它們互相通信才行。 一般有兩種方案可實現這種功能:

  • 輪詢(polling) 讓內核定期對設備的狀態進行查詢,然后做出相應的處理;
  • 中斷(interrupt) 讓硬件在需要的時候向內核發出信號(變內核主動為硬件主動)。

使用輪詢的方式會占用CPU比較多的時間,效率極低。例如:要讀取一個按鍵有沒有被按下時,一個進程需要不斷地查詢按鍵有沒有被按下。這樣這個任務就占用CPU大量得時間,使得CPU做了大量的無用功。使用中斷提供這樣的一個機制。當按鍵沒有被按下的時候,掛起當前進程,將控制權轉交給其他進程。當按鍵按下的時候,操作系統把當前進程設為活動的,從而允許該進程繼續執行。

二、linux中斷管理

linux 內核將所有的中斷統一編號,使用一個 irq_desc 結構體數組描述中斷。一個數組項對用一個中斷(或者是一組中斷,它們共用中斷號)。 struct irq_desc 結構體記錄了,中斷的名稱、中斷狀態,底層硬件訪問接口(使能中斷,屏蔽中斷,清除中斷),中斷處理函數的入口, 通過它可以調用用戶注冊的中斷處理函數。

1、struct irq_desc

struct irq_descinclude\linux\irq.h 文件里面定義

struct irq_desc {
	irq_flow_handler_t	handle_irq;    /* 當前中斷的處理函數入口 */
	struct irq_chip		*chip;         /* 底層硬件訪問 */
        ...
	struct irqaction	*action;	/* 用戶注冊的中斷處理函數鏈表 */
	unsigned int		status;		/* IRQ狀態 */

	...

	const char		*name;         /* 中斷函數名 */
} ____cacheline_internodealigned_in_smp;

a. handle_irq

handle_irq 是這個或者是這組的中斷的處理函數入口。發生中斷時,會調用asm_do_IRQ函數。在這個函數里面根據中斷號調用相應irq_desc數組項的handle_irq。 在handle_irq里面會使用chip成員的接口來使能、屏蔽、清除中斷。還會一一調用用戶注冊在action鏈表里面的處理函數。

b. struct irq_chip

struct irq_chip 在 include\linux\irq.h 文件里面定義

struct irq_chip {
    const char	*name;
    /* 啟動中斷,如果不設置則缺省為 "enable" */
    unsigned int	(*startup)(unsigned int irq);       
    /* 關閉中斷,如果不設置則缺省為 "disable" */
    void		(*shutdown)(unsigned int irq);      
    /* 使能中斷,如果不設置則缺省為"unmask" */  
    void		(*enable)(unsigned int irq);
    /* 禁止中斷,如果不設置則缺省為"mask" */  
    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);
  ....
};

c. struct irqaction

struct irqaction 結構體在 include\linux\interrupt.h 文件里面定義。 用戶注冊的每個中斷處理函數都用一個 irqaction 結構體來描述一個中斷(例如共享中斷)可以有多個處理函數。 它們的irqaction結構以action 為表頭鏈成一個鏈表

struct irqaction {
    /* 用戶注冊的中斷處理函數 */
    irq_handler_t handler;
    /* 中斷的標志,是否是共享中斷,中斷的觸發方式是電平觸發,還是邊沿觸發 */
    unsigned long flags;

    cpumask_t mask;
    /* 用戶注冊時,給的中斷的名字 */
    const char *name;
    /* handler 中斷函數的參數,也可以用來區分共享中斷 */
    void *dev_id;
    /* 鏈表的指針 */
    struct irqaction *next;
    /* 中斷號 */
    int irq;
    struct proc_dir_entry *dir;
};

2. 小結

對於 struct irq_desc數組 、 struct irq_chip結構體 和 struct irqaction 三者之間的關系,如下圖:

三、中斷處理初始化

1、中斷處理初始化

init\Main.c 文件的 start_kernel 函數里面調用的init_IRQ() 函數就是中斷體系結構的初始化

2、init_IRQ 函數

init_IRQ 用來初始化中斷處理體系結構, 在arch\arm\kernel\irq.c文件里面

void __init init_IRQ(void)
{
	int irq;
        /* 初始化irq_desc[] 每一項的中斷狀態 */
	for (irq = 0; irq < NR_IRQS; irq++)
		irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
    ...
        /* 架構相關的中斷初始化函數 */
	init_arch_irq();
}

3、 init_arch_irq

init_arch_irq 是一個函數指針,arch\arm\kernel\setup.c 文件 setup_arch() 函數被初始化

void __init setup_arch(char **cmdline_p)
{
    ...
    init_arch_irq = mdesc->init_irq;
    ...
}

mdesc->init_irq 指向的是 arch\arm\plat-s3c24xx\irq.c 文件的s3c24xx_init_irq()函數。 MACHINE_START(S3C2440, "SMDK2440") 是一個宏,用來定義 struct machine_desc 結構體 結構體在 arch\arm\mach-s3c2440\Mach-smdk2440.c文件里面定義並且初始化 init_irq指向s3c24xx_init_irq()函數

MACHINE_START(S3C2440, "SMDK2440")
	/* 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成員在這里初始化 */
	.init_irq	= s3c24xx_init_irq,
	.map_io		= smdk2440_map_io,
	.init_machine	= smdk2440_machine_init,
	.timer		= &s3c24xx_timer,
MACHINE_END

4、s3c24xx_init_irq函數

s3c24xx_init_irq()函數在arch\arm\plat-s3c24xx\irq.c定義(部分代碼如下面的代碼塊),他為所有的所有與芯片操作相關的數據結構(irq_desc[irq].chip),,並且初始化了處理函數入口(irq_desc[irq].handle_irq)。 以IRQ_EINT0IRQ_EINT3 為例,set_irq_chip函數就是將irq_desc[irqno].chip = &s3c_irq_eint0t4,以后就可以通過irq_desc[irqno].chip結構的函數指針來設置觸方式,使能中斷、屏蔽中斷了 set_irq_handler函數就是設置中斷處理函數入口 將irq_desc[irqno].handle_irq = handle_edge_irq 發生中斷時,handle_edge_irq會調用用戶具體注冊的處理函數 irq_desc[irqno].falgs設置為IRQF_VALID

void __init s3c24xx_init_irq(void)
{
    /* 中斷號是組的初始化 */
    set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
    set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
    ...
    /* external interrupts */
    for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
        irqdbf("registering irq %d (ext int)\n", irqno);
        set_irq_chip(irqno, &s3c_irq_eint0t4);
        set_irq_handler(irqno, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }
    for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
        irqdbf("registering irq %d (extended s3c irq)\n", irqno);
        set_irq_chip(irqno, &s3c_irqext_chip);
        set_irq_handler(irqno, handle_edge_irq);
        set_irq_flags(irqno, IRQF_VALID);
    }
    ...
    
}

###5、set_irq_chip函數

int set_irq_chip(unsigned int irq, struct irq_chip *chip)
{
	struct irq_desc *desc;
	unsigned long flags;
        /* 判斷是否超過最大的中斷號 */
	if (irq >= NR_IRQS) {
		printk(KERN_ERR "Trying to install chip for IRQ%d\n", irq);
		WARN_ON(1);
		return -EINVAL;
	}
         
	if (!chip)
		chip = &no_irq_chip;
        /* 通過中斷號找到irq_desc數組對應的數組項 */
	desc = irq_desc + irq;
	spin_lock_irqsave(&desc->lock, flags);
        /* 判斷 chip 的成員即&s3c_irq_eint0t4的成員是否為空,如果為空就設置為默認的操作函數 */
	irq_chip_set_defaults(chip);
        /* 設置irq_desc[].chip成員 */
	desc->chip = chip;
	spin_unlock_irqrestore(&desc->lock, flags);
	return 0;

###5、set_irq_handler函數

set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
{
	__set_irq_handler(irq, handle, 0, NULL);
}


/***************************************************************************************/


__set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
		  const char *name)
{
    struct irq_desc *desc;
    unsigned long flags;
    /* 通過中斷號找到irq_desc數組對應的數組項 */
    desc = irq_desc + irq;
   
   ...
    /*  中間還會做一些判斷 */
   ...
    /* 設置中斷處理函數,名字*/
    desc->handle_irq = handle;
    desc->name = name;
    /* 設置中斷的狀態,開啟中斷 */
    if (handle != handle_bad_irq && is_chained) {
	desc->status &= ~IRQ_DISABLED;
	desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE;
	desc->depth = 0;
	desc->chip->unmask(irq);
    }

}

###6、set_irq_flags函數

void set_irq_flags(unsigned int irq, unsigned int iflags)
{
	struct irq_desc *desc;
	unsigned long flags;

	if (irq >= NR_IRQS) {
		printk(KERN_ERR "Trying to set irq flags for IRQ%d\n", irq);
		return;
	}
        /* 找到數組項 */
	desc = irq_desc + irq;
	spin_lock_irqsave(&desc->lock, flags);

       /* 設置中斷狀態 */
	desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE | IRQ_NOAUTOEN;
	if (iflags & IRQF_VALID)
		desc->status &= ~IRQ_NOREQUEST;
	if (iflags & IRQF_PROBE)
		desc->status &= ~IRQ_NOPROBE;
	if (!(iflags & IRQF_NOAUTOEN))
		desc->status &= ~IRQ_NOAUTOEN;
	spin_unlock_irqrestore(&desc->lock, flags);
}

#四、總結

中斷處理體系結構的初始化的過程其實就是對irq_desc[]數組的每一項初始化進行初始化. 一個中斷或者一組中斷通過irq_desc[]的一個數組項來管理。 數組項里面的 handle_irq chip action 三個重要的結構體成員。

  • handle_irq是當前的中斷處理函數
  • chip底層硬件相關的處理函數(設置中斷的觸發方式,屏蔽中斷,使能中斷)
  • action鏈表頭,用戶注冊的處理函數都鏈接到這個鏈表里面,發生中斷的時候,就會從之里面調用用戶注冊進來的中斷服務函數


免責聲明!

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



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