国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開(kāi)通VIP
linux中斷源碼分析3/4--中斷發(fā)生
http://blog.chinaunix.net/uid-26772321-id-5053324.html2015
回顧
上篇文章已經(jīng)描述了中斷描述符表和中斷描述符數(shù)組的初始化,由于在初始化期間系統(tǒng)關(guān)閉了中斷(通過(guò)設(shè)置CPU的EFLAGS寄存器的IF標(biāo)志位為0),當(dāng)整個(gè)中斷和異常的初始化完成后,系統(tǒng)會(huì)開(kāi)啟中斷(設(shè)置CPU的EFLAGS寄存器的IF標(biāo)志位為1),此時(shí)整個(gè)系統(tǒng)的中斷已經(jīng)開(kāi)始可以使用了。本篇文章我們具體研究一次典型中斷發(fā)生時(shí)的運(yùn)行流程。
禁止調(diào)度和搶占
首先我們需要了解,當(dāng)系統(tǒng)處于中斷上下文時(shí),是禁止發(fā)生調(diào)度和搶占的。進(jìn)程的thread_info中有個(gè)preempt_count成員變量,其作為一個(gè)變量,包含了3個(gè)計(jì)數(shù)器和一個(gè)標(biāo)志位,如下:
描述
解釋
0~7
搶占計(jì)數(shù)器
也可以說(shuō)是鎖占有數(shù)
8~15
軟中斷計(jì)數(shù)器
記錄軟中斷被禁用次數(shù),0表示可以進(jìn)行軟中斷
16~27
硬中斷計(jì)數(shù)器
表示中斷處理嵌套次數(shù),irq_enter()增加它,irq_exit()減少它
28
PREEMPT_ACTIVE標(biāo)志
表明正在進(jìn)行內(nèi)核搶占,設(shè)置此標(biāo)志也禁止了搶占
當(dāng)進(jìn)入到中斷時(shí),中斷處理程序會(huì)調(diào)用irq_enter()函數(shù)禁止搶占和調(diào)度。當(dāng)中斷退出時(shí),會(huì)通過(guò)irq_exit()減少其硬件計(jì)數(shù)器。我們需要清楚的就是,無(wú)論系統(tǒng)處于硬中斷還是軟中斷,調(diào)度和搶占都是被禁止的。
中斷產(chǎn)生
我們需要先明確一下,中斷控制器與CPU相連的三種線:INTR、數(shù)據(jù)線、INTA。
在硬件電路中,中斷的產(chǎn)生發(fā)生一般只有兩種,分別是:電平觸發(fā)方式和邊沿觸發(fā)方式。當(dāng)一個(gè)外部設(shè)備產(chǎn)生中斷,中斷信號(hào)會(huì)沿著中斷線到達(dá)中斷控制器。中斷控制器接收到該外部設(shè)備的中斷信號(hào)后首先會(huì)檢測(cè)自己的中斷屏蔽寄存器是否屏蔽該中斷。如果沒(méi)有,則設(shè)置中斷請(qǐng)求寄存器中中斷向量號(hào)對(duì)應(yīng)的位,并將INTR拉高用于通知CPU,CPU每當(dāng)執(zhí)行完一條指令時(shí)都會(huì)去檢查INTR引腳是否有信號(hào)(這是CPU自動(dòng)進(jìn)行的),如果有信號(hào),CPU還會(huì)檢查EFLAGS寄存器的IF標(biāo)志位是否禁止了中斷(IF = 0),如果CPU未禁止中斷,CPU會(huì)自動(dòng)通過(guò)INTA信號(hào)線應(yīng)答中斷控制器。CPU再次通過(guò)INTA信號(hào)線通知中斷控制器,此時(shí)中斷控制器會(huì)把中斷向量號(hào)送到數(shù)據(jù)線上,CPU讀取數(shù)據(jù)線獲取中斷向量號(hào)。到這里實(shí)際上中斷向量號(hào)已經(jīng)發(fā)送給CPU了,如果中斷控制器是AEIO模式,則會(huì)自動(dòng)清除中斷向量號(hào)對(duì)應(yīng)的中斷請(qǐng)求寄存器的位,如果是EIO模式,則等待CPU發(fā)送的EIO信號(hào)后在清除中斷向量號(hào)對(duì)應(yīng)的中斷請(qǐng)求寄存器的位。
用步驟描述就是:
中斷控制器收到中斷信號(hào)
中斷控制器檢查中斷屏蔽寄存器是否屏蔽該中斷,若屏蔽直接丟棄
中斷控制器設(shè)置該中斷所在的中斷請(qǐng)求寄存器位
通過(guò)INTR通知CPU
CPU收到INTR信號(hào),檢查是否屏蔽中斷,若屏蔽直接無(wú)視
CPU通過(guò)INTA應(yīng)答中斷控制器
CPU再次通過(guò)INTA應(yīng)答中斷控制器,中斷控制器將中斷向量號(hào)放入數(shù)據(jù)線
CPU讀取數(shù)據(jù)線上的中斷向量號(hào)
若中斷控制器為EIO模式,CPU發(fā)送EIO信號(hào)給中斷控制器,中斷控制器清除中斷向量號(hào)對(duì)應(yīng)的中斷請(qǐng)求寄存器位
SMP系統(tǒng)
在SMP系統(tǒng),也就是多核情況下,外部的中斷控制器有可能會(huì)于多個(gè)CPU相連,這時(shí)候當(dāng)一個(gè)中斷產(chǎn)生時(shí),中斷控制器有兩種方式將此中斷送到CPU上,分別是靜態(tài)分發(fā)和動(dòng)態(tài)分發(fā)。區(qū)別就是靜態(tài)分發(fā)設(shè)置了指定中斷送往指定的一個(gè)或多個(gè)CPU上。動(dòng)態(tài)分發(fā)則是由中斷控制器控制中斷應(yīng)該發(fā)往哪個(gè)CPU或CPU組。
CPU已經(jīng)接收到了中斷信號(hào)以及中斷向量號(hào)。此時(shí)CPU會(huì)自動(dòng)跳轉(zhuǎn)到中斷描述符表地址,以中斷向量號(hào)作為一個(gè)偏移量,直接訪問(wèn)中斷向量號(hào)對(duì)應(yīng)的門描述符。在門描述符中,有個(gè)特權(quán)級(jí)(DPL),系統(tǒng)會(huì)先檢查這個(gè)位,然后清除EFLAGS的IF標(biāo)志位(這也說(shuō)明了發(fā)發(fā)生中斷時(shí)實(shí)際上CPU是禁止其他可屏蔽中斷的),之后轉(zhuǎn)到描述符中的中斷處理程序中。在上一篇文章我們知道,所有的中斷門描述符的中斷處理程序都被初始化成了interrupt[i],它是一段匯編代碼。
interrupt[i]
interrupt[i]的每個(gè)元素都相同,執(zhí)行相同的匯編代碼,這段匯編代碼實(shí)際上很簡(jiǎn)單,它主要工作就是將中斷向量號(hào)和被中斷上下文(進(jìn)程上下文或者中斷上下文)保存到棧中,最后調(diào)用do_IRQ函數(shù)。
# 代碼地址:arch/x86/kernel/entry_32.S
# 開(kāi)始
1: pushl_cfi $(~vector+0x80) /* Note: always in signed byte range */ # 先會(huì)執(zhí)行這一句,將中斷向量號(hào)取反然后加上0x80壓入棧中
.if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
jmp 2f # 數(shù)字定義的標(biāo)號(hào)為臨時(shí)標(biāo)號(hào),可以任意重復(fù)定義,例如:"2f"代表正向第一次出現(xiàn)的標(biāo)號(hào)"2:",3b代表反向第一次出現(xiàn)的標(biāo)號(hào)"3:"
.endif
.previous # .previous使匯編器返回到該自定義段之前的段進(jìn)行匯編,則回到上面的數(shù)據(jù)段
.long 1b # 在數(shù)據(jù)段中執(zhí)行標(biāo)號(hào)1的操作
.section .entry.text, "ax" # 回到代碼段
vector=vector+1
.endif
.endr
2: jmp common_interrupt
common_interrupt:
ASM_CLAC
addl $-0x80,(%esp) # 此時(shí)棧頂是(~vector + 0x80),這里再加上-0x80,實(shí)際就是中斷向量號(hào)取反,用于區(qū)別系統(tǒng)調(diào)用,系統(tǒng)調(diào)用是正數(shù),中斷向量是負(fù)數(shù)
SAVE_ALL # 保存現(xiàn)場(chǎng),將寄存器值壓入棧中
TRACE_IRQS_OFF # 關(guān)閉中斷跟蹤
movl %esp,%eax # 將棧指針保存到eax寄存器,供do_IRQ使用
call do_IRQ # 調(diào)用do_IRQ
jmp ret_from_intr # 跳轉(zhuǎn)到ret_from_intr,進(jìn)行中斷返回的一些處理
ENDPROC(common_interrupt)
CFI_ENDPROC
do_IRQ
這是中斷處理的核心函數(shù),來(lái)到這里時(shí),系統(tǒng)已經(jīng)做了兩件事
系統(tǒng)屏蔽了所有可屏蔽中斷(清除了CPU的IF標(biāo)志位,由CPU自動(dòng)完成)
將中斷向量號(hào)和所有寄存器值保存到內(nèi)核棧中
在do_IRQ中,首先會(huì)添加硬中斷計(jì)數(shù)器,此行為導(dǎo)致了中斷期間禁止調(diào)度發(fā)送,此后會(huì)根據(jù)中斷向量號(hào)從vector_irq[]數(shù)組中獲取對(duì)應(yīng)的中斷號(hào),并調(diào)用handle_irq()函數(shù)出來(lái)該中斷號(hào)對(duì)應(yīng)的中斷出來(lái)例程。
__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
/* 將棧頂?shù)刂繁4娴饺肿兞縚_irq_regs中,old_regs用于保存現(xiàn)在的__irq_regs值,這一行代碼很重要,實(shí)現(xiàn)了嵌套中斷情況下的現(xiàn)場(chǎng)保存與還原 */
struct pt_regs *old_regs = set_irq_regs(regs);
/* 獲取中斷向量號(hào),因?yàn)橹袛嘞蛄刻?hào)是以取反方式保存的,這里再次取反 */
unsigned vector = ~regs->orig_ax;
/* 中斷向量號(hào) */
unsigned irq;
/* 硬中斷計(jì)數(shù)器增加,硬中斷計(jì)數(shù)器保存在preempt_count */
irq_enter();
/* 這里開(kāi)始禁止調(diào)度,因?yàn)閜reempt_count不為0 */
/* 退出idle進(jìn)程(如果當(dāng)前進(jìn)程是idle進(jìn)程的情況下) */
exit_idle();
/* 根據(jù)中斷向量號(hào)獲取中斷號(hào) */
irq = __this_cpu_read(vector_irq[vector]);
/* 主要函數(shù)是handle_irq,進(jìn)行中斷服務(wù)例程的處理 */
if (!handle_irq(irq, regs)) {
/* EIO模式的應(yīng)答 */
ack_APIC_irq();
/* 該中斷號(hào)并沒(méi)有發(fā)生過(guò)多次觸發(fā) */
if (irq != VECTOR_RETRIGGERED) {
pr_emerg_ratelimited("%s: %d.%d No irq handler for vector (irq %d)\n",
__func__, smp_processor_id(),
vector, irq);
} else {
/* 將此中斷向量號(hào)對(duì)應(yīng)的vector_irq設(shè)置為未定義 */
__this_cpu_write(vector_irq[vector], VECTOR_UNDEFINED);
}
}
/* 硬中斷計(jì)數(shù)器減少 */
irq_exit();
/* 這里開(kāi)始允許調(diào)度 */
/* 恢復(fù)原來(lái)的__irq_regs值 */
set_irq_regs(old_regs);
return 1;
}
do_IRQ()函數(shù)中最重要的就是handle_irq()處理了,我們看看
bool handle_irq(unsigned irq, struct pt_regs *regs)
{
struct irq_desc *desc;
int overflow;
/* 檢查棧是否溢出 */
overflow = check_stack_overflow();
/* 獲取中斷描述符 */
desc = irq_to_desc(irq);
/* 檢查是否獲取到中斷描述符 */
if (unlikely(!desc))
return false;
/* 檢查使用的棧,有兩種情況,如果進(jìn)程的內(nèi)核棧配置為8K,則使用進(jìn)程的內(nèi)核棧,如果為4K,系統(tǒng)會(huì)專門為所有中斷分配一個(gè)4K的棧專門用于硬中斷處理?xiàng)?,一個(gè)4K專門用于軟中斷處理?xiàng)?,還有一個(gè)4K專門用于異常處理?xiàng)?*/
if (user_mode_vm(regs) || !execute_on_irq_stack(overflow, desc, irq)) {
if (unlikely(overflow))
print_stack_overflow();
/* 執(zhí)行handle_irq */
desc->handle_irq(irq, desc);
}
return true;
}
好的,最后執(zhí)行中斷描述符中的handle_irq指針?biāo)负瘮?shù),我們回憶一下,在初始化階段,所有的中斷描述符的handle_irq指針指向了handle_level_irq()函數(shù),文章開(kāi)頭我們也說(shuō)過(guò),中斷產(chǎn)生方式有兩種:一種電平觸發(fā)、一種是邊沿觸發(fā)。handle_level_irq()函數(shù)就是用于處理電平觸發(fā)的情況,系統(tǒng)內(nèi)建了一些handle_irq函數(shù),具體定義在include/linux/irq.h文件中,我們羅列幾種常用的:
handle_simple_irq()  簡(jiǎn)單處理情況處理函數(shù)
handle_level_irq()     電平觸發(fā)方式情況處理函數(shù)
handle_edge_irq()        邊沿觸發(fā)方式情況處理函數(shù)
handle_fasteoi_irq()     用于需要EOI回應(yīng)的中斷控制器
handle_percpu_irq()     此中斷只需要單一CPU響應(yīng)的處理函數(shù)
handle_nested_irq()     用于處理使用線程的嵌套中斷
我們主要看看handle_level_irq()函數(shù)函數(shù),有興趣的朋友也可以看看其他的,因?yàn)橛|發(fā)方式不同,通知中斷控制器、CPU屏蔽、中斷狀態(tài)設(shè)置的時(shí)機(jī)都不同,它們的代碼都在kernel/irq/chip.c中。
/* 用于電平中斷,電平中斷特點(diǎn):
* 只要設(shè)備的中斷請(qǐng)求引腳(中斷線)保持在預(yù)設(shè)的觸發(fā)電平,中斷就會(huì)一直被請(qǐng)求,所以,為了避免同一中斷被重復(fù)響應(yīng),必須在處理中斷前先把mask irq,然后ack irq,以便復(fù)位設(shè)備的中斷請(qǐng)求引腳,響應(yīng)完成后再unmask irq
*/
void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
/* 通知中斷控制器屏蔽該中斷線,并設(shè)置中斷描述符屏蔽該中斷 */
mask_ack_irq(desc);
/* 檢查此irq是否處于運(yùn)行狀態(tài),也就是檢查IRQD_IRQ_INPROGRESS標(biāo)志和IRQD_WAKEUP_ARMED標(biāo)志。大家可以看看,還會(huì)檢查poll */
if (!irq_may_run(desc))
goto out_unlock;
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
/* 增加此中斷號(hào)所在proc中的中斷次數(shù) */
kstat_incr_irqs_this_cpu(irq, desc);
/*
* If its disabled or no action available
* keep it masked and get out of here
*/
/* 判斷IRQ是否有中斷服務(wù)例程(irqaction)和是否被系統(tǒng)禁用 */
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
desc->istate |= IRQS_PENDING;
goto out_unlock;
}
/* 在里面執(zhí)行中斷服務(wù)例程 */
handle_irq_event(desc);
/* 通知中斷控制器恢復(fù)此中斷線 */
cond_unmask_irq(desc);
out_unlock:
raw_spin_unlock(&desc->lock);
}
這個(gè)函數(shù)還是比較簡(jiǎn)單,看handle_irq_event()函數(shù):
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
struct irqaction *action = desc->action;
irqreturn_t ret;
desc->istate &= ~IRQS_PENDING;
/* 設(shè)置該中斷處理正在執(zhí)行,設(shè)置此中斷號(hào)的狀態(tài)為IRQD_IRQ_INPROGRESS */
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock);
/* 主要,具體看 */
ret = handle_irq_event_percpu(desc, action);
raw_spin_lock(&desc->lock);
/* 取消此中斷號(hào)的IRQD_IRQ_INPROGRESS狀態(tài) */
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
return ret;
}
再看handle_irq_event_percpu()函數(shù):
irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
irqreturn_t retval = IRQ_NONE;
unsigned int flags = 0, irq = desc->irq_data.irq;
/* desc中的action是一個(gè)鏈表,每個(gè)節(jié)點(diǎn)包含一個(gè)處理函數(shù),這個(gè)循環(huán)是遍歷一次action鏈表,分別執(zhí)行一次它們的處理函數(shù) */
do {
irqreturn_t res;
/* 用于中斷跟蹤 */
trace_irq_handler_entry(irq, action);
/* 執(zhí)行處理,在驅(qū)動(dòng)中定義的中斷處理最后就是被賦值到中斷服務(wù)例程action的handler指針上,這里就執(zhí)行了驅(qū)動(dòng)中定義的中斷處理 */
res = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
irq, action->handler))
local_irq_disable();
/* 中斷返回值處理 */
switch (res) {
/* 需要喚醒該中斷處理例程的中斷線程 */
case IRQ_WAKE_THREAD:
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
/* 該中斷服務(wù)例程沒(méi)有中斷線程 */
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
/* 喚醒線程 */
__irq_wake_thread(desc, action);
/* Fall through to add to randomness */
case IRQ_HANDLED:
flags |= action->flags;
break;
default:
break;
}
retval |= res;
/* 下一個(gè)中斷服務(wù)例程 */
action = action->next;
} while (action);
add_interrupt_randomness(irq, flags);
/* 中斷調(diào)試會(huì)使用 */
if (!noirqdebug)
note_interrupt(irq, desc, retval);
return retval;
}
其實(shí)代碼上很簡(jiǎn)單,我們需要注意幾個(gè)屏蔽中斷的方式:清除EFLAGS的IF標(biāo)志、通知中斷控制器屏蔽指定中斷、設(shè)置中斷描述符的狀態(tài)為IRQD_IRQ_INPROGRESS。在上述代碼中這三種狀態(tài)都使用到了,我們具體解釋一下:
清除EFLAGS的IF標(biāo)志:CPU禁止中斷,當(dāng)CPU進(jìn)入到中斷處理時(shí)自動(dòng)會(huì)清除EFLAGS的IF標(biāo)志,也就是進(jìn)入中斷處理時(shí)會(huì)自動(dòng)禁止中斷。在SMP系統(tǒng)中,就是單個(gè)CPU禁止中斷。
通知中斷控制器屏蔽指定中斷:在中斷控制器處就屏蔽中斷,這樣該中斷產(chǎn)生后并不會(huì)發(fā)到CPU上。在SMP系統(tǒng)中,效果相當(dāng)于所有CPU屏蔽了此中斷。系統(tǒng)在執(zhí)行此中斷的中斷處理函數(shù)才會(huì)要求中斷控制器屏蔽該中斷,所以沒(méi)必要在此中斷的處理過(guò)程中中斷控制器再發(fā)一次中斷信號(hào)給CPU。
設(shè)置中斷描述符的狀態(tài)為IRQD_IRQ_INPROGRESS:在SMP系統(tǒng)中,同一個(gè)中斷信號(hào)有可能發(fā)往多個(gè)CPU,但是中斷處理只應(yīng)該處理一次,所以設(shè)置狀態(tài)為IRQD_IRQ_INPROGRESS,其他CPU執(zhí)行此中斷時(shí)都會(huì)先檢查此狀態(tài)(可看handle_level_irq()函數(shù))。
所以在SMP系統(tǒng)下,對(duì)于handle_level_irq而言,一次典型的情況是:中斷控制器接收到中斷信號(hào),發(fā)送給一個(gè)或多個(gè)CPU,收到的CPU會(huì)自動(dòng)禁止中斷,并執(zhí)行中斷處理函數(shù),在中斷處理函數(shù)中CPU會(huì)通知中斷控制器屏蔽該中斷,之后當(dāng)執(zhí)行中斷服務(wù)例程時(shí)會(huì)設(shè)置該中斷描述符的狀態(tài)為IRQD_IRQ_INPROGRESS,表明其他CPU如果執(zhí)行該中斷就直接退出,因?yàn)楸綜PU已經(jīng)在處理了。
本文為原創(chuàng),轉(zhuǎn)載請(qǐng)注明:http://blog.chinaunix.net/uid/26772321.html
回顧
上篇文章已經(jīng)描述了中斷描述符表和中斷描述符數(shù)組的初始化,由于在初始化期間系統(tǒng)關(guān)閉了中斷(通過(guò)設(shè)置CPU的EFLAGS寄存器的IF標(biāo)志位為0),當(dāng)整個(gè)中斷和異常的初始化完成后,系統(tǒng)會(huì)開(kāi)啟中斷(設(shè)置CPU的EFLAGS寄存器的IF標(biāo)志位為1),此時(shí)整個(gè)系統(tǒng)的中斷已經(jīng)開(kāi)始可以使用了。本篇文章我們具體研究一次典型中斷發(fā)生時(shí)的運(yùn)行流程。
禁止調(diào)度和搶占
首先我們需要了解,當(dāng)系統(tǒng)處于中斷上下文時(shí),是禁止發(fā)生調(diào)度和搶占的。進(jìn)程的thread_info中有個(gè)preempt_count成員變量,其作為一個(gè)變量,包含了3個(gè)計(jì)數(shù)器和一個(gè)標(biāo)志位,如下:
描述
解釋
0~7
搶占計(jì)數(shù)器
也可以說(shuō)是鎖占有數(shù)
8~15
軟中斷計(jì)數(shù)器
記錄軟中斷被禁用次數(shù),0表示可以進(jìn)行軟中斷
16~27
硬中斷計(jì)數(shù)器
表示中斷處理嵌套次數(shù),irq_enter()增加它,irq_exit()減少它
28
PREEMPT_ACTIVE標(biāo)志
表明正在進(jìn)行內(nèi)核搶占,設(shè)置此標(biāo)志也禁止了搶占
當(dāng)進(jìn)入到中斷時(shí),中斷處理程序會(huì)調(diào)用irq_enter()函數(shù)禁止搶占和調(diào)度。當(dāng)中斷退出時(shí),會(huì)通過(guò)irq_exit()減少其硬件計(jì)數(shù)器。我們需要清楚的就是,無(wú)論系統(tǒng)處于硬中斷還是軟中斷,調(diào)度和搶占都是被禁止的。
中斷產(chǎn)生
我們需要先明確一下,中斷控制器與CPU相連的三種線:INTR、數(shù)據(jù)線、INTA。
在硬件電路中,中斷的產(chǎn)生發(fā)生一般只有兩種,分別是:電平觸發(fā)方式和邊沿觸發(fā)方式。當(dāng)一個(gè)外部設(shè)備產(chǎn)生中斷,中斷信號(hào)會(huì)沿著中斷線到達(dá)中斷控制器。中斷控制器接收到該外部設(shè)備的中斷信號(hào)后首先會(huì)檢測(cè)自己的中斷屏蔽寄存器是否屏蔽該中斷。如果沒(méi)有,則設(shè)置中斷請(qǐng)求寄存器中中斷向量號(hào)對(duì)應(yīng)的位,并將INTR拉高用于通知CPU,CPU每當(dāng)執(zhí)行完一條指令時(shí)都會(huì)去檢查INTR引腳是否有信號(hào)(這是CPU自動(dòng)進(jìn)行的),如果有信號(hào),CPU還會(huì)檢查EFLAGS寄存器的IF標(biāo)志位是否禁止了中斷(IF = 0),如果CPU未禁止中斷,CPU會(huì)自動(dòng)通過(guò)INTA信號(hào)線應(yīng)答中斷控制器。CPU再次通過(guò)INTA信號(hào)線通知中斷控制器,此時(shí)中斷控制器會(huì)把中斷向量號(hào)送到數(shù)據(jù)線上,CPU讀取數(shù)據(jù)線獲取中斷向量號(hào)。到這里實(shí)際上中斷向量號(hào)已經(jīng)發(fā)送給CPU了,如果中斷控制器是AEIO模式,則會(huì)自動(dòng)清除中斷向量號(hào)對(duì)應(yīng)的中斷請(qǐng)求寄存器的位,如果是EIO模式,則等待CPU發(fā)送的EIO信號(hào)后在清除中斷向量號(hào)對(duì)應(yīng)的中斷請(qǐng)求寄存器的位。
用步驟描述就是:
中斷控制器收到中斷信號(hào)
中斷控制器檢查中斷屏蔽寄存器是否屏蔽該中斷,若屏蔽直接丟棄
中斷控制器設(shè)置該中斷所在的中斷請(qǐng)求寄存器位
通過(guò)INTR通知CPU
CPU收到INTR信號(hào),檢查是否屏蔽中斷,若屏蔽直接無(wú)視
CPU通過(guò)INTA應(yīng)答中斷控制器
CPU再次通過(guò)INTA應(yīng)答中斷控制器,中斷控制器將中斷向量號(hào)放入數(shù)據(jù)線
CPU讀取數(shù)據(jù)線上的中斷向量號(hào)
若中斷控制器為EIO模式,CPU發(fā)送EIO信號(hào)給中斷控制器,中斷控制器清除中斷向量號(hào)對(duì)應(yīng)的中斷請(qǐng)求寄存器位
SMP系統(tǒng)
在SMP系統(tǒng),也就是多核情況下,外部的中斷控制器有可能會(huì)于多個(gè)CPU相連,這時(shí)候當(dāng)一個(gè)中斷產(chǎn)生時(shí),中斷控制器有兩種方式將此中斷送到CPU上,分別是靜態(tài)分發(fā)和動(dòng)態(tài)分發(fā)。區(qū)別就是靜態(tài)分發(fā)設(shè)置了指定中斷送往指定的一個(gè)或多個(gè)CPU上。動(dòng)態(tài)分發(fā)則是由中斷控制器控制中斷應(yīng)該發(fā)往哪個(gè)CPU或CPU組。
CPU已經(jīng)接收到了中斷信號(hào)以及中斷向量號(hào)。此時(shí)CPU會(huì)自動(dòng)跳轉(zhuǎn)到中斷描述符表地址,以中斷向量號(hào)作為一個(gè)偏移量,直接訪問(wèn)中斷向量號(hào)對(duì)應(yīng)的門描述符。在門描述符中,有個(gè)特權(quán)級(jí)(DPL),系統(tǒng)會(huì)先檢查這個(gè)位,然后清除EFLAGS的IF標(biāo)志位(這也說(shuō)明了發(fā)發(fā)生中斷時(shí)實(shí)際上CPU是禁止其他可屏蔽中斷的),之后轉(zhuǎn)到描述符中的中斷處理程序中。在上一篇文章我們知道,所有的中斷門描述符的中斷處理程序都被初始化成了interrupt[i],它是一段匯編代碼。
interrupt[i]
interrupt[i]的每個(gè)元素都相同,執(zhí)行相同的匯編代碼,這段匯編代碼實(shí)際上很簡(jiǎn)單,它主要工作就是將中斷向量號(hào)和被中斷上下文(進(jìn)程上下文或者中斷上下文)保存到棧中,最后調(diào)用do_IRQ函數(shù)。
# 代碼地址:arch/x86/kernel/entry_32.S
# 開(kāi)始
1: pushl_cfi $(~vector+0x80) /* Note: always in signed byte range */ # 先會(huì)執(zhí)行這一句,將中斷向量號(hào)取反然后加上0x80壓入棧中
.if ((vector-FIRST_EXTERNAL_VECTOR)%7) <> 6
jmp 2f # 數(shù)字定義的標(biāo)號(hào)為臨時(shí)標(biāo)號(hào),可以任意重復(fù)定義,例如:"2f"代表正向第一次出現(xiàn)的標(biāo)號(hào)"2:",3b代表反向第一次出現(xiàn)的標(biāo)號(hào)"3:"
.endif
.previous # .previous使匯編器返回到該自定義段之前的段進(jìn)行匯編,則回到上面的數(shù)據(jù)段
.long 1b # 在數(shù)據(jù)段中執(zhí)行標(biāo)號(hào)1的操作
.section .entry.text, "ax" # 回到代碼段
vector=vector+1
.endif
.endr
2: jmp common_interrupt
common_interrupt:
ASM_CLAC
addl $-0x80,(%esp) # 此時(shí)棧頂是(~vector + 0x80),這里再加上-0x80,實(shí)際就是中斷向量號(hào)取反,用于區(qū)別系統(tǒng)調(diào)用,系統(tǒng)調(diào)用是正數(shù),中斷向量是負(fù)數(shù)
SAVE_ALL # 保存現(xiàn)場(chǎng),將寄存器值壓入棧中
TRACE_IRQS_OFF # 關(guān)閉中斷跟蹤
movl %esp,%eax # 將棧指針保存到eax寄存器,供do_IRQ使用
call do_IRQ # 調(diào)用do_IRQ
jmp ret_from_intr # 跳轉(zhuǎn)到ret_from_intr,進(jìn)行中斷返回的一些處理
ENDPROC(common_interrupt)
CFI_ENDPROC
do_IRQ
這是中斷處理的核心函數(shù),來(lái)到這里時(shí),系統(tǒng)已經(jīng)做了兩件事
系統(tǒng)屏蔽了所有可屏蔽中斷(清除了CPU的IF標(biāo)志位,由CPU自動(dòng)完成)
將中斷向量號(hào)和所有寄存器值保存到內(nèi)核棧中
在do_IRQ中,首先會(huì)添加硬中斷計(jì)數(shù)器,此行為導(dǎo)致了中斷期間禁止調(diào)度發(fā)送,此后會(huì)根據(jù)中斷向量號(hào)從vector_irq[]數(shù)組中獲取對(duì)應(yīng)的中斷號(hào),并調(diào)用handle_irq()函數(shù)出來(lái)該中斷號(hào)對(duì)應(yīng)的中斷出來(lái)例程。
__visible unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
/* 將棧頂?shù)刂繁4娴饺肿兞縚_irq_regs中,old_regs用于保存現(xiàn)在的__irq_regs值,這一行代碼很重要,實(shí)現(xiàn)了嵌套中斷情況下的現(xiàn)場(chǎng)保存與還原 */
struct pt_regs *old_regs = set_irq_regs(regs);
/* 獲取中斷向量號(hào),因?yàn)橹袛嘞蛄刻?hào)是以取反方式保存的,這里再次取反 */
unsigned vector = ~regs->orig_ax;
/* 中斷向量號(hào) */
unsigned irq;
/* 硬中斷計(jì)數(shù)器增加,硬中斷計(jì)數(shù)器保存在preempt_count */
irq_enter();
/* 這里開(kāi)始禁止調(diào)度,因?yàn)閜reempt_count不為0 */
/* 退出idle進(jìn)程(如果當(dāng)前進(jìn)程是idle進(jìn)程的情況下) */
exit_idle();
/* 根據(jù)中斷向量號(hào)獲取中斷號(hào) */
irq = __this_cpu_read(vector_irq[vector]);
/* 主要函數(shù)是handle_irq,進(jìn)行中斷服務(wù)例程的處理 */
if (!handle_irq(irq, regs)) {
/* EIO模式的應(yīng)答 */
ack_APIC_irq();
/* 該中斷號(hào)并沒(méi)有發(fā)生過(guò)多次觸發(fā) */
if (irq != VECTOR_RETRIGGERED) {
pr_emerg_ratelimited("%s: %d.%d No irq handler for vector (irq %d)\n",
__func__, smp_processor_id(),
vector, irq);
} else {
/* 將此中斷向量號(hào)對(duì)應(yīng)的vector_irq設(shè)置為未定義 */
__this_cpu_write(vector_irq[vector], VECTOR_UNDEFINED);
}
}
/* 硬中斷計(jì)數(shù)器減少 */
irq_exit();
/* 這里開(kāi)始允許調(diào)度 */
/* 恢復(fù)原來(lái)的__irq_regs值 */
set_irq_regs(old_regs);
return 1;
}
do_IRQ()函數(shù)中最重要的就是handle_irq()處理了,我們看看
bool handle_irq(unsigned irq, struct pt_regs *regs)
{
struct irq_desc *desc;
int overflow;
/* 檢查棧是否溢出 */
overflow = check_stack_overflow();
/* 獲取中斷描述符 */
desc = irq_to_desc(irq);
/* 檢查是否獲取到中斷描述符 */
if (unlikely(!desc))
return false;
/* 檢查使用的棧,有兩種情況,如果進(jìn)程的內(nèi)核棧配置為8K,則使用進(jìn)程的內(nèi)核棧,如果為4K,系統(tǒng)會(huì)專門為所有中斷分配一個(gè)4K的棧專門用于硬中斷處理?xiàng)?,一個(gè)4K專門用于軟中斷處理?xiàng)?,還有一個(gè)4K專門用于異常處理?xiàng)?*/
if (user_mode_vm(regs) || !execute_on_irq_stack(overflow, desc, irq)) {
if (unlikely(overflow))
print_stack_overflow();
/* 執(zhí)行handle_irq */
desc->handle_irq(irq, desc);
}
return true;
}
好的,最后執(zhí)行中斷描述符中的handle_irq指針?biāo)负瘮?shù),我們回憶一下,在初始化階段,所有的中斷描述符的handle_irq指針指向了handle_level_irq()函數(shù),文章開(kāi)頭我們也說(shuō)過(guò),中斷產(chǎn)生方式有兩種:一種電平觸發(fā)、一種是邊沿觸發(fā)。handle_level_irq()函數(shù)就是用于處理電平觸發(fā)的情況,系統(tǒng)內(nèi)建了一些handle_irq函數(shù),具體定義在include/linux/irq.h文件中,我們羅列幾種常用的:
handle_simple_irq()  簡(jiǎn)單處理情況處理函數(shù)
handle_level_irq()     電平觸發(fā)方式情況處理函數(shù)
handle_edge_irq()        邊沿觸發(fā)方式情況處理函數(shù)
handle_fasteoi_irq()     用于需要EOI回應(yīng)的中斷控制器
handle_percpu_irq()     此中斷只需要單一CPU響應(yīng)的處理函數(shù)
handle_nested_irq()     用于處理使用線程的嵌套中斷
我們主要看看handle_level_irq()函數(shù)函數(shù),有興趣的朋友也可以看看其他的,因?yàn)橛|發(fā)方式不同,通知中斷控制器、CPU屏蔽、中斷狀態(tài)設(shè)置的時(shí)機(jī)都不同,它們的代碼都在kernel/irq/chip.c中。
/* 用于電平中斷,電平中斷特點(diǎn):
* 只要設(shè)備的中斷請(qǐng)求引腳(中斷線)保持在預(yù)設(shè)的觸發(fā)電平,中斷就會(huì)一直被請(qǐng)求,所以,為了避免同一中斷被重復(fù)響應(yīng),必須在處理中斷前先把mask irq,然后ack irq,以便復(fù)位設(shè)備的中斷請(qǐng)求引腳,響應(yīng)完成后再unmask irq
*/
void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
raw_spin_lock(&desc->lock);
/* 通知中斷控制器屏蔽該中斷線,并設(shè)置中斷描述符屏蔽該中斷 */
mask_ack_irq(desc);
/* 檢查此irq是否處于運(yùn)行狀態(tài),也就是檢查IRQD_IRQ_INPROGRESS標(biāo)志和IRQD_WAKEUP_ARMED標(biāo)志。大家可以看看,還會(huì)檢查poll */
if (!irq_may_run(desc))
goto out_unlock;
desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
/* 增加此中斷號(hào)所在proc中的中斷次數(shù) */
kstat_incr_irqs_this_cpu(irq, desc);
/*
* If its disabled or no action available
* keep it masked and get out of here
*/
/* 判斷IRQ是否有中斷服務(wù)例程(irqaction)和是否被系統(tǒng)禁用 */
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) {
desc->istate |= IRQS_PENDING;
goto out_unlock;
}
/* 在里面執(zhí)行中斷服務(wù)例程 */
handle_irq_event(desc);
/* 通知中斷控制器恢復(fù)此中斷線 */
cond_unmask_irq(desc);
out_unlock:
raw_spin_unlock(&desc->lock);
}
這個(gè)函數(shù)還是比較簡(jiǎn)單,看handle_irq_event()函數(shù):
irqreturn_t handle_irq_event(struct irq_desc *desc)
{
struct irqaction *action = desc->action;
irqreturn_t ret;
desc->istate &= ~IRQS_PENDING;
/* 設(shè)置該中斷處理正在執(zhí)行,設(shè)置此中斷號(hào)的狀態(tài)為IRQD_IRQ_INPROGRESS */
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
raw_spin_unlock(&desc->lock);
/* 主要,具體看 */
ret = handle_irq_event_percpu(desc, action);
raw_spin_lock(&desc->lock);
/* 取消此中斷號(hào)的IRQD_IRQ_INPROGRESS狀態(tài) */
irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
return ret;
}
再看handle_irq_event_percpu()函數(shù):
irqreturn_t
handle_irq_event_percpu(struct irq_desc *desc, struct irqaction *action)
{
irqreturn_t retval = IRQ_NONE;
unsigned int flags = 0, irq = desc->irq_data.irq;
/* desc中的action是一個(gè)鏈表,每個(gè)節(jié)點(diǎn)包含一個(gè)處理函數(shù),這個(gè)循環(huán)是遍歷一次action鏈表,分別執(zhí)行一次它們的處理函數(shù) */
do {
irqreturn_t res;
/* 用于中斷跟蹤 */
trace_irq_handler_entry(irq, action);
/* 執(zhí)行處理,在驅(qū)動(dòng)中定義的中斷處理最后就是被賦值到中斷服務(wù)例程action的handler指針上,這里就執(zhí)行了驅(qū)動(dòng)中定義的中斷處理 */
res = action->handler(irq, action->dev_id);
trace_irq_handler_exit(irq, action, res);
if (WARN_ONCE(!irqs_disabled(),"irq %u handler %pF enabled interrupts\n",
irq, action->handler))
local_irq_disable();
/* 中斷返回值處理 */
switch (res) {
/* 需要喚醒該中斷處理例程的中斷線程 */
case IRQ_WAKE_THREAD:
/*
* Catch drivers which return WAKE_THREAD but
* did not set up a thread function
*/
/* 該中斷服務(wù)例程沒(méi)有中斷線程 */
if (unlikely(!action->thread_fn)) {
warn_no_thread(irq, action);
break;
}
/* 喚醒線程 */
__irq_wake_thread(desc, action);
/* Fall through to add to randomness */
case IRQ_HANDLED:
flags |= action->flags;
break;
default:
break;
}
retval |= res;
/* 下一個(gè)中斷服務(wù)例程 */
action = action->next;
} while (action);
add_interrupt_randomness(irq, flags);
/* 中斷調(diào)試會(huì)使用 */
if (!noirqdebug)
note_interrupt(irq, desc, retval);
return retval;
}
其實(shí)代碼上很簡(jiǎn)單,我們需要注意幾個(gè)屏蔽中斷的方式:清除EFLAGS的IF標(biāo)志、通知中斷控制器屏蔽指定中斷、設(shè)置中斷描述符的狀態(tài)為IRQD_IRQ_INPROGRESS。在上述代碼中這三種狀態(tài)都使用到了,我們具體解釋一下:
清除EFLAGS的IF標(biāo)志:CPU禁止中斷,當(dāng)CPU進(jìn)入到中斷處理時(shí)自動(dòng)會(huì)清除EFLAGS的IF標(biāo)志,也就是進(jìn)入中斷處理時(shí)會(huì)自動(dòng)禁止中斷。在SMP系統(tǒng)中,就是單個(gè)CPU禁止中斷。
通知中斷控制器屏蔽指定中斷:在中斷控制器處就屏蔽中斷,這樣該中斷產(chǎn)生后并不會(huì)發(fā)到CPU上。在SMP系統(tǒng)中,效果相當(dāng)于所有CPU屏蔽了此中斷。系統(tǒng)在執(zhí)行此中斷的中斷處理函數(shù)才會(huì)要求中斷控制器屏蔽該中斷,所以沒(méi)必要在此中斷的處理過(guò)程中中斷控制器再發(fā)一次中斷信號(hào)給CPU。
設(shè)置中斷描述符的狀態(tài)為IRQD_IRQ_INPROGRESS:在SMP系統(tǒng)中,同一個(gè)中斷信號(hào)有可能發(fā)往多個(gè)CPU,但是中斷處理只應(yīng)該處理一次,所以設(shè)置狀態(tài)為IRQD_IRQ_INPROGRESS,其他CPU執(zhí)行此中斷時(shí)都會(huì)先檢查此狀態(tài)(可看handle_level_irq()函數(shù))。
所以在SMP系統(tǒng)下,對(duì)于handle_level_irq而言,一次典型的情況是:中斷控制器接收到中斷信號(hào),發(fā)送給一個(gè)或多個(gè)CPU,收到的CPU會(huì)自動(dòng)禁止中斷,并執(zhí)行中斷處理函數(shù),在中斷處理函數(shù)中CPU會(huì)通知中斷控制器屏蔽該中斷,之后當(dāng)執(zhí)行中斷服務(wù)例程時(shí)會(huì)設(shè)置該中斷描述符的狀態(tài)為IRQD_IRQ_INPROGRESS,表明其他CPU如果執(zhí)行該中斷就直接退出,因?yàn)楸綜PU已經(jīng)在處理了。
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開(kāi)APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Linux中斷(interrupt)子系統(tǒng)之二:arch相關(guān)的硬件封裝層
Linux 內(nèi)核中斷內(nèi)幕
linux kernel的中斷子系統(tǒng)之(四):High level irq event handler
基于ARM Linux中斷、異常的處理分析
3.Linux異常處理結(jié)構(gòu)
Linux中斷實(shí)現(xiàn)淺析
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長(zhǎng)圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服