禮拜三接到一個學(xué)弟的問題,學(xué)弟在實習(xí),說有個很奇葩的bug解決不了,我說你描述一下吧
大概就是利用spi命令向flash里的某個地址寫入數(shù)據(jù),boot之后bootloader從flash的該地址里讀數(shù)據(jù),但是現(xiàn)在的問題是,偶爾讀出來的數(shù)據(jù)不對,貌似是數(shù)據(jù)沒有寫進去,該問題目前只出現(xiàn)了三次,再次復(fù)現(xiàn)不太好整
遇見這種問題,也沒太好的辦法,我讓他先單步調(diào)試,看看write的時候,spi的接口發(fā)回來的status是ok還是error
調(diào)了大概十來次,都是ok,好,基本可以排除硬件本身的問題了,接口是直接用的cubemx的,應(yīng)該也不會有問題
那么會是神馬問題呢?我自己也很納悶,按說如果硬件本身沒問題,接口是對的,數(shù)據(jù)的寫入沒有理由不成功
我問寫入和讀取的請求是怎么調(diào)用的,學(xué)弟說代碼沒法全部發(fā)給我看,我說懂的,那你就給我說說架構(gòu)方面是怎么設(shè)計的
學(xué)弟語音加屏幕繪圖操作一頓,我大概明白了,于是我自己畫了一幅圖:
學(xué)弟還給我發(fā)來了文檔的鏈接,我一看,用的是keil lib的接口:
Signal Events
以及
Mail Queue
我和學(xué)弟確認(rèn)之后的偽碼如下:
#define WRITE_REQUEST 0
#define READ_REQUEST 1
#define WRITE_SIGNAL 0x01
#define READ_SIGNAL 0x02
osMailQId mail;
// Write reuqest
void write_request (void) {
T_MEAS *mptr;
mptr = osMailAlloc(mail, osWaitForever);
// Allocate memory
mptr->info = WRITE_REQUEST;
osMailPut(mail, mptr);
// Send Mail
osSignalWait(WRITE_SIGNAL,TIMEOUT);
/*******
Do something
********/
osMailFree(mail, mptr);
// free memory allocated for mail
osThreadYield();
// Cooperative multitasking
}
// Read reuqest
void read_request (void) {
T_MEAS *mptr;
mptr = osMailAlloc(mail, osWaitForever);
// Allocate memory
mptr->info = READ_REQUEST;
osMailPut(mail, mptr);
// Send Mail
osSignalWait(READ_SIGNAL,TIMEOUT);
/*******
Do something
********/
osMailFree(mail, mptr);
// Delete the request from the message queue
osThreadYield();
// Cooperative multitasking
}
//Message Queue
void message_queue_thread (void) {
T_MEAS *rptr;
osEvent evt;
for (;;) {
evt = osMailGet(mail, osWaitForever);
// wait for mail
if (evt.status == osEventMail) {
if (evt.info == WRITE_REQUEST)
{
/********
Write data to the flash
*********/
if(write succeed)
{
osSignalSet(thread_id, WRITE_SIGNAL);
//Send write signal
}
}
if (evt.info == READ_REQUEST)
{
/********
Read data to the flash
*********/
if(read succeed)
{
osSignalSet(thread_id, READ_SIGNAL);
//Send write signal
}
}
}
}
}
看著貌似沒有什么問題,總共三個函數(shù),兩個發(fā)請求,一個處理請求,發(fā)送請求的函數(shù)向請求隊列即message queue存儲一個請求,處理請求的函數(shù)負(fù)責(zé)接收和處理該請求,處理結(jié)束以后向發(fā)送請求的線程發(fā)送一個信號量,發(fā)送請求的線程從阻塞處,即osSignalWait處繼續(xù)向下運行,從請求隊列里free掉該請求
那么問題來了,請求是從哪里free掉的呢?
這其實是一個典型的生產(chǎn)消費者模型,發(fā)送讀和寫請求的線程皆為生產(chǎn)者,處理請求的線程為消費者,但是現(xiàn)在,從請求隊列里remove掉請求的操作被生產(chǎn)者自己執(zhí)行了,而根據(jù)經(jīng)典理論,這個操作的執(zhí)行權(quán),應(yīng)該屬于消費者,即處理請求的線程
我和學(xué)弟說了大概我的意思,學(xué)弟表示,可是從邏輯上看似乎這么些也沒有問題,我說,沒關(guān)系,咱們現(xiàn)在來設(shè)想一種race condition:
你看,如果你先發(fā)送的是寫請求,緊隨其后跟了一個讀請求,而在處理線程里,讀請求的完成快于寫請求,也就是雖然寫請求的發(fā)送早于讀請求,但是讀請求的完成早于寫請求,讀請求被消息隊列free掉也早于寫請求,這會導(dǎo)致兩種情況:
-
你的寫請求壓根還沒完成,讀請求就完成了,被消息隊列處理并被讀請求自己free了,等于讀了個寂寞,那么你不可能讀到寫進去的數(shù)據(jù)
-
因為我們并不知道keil接口的具體實現(xiàn),庫是以庫文件的形式呈現(xiàn)的,你看不到代碼,如果這個osMailFree相當(dāng)于queue的pop,應(yīng)該彈出第一個元素,可生產(chǎn)者自己free掉自己的請求時無法保證該請求的指針指向的是第一個元素,那么有可能造成未知的指針問題,這更為嚴(yán)重,有潛在的內(nèi)存泄露可能
針對這種問題我的方案是這樣的:
#define WRITE_REQUEST 0
#define READ_REQUEST 1
#define WRITE_SIGNAL 0x01
#define READ_SIGNAL 0x02
#define FINISH_SIGNAL 0x03
osMailQId mail;
// Write reuqest
void write_request (void) {
T_MEAS *mptr;
mptr = osMailAlloc(mail, osWaitForever);
// Allocate memory
mptr->info = WRITE_REQUEST;
osMailPut(mail, mptr);
// Send Mail
osDelay(
10);
osSignalWait(WRITE_SIGNAL,TIMEOUT);
/*******
Do something
********/
osSignalSet(thread_id, FINISH_SIGNAL);
// free memory allocated for mail
osThreadYield();
// Cooperative multitasking
}
// Read reuqest
void read_request (void) {
T_MEAS *mptr;
mptr = osMailAlloc(mail, osWaitForever);
// Allocate memory
mptr->info = READ_REQUEST;
osMailPut(mail, mptr);
// Send Mail
osDelay(
10);
osSignalWait(READ_SIGNAL,TIMEOUT);
/*******
Do something
********/
osDelay(
10);
osSignalSet(thread_id, FINISH_SIGNAL);
osThreadYield();
// Cooperative multitasking
}
//Message Queue
void message_queue_thread (void) {
T_MEAS *rptr;
osEvent evt;
for (;;) {
evt = osMailGet(mail, osWaitForever);
// wait for mail
if (evt.status == osEventMail) {
if (evt.info == WRITE_REQUEST)
{
/********
Write data to the flash
*********/
if(write succeed)
{
osSignalSet(thread_id, WRITE_SIGNAL);
//Send write signal
}
}
if (evt.info == READ_REQUEST)
{
/********
Read data to the flash
*********/
if(read succeed)
{
osSignalSet(thread_id, READ_SIGNAL);
//Send write signal
}
}
osDelay(
10);
osSignalWait(FINISH_SIGNAL,TIMEOUT);
osMailFree(mail, evt);
}
}
}
核心思想就是最簡單的,讓生產(chǎn)者負(fù)責(zé)生產(chǎn),由消費者負(fù)責(zé)消費,避免他們之間串線,才能有效保證任務(wù)按照正確的順序運行,osDelay不能夠省略,否則可能造成經(jīng)典的“你丫先把手撒開”“你TM先撒開我再撒開”的死鎖問題
希望學(xué)弟調(diào)試順利,對于不能一直復(fù)現(xiàn)的問題,其實也很難看到直觀的效果,如果以后沒再出現(xiàn)相應(yīng)的問題,就當(dāng)他解決了吧
本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。