論Linux進程線程同步在嵌入式驅動開發中的重要性(基於模擬IIC亂碼場景分析)——續集

在學習資料滿天飛的大環境下,知識變得非常零散,體系化的知識並不多,這就導致很多人每天都努力學習到感動自己,最終卻收效甚微,甚至放棄學習。我的使命就是過濾掉大量的垃圾信息,將知識體系化,以短平快的方式直達問題本質,把大家從大海撈針的痛苦中解脫出來。

1 問題

在《論Linux進程/線程同步在嵌入式驅動開發中的重要性(基於模擬IIC亂碼場景分析)》中最終採用了互斥鎖的方案(方案三)。誤碼率降低了,但是並沒有根治。


2 分析

壓力12個小時後,結果表明讀寫EEPROM時確實還存在誤碼。明明以GPIO的Port爲粒度增加了互斥鎖,爲什麼還是出現誤碼呢?

2.1 理論分析

正確、快速且萬無一失的思路應該是這樣的(我這也是馬後炮,一開始定位時確實走了一些彎路,靜下心來後發現還是不要着急動手,理順思路最最重要):

–>既然互斥鎖在理論分析上是可以完全解決這個問題的,那就是因爲鎖的使用不當導致的 。

–>鎖的使用不當包含兩種情況:資源選擇不當;臨界區選擇不當。

–> 首先,排查下鎖定的是什麼資源呢?是GPIO Port鎖資源和相關的內存資源。這裏的內存資源指的是在實現GPIO模擬IIC協議時用到的內存(變量)。分析到這裏就發現,在資源的選取上確實有些不妥。雖然,在上一篇博客中我們解決了一個資源問題——鎖Pin換成了鎖Port。但是,從邏輯上來分析,資源是分層的:GPIO Port資源是屬於GPIO驅動層的;模擬IIC協議使用到的資源是IIC協議層的。所以,互斥鎖也是要分層的,也就是至少需要2把鎖,一把鎖GPIO Port,一把鎖IIC總線。

Tips:當一個線程申請不止一把鎖時一定要注意下述問題:當前使用的鎖是否支持嵌套;鎖的依賴關係;拿鎖的順序。以防止鎖不生效或者引起死鎖情況。

–>再次,排查臨界區。有一個思路一定要轉變過來,不是鎖決定了臨界區在哪兒,而是——資源。爲什麼這麼說呢?因爲雖然鎖決定了臨界區的邊界,但資源決定了臨界區本身,資源纔是核心。所以,排查臨界區不是全局搜索鎖使用的地方,而是要搜索所有使用該資源的地方。針對本問題就是GPIO 的一個Port。

Tips:資源纔是核心。一定要牢記這句話,不管是優先級還是時間片,亦或是鎖等等概念,全部是圍繞資源展開和衍生出來的,這和人類社會是一樣的。舉個簡單的例子,爲什麼會有伊拉克戰爭?因爲搶奪石油資源。有了戰爭,纔有戰鬥機、纔有坦克、航母、AK47,纔有戰略、戰術……所有的一切都圍繞資源展開,所以,核心是資源。

2.2 實際定位

如果按照上述思路進行操作,可以快速將問題發現並解決。可惜在真實的場景中,我們還是踩了兩個小坑。

一個就是我對臨界區的認知還停留在鎖上面,所以,沒有在進行臨界區排查時,排查漏掉了一個地方。這個地方非常隱晦,是一個SPI控制器,居然使用了三個GPIO引腳來做片選(老單板遺留問題,代碼兼容性沒做好),而且並不是在讀寫函數中操作了這三個引腳,而是在初始化函數中。而恰恰初始化函數又在每次讀寫的時候被調用了,所以着也影響了問題定位過程中的判斷。

Tips:從這個坑中我們可以吸取的經驗教訓是:

0)提升對概念的認知才能精準把控問題,當然,很多時候出現問題才能提升認知;

1)軟件要參與硬件評審,不允許無謂的硬件資源重疊交叉情況存在;

2)在多款設備兼容修改時不要放過任何一個細節;

3)初始化就是初始化函數,初始化原則上只調用一次,避免多次調用,如果例外請更改函數名或添加警告說明。

另外一個坑就是,在定位過程中,我們發現有一個線程在使用GPIO Port時沒有拿鎖(屬於臨界區使用錯誤)。當掛起該線程時,問題確實有所緩解。於是,我們“似乎”掌握了一種好用的調試手段(就是從這裏把我們帶入了坑中,這個時候其實我已經想着去查找所有使用GPIO的代碼了,但是,但是思路還是被打斷了)。恰恰就在這個時候,測試人員又提供了一條線索(現在看來如果沒有一個清晰的定位思路,線索太多也並非好事):掛起另外一個線程後,該問題就基本不復現了。這就進一步將我們引入了坑裏(因爲負責人啊、領導啊都來盯這個線程的問題),注意力一下就轉移到這個線程上去了,關鍵這個線程還是別的組負責的線程。什麼找人啊,溝通啊,甚至是扯皮啊,老司機們都懂哈~時間就這麼一點點被拉長了。

