昨天在测试新写的任务池模块时,出现了莫名其妙的问题。
代码基本如下:
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也可能出错,只是可能运气好,正好错过了。
这种线程不安全的函数会不会出问题很大程度上就是看运气。
解决方案也很清晰,通过某种方法避免不同线程同时调用线程不安全函数就行了。
看来以后使用到这些标准库中的函数时得多小心啦。