创建线程Creating Threads
一旦RTOS开始运行,就会有很多系统调用来管理和控制活跃的线程。默认情况下,main()函数自动被创建为第一个可运行的线程。在第一个例子里我们使用main()函数创建了其他线程,并且随后让main()结束运行。然而我们还可以让main当成一个真正的thread使用。首先,我们需要获取它的ID号。此时,我们第一个要调用的RTOS函数就是osThreadGetId(),这个函数返回当前运行thread的ID号,并把它存在ID句柄里。当我们未来某一时刻在OS调用中需要这个线程时,我们使用的就是这个线程的句柄,而非线程的函数名。
-
osThreadId main_id;//创建线程句柄
-
void main(void)
-
{
-
/*获取main线程的ID号*/
-
main_id = osThreadGetId();
-
while(1)
-
{}
-
}
现在我们有了main的ID句柄,就可以创建其他应用线程,并随后调用osTermainate(main_id)来结束main线程。相比让一个线程运行到最后一个花括号来结束,这种结束方法更好一些。当然,我们还可以添加while(1)这个循环来继续使用main函数。
在第一个例子中我们就看到main线程可以作为一个launcher 线程来创建其他应用线程。仅需要两步就行:第一步,定义线程结构体(同时还可以定义线程传递参数)。
-
osThreadId thread_id; //线程句柄
-
void thread1(void const *argument); //thread1的函数原型
-
osThreadDef(thread1, osPriorityNormal, 1, 0); //线程定义结构体
定义线程结构体需要先定义线程函数名、线程优先级、创建线程的实例化个数和它的栈大小。这些参数后续会详细说明。一旦定义了线程结构体,我们就可以用osThreadCreated() API来创建线程。线程经常在main线程里创建,当然也可以在其他地方。
thread1_id = osThreadCreated(osThread(thread1), NULL);
上面这条代码创建线程并启动它运行。另外,在启动线程时可以给它传递个参数。
-
uint32_t startupParameter = 0x23;
-
thread1_id = osThreadCreate(osThread(thread1), startupParameter);
当创建线程时,还将给它分配一个栈,用来存储上下文切换的数据。注意不要和Cortex处理器栈混淆,它只是分配给线程的一段存储空间。在RTOS配置文件里已经定义了一个默认的栈大小,当然我们也可以自定义栈的大小。osThreadDef()这个函数的最后一个参数设为0时表示使用默认栈大小。如果需要的话,也可以通过在线程结构体里定义一个更大的栈来增加额外的存储资源。
-
osThreadDef(thread1, osPriorityNomal, 1, 0); //分配默认栈大小
-
osThreadDef(thread2, osPriorityNomal, 1, 1024); //分配1kB的栈大小
当然,如果你分配了更大的栈空间,在RTOS配置文件里就需要增加额外的存储空间。
线程的管理和优先级Thread Management and Priority
在创建线程的时候就会给它分配一个优先级。RTOS的调度器靠线程的优先级来决定要调度哪个线程运行。假如同时有几个线程都准备运行,优先级最高的线程就会进入运行状态。如果一个高优先级线程处于准备运行状态,它就会抢占正在运行的低优先级线程。有一点非常重要,高优先级线程占用CPU运行的时候永远不会停止,除非有RTOS API调用来阻塞它,或者有更高优先级的线程来抢占它。线程的优先级在线程结构体中定义,下面列出可用的优先级定义,其中一般默认的优先级是osPriorityNormal。
CMSIS-RTOS Priority Levels |
---|
osPriorityIdle |
osPriorityLow |
osPriorityBelowNormal |
osPriorityNormal |
osPriorityAboveNormal |
osPriorityHigh |
osPriorityRealTime |
osPriorityError |
一旦线程开始运行,我们就可以使用几个OS系统调用来管理线程。当然也可以通过其他函数或在它自己代码里面提升或者降低它的优先级。
osStatus osThreadSetPriority(threadID, priority);
osPriority osThreadGetPriority(threadID);
在创建线程之后,也可以通过它自己或者其他活跃的线程来删除它。再强调一次,我们使用thread ID而不是它的函数名来操作。
osStatus = osThreadTermainate(threadID1);
另外,同优先级线程间的切换有一种特殊情况,就是使用协作(co-operative)切换方式:
osStatus osThreadYield();//切换到另外一个准备运行的线程
多重实例化Multiple Instances
RTOS的一个有趣功能就是可以针对一个线程进行多个实例化,例如你可以基于一个线程代码创建多个用于控制UART的实例,此时,每个UART的实例都会管理一个不同的UART硬件。
首先我们要创建线程结构体,并设置线程实例个数为2:
osThreadDef(thread1, osPriorityNormal, 2, 0);
空闲线程Idle Demon
CMSIS-RTOS提供的最后一个定时器服务函数并不是一个真正的定时器,但是这里是最合适讨论它的地方。如果在我们的RTOS程序里没有任何线程正在运行,或者准备运行(举个例子,所有的线程都处于等待延时函数中),那么RTOS就会利用空闲的运行时间调用一个“Idle Demon”的线程,这个函数同样位于RTX_Conf_CM.c文件里面,空闲线程拥有一个低优先级,只有在没有其他任何线程准备运行的情况下才会运行。
void os_idle_demon(void)
{
for(;;){
/*此处包含一些用户的的可选代码,在没有线程运行时执行*/
}
}
你可以在这个线程里加入任何代码,但是同样要遵守常规线程的基本准则,一个最简单的应用就是在此线程中添加控制器MCU的低功耗模式。
void os_idle_demon(void)
{
__wfe();
}
下一步什么情况取决于MCU的功耗模式选择,至少CPU会暂停,直到sysTick产生中断并继续执行调度器。如果有线程准备运行,CPU就会去执行这个线程,否则,就会重新进入空闲线程,系统也会重新进入睡眠状态。
线程间通信 Inter-Thread Communication
前面我们已经学习了如何把你的应用代码设计成独立的线程,以及如何访问RTOS的时间服务函数。在实际的工程应用中,线程间的通信是必不可少的,任何一个RTOS都会支持几种通信方式来连接各种不同的线程。CMSIS-RTOS API支持的通信方式有:信号(signals),信号量(semaphores),互斥锁(mutexes),邮箱(mailboxes)和消息队列(message queues)。所有这些首要的核心概念就是并发性。在这一章,将集中讨论多任务间的同步问题。
信号 Signals
CMSIS RTOS RTX支持单线程16种信号标志,这些信号存储在线程控制块里,一个线程会暂停执行,直到一个或一组信号标志被其他线程置位。
每个线程有16个信号标志,一个线程可能被置于等待状态,直到一种模式的信号被其他线程置位。然后此线程就会返回等待状态,并等待调度器调度到运行状态。
调用信号等待后,系统会挂起正在运行的线程,并把它置于等待事件(wait_event)状态,直到所有的信号标志被置位,线程才会恢复运行。当然也可以加入超时机制,以便让线程可以顺利返回等待状态。默认初始化超时值是0xFFFF。
osEvent osSignalWait(int32_t signals, uint32_t millisec);
调用osSignalWait之后会把信号复位,如果后面还有信号被置位,线程就可以继续执行,你可以读osEvent.value.signals的值来获取哪个标志被置位。
任何一个线程都可以置位或清除另外一个线程的信号:
int32_t osSinalSet(osThreadId thread_id, int32_t signals);
int32_t osSignalClear(osThreadId thread_id, int32_t signals);
来源:https://blog.csdn.net/ichamber/article/details/53116253