兜了一大圈,最終才找到是“一個SPI控制器使用了三個GPIO引腳做片選,而且沒拿鎖” 搗的鬼。

Tips:

1)對於上面提到的“好的調試手段”,靠掛起線程來定位的方式,有時候對於定界是有幫助的,但對於定位公共函數或公共模塊的問題絕對屬於“遠水解近渴”的行爲。尤其是在線程使用並不規範的場景中(你都不知道哪些函數最終被哪個線程調用了)。

2)有了思路一定要寫下來,防止因爲加班、熬夜或者領導催促等等因素打亂自己的思路和節奏。


到這裏還沒結束,大多數人查到了問題就匆匆收尾慶祝了,但是有一個問題還需要深究,這樣才能汲取更多的經驗。

這個問題就是SPI控制器代碼誤操作了GPIO引腳怎麼就影響到了EEPROM使用的IIC總線呢?

看過這個問題前傳的同學還記得這個圖吧

img

我查過兩個線程相互影響的引腳確實在同一個Port上。我又查看了兩個線程的優先級,發現訪問“SPI和它綁定的三個引腳”的線程優先級高,而通過訪問IIC總線訪問EEPROM的線程優先級低。按照“後手優勢”的理論,沒道理IIC會受到影響啊,受到影響的應該是SPI(當然受到影響也表現不出來,因爲那三個GPIO引腳在新板卡上是沒有接東西的)。

分析到這裏我的認知還侷限在單核CPU的分析維度裏,而我們用的處理器是8核心的。當我推理受到阻礙的時候,我意識到了這個問題,對,應該就是SMP導致的“後手優勢”失效。我去查看了兩個線程的親和性,果然在兩個核心上。而此前這兩個線程是在同一個核心上的,由於該核業務壓力太大,所以做了調整,從而將該問題暴露出來了。

到這裏,這個問題才分析圓滿。

Tips:
1)使用SMP系統時一定要注意思維不能僅僅侷限在單核心的優先級上。因爲優先級的概念也是因資源而生,多核心CPU的ALU資源變多了,所以,優先級的概念也要跟着擴充。優先級針對單個ALU資源纔有意義。
2)線程的親和性千萬不要隨意或經常變動;如果因調整親和性引發了問題,記得去檢查鎖的使用情況是否符合SMP的使用規範。

3 解決

分析和定位透徹了,解決從來都不是問題。

解決方案如下:

1.將新平臺上SPI附加的控制三個GPIO引腳的代碼去除。

2.加兩層鎖:IIC總線加一層;GPIO驅動加一層。


4 覆盤

4.1 定位思路總結

雖然文中一直在以競態和同步爲主線分析和解決問題,但是原始的問題本身是EEPROM讀寫問題。所以,爲了完整性也將EEPROM定位過程中遇到的問題一併總結如下:

1.如果是多核、多進程/線程系統,檢查是否加鎖了,鎖加的對不對。
(1) 鎖定的資源劃分是否合理
比如GPIO鎖定的是引腳(Pin)還是端口(Port)。
(2)鎖定的資源臨界區選取是否合理
比如要求加鎖的位置太靠上層,是否會導致有人忘記加鎖。
(3) 是否有些模塊忘記加鎖
比如壓根兒就沒加鎖。
再比如,一個模塊依賴了兩個資源,但是隻加了一把鎖。

2.檢查是否配置成開漏模式。
3.檢查是否有寫保護。
4.檢查換頁處邏輯處理是否有問題。
5.檢查SDA線是否有死鎖情況。
6.檢查時延問題
(1)檢查幀與幀之間的時延是否滿足要求。
(2)檢查ACK等待時延是否滿足要求。
7.確認IIC時序是否正確。

4.2 理論分析與實踐相結合

從本例中可以看出,如果理論分析完美無缺確實可以分分鐘定位一個“看不見摸不着”的問題。但實際定位問題時會受到心態、環境、時間等因素的影響,往往做不到很完美。因爲,完美的理論也需要從實踐中得來(俗話說馬後炮,哈哈)。但是,馬後炮打多了,練熟了,再次遇到這個問題時,就可以分分鐘將小強斬殺。這就是所謂的經驗。


<完>

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章