您好,登錄后才能下訂單哦!
這期內容當中小編將會給大家帶來有關什么是linux異常體系結構,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
以arm處理器為例, 外部中斷和處理器內核異常(soc內部)都屬于異常, 異常都是相對于主程序來講的, 當soc正常執行主程序時, 中斷和異常都可以打斷它, 依據異常對于主程序所體現出來的"中斷"性質可以區分出中斷和異常的區別:
異常: 由于soc本身的內核活動產生的, 如當執行主程序時候由于arm soc預取指令/數據而產生異常等, 這個異常來自soc的內核, 所以對于soc內核來說是"同步"的.
中斷: 當soc在執行主程序的時候, 各種片上外設和外部中斷引腳可以產生一個異常來中斷soc當前正在執行的程序, 這個異常信號來自soc的內核以外, 所以對于soc內核來說他是"異步"的
在arm架構的linux系統下, 共使用了5種異常: 1. 未定義指令異常 2. 指令預取中止異常 3. 數據訪問中止異常 4. 中斷異常 5. swi異常.
linux內核在(linux)/include/asm-arm/arch-s3c2410/irqs.h 中將所有的中斷統一編號, 另外 linux使用一個結構體數組來描述中斷, 并且這個數組的下標就是中斷號. 每個數組項對應一組中斷,
file: (linux)/include/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; /* cat /proc/interrupts 顯示的中斷名稱 */ } ____cacheline_internodealigned_in_smp;
其中的 chip 提供了底層的硬件訪問:
file: (linux)/include/linux/irq.h struct irq_chip { const char *name; unsigned int (*startup)(unsigned int irq); /* 啟動中斷, 缺省為 "enable" */ void (*shutdown)(unsigned int irq); /* 關閉中斷, 缺省為 "disable" */ void (*enable)(unsigned int irq); /* 使能中斷, 缺省為 "unmask" */ void (*disable)(unsigned int irq); /* 關閉中斷, 缺省為 "mask" */ void (*ack)(unsigned int irq); /* 響應中斷, 通常為清除當前中斷使得可以接收下一個中斷 */ void (*mask)(unsigned int irq); /* 屏蔽中斷源 */ void (*mask_ack)(unsigned int irq); /* 屏蔽和響應中斷 */ void (*unmask)(unsigned int irq); /* 開啟中斷源 */ ... };
另外irqaction結構體為:
file: (linux)/include/linux/interrupt.h struct irqaction { irq_handler_t handler; //用戶注冊的中斷處理函數 unsigned long flags; //中斷標志 cpumask_t mask; //用于smp const char *name; //用戶注冊的名字 "cat /proc/interrupts" 可以看到 void *dev_id; //用戶傳給上面的handler參數,還可以用來區分共享中斷 struct irqaction *next; //指向下一個irqaction結構 int irq; //中斷號 struct proc_dir_entry *dir; };
中斷執行的流程是: 中斷到來時總中斷入口函數 asm_do_IRQ() 根據 中斷號 找到 irq_desc 結構體數組中的對應項, 并調用 irq_desc 結構體中的 handle_irq() 函數, handle_irq 函數使用 chip 結構體中的函數 清除/屏蔽/重新使能中斷, 最后一一調用 action 鏈表中注冊的中斷處理函數.
在 (linux)/init/main.c 的 start_kernel() 函數中的 trap_init() / init_IRQ() 來設置異常的處理函數.
1. trap_init() 和 early_trap_init() 函數 通過下邊語句將異常向量復制到 0xffff0000 地址處, 將中斷服務函數復制到 0xffff0000 + 0x200地址處
file: (linux)/arch/arm/kernel memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start); memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
其中start和end如下:
.globl __vectors_start __vectors_start: swi SYS_ERROR0 b vector_und + stubs_offset ldr pc, .LCvswi + stubs_offset b vector_pabt + stubs_offset b vector_dabt + stubs_offset b vector_addrexcptn + stubs_offset b vector_irq + stubs_offset b vector_fiq + stubs_offset .globl __vectors_end __vectors_end:
負責初始化的為 init_IRQ,因其與具體開發板密切相關,所以需要單獨列出來. 它用來初始化中斷的框架,設置各個中斷的默認處理函數. 當發生中斷時候進入中斷總入口 asm_do_IRQ() 調用init_IRQ()設置的函數
file: (linux)/arch/arm/kernel/irq.c void __init init_IRQ(void) { int irq; for (irq = 0; irq < NR_IRQS; irq++) irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE; init_arch_irq(); }
1 for循環中將 irq_desc[] 數組中每一項的狀態都設置為 IRQ_NOREQUEST | IRQ_NOPROBE (未請求 | 未探測)
2 init_arch_irq 其實是一個函數指針, 定義如下:
file: (linux)/arch/arm/kernel/irq.c void (*init_arch_irq)(void) __initdata = NULL;
2440移植好的linux系統在啟動的時候通過(linux)/arch/arm/kernel/setup.c 的 setup_arch() 獲取到 machine_desc 結構體, 然后將 init_arch_irq 這個函數指針初始化為 s3c24xx_init_irq() .
file: (linux/arch/plat-s3c24xx/irq.c 的 s3c24xx_init_irq) void __init s3c24xx_init_irq(void) { ... /* first, clear all interrupts pending... */ ... /* register the main interrupts */ ... /* setup the cascade irq handlers */ set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7); set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8); ... /* external interrupts */ //外部中斷 0 ~ 3 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); } //外部中斷4 ~ 23 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); } /* register the uart interrupts */ ... }
1. 清除所有中斷的 pending 位
2. 進一步設置 irq_desc[] 中元素的 chip , handle_irq字段.
set_irq_chip(irqno, &s3c_irqext_chip) 結果是: irq_desc[irqno]. chip = &s3c_irqext_chip; 之后就可以使用 irq_desc[irqno].chip 結構體的成員來設置觸發方式/使能中斷/禁止中斷等
set_irq_handler(irqno, handle_edge_irq) 結果是: irq_desc[irqno].handle_irq = handle_edge_irq; 這是該中斷號對應的中斷函數入口
set_irq_flags(irqno, IRQF_VALID) 結果是: 消除 irqno 對應的IRQ_NOREQUEST標志, 告訴系統該中斷被可申請使用了.中斷申請的時候會查看該標志如果設置了表示中斷尚不可用
執行完 init_IRQ之后 irq_desc[] 中各個數組項的chip, handle_irq字段都被設置好了.
驅動程序使用 request_irq 來向內核注冊中斷處理函數, request_irq 根據中斷號找到 irq_desc[] 數組項, 在數組項的 action 鏈表中添加一個表項.
file: (linux)/kernel/irq/manage.c int request_irq(unsigned int irq, irq_handle_t handler, unsinged long irqflags, const char *devname, void *dev_id) { struct irqaction *action; //創建 irqaction 結構體指針 ... action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);//填充 irqaction 結構體各個元素 action->handler = handler; action->flags = irqflags; cpus_clear(action->mask); action->name = devname; action->next = NULL; action->dev_id = dev_id; retval = setup_irq(irq, action); ... }
setup_irq函數有幾個功能:
1. 將 action 連接入 irq_desc[irq]的action鏈表中
2. irq_desc[].chip 結構體中沒有被 init_irq() 設置的元素設置為默認值
3. 使用傳入的 irqflags 作為參數 調用 chip->set_type來設置中斷: 設置為外部中斷, 設置觸發方式(高電平/低電平/上升沿/下降沿)
4. 啟動中斷, 調用chip->startup 或 chip->enable 使能中斷,
總之, 調用了request_irq之后: irq_desc[]的action結構體已鏈入action鏈表, 中斷觸發方式也設置好了, 中斷已被使能. 現在中斷已經可以發生并處理了
(4) 中斷具體的執行流程
中斷的總入口是 asm_do_IRQ()
file: (linux)/arch/arm/kernel/irq.c asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs){ ... struct irq_desc *desc = irq_desc + irq; ... desc_handle_irq(irq, desc); ... } file: (linux)/include/asm-arm/mach/irq.h static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc){ desc->handle_irq(irq, desc); }
irq 取值范圍: IRQ0_EINT0~(IRQ_EINT0+31). 因為2440的INTPND共32bit, 但是irq_desc[]卻遠不止32個元素, 因為有些個中斷是共用同一個位, 如 EINT8~23. 所以當 asm_do_IRQ 的 irq 是對應于"一組"子中斷的時候, irq_desc[irq].handle_irq 函數還需要判斷出到底是哪個子中斷申請的中斷, 假設該子中斷號為irqno 那么繼續調用 irq_decs[irqno].handle_irq 來處理
以外部中斷EINT8~23為例來講解中斷調用的詳細過程:
1 當EINT8~23 中任意一個中斷觸發的時候, INTOFFSET 寄存器的值都是 5. asm_do_IRQ 的 irq 就是IRQ_EINT0+5 即 IRQ_EINT8t23, 然后調用 irq_desc[IRQ_EINT0t23].handle_irq 來處理
2 irq_desc[IRQ_EINT8t23].handle_irq 設置是在中斷初始化的時候完成的: init_IRQ -> init_arch_irq() 該函數指針在linux啟動的時候被賦值為 s3c24xx_init_irq ->
file: (linux)/arch/plat-s3c24xx/irq.c set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7); set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8); //irq_desc[IRQ_EINT8t23].handle_irq 設置為 s3c_irq_demux_extint8 set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0); set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1); set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2); set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);
接下來看看 s3c_irq_demux_extint8 做了些什么
static void s3c_irq_demux_extint8(unsigned int irq, struct irq_desc *desc){ unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND); unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK); eintpnd &= ~eintmsk; eintpnd &= ~0xff; /* ignore lower irqs */ /* we may as well handle all the pending IRQs here */ while (eintpnd) { irq = __ffs(eintpnd); eintpnd &= ~(1<<irq); irq += (IRQ_EINT4 - 4); desc_handle_irq(irq, irq_desc + irq); } }
他首先讀取了EINTPEND/EINTMASK寄存器, 查找出具體發生的中斷源, 然后重新計算中斷號, 最后使用新計算出的這個中斷號調用 irq_desc[新中斷號].handle_irq
s3c_irq_demux_extint8 與 handle_edge_irq / handle_level_irq 是什么關系?
s3c_irq_demux_extint8最后調用 irq_desc[新中斷號].handle_irq 這個入口函數就是在 s3c24xx_init_irq 中定義的中斷入口函數 set_irq_handler(irqno, handle_edge_irq); 中的 handle_edge_irq
上述就是小編為大家分享的什么是linux異常體系結構了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。