在线时间13 小时
UID3494883
ST金币214
蝴蝶豆0
注册时间2018-7-23
该用户从未签到
中级会员
- 最后登录
- 2020-9-29
|
楼主 |
发表于 2019-1-25 15:35:00
|
显示全部楼层
4、删除动态线程函数
当不在需要使用某个动态线程的时候,可通过删除动态线程函数把线程完全删除掉。调用该函数后,线程对象将会被移出线程队列并且从内核对象管理器中删除,线程占用的堆栈空间也会被释放,收回的空间将重新用于其他的内存分配。实际上,用 rt_thread_delete() 函数删除线程接口,仅仅是把相应的线程状态更改为 RT_THREAD_CLOSE 状态,然后放入到 rt_thread_defunct 队列中;而真正的删除动作(释放线程控制块和释放线程栈)需要到下一次执行空闲线程时,由空闲线程完成最后的线程删除动作。这个函数是对应动态线程创建函数的。
rt_err_t rt_thread_delete(rt_thread_t thread);
(1)入口参数:
thread:要删除的线程句柄。
(2)返回值:
RT_EOK:删除线程成功。
RT_ERROR:删除线程失败。
5、静态线程创建函数
静态线程创建函数也就是线程初始化,之所以称为线程初始化时因为静态线程的线程句柄(或者说线程控制块指针)、线程栈由用户提供。静态线程是指线程控制块、线程运行栈一般都设置为全局变量,在编译时就被确定、被分配处理,内核不负责动态分配内存空间。需要注意的是,用户提供的栈首地址需做系统对齐(例如 ARM 上需要做 4 字节对齐)。
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);
(1)入口参数:
thread:线程句柄,线程句柄由用户提供出来,并指向对应的线程控制块内存地址。
name:线程的名称;线程名称的最大长度由 rtconfig.h 中定义的 RT_NAME_MAX 宏指定,多余部分会被自动截掉。
entry:线程入口函数。
parameter:线程入口函数参数。
stack_start:线程栈起始地址。
stack_size:线程栈大小,单位是字节,大多数系统中需要做栈空间地址对齐, ARM体系结构中需要向 4 字节地址对齐。
priority:线程的优先级,范围:0~RT_THREAD_PRIORITY_MAX 。
tick:线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度,这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行。
(2)返回值:
RT_EOK:线程创建成功。
RT_ERROR:线程创建失败。
6、删除静态线程函数
当不再需要某个静态线程的时候,可以使用使用 rt_thread_detach() 将使线程对象在线程队列和内核对象管理器中被脱离。
rt_err_t rt_thread_detach(rt_thread_t thread);
(1)入口参数:
thread:线程句柄,它应该是由 rt_thread_init 进行初始化的线程句柄。
(2)返回值:
RT_EOK:线程脱离成功。
RT_ERROR:线程脱离失败
7、启动线程函数
前面讲到动态线程的创建和静态线程的创建,那么要想让线程运行起来,还需要启动线程,就像FreeRTOS或UCOS的启动任务调度一样,使用启动线程函数来启动线程调度,当调用启动线程函数时,将把线程的状态更改为就绪状态,并放到相应优先级队列中等待调度。如果新启动的线程优先级比当前线程优先级高,将立刻切换到这个线程。
rt_err_t rt_thread_startup(rt_thread_t thread);
(1)入口参数:
thread:要启动的线程句柄。
(2)返回值:
RT_EOK:线程启动成功。
RT_ERROR:线程起动失败。
8、挂起线程函数
当线程调用 rt_thread_delay() 时,线程将主动挂起;当调用 rt_sem_take(),rt_mb_recv() 等函数时,资源不可使用也将导致线程挂起。处于挂起状态的线程,如果其等待的资源超时(超过其设定的等待时间),那么该线程将不再等待这些资源,并返回到就绪状态;或者,当其他线程释放掉该线程所等待的资源时,该线程也会返回到就绪状态。
rt_err_t rt_thread_suspend (rt_thread_t thread);
(1)入口参数:
thread:要挂起的线程句柄。
(2)返回值:
RT_EOK:线程挂起成功。
RT_ERROR:线程挂起失败,因为该线程的状态并不是就绪状态。
注 意 事 项:通 常 不 应 该 使 用 这 个 函 数 来 挂 起 线 程 本 身, 如 果 确 实 需 要 采 用rt_thread_suspend() 函 数 挂 起 当
前线程, 需 要 在 调 用 rt_thread_suspend() 函 数 后 立 刻 调 用rt_schedule() 函数进行手动的线程上下文切换。用户只需要了
该接口的作用,RT-Thread不推荐使用该接口。
9、恢复线程函数
恢复线程就是让挂起的线程重新进入就绪状态,并将线程放入系统的就绪队列中;如果被恢复线程在所有就绪态线程中,位于最高优先级链表的第一位,那么系统将进行线程上下文的切换。
rt_err_t rt_thread_resume (rt_thread_t thread);
(1)入口参数:
thread:要恢复的线程句柄。
(2)返回值:
RT_EOK:线程恢复成功。
RT_ERROR:线程恢复失败,因为该线程的状态并不是RT_THREAD_SUSPEND状态。
10、使线程让出处理器资源函数
在执行莫一个线程,当该线程需要完成的事情已经完成了,但还有时间片剩余,那么这个时候可以考虑主动要求让出处理器资源,那么可以用使线程让出处理器资源函数,函数如下。
rt_err_t rt_thread_yield(void);
(1)返回值:
RT_EOK:让出处理器资源成功。
(2)调用该函数后,当前线程首先把自己从它所在的就绪优先级线程队列中删除,然后把自己挂到这个优先级队列链表的尾部,然后激活调度器进行线程上下文切换(如果当前优先级只有这一个线程,则这个线程继续执行,不进行上下文切换动作)。
(3)rt_thread_yield() 函数和 rt_schedule() 函数比较相像,但在有相同优先级的其他就绪态线程存在时,系统的行为却完全不一样。执行 rt_thread_yield() 函数后,当前线程被换出,相同优先级的下一个就绪线程将被执行。而执行 rt_schedule() 函数后,当前线程并不一定被换出,即使被换出,也不会被放到就绪线程链表的尾部,而是在系统中选取就绪的优先级最高的线程执行(如果系统中没有比当前线程优先级更高的线程存在,那么执行完 rt_schedule() 函数后,系统将继续执行当前线程)。
四、基于STM32的线程应用示例
前面都是讲了线程的一些概念,接下来,使用RTT&正点原子联合出品的IoT Board潘多拉开发板来进行实际的操作,创建两个线程,一个是动态创建用于实现潘多拉开发板上面的RGB绿灯循环每隔500ms亮后再个500ms灭,一个静态创建用于实现潘多拉开发板的按键控制RGB红灯亮和灭,按下KEY0时RGB红灯亮同时FinSH打印led_on,按下KEY1时RGB红灯灭同时FinSH打印led_off。这里不实现线程删除、脱离、挂起与恢复,请有兴趣的读者自行尝试实现。
1、实现代码
(1)main.c:
#include "main.h"
#include "board.h"
#include "rtthread.h"
#include "data_typedef.h"
#include "delay.h"
#include "led.h"
#include "key.h"
void led_start(void);
void key_start(void);
int main(void)
{
led_start();
key_start();
return 0;
}
ALIGN(RT_ALIGN_SIZE)
static char led_thread_stack[1024];
static struct rt_thread led_thread;
void led_thread_entry(void *parameter)
{
while(1)
{
LED_G(0);
rt_thread_mdelay(500);
LED_G(1);
rt_thread_mdelay(500);
}
}
void led_start(void)
{
rt_err_t res;
/* 静态创建LED线程 */
res = rt_thread_init(&led_thread,
"led",
led_thread_entry,
RT_NULL,
&led_thread_stack[0],
sizeof(led_thread_stack),
RT_THREAD_PRIORITY_MAX / 2 - 4,
50);
/* 创建LED线程成功,则启动线程 */
if(res != RT_ERROR)
{
rt_thread_startup(&led_thread);
}
}
void key_thread_entry(void *parameter)
{
u8 key;
while(1)
{
key = key_scan(0);
if(key == KEY0_PRES)
{
LED_R(0);
rt_kprintf("led on\r\n");
}
else if(key == KEY1_PRES)
{
LED_R(1);
rt_kprintf("led off\r\n");
}
rt_thread_mdelay(1);
}
}
void key_start(void)
{
rt_thread_t key_thread = RT_NULL;;
/* 动态创建KEY线程 */
key_thread = rt_thread_create("key",
key_thread_entry,
RT_NULL,
512,
RT_THREAD_PRIORITY_MAX / 2 - 5,
50);
/* 创建KEY线程成功,则启动线程 */
if(key_thread != RT_NULL)
{
rt_thread_startup(key_thread);
}
}
(2)key.c:
#include "rtthread.h"
#include "key.h"
/**************************************************************
函数名称 : key_init
函数功能 : IoT_Board 按键初始化
输入参数 : 无
返回值 : 无
备注 : KEY0:PD10
KEY1:PD9
KEY2:PD8
WK_UPC13
**************************************************************/
void key_init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/*Configure GPIO pin : PC13 */
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/*Configure GPIO pins : PD8 PD9 PD10 */
GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
}
/**************************************************************
函数名称 : key_scan
函数功能 : IoT_Board 按键扫描
输入参数 : mode:1 --> 支持连按,0 --> 不支持连按
返回值 : 按键值
备注 : 无
**************************************************************/
u8 key_scan(u8 mode)
{
static u8 key_up = 1; /* 按键松开标志 */
if(mode == 1)
{
key_up = 1; /* 支持连按 */
}
if(key_up && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WK_UP == 1))
{
rt_thread_mdelay(10);
key_up = 0;
if(KEY0 == 0)
{
return KEY0_PRES;
}
else if(KEY1 == 0)
{
return KEY1_PRES;
}
else if(KEY2 == 0)
{
return KEY2_PRES;
}
else if(WK_UP == 1)
{
return WKUP_PRES;
}
}
else if(KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WK_UP == 0)
{
key_up = 1;
}
return NO_PRES; /* 无按键按下 */
}
(3)key.h:
#ifndef __KEY_H__
#define __KEY_H__
#include "data_typedef.h"
#define KEY0 HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_10)
#define KEY1 HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_9)
#define KEY2 HAL_GPIO_ReadPin(GPIOD, GPIO_PIN_8)
#define WK_UP HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13)
enum KEY_PRES
{
NO_PRES = 0,
KEY0_PRES = 1,
KEY1_PRES = 2,
KEY2_PRES = 3,
WKUP_PRES = 4
};
void key_init(void);
u8 key_scan(u8 mode);
#endif
2、FinSH观察结果:
可看到有led线程和key线程,优先级和代码配置的一样。
五、线程的设计要点
在设计之初就应该考虑一些因素:线程运行的上下文环境、线程的执行时间合理设计。
1、RT-Thread 中程序运行的上下文
(1)中断服务函数:中断服务函数是一种需要特别注意的上下文环境,它运行在非线程的执行环境下(一般为芯片的一种特殊运行模式(也被称作特权模式)),在这个上执行环境中不能使用挂起当前线程的操作,不允许调用任何会阻塞运行的 API 函数接口。另外需要注意的是,中断服务程序最好保持精简短小,快进快出,一般在中断服务函数中只做标记事件的发生,让对应线程去执行相关处理,因为中断服务函数的优先级高于任何优先级的线程,如果中断处理时间过长,将会导致整个系统的线程无法正常运行。所以在设计的时候必须考虑中断的频率、中断的处理时间等重要因素,以便配合对应中断处理线程的工作。
(2)普通线程:做为一个优先级明确的实时系统,如果一个线程中的程序出现了死循环操作(此处的死循环是指没有不带阻塞机制的线程循环体),那么比这个线程优先级低的线程都将无法执行,当然也包括了空闲线程,因为死循环的时候,线程不会主动让出 CPU,低优先级的线程是不可能得到 CPU 的使用权的,而高优先级的线程就可以抢占 CPU。这个情况在实时操作系统中是必须注意的一点,所以在线程中不允许出现死循环。如果一个线程只有就绪态而无阻塞态,势必会影响到其他低优先级线程的执行,所以在进行线程设计时,就应该保证线程在不活跃的时候,线程可以进入阻塞态以交出 CPU 使用权,这就需要我们自己明确知道什么情况下让线程进入阻塞态,保证低优先级线程可以正常运行。在实际设计中,一般会将紧急的处理事件的线程优先级设置得高一些。例如上面的KEY线程入口函数key_thread_entry后面加了rt_thread_mdelay(1);,如果不加,会影响FinSH的使用。
(3)空闲线程:请看前面第三章的空闲线程。
2、线程的执行时间
线程的执行时间一般是指两个方面,一是线程从开始到结束的时间,二是线程的周期。在系统设计的时这两个时间候我们都需要考虑,例如,对于事件 A对应的服务线程 Ta,系统要求的实时响应指标是 10ms,而 Ta 的最大运行时间是 1ms,那么 10ms 就是线程 Ta 的周期了,1ms 则是线程的运行时间,简单来说线程 Ta 在 10ms 内完成对事件 A 的响应即可。此时,系统中还存在着以 50ms 为周期的另一线程 Tb,它每次运行的最大时间长度是100us。在这种情况下,即使把线程 Tb的优先级抬到比 Ta更高的位置,对系统的实时性指标也没什么影响,因为即使在 Ta 的运行过程中,Tb 抢占了 Ta 的资源,等到 Tb 执行完毕,
消耗的时间也只不过是 100us,还是在事件 A 规定的响应时间内(10ms),Ta 能够安全完成对事件 A的响应。但是假如系统中还存在线程 Tc,其运行时间为 20ms,假如将 Tc的优先级设置比 Ta 更高,那么在 Ta运行的时候,突然间被 Tc打断,等到 Tc执行完毕,那 Ta已经错过对事件 A(10ms)的响应了,这是不允许的。所以在我们设计的时候,必须考虑线程的时间,一般来说处理时间更短的线程优先级应设置更高一些。
参考文献:
1、[野火®]《RT-Thread 内核实现与应用开发实战—基于STM32》
2、RT-THREAD 编程指南
---------------------
Sanjay_Wu
|
|