一、线程管理
RT-Thread是支持多任务的操作系统,多任务是通过多线程的方式实现。线程是任务的载体,是RTT中最基本的调度单位。
线程在运行的时候,它自己会认为独占CPU运行
线程执行时的运行环境称为上下文,具体来说就是各个变量和数据,包括所有的寄存器变量、堆栈、内存信息等。
1.1、线程管理特点
RT-Thread 线程管理的主要功能是对线程进行管理和调度,系统中总共存在两类线程,分别是系统线程和用户线程,系统线程是由 RT-Thread 内核创建的线程,用户线程是由应用程序创建的线程,这两类线
程都会从内核对象容器中分配线程对象,当线程被删除时,也会被从对象容器中删除。
RT-Thread 的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到 CPU 的使用权。
当调度器调度线程切换时,先将当前线程上下文保存起来,当再切回到这个线程时,线程调度器将该线程的上下文信息恢复。
1.2、线程工作机制
1.2.1、线程控制块
线程控制块由结构体 struct rt_thread 表示,线程控制块是操作系统用于管理线程的一个数据结构,它会存放线程的一些信息,例如优先级、线程名称、线程状态等,也包含线程与线程之间连接用的链表结构,线程等待事件集合等。
struct rt_thread
{
/* rt object */
char name[RT_NAME_MAX]; /**< the name of thread */
rt_uint8_t type; /**< type of object */
rt_uint8_t flags; /**< thread's flags */
rt_list_t list; /**< the object list */
rt_list_t tlist; /**< the thread list */
/* stack point and entry */
void *sp; /**< stack point 栈指针*/
void *entry; /**< entry */
void *parameter; /**< parameter */
void *stack_addr; /**< stack address point 栈地址指针*/
rt_uint32_t stack_size; /**< stack size */
/* error code */
rt_err_t error; /**< error code */
rt_uint8_t stat; /**< thread status 线程状态*/
/* priority */
rt_uint8_t current_priority; /**< current priority */
rt_uint8_t init_priority; /**< initialized priority */
rt_uint32_t number_mask;
...
rt_ubase_t init_tick; /**< thread's initialized tick */
rt_ubase_t remaining_tick; /**< remaining tick */
struct rt_timer thread_timer; /**< built-in thread timer */
void (*cleanup)(struct rt_thread *tid); /**< cleanup function when thread exit */
rt_uint32_t user_data; /**< private user data beyond this thread */
};
注:cleanup函数指针指向的函数,会在线程退出的时候,被idle线程回调一次,执行用户设置的清理现场等工作。
1.2.2、线程属性
☐ 线程栈
RT-Thread 线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。
☐ 线程状态
☐ 线程优先级
RT-Thread 最大支持 256 个线程优先级 (0~255),数值越小的优先级越高,0 为最高优先级。在一些资源比较紧张的系统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置;对于 ARM Cortex-M系列,普遍采用 32 个优先级。最低优先级默认分配给空闲线程使用,用户一般不使用。在系统中,当有比当前线程优先级更高的线程就绪时,当前线程将立刻被换出,高优先级线程抢占处理器运行
☐ 时间片
每个线程都有时间片这个参数,但时间片仅对优先级相同的就绪态线程有效。
注意:
作为一个实时系统,一个优先级明确的实时系统,如果一个线程中的程序陷入了死循环操作,那么比它优先级低的线程都将不能够得到执行。所以在实时操作系统中必须注意的一点就是:线程中不能陷入死循环操作,必须要有让出 CPU使用权的动作,如循环中调用延时函数或者主动挂起。
1.2.3、线程状态之间切换
注:就绪状态和运行状态是等同的
1.2.4、系统线程
RT-Thread 内核中的系统线程有空闲线程和主线程。
1.3、线程相关操作
线程相关的操作包括:创建/初始化、启动、运行、删除/脱离。
动态线程与静态线程的区别是:动态线程是系统自动从动态内存堆上分配栈空间与线程句柄(初始化 heap 之后才能使用 create 创建动态线程),静态线程是由用户分配栈空间与线程句柄。
1.3.1、创建和删除线程
/**
* 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)
/**
* This function will delete a thread. The thread object will be removed from
* thread queue and deleted from system object management in the idle thread.
*
* @param thread the thread to be deleted
*
* @return the operation status, RT_EOK on OK, -RT_ERROR on error
*/
rt_err_t rt_thread_delete(rt_thread_t thread)
1.3.2、初始化和脱离线程
线程的初始化可以使用下面的函数接口完成,来初始化静态线程对象:
/**
* This function will initialize a thread, normally it's used to initialize a
* static thread object.
*
* @param thread the static thread object
* @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_start the start address of thread stack
* @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 operation status, RT_EOK on OK, -RT_ERROR on error
*/
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)
/**
* This function will detach a thread. The thread object will be removed from
* thread queue and detached/deleted from system object management.
*
* @param thread the thread to be deleted
*
* @return the operation status, RT_EOK on OK, -RT_ERROR on error
*/
rt_err_t rt_thread_detach(rt_thread_t thread)
1.3.3、启动线程
/**
* This function will start a thread and put it to system ready queue
*
* @param thread the thread to be started
*
* @return the operation status, RT_EOK on OK, -RT_ERROR on error
*/
rt_err_t rt_thread_startup(rt_thread_t thread)
注:当调用这个函数时,将把线程的状态更改为就绪状态,并放到相应优先级队列中等待调度。如果新启动的线程优先级比当前线程优先级高,将立刻切换到这个线程。
1.3.4、获得当前线程
在程序的运行过程中,相同的一段代码可能会被多个线程执行,在执行的时候可以通过下面的函数接口获得当前执行的线程句柄
/**
* This function will return self thread object
*
* @return the self thread object , failed RT_NULL
*/
rt_thread_t rt_thread_self(void)
1.3.5、让出处理器资源
/**
* This function will let current thread yield processor, and scheduler will
* choose a highest(բ/ָĊߓŏȼ) thread to run. After yield processor, the current thread
* is still in READY state.
*
* @return RT_EOK
*/
rt_err_t rt_thread_yield(void)
1.3.6、线程睡眠
/**
* This function will let current thread sleep for some ticks.
*
* @param tick the sleep ticks
*
* @return RT_EOK
*/
rt_err_t rt_thread_sleep(rt_tick_t tick)
rt_err_t rt_thread_delay(rt_tick_t tick)
/**
* This function will let current thread delay for some milliseconds.
*
* @param tick the delay time
*
* @return RT_EOK
*/
rt_err_t rt_thread_mdelay(rt_int32_t ms)
1.3.7、控制线程函数
/**
* This function will control thread behaviors according to control command.
*
* @param thread the specified thread to be controlled
* @param cmd the control command, which includes
* RT_THREAD_CTRL_CHANGE_PRIORITY for changing priority level of thread;
* RT_THREAD_CTRL_STARTUP for starting a thread; == rt_thread_startup()
* RT_THREAD_CTRL_CLOSE for delete a thread; == rt_thread_delete()
* RT_THREAD_CTRL_BIND_CPU for bind the thread to a CPU.
* @param arg the argument of control command
*
* @return RT_EOK
*/
rt_err_t rt_thread_control(rt_thread_t thread, int cmd, void *arg)
1.3.8、设置和删除idle线程hook函数
/**
* @ingroup Hook
* This function sets a hook function to idle thread loop. When the system performs
* idle loop, this hook function should be invoked.
*
* @param hook the specified hook function
*
* @return RT_EOK: set OK
* -RT_EFULL: hook list is full
*
* @note the hook function must be simple and never be blocked or suspend.
*/
rt_err_t rt_thread_idle_sethook(void (*hook)(void))
/**
* delete the idle hook on hook list
*
* @param hook the specified hook function
*
* @return RT_EOK: delete OK
* -RT_ENOSYS: hook was not found
*/
rt_err_t rt_thread_idle_delhook(void (*hook)(void))
注意:空闲线程是一个线程状态永远为就绪态的线程,因此设置的钩子函数必须保证空闲线程在任何时刻都不会处于挂起状态,例如 rt_thread_delay(),rt_sem_take() 等可能会导致线程挂起的函数都不能使用。
1.3.9、设置调度器hook函数
在整个系统的运行时,系统都处于线程运行、中断触发 - 响应中断、切换到其他线程,甚至是线程间的切换过程中,或者说系统的上下文切换是系统中最普遍的事件。有时用户可能会想知道在一个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个相应的钩子函数。在系统线程切换时,这个钩子函数将被调用
/**
* This function will set a hook function, which will be invoked when thread
* switch happens.
*
* @param hook the hook function
*/
void rt_scheduler_sethook(void (*hook)(struct rt_thread *from, struct rt_thread *to))
1.4、线程应用案例
1.4.1、创建线程
见参考代码
1.4.2、线程调度器hook
见参考代码
二、时钟管理
操作系统需要通过时间来规范其任务,本章主要介绍时钟节拍和基于时钟节拍的定时器。
2.1、时钟节拍
任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件,如线程的延时、线程的时间片轮转调度以及定时器超时等。
RT-Thread 中,时钟节拍的长度可以根据 RT_TICK_PER_SECOND 的定义来调整。 rtconfig.h配置文件中定义:
/*
*频率是1000HZ 周期是1/1000 s
*所以节拍是1ms
*/
#define RT_TICK_PER_SECOND 1000
系统滴答定时器中断处理函数(每1ms触发一次systick定时器中断):
2.2、获取系统节拍
2.2.1、获取系统计数函数
/**
* This function will return current tick from operating system startup
*
* @return current tick
*/
rt_tick_t rt_tick_get(void)
2.2.2、实例
/*
*...
*[D/main] tm:7018
*[D/main] tm:8020
*[D/main] tm:9022
*[D/main] tm:10024
*...
*/
int main(void)
{
rt_tick_t tm = 0;
LOG_D("Hello RT-Thread!");
while (1)
{
tm = rt_tick_get();
LOG_D("tm:%-d\n",tm);
rt_thread_mdelay(1000);
}
return RT_EOK;
}
2.3、定时器
定时器,是指从指定的时刻开始,经过一定的指定时间后触发一个事件,定时器有硬件定时器和软件定时器之分:
硬件定时器: 芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。
硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。
软件定时器: 由操作系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受数目限制的定时器服务。
RT-Thread操作系统提供软件实现的定时器,以时钟节拍(OS Tick)的时间长度为单位,即定时数值必须是OS Tick的整数倍
2.3.1、RT_Thread定时器介绍
RT-Thread 的定时器提供两类定时器机制:
☐ 第一类是单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动停止。
☐ 第二类是周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动的停止,否则将永远持续执行下去
根据定时器超时函数执行时所处的上下文环境,RT-Thread的定时器可以分为HARD_TIMER模式和SOFT_TIMER模式。
HARD_TIMER模式:中断上下文
定时器超时函数的要求:执行时间应该尽量短,执行时不应导致当前上下文挂起、等待。例如在中断上下文中执行的超时函数它不应该试图去申请动态内存、释放动态内存等
SOFT_TIMER模式:线程上下文
该模式被启用后,系统会在初始化时创建一个 timer 线程,然后 SOFT_TIMER 模式的定时器超时函数在都会在timer线程的上下文环境中执行
2.3.2、定时器源码分析
(1) RT-Thread OS启动阶段,执行rtthread_startup函数,在该函数中调用了定时器初始化函数
/* timer system initialization */
rt_system_timer_init();
/* timer thread initialization */
rt_system_timer_thread_init();
(2)rt_system_timer_init (硬件定时器初始化)
struct rt_list_node
{
struct rt_list_node *next; /**< point to next node. */
struct rt_list_node *prev; /**< point to prev node. */
};
typedef struct rt_list_node rt_list_t;
void rt_system_timer_init(void)
{
int i;
/* 结构体数组,在初始化的时候只有一个元素,就是链表头,后期添加定时器,按定时器定时时间顺序进行排序插入*/ for (i = 0; i < sizeof(rt_timer_list) / sizeof(rt_timer_list[0]); i++)
{
rt_list_init(rt_timer_list + i);
}
}
/**
* @brief initialize a list t퍷
*
* @param l list to be initialized
*/
rt_inline void rt_list_init(rt_list_t *l)
{
l->next = l->prev = l;
}
(3)rt_system_timer_thread_init(软件定时器初始化)
/**
* @ingroup SystemInit
*
* This function will initialize system timer thread
*/
void rt_system_timer_thread_init(void)
{
#ifdef RT_USING_TIMER_SOFT
int i;
/* 初始化链表头*/
for (i = 0;i < sizeof(rt_soft_timer_list) / sizeof(rt_soft_timer_list[0]);i++)
{
rt_list_init(rt_soft_timer_list + i);
}
/* start software timer thread */
rt_thread_init(&timer_thread, //启动一个定时器线程(软件定时器方式)
"timer",
rt_thread_timer_entry,
RT_NULL,
&timer_thread_stack[0],
sizeof(timer_thread_stack),
RT_TIMER_THREAD_PRIO,
10);
/* startup */
rt_thread_startup(&timer_thread);
#endif
}
2.3.3、定时器工作机制
下面以一个例子来说明 RT-Thread 定时器的工作机制。在 RT-Thread 定时器模块中维护着两个重要的全局变量:
☐ 当前系统经过的 tick 时间 rt_tick(当硬件定时器中断来临时,它将加 1) ;
☐ 定时器链表 rt_timer_list。系统新创建并激活的定时器都会按照以超时时间排序的方式插入到rt_timer_list 链表中。
如下图所示,系统当前tick值为20,在当前系统中已经创建并启动了三个定时器,分别是定时时间为50个tick的Timer1、100个tick的Timer2和500个tick的Timer3,这三个定时器分别加上系统
当前时间 rt_tick=20,从小到大排序链接在 rt_timer_list 链表中,形成如图所示的定时器链表结构。
而 rt_tick 随着硬件定时器的触发一直在增长(每一次硬件定时器中断来临,rt_tick 变量会加 1) ,50个tick以后,rt_tick从20增长到70,与Timer1的timeout值相等,这时会触发与Timer1定时器相关联的超时函数,同时将Timer1从rt_timer_list链表上删除。同理,100个tick和500个tick过去后,与Timer2 和 Timer3 定时器相关联的超时函数会被触发,接着将 Time2 和 Timer3 定时器从 rt_timer_list链表中删除。
如果系统当前定时器状态在 10 个 tick 以后(rt_tick=30)有一个任务新创建了一个 tick 值为 300 的Timer4定时器,由于Timer4定时器的timeout=rt_tick+300=330,因此它将被插入到Timer2和Timer3定时器中间,形成如下图所示链表结构:
2.3.4、定时器相关接口
☐ 动态创建一个定时器和删除定时器
/**
* This function will create a timer
*
* @param name the name of timer
* @param timeout the timeout function
* @param parameter the parameter of timeout function
* @param time the tick of timer
* @param flag the flag of timer
* #define RT_TIMER_FLAG_ONE_SHOT 0x0 /**< one shot timer */
* #define RT_TIMER_FLAG_PERIODIC 0x2 /**< periodic timer */
* #define RT_TIMER_FLAG_HARD_TIMER 0x0 /**< hard timer,the timer's callback function will be called in tick isr. */
* #define RT_TIMER_FLAG_SOFT_TIMER 0x4 /**< soft timer,the timer's callback function will be called in timer thread. */
* @return the created timer object
*/
rt_timer_t rt_timer_create(const char *name,
void (*timeout)(void *parameter),
void *parameter,
rt_tick_t time,
rt_uint8_t flag)
/**
* This function will delete a timer and release timer memory
*
* @param timer the timer to be deleted
*
* @return the operation status, RT_EOK on OK; RT_ERROR on error
*/
rt_err_t rt_timer_delete(rt_timer_t timer)
☐ 初始化和脱离定时器
/**
* This function will initialize a timer, normally this function is used to
* initialize a static timer object.
*
* @param timer the static timer object (typedef struct rt_timer *rt_timer_t;)
* @param name the name of timer
* @param timeout the timeout function
* @param parameter the parameter of timeout function
* @param time the tick of timer
* @param flag the flag of timer
*/
void rt_timer_init(rt_timer_t timer,
const char *name,
void (*timeout)(void *parameter),
void *parameter,
rt_tick_t time,
rt_uint8_t flag)
静态定时器不需要再使用时,可以使用下面的函数接口:
/**
* This function will detach a timer from timer management.
*
* @param timer the static timer object
*
* @return the operation status, RT_EOK on OK; RT_ERROR on error
*/
rt_err_t rt_timer_detach(rt_timer_t timer)
☐ 启动和停止定时器
/**
* This function will start the timer
*
* @param timer the timer to be started
*
* @return the operation status, RT_EOK on OK, -RT_ERROR on error
*/
rt_err_t rt_timer_start(rt_timer_t timer)
若想使它停止,可以使用下面的函数接口:
/**
* This function will stop the timer
*
* @param timer the timer to be stopped
*
* @return the operation status, RT_EOK on OK, -RT_ERROR on error
*/
rt_err_t rt_timer_stop(rt_timer_t timer)
☐ 控制定时器
/**
* This function will get or set some options of the timer
*
* @param timer the timer to be get or set
* @param cmd the control command
* @param arg the argument
* #define RT_TIMER_CTRL_SET_TIME 0x0 /**< set timer control command */
* #define RT_TIMER_CTRL_GET_TIME 0x1 /**< get timer control command */
* #define RT_TIMER_CTRL_SET_ONESHOT 0x2 /**< change timer to one shot */
* #define RT_TIMER_CTRL_SET_PERIODIC 0x3 /**< change timer to periodic */
* @return RT_EOK
*/
rt_err_t rt_timer_control(rt_timer_t timer, int cmd, void *arg)
2.3.5、定时器示例
见源码
2.4、高精度延时
注意:这个函数只支持低于1个OS Tick的延时, 否则SysTick会出现溢出而不能够获得指定的延时时间
/**
* This function will delay for some us.
*
* @param us the delay time of us
*/
void rt_hw_us_delay(rt_uint32_t us)