天天看點

CodeWarrior 5.1的sprintf函數是線程不安全的!

昨天在測試新寫的任務池子產品時,出現了莫名其妙的問題。

代碼基本如下:

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的三個線程給線程池,然後不斷配置設定任務給線程池做;任務很簡單,就是列印出自己的優先級以及發過來的消息。

正常來說應該輸出是這樣的:

CodeWarrior 5.1的sprintf函數是線程不安全的!

非常的整齊。

但實際上測試的時候卻時不時地列印不全:

CodeWarrior 5.1的sprintf函數是線程不安全的!

而且隻要稍微變一點代碼,列印不全的位置也會變,但總是優先級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也可能出錯,隻是可能運氣好,正好錯過了。

這種線程不安全的函數會不會出問題很大程度上就是看運氣。

解決方案也很清晰,通過某種方法避免不同線程同時調用線程不安全函數就行了。

看來以後使用到這些标準庫中的函數時得多小心啦。

繼續閱讀