本合集分享的是,我當(dāng)初學(xué)習(xí)Linux驅(qū)動(dòng)的來(lái)時(shí)路——《《驅(qū)動(dòng)之路》開篇:自序&前言》。
正文
在嵌入式Linux開發(fā)中,中斷就像設(shè)備的“緊急呼叫器”——鍵盤按下、串口收數(shù)、ADC采集完成、定時(shí)器溢出,本質(zhì)都是硬件在給CPU打“緊急電話”:“停下手頭的活,先處理我這急事!”
很多開發(fā)者調(diào)試中斷時(shí),總會(huì)陷入困惑:“明明中斷觸發(fā)了,服務(wù)函數(shù)卻沒(méi)反應(yīng)?”“中斷處理完,系統(tǒng)直接卡成PPT?”其實(shí)不是代碼寫得差,而是沒(méi)摸清Linux中斷的“處理規(guī)矩”——它不是“一喊就辦”的直腸子,而是一套環(huán)環(huán)相扣的標(biāo)準(zhǔn)化流程,從“打電話”(觸發(fā))到“掛電話”(恢復(fù)),每一步都有明確分工。
1 中斷的“本質(zhì)”
用一句大白話總結(jié):中斷,就是硬件主動(dòng)給CPU發(fā)“緊急求助信號(hào)”,強(qiáng)行打斷CPU當(dāng)前的工作,讓它優(yōu)先處理緊急事務(wù),等事辦完,再回頭接著干之前的活。
做個(gè)生活化類比,瞬間理解:
CPU = 正在專心寫代碼的程序員;
中斷 = 突然響起的緊急電話(客戶催單、領(lǐng)導(dǎo)查進(jìn)度);
流程對(duì)應(yīng):程序員專心寫代碼(CPU執(zhí)行主程序)--> 電話鈴響(中斷觸發(fā))-->程序員停下手中活,記住自己寫到哪一行(保存現(xiàn)場(chǎng))-->接電話,快速處理緊急事(執(zhí)行中斷處理)-->掛電話,回到之前的位置,繼續(xù)寫代碼(恢復(fù)現(xiàn)場(chǎng)、繼續(xù)執(zhí)行)。
2 中斷兩個(gè)核心
Linux中斷處理不是CPU“單打獨(dú)斗”,需要兩個(gè)核心角色配合,就像“接線員+調(diào)度員”:
IRQ控制器(如GIC,通用中斷控制器):硬件層面的“接線員”,專門接收所有硬件的“緊急電話”,篩選優(yōu)先級(jí),再轉(zhuǎn)給CPU,避免混亂;
內(nèi)核中斷子系統(tǒng):系統(tǒng)層面的“調(diào)度員”,負(fù)責(zé)管理所有中斷,找到對(duì)應(yīng)的“處理人員”(中斷服務(wù)函數(shù)),安排處理順序,確保高效推進(jìn)。
3 Linux中斷處理流程
Linux中斷處理的核心邏輯很簡(jiǎn)單:先快速響應(yīng),再慢慢處理,最后恢復(fù)原樣。以下基于最常用的ARM架構(gòu)(如RK3576開發(fā)板),一步步拆解完整流程。
[1.中斷觸發(fā)]-->硬件喊“救命”(如按鍵按下);
[2.IRQ控制器響應(yīng)] --> 接線員分診:判斷緊急程度(判斷中斷優(yōu)先級(jí));
[3.CPU響應(yīng)中斷] --> 程序員停活,記位置(保存現(xiàn)場(chǎng)——保存在內(nèi)存,稱為棧。);
[4.內(nèi)核中斷調(diào)度] --> 調(diào)度員找處理人員(根據(jù)中斷號(hào)找到對(duì)應(yīng)的中斷服務(wù)函數(shù));
[5.執(zhí)行中斷處理(上下半部)] --> 事辦完了;
[6.恢復(fù)現(xiàn)場(chǎng)] --> 程序員回頭繼續(xù)干活;
[7.退出中斷,等下一個(gè)電話]。
步驟1:中斷觸發(fā)——硬件“打通緊急電話”
這是整個(gè)處理流程的起點(diǎn),也是最基礎(chǔ)的一步。當(dāng)硬件發(fā)生特定事件時(shí),會(huì)主動(dòng)向IRQ控制器發(fā)送“緊急電信號(hào)”,相當(dāng)于打通了“緊急電話”,告訴系統(tǒng)“我有急事,快處理”。
常見(jiàn)觸發(fā)場(chǎng)景:GPIO引腳檢測(cè)到高電平(按鍵按下)、UART接收完1字節(jié)數(shù)據(jù)、ADC采集完一次電壓、定時(shí)器溢出,這些硬件都會(huì)主動(dòng)“打電話”求助。
步驟2:IRQ控制器響應(yīng)——接線員“分診排序”
IRQ控制器這個(gè)“接線員”收到電話后,不會(huì)直接轉(zhuǎn)給CPU,而是先做3件事,避免“亂套”,確保高優(yōu)先級(jí)的事優(yōu)先處理:
確認(rèn)來(lái)電者:搞清楚是哪個(gè)硬件(比如GPIO1、UART2)打的電話,避免找錯(cuò)“處理人員”;
判斷緊急程度:對(duì)比當(dāng)前所有未處理的“電話”,優(yōu)先轉(zhuǎn)接高優(yōu)先級(jí)中斷(比如硬件故障中斷,比串口接收這種“小事”更緊急);
屏蔽低優(yōu)先級(jí)電話:處理當(dāng)前電話時(shí),暫時(shí)不接同等級(jí)或更低等級(jí)的電話,避免被打斷,確保當(dāng)前緊急事能順利處理。
做完這3件事,“接線員”才會(huì)把電話轉(zhuǎn)給CPU,通知它“有緊急事,快接”。
步驟3:CPU響應(yīng)中斷——程序員“停活記位置”
CPU收到“接線員”的通知后,會(huì)立刻停下當(dāng)前正在做的事(比如執(zhí)行主程序、跑應(yīng)用線程),然后做兩件關(guān)鍵操作,避免“忘了自己之前干到哪”:
保存現(xiàn)場(chǎng):把當(dāng)前CPU寄存器里的內(nèi)容(比如正在執(zhí)行的代碼位置、臨時(shí)數(shù)據(jù))保存到“備忘錄”(棧)里,確保后續(xù)能準(zhǔn)確回到當(dāng)前位置;
暫時(shí)關(guān)斷部分電話(可選):根據(jù)系統(tǒng)配置,暫時(shí)不接某些低優(yōu)先級(jí)電話(避免被打擾),然后跳轉(zhuǎn)到“處理人員辦公室”(內(nèi)核中斷入口地址)。
步驟4:內(nèi)核中斷調(diào)度——調(diào)度員“找處理人員”
CPU跳到內(nèi)核中斷入口后,“調(diào)度員”(內(nèi)核中斷子系統(tǒng))就開始工作了,核心任務(wù)是“快速找到能處理這個(gè)電話的人”,不浪費(fèi)時(shí)間:
要“來(lái)電編號(hào)”:內(nèi)核讀取IRQ控制器的寄存器,獲取中斷號(hào)——每個(gè)硬件的中斷都有唯一編號(hào)(比如GPIO1的中斷號(hào)是60),相當(dāng)于“來(lái)電者的身份證”;
查“通訊錄”:通過(guò)中斷號(hào),在內(nèi)核的“中斷向量表”(相當(dāng)于通訊錄)里,找到對(duì)應(yīng)的“處理人員”——也就是我們提前寫好的中斷服務(wù)函數(shù)(ISR);
安排順序:如果有多個(gè)“電話”在等,就按優(yōu)先級(jí)排序,讓“處理人員”一個(gè)個(gè)處理,不插隊(duì)、不混亂。
步驟5:執(zhí)行中斷處理——核心中的核心,分“快慢活”
這是最關(guān)鍵的一步,也是很多開發(fā)者踩坑的地方。Linux很聰明,它把中斷處理分成了“快慢兩部分”(上下半部機(jī)制),就像處理緊急電話:先快速說(shuō)清核心訴求,再慢慢細(xì)化處理,避免占用CPU太久,導(dǎo)致系統(tǒng)卡頓。
上半部(Top Half):急活快辦
相當(dāng)于“接電話時(shí)快速說(shuō)清訴求”,只處理最緊急、最核心的事,速度要快(通常幾十微秒搞定),期間不允許被任何中斷打斷(相當(dāng)于接電話時(shí)不允許被插話)。
核心工作:清除中斷標(biāo)志(告訴硬件“我收到你的請(qǐng)求了,別再一直打”)、保存關(guān)鍵數(shù)據(jù)(比如串口接收的1字節(jié)數(shù)據(jù));對(duì)應(yīng)代碼:我們寫的中斷服務(wù)函數(shù)(ISR),通過(guò)request_irq 函數(shù)注冊(cè)給內(nèi)核。
下半部(Bottom Half):慢活細(xì)辦
相當(dāng)于“掛電話后整理訴求、解決問(wèn)題”,處理耗時(shí)的后續(xù)工作,可被高優(yōu)先級(jí)中斷打斷(比如處理后續(xù)工作時(shí),更高優(yōu)先級(jí)的電話打進(jìn)來(lái),就先暫停),不占用CPU的“緊急通道”。
核心工作:解析數(shù)據(jù)、上報(bào)數(shù)據(jù)給應(yīng)用層、處理異常情況;
常用實(shí)現(xiàn)方式:workqueue(最常用,適合需要“暫停等待”的操作)、tasklet(適合快速處理、不暫停的活)。
舉個(gè)串口接收的例子,一看就懂: - 上半部(急活):1毫秒搞定——讀取串口寄存器的1字節(jié)數(shù)據(jù),保存到緩沖區(qū),清除中斷標(biāo)志; - 下半部(慢活):慢慢處理——解析緩沖區(qū)的所有數(shù)據(jù),判斷數(shù)據(jù)是否完整,再發(fā)送給應(yīng)用層。
步驟6:恢復(fù)現(xiàn)場(chǎng)——程序員“回頭繼續(xù)干活”
上下半部的工作都做完后,“調(diào)度員”會(huì)從“備忘錄”(棧)里,把步驟3保存的CPU寄存器內(nèi)容恢復(fù)回來(lái)——相當(dāng)于程序員看完“備忘錄”,記起自己之前寫到哪一行代碼,回到原來(lái)的位置,繼續(xù)執(zhí)行主程序。
步驟7:退出中斷——等待下一個(gè)“緊急電話”
恢復(fù)現(xiàn)場(chǎng)后,CPU退出“中斷模式”,重新開啟中斷響應(yīng)(如果步驟3中關(guān)閉了部分中斷),就像程序員掛掉電話,繼續(xù)專心寫代碼,等待下一個(gè)緊急電話打進(jìn)來(lái)——整個(gè)中斷處理流程,完美閉環(huán)。
4 手把手寫中斷處理代碼
理解了流程,最關(guān)鍵的是“怎么寫代碼實(shí)現(xiàn)”。下面以GPIO中斷為例,寫一段Linux內(nèi)核驅(qū)動(dòng)代碼,涵蓋中斷注冊(cè)、上半部、下半部,新手也能直接復(fù)制使用,看完就能上手調(diào)試。
#include#include#include// 中斷號(hào)(需根據(jù)設(shè)備樹配置,比如RK3576的GPIO1中斷號(hào)是60)#defineGPIO_IRQ_NUM60// GPIO引腳(示例:GPIO1_0,計(jì)算方式:1*32 + 0)#defineGPIO_PIN1*32+0// 下半部:用workqueue處理耗時(shí)操作(后續(xù)工作處理員)staticstructwork_structgpio_work;// 上半部:中斷服務(wù)函數(shù)(急活處理員,快速響應(yīng))staticirqreturn_tgpio_irq_handler(intirq,void*dev_id){// 1. 清除中斷標(biāo)志(必須做!不然硬件會(huì)一直發(fā)中斷,相當(dāng)于電話一直響)gpio_clear_int_edge(GPIO_PIN);// 2. 把耗時(shí)活交給下半部,讓上半部專心干急活schedule_work(&gpio_work);returnIRQ_HANDLED;// 告訴內(nèi)核:這個(gè)中斷我處理完了}// 下半部:workqueue處理函數(shù)(慢活細(xì)辦)staticvoidgpio_work_handler(structwork_struct *work){// 處理耗時(shí)操作:讀取GPIO電平、上報(bào)數(shù)據(jù)等printk("GPIO中斷下半部:當(dāng)前引腳電平 = %dn",gpio_get_value(GPIO_PIN));}// 中斷初始化(驅(qū)動(dòng)加載時(shí)執(zhí)行,給處理員分配任務(wù))staticintgpio_irq_init(void){intret;// 1. 初始化下半部(給慢活處理員安排工位)INIT_WORK(&gpio_work, gpio_work_handler);// 2. 注冊(cè)中斷(給中斷掛號(hào),指定處理員和觸發(fā)方式)ret =request_irq(GPIO_IRQ_NUM,gpio_irq_handler,IRQF_TRIGGER_RISING,// 上升沿觸發(fā)(按鍵按下時(shí)觸發(fā))"gpio-irq-demo", // 中斷名稱(調(diào)試時(shí)方便查找)NULL); // dev_id(中斷共享時(shí)使用)if(ret 0) {printk("中斷注冊(cè)失??!錯(cuò)誤碼:%dn", ret);return?ret;}printk("GPIO中斷注冊(cè)成功,等待觸發(fā)~n");return?0;}// 驅(qū)動(dòng)卸載時(shí)釋放中斷(處理員下班,歸還工位)static?void?gpio_irq_exit(void)?{// 1. 取消未完成的慢活,避免處理員下班了還在干活cancel_work_sync(&gpio_work);// 2. 釋放中斷,注銷掛號(hào),歸還系統(tǒng)資源free_irq(GPIO_IRQ_NUM,?NULL);printk("GPIO中斷釋放成功,處理員下班啦~n");}module_init(gpio_irq_init); ?// 驅(qū)動(dòng)加載入口module_exit(gpio_irq_exit); ?// 驅(qū)動(dòng)卸載入口MODULE_LICENSE("GPL"); ? ? ??// 內(nèi)核驅(qū)動(dòng)必加,遵循GPL協(xié)議
5 三個(gè)關(guān)鍵細(xì)節(jié)(避坑重點(diǎn))
觸發(fā)方式:常用3種——上升沿(按鍵按下)、下降沿(按鍵松開)、雙邊沿(按下/松開都觸發(fā)),要和硬件實(shí)際情況匹配;
上半部禁忌:絕對(duì)不能“摸魚”——不能調(diào)用msleep、kmalloc等可能“暫?!钡暮瘮?shù),執(zhí)行時(shí)間必須控制在幾十微秒內(nèi),不然會(huì)阻塞其他中斷;
下半部選擇:需要“暫停等待”(比如訪問(wèn)內(nèi)核資源)用workqueue;不需要暫停、快速處理用tasklet,按需選擇即可。
6 三個(gè)常見(jiàn)問(wèn)題
調(diào)試中斷時(shí),新手最容易遇到以下3個(gè)問(wèn)題,結(jié)合前面的流程,給大家講清原因和解決方法,不用再對(duì)著屏幕抓頭發(fā)。
問(wèn)題1:中斷不觸發(fā)
可能原因:設(shè)備樹配置錯(cuò)(中斷號(hào)、GPIO引腳和硬件對(duì)不上)、中斷未開啟(IRQ控制器沒(méi)啟用該中斷)、硬件接線錯(cuò)(GPIO引腳沒(méi)接對(duì),信號(hào)傳不過(guò)來(lái));
排查方法:核對(duì)設(shè)備樹和硬件手冊(cè)、用
cat/proc/interrupts
查看中斷是否注冊(cè)成功、用萬(wàn)用表測(cè)GPIO電平,確認(rèn)信號(hào)正常。
問(wèn)題2:中斷觸發(fā),但I(xiàn)SR不執(zhí)行
可能原因:中斷優(yōu)先級(jí)太低(被高優(yōu)先級(jí)中斷阻塞)、中斷被屏蔽(用local_irq_disable()關(guān)閉了中斷)、注冊(cè)參數(shù)錯(cuò)(觸發(fā)方式和硬件不匹配);
排查方法:用
cat/proc/interrupts
看中斷觸發(fā)次數(shù)、調(diào)整中斷優(yōu)先級(jí)、核對(duì)觸發(fā)方式和硬件一致。
問(wèn)題3:中斷處理導(dǎo)致系統(tǒng)卡頓
可能原因:上半部干了慢活(在ISR里寫數(shù)據(jù)解析、循環(huán)等耗時(shí)操作)、下半部未正確實(shí)現(xiàn)(后續(xù)工作堆積);
解決方案:把耗時(shí)操作全部移到下半部、優(yōu)化上半部代碼(控制在幾十微秒內(nèi))、不調(diào)用會(huì)“暫停”的函數(shù)。
7 總結(jié)
1. 流程本質(zhì):硬件觸發(fā)→IRQ分診→CPU響應(yīng)→內(nèi)核調(diào)度→上下半部處理→恢復(fù)現(xiàn)場(chǎng)→退出中斷,全程分工明確,高效有序;
2. 核心設(shè)計(jì):上下半部機(jī)制是靈魂,目的是“快速響應(yīng)緊急事,不耽誤正?;睢?,平衡實(shí)時(shí)性和系統(tǒng)流暢度;
3. 調(diào)試技巧:遇到問(wèn)題,先查/proc/interrupts看中斷狀態(tài),再用dmesg看內(nèi)核日志,聚焦“觸發(fā)→注冊(cè)→執(zhí)行”三個(gè)環(huán)節(jié)。
(完)
審核編輯 黃宇
-
驅(qū)動(dòng)
+關(guān)注
關(guān)注
12文章
1994瀏覽量
88727 -
Linux
+關(guān)注
關(guān)注
88文章
11821瀏覽量
219598
發(fā)布評(píng)論請(qǐng)先 登錄
如何理解Linux內(nèi)核中的PCIe驅(qū)動(dòng)
驅(qū)動(dòng)之路#20:Pinctrl 在手,引腳復(fù)用很順手
電子人一定要學(xué)會(huì)的20種模擬電路
Linux內(nèi)核驅(qū)動(dòng)開發(fā)的技術(shù)核心精要
驅(qū)動(dòng)之路#04:LCD 驅(qū)動(dòng)程序分析(基于RK3576)
驅(qū)動(dòng)之路#03:LCD 時(shí)序參數(shù)分析
RK806中斷處理流程深度解析:從架構(gòu)到調(diào)試實(shí)戰(zhàn)
【「Linux 設(shè)備驅(qū)動(dòng)開發(fā)(第 2 版)」閱讀體驗(yàn)】Linux內(nèi)核開發(fā)基礎(chǔ)
Linux驅(qū)動(dòng)開發(fā)的必備知識(shí)
RK3588核心板/開發(fā)板RT-Linux系統(tǒng)實(shí)時(shí)性及硬件中斷延遲測(cè)試
【免費(fèi)送書】成為硬核Linux開發(fā)者:《Linux 設(shè)備驅(qū)動(dòng)開發(fā)(第 2 版)》
RK3576核心板/開發(fā)板RT-Linux系統(tǒng)實(shí)時(shí)性及硬件中斷延遲測(cè)試
驅(qū)動(dòng)之路#23:Linux中斷(面試必考題)
評(píng)論