1. 中斷的概念
中斷是指在CPU正常運行期間,由于內外部事件或由程序預先安排的事件引起的 CPU 暫時停止正在運行的程序,轉而為該內部或外部事件或預先安排的事件服務的程序中去,服務完畢后再返回去繼續運行被暫時中斷的程序。Linux中通常分為外部中斷(又叫硬件中斷)和內部中斷(又叫異常)。
軟件對硬件進行配置后,軟件期望等待硬件的某種狀態(比如,收到了數據),這里有兩種方式,一種是輪詢(polling):CPU 不斷的去讀硬件狀態。另一種是當硬件完成某種事件后,給 CPU 一個中斷,讓 CPU 停下手上的事情,去處理這個中斷。很顯然,中斷的交互方式提高了系統的吞吐。
當 CPU 收到一個中斷 (IRQ)的時候,會去執行該中斷對應的處理函數(ISR)。普通情況下,會有一個中斷向量表,向量表中定義了 CPU 對應的每一個外設資源的中斷處理程序的入口,當發生對應的中斷的時候, CPU 直接跳轉到這個入口執行程序。也就是中斷上下文。(注意:中斷上下文中,不可阻塞睡眠)。
2. Linux 中斷 top/bottom
玩過 MCU 的人都知道,中斷服務程序的設計最好是快速完成任務并退出,因為此刻系統處于被中斷中。但是在 ISR 中又有一些必須完成的事情,比如:清中斷標志,讀/寫數據,寄存器操作等。
在 Linux 中,同樣也是這個要求,希望盡快的完成 ISR。但事與愿違,有些 ISR 中任務繁重,會消耗很多時間,導致響應速度變差。Linux 中針對這種情況,將中斷分為了兩部分:
- 上半部(top half):收到一個中斷,立即執行,有嚴格的時間限制,只做一些必要的工作,比如:應答,復位等。這些工作都是在所有中斷被禁止的情況下完成的。
- 底半部(bottom half):能夠被推遲到后面完成的任務會在底半部進行。在適合的時機,下半部會被開中斷執行。
3. 中斷處理程序
驅動程序可以使用接口:
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
像系統申請注冊一個中斷處理程序。
其中的參數:
中斷標志 flag 的含義:
調用 request _irq 成功執行返回 0。常見錯誤是 -EBUSY,表示給定的中斷線已經在使用(或者沒有指定 IRQF_SHARED)
注意:request_irq 函數可能引起睡眠,所以不允許在中斷上下文或者不允許睡眠的代碼中調用。
釋放中斷:
const void *free_irq(unsigned int irq, void *dev_id)
用于釋放中斷處理函數。
注意:Linux 中的中斷處理程序是無須重入的。當給定的中斷處理程序正在執行的時候,其中斷線在所有的處理器上都會被屏蔽掉,以防在同一個中斷線上又接收到另一個新的中斷。通常情況下,除了該中斷的其他中斷都是打開的,也就是說其他的中斷線上的重點都能夠被處理,但是當前的中斷線總是被禁止的,故,同一個中斷處理程序是絕對不會被自己嵌套的。
4. 中斷上下文
與進程上下文不一樣,內核執行中斷服務程序的時候,處于中斷上下文。中斷處理程序并沒有自己的獨立的棧,而是使用了內核棧,其大小一般是有限制的(32bit 機器 8KB)。所以其必須短小精悍。同時中斷服務程序是打斷了正常的程序流程,這一點上也必須保證快速的執行。同時中斷上下文中是不允許睡眠,阻塞的。
中斷上下文不能睡眠的原因是:
1、 中斷處理的時候,不應該發生進程切換,因為在中斷context中,唯一能打斷當前中斷handler的只有更高優先級的中斷,它不會被進程打斷,如果在 中斷context中休眠,則沒有辦法喚醒它,因為所有的wake_up_xxx都是針對某個進程而言的,而在中斷context中,沒有進程的概念,沒 有一個task_struct(這點對于softirq和tasklet一樣),因此真的休眠了,比如調用了會導致block的例程,內核幾乎肯定會死。
2、schedule()在切換進程時,保存當前的進程上下文(CPU寄存器的值、進程的狀態以及堆棧中的內容),以便以后恢復此進程運行。中斷發生后,內核會先保存當前被中斷的進程上下文(在調用中斷處理程序后恢復);
但在中斷處理程序里,CPU寄存器的值肯定已經變化了吧(最重要的程序計數器PC、堆棧SP等),如果此時因為睡眠或阻塞操作調用了schedule(),則保存的進程上下文就不是當前的進程context了.所以不可以在中斷處理程序中調用schedule()。
3、內核中schedule()函數本身在進來的時候判斷是否處于中斷上下文:
if(unlikely(in_interrupt()))
BUG();
因此,強行調用schedule()的結果就是內核BUG。
4、中斷handler會使用被中斷的進程內核堆棧,但不會對它有任何影響,因為handler使用完后會完全清除它使用的那部分堆棧,恢復被中斷前的原貌。
5、處于中斷context時候,內核是不可搶占的。因此,如果休眠,則內核一定掛起。
5. 舉例
比如 RTC 驅動程序 (drivers/char/rtc.c)。在 RTC 驅動的初始化階段,會調用到 rtc_init 函數:
module_init(rtc_init);
在這個初始化函數中調用到了 request_irq 用于申請中斷資源,并注冊服務程序:
static int __init rtc_init(void)
{
...
rtc_int_handler_ptr = rtc_interrupt;
...
request_irq(RTC_IRQ, rtc_int_handler_ptr, 0, "rtc", NULL)
...
}
RTC_IRQ 是中斷號,和處理器綁定。
rtc_interrupt 是中斷處理程序:
static irqreturn_t rtc_interrupt(int irq, void dev_id)
{
/
- Can be an alarm interrupt, update complete interrupt,
- or a periodic interrupt. We store the status in the
- low byte and the number of interrupts received since
- the last read in the remainder of rtc_irq_data.
*/
spin_lock(&rtc_lock);
rtc_irq_data += 0x100;
rtc_irq_data &= ~0xff;
if (is_hpet_enabled()) {
/*
* In this case it is HPET RTC interrupt handler
* calling us, with the interrupt information
* passed as arg1, instead of irq.
*/
rtc_irq_data |= (unsigned long)irq & 0xF0;
} else {
rtc_irq_data |= (CMOS_READ(RTC_INTR_FLAGS) & 0xF0);
}
if (rtc_status & RTC_TIMER_ON)
mod_timer(&rtc_irq_timer, jiffies + HZ/rtc_freq + 2*HZ/100);
spin_unlock(&rtc_lock);
wake_up_interruptible(&rtc_wait);
kill_fasync(&rtc_async_queue, SIGIO, POLL_IN);
return IRQ_HANDLED;
}
每次收到 RTC 中斷,就會調用進這個函數。
6. 中斷處理流程
發生中斷時,CPU執行異常向量vector_irq的代碼, 即異常向量表中的中斷異常的代碼,它是一個跳轉指令,跳去執行真正的中斷處理程序,在vector_irq里面,最終會調用中斷處理的總入口函數。
C 語言的入口為 :asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
asmlinkage void __exception_irq_entry
asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
handle_IRQ(irq, regs);
}
該函數的入參 irq 為中斷號。
asm_do_IRQ -> handle_IRQ
void handle_IRQ(unsigned int irq, struct pt_regs *regs)
{
__handle_domain_irq(NULL, irq, false, regs);
}
handle_IRQ -> __handle_domain_irq
int __handle_domain_irq(struct irq_domain *domain, unsigned int hwirq,
bool lookup, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
unsigned int irq = hwirq;
int ret = 0;
irq_enter();
#ifdef CONFIG_IRQ_DOMAIN
if (lookup)
irq = irq_find_mapping(domain, hwirq);
#endif
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (unlikely(!irq || irq >= nr_irqs)) {
ack_bad_irq(irq);
ret = -EINVAL;
} else {
generic_handle_irq(irq);
}
irq_exit();
set_irq_regs(old_regs);
return ret;
}
這里請注意:
先調用了 irq_enter 標記進入了硬件中斷:
irq_enter是更新一些系統的統計信息,同時在__irq_enter宏中禁止了進程的搶占。雖然在產生IRQ時,ARM會自動把CPSR中的I位置位,禁止新的IRQ請求,直到中斷控制轉到相應的流控層后才通過local_irq_enable()打開。那為何還要禁止搶占?這是因為要考慮中斷嵌套的問題,一旦流控層或驅動程序主動通過local_irq_enable打開了IRQ,而此時該中斷還沒處理完成,新的irq請求到達,這時代碼會再次進入irq_enter,在本次嵌套中斷返回時,內核不希望進行搶占調度,而是要等到最外層的中斷處理完成后才做出調度動作,所以才有了禁止搶占這一處理
再調用 generic_handle_irq
最后調用 irq_exit 刪除進入硬件中斷的標記
__handle_domain_irq -> generic_handle_irq
int generic_handle_irq(unsigned int irq)
{
struct irq_desc *desc = irq_to_desc(irq);
if (!desc)
return -EINVAL;
generic_handle_irq_desc(desc);
return 0;
}
EXPORT_SYMBOL_GPL(generic_handle_irq);
首先在函數irq_to_desc中根據發生中斷的中斷號,去取出它的 irq_desc 中斷描述結構,然后調用 generic_handle_irq_desc:
static inline void generic_handle_irq_desc(struct irq_desc *desc)
{
desc->handle_irq(desc);
}
這里調用了 handle_irq 函數。
所以,在上述流程中,還需要分析 irq_to_desc 流程:
struct irq_desc *irq_to_desc(unsigned int irq)
{
return (irq < NR_IRQS) ? irq_desc + irq : NULL;
}
EXPORT_SYMBOL(irq_to_desc);
NR_IRQS 是支持的總的中斷個數,當然,irq 不能夠大于這個數目。所以返回 irq_desc + irq。
irq_desc 是一個全局的數組:
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
這里是這個數組的初始化的地方。所有的 handle_irq 函數都被初始化成為了 handle_bad_irq。
細心的觀眾可能發現了,調用這個 desc->handle_irq(desc) 函數,并不是咱們注冊進去的中斷處理函數啊,因為兩個函數的原型定義都不一樣。這個 handle_irq 是 irq_flow_handler_t 類型,而我們注冊進去的服務程序是 irq_handler_t,這兩個明顯不是同一個東西,所以這里我們還需要繼續分析。
6.1 中斷相關的數據結構
Linux 中斷相關的數據結構有 3 個
irq_desc 結構如下
struct irq_desc {
struct irq_common_data irq_common_data;
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;
const struct cpumask *percpu_affinity;
#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
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
struct dentry *debugfs_file;
const char *dev_name;
#endif
#ifdef CONFIG_SPARSE_IRQ
struct rcu_head rcu;
struct kobject kobj;
#endif
struct mutex request_mutex;
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;
irqaction 結構如下:
/**
- struct irqaction - per interrupt action descriptor
- @handler: interrupt handler function
- @name: name of the device
- @dev_id: cookie to identify the device
- @percpu_dev_id: cookie to identify the device
- @next: pointer to the next irqaction for shared interrupts
- @irq: interrupt number
- @flags: flags (see IRQF_* above)
- @thread_fn: interrupt handler function for threaded interrupts
- @thread: thread pointer for threaded interrupts
- @secondary: pointer to secondary irqaction (force threading)
- @thread_flags: flags related to @thread
- @thread_mask: bitmask for keeping track of @thread activity
- @dir: pointer to the proc/irq/NN/name entry
*/
struct irqaction {
irq_handler_t handler;
void *dev_id;
void __percpu *percpu_dev_id;
struct irqaction *next;
irq_handler_t thread_fn;
struct task_struct *thread;
struct irqaction *secondary;
unsigned int irq;
unsigned int flags;
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
irq_chip 描述如下:
/**
struct irq_chip - hardware interrupt chip descriptor
@parent_device: pointer to parent device for irqchip
@name: name for /proc/interrupts
@irq_startup: start up the interrupt (defaults to ->enable if NULL)
@irq_shutdown: shut down the interrupt (defaults to ->disable if NULL)
@irq_enable: enable the interrupt (defaults to chip->unmask if NULL)
@irq_disable: disable the interrupt
@irq_ack: start of a new interrupt
@irq_mask: mask an interrupt source
@irq_mask_ack: ack and mask an interrupt source
@irq_unmask: unmask an interrupt source
@irq_eoi: end of interrupt
@irq_set_affinity: Set the CPU affinity on SMP machines. If the force
argument is true, it tells the driver to
unconditionally apply the affinity setting. Sanity
checks against the supplied affinity mask are not
required. This is used for CPU hotplug where the
target CPU is not yet set in the cpu_online_mask.
@irq_retrigger: resend an IRQ to the CPU
@irq_set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
@irq_set_wake: enable/disable power-management wake-on of an IRQ
@irq_bus_lock: function to lock access to slow bus (i2c) chips
@irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
@irq_cpu_online: configure an interrupt source for a secondary CPU
@irq_cpu_offline: un-configure an interrupt source for a secondary CPU
@irq_suspend: function called from core code on suspend once per
chip, when one or more interrupts are installed
@irq_resume: function called from core code on resume once per chip,
when one ore more interrupts are installed
@irq_pm_shutdown: function called from core code on shutdown once per chip
@irq_calc_mask: Optional function to set irq_data.mask for special cases
@irq_print_chip: optional to print special chip info in show_interrupts
@irq_request_resources: optional to request resources before calling
any other callback related to this irq
@irq_release_resources: optional to release resources acquired with
irq_request_resources
@irq_compose_msi_msg: optional to compose message content for MSI
@irq_write_msi_msg: optional to write message content for MSI
@irq_get_irqchip_state: return the internal state of an interrupt
@irq_set_irqchip_state: set the internal state of a interrupt
@irq_set_vcpu_affinity: optional to target a vCPU in a virtual machine
@ipi_send_single: send a single IPI to destination cpus
@ipi_send_mask: send an IPI to destination cpus in cpumask
@flags: chip specific flags
*/
struct irq_chip {
struct device *parent_device;
const char *name;
unsigned int (*irq_startup)(struct irq_data *data);
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
void (*irq_disable)(struct irq_data *data);void (*irq_ack)(struct irq_data *data);
void (*irq_mask)(struct irq_data *data);
void (*irq_mask_ack)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data);int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
int (*irq_retrigger)(struct irq_data *data);
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
int (*irq_set_wake)(struct irq_data *data, unsigned int on);void (*irq_bus_lock)(struct irq_data *data);
void (*irq_bus_sync_unlock)(struct irq_data *data);void (*irq_cpu_online)(struct irq_data *data);
void (*irq_cpu_offline)(struct irq_data *data);void (*irq_suspend)(struct irq_data *data);
void (*irq_resume)(struct irq_data *data);
void (*irq_pm_shutdown)(struct irq_data *data);void (*irq_calc_mask)(struct irq_data *data);
void (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
int (*irq_request_resources)(struct irq_data *data);
void (*irq_release_resources)(struct irq_data *data);void (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
void (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);int (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
int (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);int (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);
void (*ipi_send_single)(struct irq_data *data, unsigned int cpu);
void (*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);unsigned long flags;
};
irq_chip 是一串和芯片相關的函數指針,這里定義的非常的全面,基本上和 IRQ 相關的可能出現的操作都全部定義進去了,具體根據不同的芯片,需要在不同的芯片的地方去初始化這個結構,然后這個結構會嵌入到通用的 IRQ 處理軟件中去使用,使得軟件處理邏輯和芯片邏輯完全的分開。
好,我們接下來繼續前進。
6.2 初始化 Chip 相關的 IRQ
眾所周知,啟動的時候,C 語言從 start_kernel 開始,在這里面,調用了和 machine 相關的 IRQ 的初始化 init_IRQ():
asmlinkage __visible void __init start_kernel(void)
{
char *command_line;
char *after_dashes;
.....
early_irq_init();
init_IRQ();
.....
}
在 init_IRQ 中,調用了machine_desc->init_irq():
void __init init_IRQ(void)
{
int ret;
if (IS_ENABLED(CONFIG_OF) && !machine_desc->init_irq)
irqchip_init();
else
machine_desc->init_irq();
if (IS_ENABLED(CONFIG_OF) && IS_ENABLED(CONFIG_CACHE_L2X0) &&
(machine_desc->l2c_aux_mask || machine_desc->l2c_aux_val)) {
if (!outer_cache.write_sec)
outer_cache.write_sec = machine_desc->l2c_write_sec;
ret = l2x0_of_init(machine_desc->l2c_aux_val,
machine_desc->l2c_aux_mask);
if (ret && ret != -ENODEV)
pr_err("L2C: failed to init: %dn", ret);
}
uniphier_cache_init();
}
machine_desc->init_irq() 完成對中斷控制器的初始化,為每個irq_desc結構安裝合適的流控handler,為每個irq_desc結構安裝irq_chip指針,使他指向正確的中斷控制器所對應的irq_chip結構的實例,同時,如果該平臺中的中斷線有多路復用(多個中斷公用一個irq中斷線)的情況,還應該初始化irq_desc中相應的字段和標志,以便實現中斷控制器的級聯。
這里初始化的時候回調用到具體的芯片相關的中斷初始化的地方。
例如:
int __init s5p_init_irq_eint(void)
{
int irq;
for (irq = IRQ_EINT(0); irq <= IRQ_EINT(15); irq++)
irq_set_chip(irq, &s5p_irq_vic_eint);
for (irq = IRQ_EINT(16); irq <= IRQ_EINT(31); irq++) {
irq_set_chip_and_handler(irq, &s5p_irq_eint, handle_level_irq);
set_irq_flags(irq, IRQF_VALID);
}
irq_set_chained_handler(IRQ_EINT16_31, s5p_irq_demux_eint16_31);
return 0;
}
而在這些里面,都回去調用類似于:
void
irq_set_chip_and_handler_name(unsigned int irq, struct irq_chip *chip,
irq_flow_handler_t handle, const char *name);
irq_set_handler(unsigned int irq, irq_flow_handler_t handle)
{
__irq_set_handler(irq, handle, 0, NULL);
}
static inline void
irq_set_chained_handler(unsigned int irq, irq_flow_handler_t handle)
{
__irq_set_handler(irq, handle, 1, NULL);
}
void
irq_set_chained_handler_and_data(unsigned int irq, irq_flow_handler_t handle,
void *data);
這些函數定義在 include/linux/irq.h 文件。是對芯片初始化的時候可見的 APIs,用于指定中斷“流控”中的 :
irq_flow_handler_t handle
也就是中斷來的時候,最后那個函數調用。
中斷流控函數,分幾種,電平觸發的中斷,邊沿觸發的,等:
/*
- Built-in IRQ handlers for various IRQ types,
- callable via desc->handle_irq()
*/
extern void handle_level_irq(struct irq_desc *desc);
extern void handle_fasteoi_irq(struct irq_desc *desc);
extern void handle_edge_irq(struct irq_desc *desc);
extern void handle_edge_eoi_irq(struct irq_desc *desc);
extern void handle_simple_irq(struct irq_desc *desc);
extern void handle_untracked_irq(struct irq_desc *desc);
extern void handle_percpu_irq(struct irq_desc *desc);
extern void handle_percpu_devid_irq(struct irq_desc *desc);
extern void handle_bad_irq(struct irq_desc *desc);
extern void handle_nested_irq(unsigned int irq);
而在這些處理函數里,會去調用到 :handle_irq_event
比如:
/**
handle_level_irq - Level type irq handler
@desc: the interrupt description structure for this irq
Level type interrupts are active as long as the hardware line has
the active level. This may require to mask the interrupt and unmask
it after the associated handler has acknowledged the device, so the
interrupt line is back to inactive.
*/
void handle_level_irq(struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
mask_ack_irq(desc);if (!irq_may_run(desc))
goto out_unlock;desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
/*
- If its disabled or no action available
- keep it masked and get out of here
*/
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
desc->istate |= IRQS_PENDING;
goto out_unlock;
}
kstat_incr_irqs_this_cpu(desc);
handle_irq_event(desc);cond_unmask_irq(desc);
out_unlock:
raw_spin_unlock(&desc->lock);
}
而這個 handle_irq_event 則是調用了處理,handle_irq_event_percpu:
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
irqreturn_t ret;
desc->istate &= ~IRQS_PENDING;
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock);
ret = handle_irq_event_percpu(desc);
raw_spin_lock(&desc->lock);
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
return ret;
}
handle_irq_event_percpu->__handle_irq_event_percpu-> 【action->handler()】
這里終于看到了調用 的地方了,就是咱們通過 request_irq 注冊進去的函數
7. /proc/interrupts
這個 proc 下放置了對應中斷號的中斷次數和對應的 dev-name
-
cpu
+關注
關注
68文章
11031瀏覽量
215941 -
Linux
+關注
關注
87文章
11456瀏覽量
212750 -
程序
+關注
關注
117文章
3820瀏覽量
82383 -
函數
+關注
關注
3文章
4367瀏覽量
64155
發布評論請先 登錄
Linux內核中斷設計與實現
linux中斷處理機制 中斷處理過程

淺析linux gpio中斷
面向嵌入式Linux系統的軟中斷設計與實現
Linux 2.6 中斷處理原理簡介
linux驅動之中斷處理過程C程序部分
linux中斷處理之IRQ中斷
riscv中的plic中斷處理與eclic詳解

評論