昨天在測試新寫的任務池子產品時,出現了莫名其妙的問題。
代碼基本如下:
static void AppTaskStart (void *p_arg){
INT8U err;
……
TP_Init();
while (DEF_TRUE){
TP_AssignWork(SCITask,"aaaaaaaaaaaa\r");
TP_AssignWork(SCITask,"abbbbbbbbbbb\r");
TP_AssignWork(SCITask,"accccccccccc\r");
OSTimeDly();
TP_AssignWork(SCITask,"addddddddddd\r");
TP_AssignWork(SCITask,"aeeeeeeeeeee\r");
TP_AssignWork(SCITask,"afffffffffff\r");
TP_AssignWork(SCITask,"aggggggggggg\r");
TP_AssignWork(SCITask,"ahhhhhhhhhhh\r");
OSTimeDly();
TP_AssignWork(SCITask,"aiiiiiiiiiii\r");
TP_AssignWork(SCITask,"ajjjjjjjjjjj\r");
OSTimeDlyHMSM(,,,);
}
}
static void SCITask(void *msg){
OS_TCB tcb;
INT8U buf[];
char *s = msg;
INT16U len;
OSTaskQuery(OS_PRIO_SELF,&tcb);
len = sprintf(buf,"worker's priority:%d\rmsg:%s",tcb.OSTCBPrio,s);
SCI_PutCharsB_Mutex(SCI0,buf,len,);
OSTimeDly();
}
簡單來說就是,我建立了優先級為20、21、22的三個線程給線程池,然後不斷配置設定任務給線程池做;任務很簡單,就是列印出自己的優先級以及發過來的消息。
正常來說應該輸出是這樣的:
非常的整齊。
但實際上測試的時候卻時不時地列印不全:
而且隻要稍微變一點代碼,列印不全的位置也會變,但總是優先級21的那個出問題。
定位了半天,最終發現就是sprintf這句出的問題,執行完後,緩沖區裡可能是錯誤的值了。
static void SCITask(void *msg){
……
len = sprintf(buf,"worker's priority:%d\rmsg:%s",tcb.OSTCBPrio,s);
……
}
然後又一通調試,最終想到會不會是任務切換的問題。
于是,在前後加了鎖定:
static void SCITask(void *msg){
OS_TCB tcb;
INT8U buf[];
char *s = msg;
INT16U len;
OSTaskQuery(OS_PRIO_SELF,&tcb);
OSSchedLock();
len = sprintf(buf,"worker's priority:%d\rmsg:%s",tcb.OSTCBPrio,s);
OSSchedUnlock();
SCI_PutCharsB_Mutex(SCI,buf,len,);
OSTimeDly();
}
結果,就再沒出現過問題了。。。。
一般線程沖突這種事情是因為使用了全局變量,多個線程通路同個全局變量導緻的。講道理來說sprintf正常的實作感覺不需要使用全局變量的,因為需要的東西都用參數傳遞了。但事實上就是有線程沖突的問題。
于是到map檔案中看看是不是真的配置設定了什麼全局變量。
MODULE: -- PRINTF.C.o (ansixbi.lib) --
- PROCEDURES:
vprintf FE816F .text
_out FE85BE .text
deposit_char FE85C7 A .text
sprintf FE85D1 D .text
- VARIABLES:
pow10 C051 .rodata
pow16 C079 .rodata
pow8 C099 C .rodata
!>>> pbuf <<<<<!!!!!! .bss
這個pbuf好像有問題!
于是乎進到源檔案中檢視pbuf到底是用來幹嘛的:
……
static struct __print_buf {
LIBDEF_StringPtr s;
#ifdef __RS08__
unsigned char jmp_opcode;
#endif
void (*outc)(char); /*lint !e960 MISRA 16.3 REQ, this is a function pointer declaration */
unsigned int i;
} pbuf;
……
int sprintf(LIBDEF_StringPtr s, LIBDEF_ConstStringPtr format, ...) { /*lint !e960 MISRA 16.1 REQ, standard library function implementation */
va_list args;
pbuf.i = ;
pbuf.s = s;
pbuf.outc = deposit_char;
/*lint -e{643} misleading warning ('&format' does not have a far pointer type) */
/*lint -e{928} , MISRA 11.4 ADV, safe conversion to 'char *' */
va_start(args, format);
(void)vprintf(format, args);
pbuf.s[pbuf.i] = (char);
va_end(args);
return (int)(pbuf.i); /*lint !e438 'va_end' must be invoked before return in a variadic function */
}
int vsprintf(LIBDEF_StringPtr s, LIBDEF_ConstStringPtr format, va_list args) {
pbuf.i = ;
pbuf.s = s;
pbuf.outc = deposit_char;
(void)vprintf(format, args);
pbuf.s[pbuf.i] = (char);
return (int)(pbuf.i);
}
……
sprinf中居然使用了一個全局變量!
怪不得多線程使用sprintf時會出現問題。
于是乎問題就明朗了。
20和21兩個任務都使用了sprintf,在發生邊界條件:21調用sprintf時正好發生了任務切換進入20,而20也在調用sprintf。時就可能發生沖突,導緻pbuf變量被改寫,而20的優先級高于21,是以隻有21有可能出問題,列印出錯。其實22也可能出錯,隻是可能運氣好,正好錯過了。
這種線程不安全的函數會不會出問題很大程度上就是看運氣。
解決方案也很清晰,通過某種方法避免不同線程同時調用線程不安全函數就行了。
看來以後使用到這些标準庫中的函數時得多小心啦。