在一個(gè)系統(tǒng)中,中斷時(shí)常發(fā)生,而且線程調(diào)度也是由一個(gè)硬件定時(shí)器時(shí)時(shí)刻刻發(fā)出中斷來支撐的??梢哉f中斷就是linux系統(tǒng)的靈魂。
由于中斷來源很多,加上一個(gè)硬件系統(tǒng)中會(huì)存在許多中斷控制器級(jí)聯(lián),因此會(huì)產(chǎn)生中斷管理上的麻煩,Linux管理中斷也是比較復(fù)雜,用到了很多數(shù)據(jù)結(jié)構(gòu),如果一開始就深究這些數(shù)據(jù)結(jié)構(gòu)實(shí)例如何組織,只能是事倍功半,因此先進(jìn)行情景分析,看看當(dāng)一個(gè)中斷在設(shè)備產(chǎn)生后,會(huì)發(fā)生什么事情。
一、中斷控制器的級(jí)聯(lián)情況
下面一張圖可以涵蓋兩種級(jí)聯(lián)情況
圖中的GIC、GPC、GPIO控制器都能作為中斷控制器,其中我認(rèn)為GIC才是硬件上真正意義的中斷控制器,而其他兩個(gè)之所以能被稱為中斷控制器,是因?yàn)樗麄円残枰直嫦乱患?jí)的中斷源。
還可以看到這里基本忽略了SGI和PPI中斷,因?yàn)檫@些中斷很可能不來自于設(shè)備。
一對(duì)一的級(jí)聯(lián)
從GPC到GIC的中斷,是一對(duì)一的。這種有個(gè)特點(diǎn)就是GIC發(fā)生了中斷后,無需判斷是下一級(jí)哪個(gè)GPC的哪個(gè)中斷發(fā)生了。舉個(gè)例子,如果是GIC 的128號(hào)中斷發(fā)生了,無需再判斷是GPC哪個(gè)中斷傳遞給了GIC的128號(hào)中斷,直接就能斷定是GPC對(duì)應(yīng)的128號(hào)中斷產(chǎn)生了。
多對(duì)一的級(jí)聯(lián)
從GPIO到GPC的5號(hào)中斷,是多對(duì)一的關(guān)系。這個(gè)源自于GPIO的一個(gè)特點(diǎn),就是有可能多個(gè)GPIO信號(hào)只能聯(lián)合產(chǎn)生一個(gè)中斷給到上一級(jí)中斷控制器,這里上一級(jí)的中斷控制器就是GPC。舉個(gè)例子,假如GIC發(fā)生了中斷,讀取GIC寄存器知道發(fā)生了GIC的122號(hào)中斷,那么也就知道了是GPC的5號(hào)中斷產(chǎn)生了,已知GPIO會(huì)導(dǎo)致GPC5號(hào)中斷產(chǎn)生,但是是哪個(gè)GPIO的產(chǎn)生呢?那么就需要在GPIO控制器的驅(qū)動(dòng)程序中分辨一下。
以上兩種級(jí)聯(lián)的情況,決定了當(dāng)一個(gè)中斷產(chǎn)生后,途徑的中斷控制器的中斷執(zhí)行過程。
二、linux對(duì)中斷號(hào)的管理
通常如果一個(gè)設(shè)備要作為一個(gè)中斷源,會(huì)在設(shè)備樹指定使用到哪個(gè)中斷控制器,使用到這個(gè)中斷控制器里面的第x個(gè)中斷。但是這個(gè)數(shù)字并不能作為唯一標(biāo)識(shí)這個(gè)中斷源的號(hào)碼,原因在于同一個(gè)中斷源,在gpio上會(huì)表示x號(hào)中斷,而在gpc上面,則可能是y號(hào)中斷。
舉個(gè)例子,比如有個(gè)按鍵key使用到了gpio中斷號(hào)為4的,gpio控制器會(huì)向上gpc產(chǎn)生一個(gè)的中斷號(hào)為5,同一個(gè)中斷,一下子從4變成5,那么我們指定的4 和5都具有很強(qiáng)的硬件關(guān)系,并不能給到linux去標(biāo)識(shí)唯一的中斷。
因此,linux中的中斷號(hào)稱為linux irq,是一個(gè)系統(tǒng)軟件上的索引。在編寫設(shè)備驅(qū)動(dòng)的時(shí)候,都會(huì)根據(jù)這個(gè)irq去申請(qǐng)中斷并且注冊(cè)中斷服務(wù)函數(shù)。
linux irq如何轉(zhuǎn)換?
linux irq實(shí)際上是在系統(tǒng)初始化,在解析設(shè)備樹的時(shí)候根據(jù)在設(shè)備樹定義的中斷信息將和硬件相關(guān)的中斷號(hào)轉(zhuǎn)化成linux irq。
以上是對(duì)于按鍵編寫的設(shè)備樹節(jié)點(diǎn),使用到GPIO1中斷控制器,并且用到了GPIO的18號(hào)中斷,觸發(fā)中斷類型是雙邊沿。那么在解析設(shè)備樹的時(shí)候,18的硬件中斷號(hào)就會(huì)被轉(zhuǎn)換成唯一標(biāo)識(shí)這個(gè)中斷的linux irq。至于解析過程涉及代碼比較多,這里只是情景分析,就不詳細(xì)追代碼了。
三、中斷描述符
這里還要介紹一下中斷描述符這個(gè)數(shù)據(jù)結(jié)構(gòu)。一個(gè)linux irq對(duì)應(yīng)一個(gè)中斷描述符irq_desc,通過linux irq就能找到對(duì)應(yīng)的中斷描述符。看一下這個(gè)數(shù)據(jù)結(jié)構(gòu)可以大概知道中斷描述符有什么用
irq_desc
struct irq_desc {
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler;
#endif
struct irqaction *action; /* IRQ action list */
unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;
atomic_t threads_handled;
int threads_handled_last;
raw_spinlock_t lock;
struct cpumask *percpu_enabled;
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint;
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask;
#endif
#endif
unsigned long threads_oneshot;
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
#ifdef CONFIG_PM_SLEEP
unsigned int nr_actions;
unsigned int no_suspend_depth;
unsigned int cond_suspend_depth;
unsigned int force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;