一、新建线程,并完成一个LED闪烁,介绍我们完成一个最简单的程序需要做些什么。rtt提供自己的GPIO驱动架构,IO的初始化(rt_pin_mode())、写入(rt_pin_write())、读入(rt_pin_read())三个函数。
- 我们以LED闪烁为诉求,新建test.c文件,我们先写个要调用的函数led_entry(),然后写创建线程分配资源的函数led_test()
static void led_entry(void *parameter) { rt_pin_mode(GPIOF9,PIN_MODE_OUTPUT); while (1) { rt_pin_write(GPIOF9, PIN_HIGH); rt_thread_mdelay(500); rt_pin_write(GPIOF9, PIN_LOW); rt_thread_mdelay(500); } } void led_test(void) { rt_thread_t tid1; /* 创建线程1,名称是thread1,入口是thread1_entry*/ tid1 = rt_thread_create("thread1", led_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); /* 如果获得线程控制块,启动这个线程 */ if (tid1 != RT_NULL) rt_thread_startup(tid1); }
- 填上头文件和必要的宏定义
#include <rtthread.h> #include <rtdevice.h> #define THREAD_PRIORITY 10 //优先级 #define THREAD_STACK_SIZE 512 //资源 #define THREAD_TIMESLICE 10 //时间片 #define GPIOF9 89 //drv_gpio.c里面查得到
-
找到main.c,添加全局函数,并且调用,完成。
extern void led_test(void); int main(void) { led_test(); return RT_EOK; }
二、主要介绍创建线程的创建函数,下面是官方给出的函数说明。
* This function will create a thread object and allocate thread object memory
* and stack.
*
* @param name the name of thread, which shall be unique
* @param entry the entry function of thread
* @param parameter the parameter of thread enter function
* @param stack_size the size of thread stack
* @param priority the priority of thread
* @param tick the time slice if there are same priority thread
*
* @return the created thread object
*/
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)
{
- priority参数代表优先级,描述线程竞争资源的能力。
- 用户可以通过rt_config.h中的RT_THREAD_PRIORITY_MAX宏来修改最大支持的优先级。
- stm32默认支持32个优先级,最低优先级预留给空闲线程。
- 任务A抢占调度当前任务的发生条件:
- 有任务A的优先级大于当前任务的优先级。
- 任务A处于就绪状态。
- 将程序中实时性要求较高的任务优先级设置成高优先级。
- tick参数代表时间片,对相同的优先级的就绪线程采用时间片轮转调度。时间片起到约束线程单次运行时长作用,单位是一个时间节拍。
- 时间片轮询,保证两个线程轮流占用处理器。
- 时间片轮询,保证两个线程轮流占用处理器。
- parameter参数可以将需要的变量传入调用的函数里
// 线程调用的时候向函数里传入了(void*)类型的1 tid = rt_thread_create("thread1", thread_entry, (void*)1, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE) //这是函数里面同样需要void*类型的参数 static void thread_entry(void* parameter)
三、空闲线程钩子函数
- 空闲线程钩子函数可以让系统在空闲的时候执行一些非紧急事务,例如系统运行指示灯闪烁,CPU使用率统计等等。
- rt_err_t rt_thread_idle_sethook(void(*hook)(void))设置钩子函数,放置在创建线程的函数中。
- rt_err_t rt_thread_idle_delhook(void(*hook)(void))删除钩子函数,放置在线程调用的函数中。
- 空闲线程是一个线程状态永远为就绪态的线程,所以钩子函数中不得包含使线程挂起的阻塞类函数,例如:rt_thread_delay()、rt_sem_take()。
- 如果需要打印系统线程的转换关系:可以在创建线程的函数中调用系统调度钩子函数rt_scheduler_sethook(),要注意的是对应的调度函数为:static void hook_of_scheduler(struct rt_thread* from, struct rt_thread* to){},里面的参数不能变。别的可以参考官方给的例程。
int scheduler_hook(void) { /* 设置调度器钩子 */ rt_scheduler_sethook(hook_of_scheduler); /* 创建线程1 */ tid1 = rt_thread_create("thread1", thread_entry, (void*)1, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (tid1 != RT_NULL) rt_thread_startup(tid1); /* 创建线程2 */ tid2 = rt_thread_create("thread2", thread_entry, (void*)2, THREAD_STACK_SIZE, THREAD_PRIORITY,THREAD_TIMESLICE - 5); if (tid2 != RT_NULL) rt_thread_startup(tid2); return 0; }
四、对调度的函数进行临界区保护
-
临界资源:一次仅允许一个线程访问的共享资源,可以是一个具体的硬件设备(比如打印机),也可以是一个变量,一个缓冲区。不论是硬件或软件的资源,多个线程必须互斥的对他们进行访问。一段时间里只允许一个线程进行访问。
-
每个线程访问临界资源的代码称为临界区,每次只允许一个线程进入临界区。
-
两个方法保护临界区:
-
禁止调度:给任务调度器加锁,不让进行线程切换
-
上锁:rt_enter_critical()
-
解锁:rt_exit_critical()
-
放在需要调度的线程里。
-
-
关闭中断:因为所有的线程调度是建立在中断的基础上。但是此时所有的中断都不能用了,所有不建议用。
void thread_entry(void *parameter) { rt_uint32_t level; while (1) { /* 关闭中断 */ level = rt_hw_interrupt_disable(); { //写自己需要的代码 } rt_hw_interrupt_enable(level); } }
-
参考:https://www.rt-thread.org/document/site/tutorial/kernel/kernel-video/