您的当前位置:首页正文

CodeWarrior 5.1的sprintf函数是线程不安全的!

2024-11-08 来源:个人技术集锦

昨天在测试新写的任务池模块时,出现了莫名其妙的问题。
代码基本如下:

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(1);
    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(1);
    TP_AssignWork(SCITask,"aiiiiiiiiiii\r");
    TP_AssignWork(SCITask,"ajjjjjjjjjjj\r");
    OSTimeDlyHMSM(0,0,0,300);
  }
}

static void SCITask(void *msg){
  OS_TCB tcb;
  INT8U buf[100];
  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,0);
  OSTimeDly(1);
}

简单来说就是,我创建了优先级为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[100];
  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(SCI0,buf,len,0);
  OSTimeDly(1);
}

结果,就再没出现过问题了。。。。

一般线程冲突这种事情是因为使用了全局变量,多个线程访问同个全局变量导致的。讲道理来说sprintf正常的实现感觉不需要使用全局变量的,因为需要的东西都用参数传递了。但事实上就是有线程冲突的问题。

于是到map文件中看看是不是真的分配了什么全局变量。

MODULE:                 -- PRINTF.C.o (ansixbi.lib) --
- PROCEDURES:
     vprintf                                 FE816F     44F    1103      53   .text       
     _out                                    FE85BE       9       9       2   .text       
     deposit_char                            FE85C7       A      10       3   .text       
     sprintf                                 FE85D1      2D      45       2   .text       
- VARIABLES:
     pow10                                     C051      28      40       4   .rodata     
     pow16                                     C079      20      32       4   .rodata     
     pow8                                      C099      2C      44       4   .rodata     
!>>> pbuf <<<<<!!!!!!                          2180       7       7      15   .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 = 0U;
  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)0;
  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 = 0U;
  pbuf.s = s;
  pbuf.outc = deposit_char;
  (void)vprintf(format, args);
  pbuf.s[pbuf.i] = (char)0;
  return (int)(pbuf.i);
}
……

sprinf中居然使用了一个全局变量!
怪不得多线程使用sprintf时会出现问题。

于是乎问题就明朗了。
20和21两个任务都使用了sprintf,在发生边界条件:21调用sprintf时正好发生了任务切换进入20,而20也在调用sprintf。时就可能发生冲突,导致pbuf变量被改写,而20的优先级高于21,所以只有21有可能出问题,打印出错。其实22也可能出错,只是可能运气好,正好错过了。

这种线程不安全的函数会不会出问题很大程度上就是看运气。

解决方案也很清晰,通过某种方法避免不同线程同时调用线程不安全函数就行了。

看来以后使用到这些标准库中的函数时得多小心啦。

Top