线程与任务等同。
可见,每个线程切换时都会将上下文保存到自己的栈中。线程栈的对齐方式为rtconfig.h中RT_ALIGN_SIZE对齐的RAM空间。
另外从编程手册的timeslice_sample.c这个例子来看,不同的线程调用同一个入口函数,实际上就是将函数的参数及局部变量复制到不同线程的堆栈中,这样就可以实现重入了。
线程控制块结构体rt_thread,也是在rtdef.h中定义。
线程状态转换图:
RTT的就绪态等同于运行态,实际上是不存在运行态的。
线程操作:
创建线程:rt_thread_create动态线程,是从系统自动从内存堆上分配栈空间和线程句柄。(初始化heap之后才能使用)线程创建不可以在中断中调用
rt_thread_init():是用户分配栈空间和线程句柄。可以在中断中调用
注:时间片参数,仅对存在同优先级的线程有效,也就是说同优先级是时间片轮转,对于不同优先级是抢占的形式。
删除线程:rt_thread_delete()对应动态线程删除。rt_thread_detach()对应静态线程删除(由于是静态,所以不能释放对象所占用的内存)。删除不应在本线程内调用。不论动态还是静态删除都可以在中断中调用。
将线程结象移出等待队列、从内核管理器删除、释放申请的堆空间。实际上,用rt_thread_delete函数删除线程接口,仅仅是把相应的线程状态更改为RT_THREAD_CLOSE状态,然后放入到rt_thread_defunct队列中;而真正的删除动作(释放线程控制块和释放线程栈)需要到下一次执行idle线程时,由idle线程完成最后的线程删除动作。
删除可以在中断中调用。
启动线程:进入调度序列,并将线程状态改为就绪态。真正的程序运行,是调用调度rt_system_scheduler_start(),启动系统调度器,然后在就绪表中选择一个优先级最高的执行。
可在中断中调用。
默认是启动了三个线程,main,tshell,tidel0,下面看一下,这三个线程的优先级分别是多少??
main线程定义在component.c中如下图:
其中RT_THREAD_PRIORITY_MAX是在rtconfig.h中定义的,最大优先级数,默认是32 ,32/3=10,所以main的优先级是10。
tshell线程定义在shell.c中
FINSH_THREAD_PRIORITY,也是定义在rtconfig.h中,默认值是20,可见main线程优先级高于FinSH线程.
最后看一下tidel0线程:
tidel0线程调用是在componet.c中启动函数rtthread_startup(void)中,如下图
定义在系统文件idle.c文件中,是静态创建的一个线程,如下图:
优先级为31级,是最小的。
获取当前执行的线程句柄:rt_thread_self(),可以知道是哪个线程在运行。可在中断中调用。
线程让出处理器使用资源:都可以中断中调用。
rt_thread_yield():在就绪表中删除此线程,然后再挂到就绪表的尾部。线程主动让出CPU.选择当前最高优先级线程执行
rt_schedule():切换最高优先级线程。所以当前线程不一定会被换出。
线程睡眠:使线程(主动)挂起指定的时间后,再恢复运行。不可在中断中调用
rt_thread_sleep():
rt_thread_delay():以上两个以系统tick为单位。
rt_thread_mdelay():以时间ms为单位。
线程挂起和恢复:rt_thread_suspend(),挂起,不推荐使用。可以在中断中调用
挂起分为主动:调用上面的线程睡眠API
被动:调用rt_sem_take()或rt_mb_recv()时资源不可使用,导致。
rt_thread_resume():恢复挂起的线程,重新进就绪表
控制线程:更改线程的优先级,开启/关闭线程。可以在中断中调用
设置调度器钩子函数rt_scheduler_sethook(位于scheduler.c中,安全的,可以在中断中调用)(在系统运行的某一路径一设置一个钩子,当运行到此位置时,转到执行钩子函数,执行完后,再返回正常的路径):在上下文切换时(包括在中断切换),会被调用的函数。
调度器相关的函数:
调度器初始化函数:
componet.c--->rtthread_startup()---->rt_system_scheduler_init();位于(scheduler.c中)
不安全,不能在中断中调用
调度器启动函数:
componet.c--->rtthread_startup()---->rt_system_scheduler_start();位于(scheduler.c中)
不安全,不能在中断中调用
执行调度:(scheduler.c中)------> void rt_schedule(void);
安全的,可以在中断中调用
空闲线程,最低优先级,是一个死循环,永远不会被挂起。在idel.c中定义。
初始化空闲线程:
componet.c--->rtthread_startup()---->rt_thread_idle_init() 位于idle.c中。
不安全,不可以在中断中调用。
设置和删除空闲钩子函数:
设置rt_thread_idle_sethook:执行空闲函数时要做的事情。钩子函数不能出现挂起操作。不可以在中断中调用。
删除rt_thread_idle_delhook:删除一个空闲线程钩子函数
真正的线程删除操作(rt-thread---->clearup()函数也放到了空闲函数中。
线程设计需要注意的点:
应该非常清楚自己的程序处于何种状态。
程序上下文:包括中断服务例程、普通线程、空闲线程。
空闲线程的钩子函数,可以完成系统运行状态的指示,省电模式等。除此之外,还应1、不会挂起idle线程 2、不应该陷入死循环,需要留出部分时间用于系统处理僵尸线程的系统资源回收。
中断服务例程:用的是主堆栈MSP,在这个上下文环境中不能使用挂起当前线程的操作。还应精简短小,是高于任何线程的存在。
普通线程:应不可以执行死循环。
线程设计要点:
嵌入式实时系统是一种被动系统,被动响应外部事件,当外部事件触发后执行设定的工作内容。需要应注意:
上下文环境:首先思考它的执行环境是什么,工作内容与工作内容间是否有重叠的部分,是否能合并到一起进行处理。
线程的状态跃迁,指线程运行中状态的变化,从就绪态过渡到挂起态。在进行线程设计时,应该保证线程在不活跃的时候,必须让出处理器。这需要明确的知道在什么情况下需要让线程从就绪态跃迁到阻塞态。
线程运行时间长度,线程所关心的一种事件或多种事件触发状态下,线程由阻塞态跃迁为就绪态执行设定的工作,再从就绪态跃迁为阻塞态所需要的时间(一般还应加上这段时间内,这个线程不会被其他线程所抢占的先决条件)。
线程运行时间长度和线程优先级设计密切相关,同时也决定着能否满足实时响应的指标。