目录
使用RTOS必然会用到线程(有些OS称为任务),而多线程是RTOS的基本功能。
将单片机需要完成的工作分割成多个相对独立的功能模块,每个功能模块在非OS的应用中是串行执行的,而在RTOS中则被定义为线程,在宏观上它们是并行执行的。
1. 创建线程
创建线程分2种情况:动态创建和静态创建。使用 rt_thread_create() 创建一个动态线程,使用 rt_thread_init() 初始化一个静态线程,动态线程与静态线程的区别是:动态线程是系统自动从动态内存堆上分配栈空间与线程句柄(初始化 heap 之后才能使用 create 创建动态线程),静态线程是由用户分配栈空间与线程句柄,线程控制块、线程运行栈一般都设置为全局变量,在编译时就被确定、被分配处理,内核不负责动态分配内存空间
所以,要使用rt_thread_create必须打开RT_USING_HEAP。
1.1 rt_thread_init
rt_err_t rt_thread_init(struct rt_thread *thread,
const char *name,
void (*entry)(void *parameter),
void *parameter,
void *stack_start,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick)
参数 | 描述 |
---|---|
thread | 线程句柄。线程句柄由用户提供出来,并指向对应的线程控制块内存地址 |
name | 线程的名称;线程名称的最大长度由 rtconfig.h 中定义的 RT_NAME_MAX 宏指定,多余部分会被自动截掉 |
entry | 线程入口函数 |
parameter | 线程入口函数参数 |
stack_start | 线程栈起始地址 |
stack_size | 线程栈大小,单位是字节。在大多数系统中需要做栈空间地址对齐(例如 ARM 体系结构中需要向 4 字节地址对齐) |
priority | 线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义),如果支持的是 256 级优先级,那么范围是从 0 ~ 255,数值越小优先级越高,0 代表最高优先级 |
tick | 线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行 |
返回 | —— |
RT_EOK | 线程创建成功 |
-RT_ERROR | 线程创建失败 |
RTT把main函数作为一个进程定义的
rt_thread_t tid;
rt_err_t result;
tid = &main_thread;
result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL,
main_stack, sizeof(main_stack), RT_MAIN_THREAD_PRIORITY, 20);
RT_ASSERT(result == RT_EOK);
1.2 rt_thread_create
rt_thread_t rt_thread_create(const char *name,
void (*entry)(void *parameter),
void *parameter,
rt_uint32_t stack_size,
rt_uint8_t priority,
rt_uint32_t tick)
参数含义与rt_thread_init相同,动态创建用的stack是自动分配的。
2. 启动线程
创建(初始化)的线程状态处于初始状态,并未进入就绪线程的调度队列,所以需要启动线程。
rt_err_t rt_thread_startup(rt_thread_t thread);
建议将所有线程创建好再启动线程以保持和其他RTOS兼容(类似ucOS的启动函数OSStart是没有参数的,是从最高优先级的任务开始)。
3. 线程睡眠
让当前运行的线程延迟一段时间,在指定的时间到达后重新运行,这就叫做 “线程睡眠”。RTT有3个睡眠函数:
rt_err_t rt_thread_sleep(rt_tick_t tick);
rt_err_t rt_thread_delay(rt_tick_t tick);
rt_err_t rt_thread_mdelay(rt_int32_t ms);
其中delay等于sleep,而rt_thread_mdelay最终也是调用delay。用户使用delay,不要用sleep(头文件中没有这个函数,所以会有编译警告)。
4. RTOS通用接口
为了方便切换其他OS,定义RTOS API接口对应RTT的API函数。
#define RTOS_HANDLE rt_thread_t
#define rtosCreateThread( \
handle, /*Thread Handle*/ \
name, /*Thread Name*/ \
entry, /*Thread Entry*/ \
parameter, /*Thread Entry Parameter*/ \
stack, /*the pointer of stack*/ \
stackSize, /*the size of stack*/ \
priority, /*Thread priority*/ \
tick /*The total tick of thread running*/ \
) \
{ \
if(stack != NULL) \
rt_thread_init(handle, name, entry, parameter, stack, stackSize, priority, tick); \
else \
handle = rt_thread_create(name, entry, parameter, stackSize, priority, tick); \
}
#define rtosStartThread( \
handle /*Thread Handle*/ \
) \
{ \
rt_thread_startup(handle); \
}
#define rtosThreadSleep( \
tick \
) \
{ \
rt_thread_delay(tick); \
}
5. 实例
这里采用动态创建进程的方式。在main函数中添加2个进程。
5.1 定义Handle
static RTOS_HANDLE handle1 = NULL;
static RTOS_HANDLE handle2 = NULL;
使用2个全局变量保存创建的handle值。
5.2 入口函数
static void thread1Entry(void *parameter)
{
uint32_t count = 0;
while (1)
{
Printf("thread1 count: %d\n", count++);
rtosThreadSleep(500 * RTOS_TICK_PER_SECOND / 1000);
}
}
static void thread2Entry(void *parameter)
{
uint32_t count = 0;
while (1)
{
Printf("thread2 count: %d\n", count++);
rtosThreadSleep(300 * RTOS_TICK_PER_SECOND / 1000);
}
}
2个进程采用类似的方式,一个每500ms打印一串字符串,另外一个300ms打印一串字符串。注意,进程需要通过Sleep或者其他方式将控制权交出,否则有可能导致低优先级的进程无法运行。
5.3 创建进程
rtosCreateThread(
handle1, //进程句柄,由OS创建完返回
"thread1", //进程名字
thread1Entry, //进程入口,也就是进程实体,执行函数
(void *)0, //进程入口参数,一般没有
NULL, //进程栈地址,这里用create的方式,所以为NULL,有些OS也没有Create的方式
256, //栈大小,如果上一个参数为NULL,则由堆中分配
7, //任务优先级
5); //任务分配的时间片大小
if(handle1 != NULL)
Printf("Create Thread 1 OK\n");
rtosCreateThread(
handle2,
"thread2",
thread2Entry,
(void *)0,
NULL,
256,
6,
5);
if(handle2 != NULL)
Printf("Create Thread 2 OK\n");
2个进程都开了256字节的栈空间,一个优先级为7,另一个为6。
5.4 开始进程
rtosStartThread(handle1);
rtosStartThread(handle2);