在ThreadX中,一般没有进程的概念,统称为线程。
关于调度器的实现细节,ThreadX可能是用汇编写的,没看懂。但大概是一个寻找最高优先级线程来运行的循环,通过_tx_thread_execute_ptr
变量来表示下一个运行的线程。其他线程状态变化的API也会修改这个变量。
ThreadX没有对可以创建的线程数量或者可以使用的优先级组合进行限制。然而,为了优化性能和最小化目标规模,开发人员应该遵循以下准则:
为了理解线程优先级对上下文切换开销的影响,考虑一个线程名为thread_1、thread_2和thread_3的三线程环境。此外,假设所有线程都挂起并等待消息。当thread_1收到消息时,它会立即将消息转发给thread_2。然后,Thread_2将消息转发给thread_3。Thread_3简单地丢弃了该消息。在每个线程处理其消息后,它将再次挂起自己,等待另一条消息。执行这三个线程所需的处理根据它们的优先级有很大的不同。如果所有线程具有相同的优先级,则在每个线程的执行之间会发生单个上下文切换。当每个线程挂起一个空消息队列时,就会发生上下文切换。
但是,如果thread_2的优先级高于thread_1, thread_3的优先级高于thread_2,则上下文切换的数量会翻倍。这是因为当tx_queue_send服务检测到一个高优先级线程已经就绪时,它内部会发生另一个上下文切换。(现在不懂,之后来改)
如果需要为这些线程设置不同的优先级,那么ThreadX抢占阈值机制可以防止这些额外的上下文切换。这是一个重要的特性,因为在线程调度期间,它允许几个不同的线程优先级,同时消除了线程执行期间发生的一些不需要的上下文切换。
创建线程时,需要指定几个参数:
创建线程时,需要给每个线程分配一个堆栈(当线程被抢占时,线程可以存储信息的地方,比如返回地址和局部变量。)
每个堆栈需要一个连续字节块,它由内存字节池 memory byte pool,或内存块池 memory block pool以及数组分配,前两者都需要提前创建。
同一内存字节池/内存块池,可以为多个线程分配堆栈。
以上皆由tx_application_define函数完成。
一旦一个线程获得了互斥锁的所有权,它将保持所有权,直到它自愿放弃互斥锁。
不管线程的优先级如何,没有一个线程可以抢占另一个线程拥有的互斥锁。这是提供线程间互斥的一个重要特性。
因此,低优先级线程应尽量避免长时间占用互斥锁。
ThreadX提供了一种灵活的动态优先级分配方法。尽管每个线程必须有一个优先级,但ThreadX对如何使用优先级没有任何限制。作为一种极端情况,所有线程都可以被分配相同的优先级,而不会发生改变。但是,在大多数情况下,优先级值的分配和修改只是为了反映线程处理过程中重要性的变化。
ThreadX提供了从0-31的优先级值,其中值0表示最高优先级,值31表示最低优先级。
当两个具有不同优先级的线程共享一个公共资源时,可能会出现优先级反转的情况:
当低优先级线程获取了高优先级线程所需的资源(如上节互斥锁),导致高优先级线程挂起时,就会出现这种情况。如果这些线程是唯一活动的线程,则优先级反转时间受低优先级线程占用资源的时间限制。这种情况是确定的,也是很正常的。
如果一个或多个中等优先级的线程在此优先级反转条件期间处于活动状态(因此抢占了较低优先级的线程),则低优先级保有资源的时间将会延长,优先级反转时间将变得不确定,应用程序可能会失败。
优先级继承和抢占阈值,可以解决优先级反转的不确定问题。
在优先级继承中,一个低优先级的线程暂时获得一个高优先级的线程的优先级,而高优先级的线程正在试图获得由低优先级线程拥有的相同的互斥锁。如此可以避免低优先级线程被中等优先级线程抢占。
当低优先级的线程释放互斥锁时,它的原始优先级将被恢复,高优先级的线程将获得互斥锁的所有权。
这个特性通过将反转时间限制在低优先级线程持有互斥锁的时间,从而消除了优先级反转。注意,优先级继承只对互斥量有效,而对计数信号量无效。
抢占阈值 Preemption-Threshold, 是ThreadX特有的特性。创建线程时,开发人员可以为禁用抢占指定优先级上限。这意味着优先级大于指定上限的线程仍然允许抢占,但是优先级等于或小于指定上限的线程不允许抢占。抢占阈值可以在线程执行期间的任何时候被修改。
例如,一个线程优先级值20和抢占阈值15,只有优先级高于15(即0到14)的线程才被允许抢占该线程。即使优先级15到19高于线程的优先级20,但仍不被允许抢占该线程。
通过更改线程的抢占阈值,同样可以解决优先级反转的问题。
ThreadX 有五个不同的线程状态:就绪、挂起、执行、终止和完成。
当一个线程准备好执行时,它会被放在就绪线程列表中。当ThreadX调度一个线程执行时,它会选择并删除就绪线程列表中具有最高优先级的线程。如果列表中的所有线程具有相同的优先级,ThreadX将选择等待时间最长的线程,并采用时间片轮转的调度方式。
在ThreadX中,TCB为结构体TX_THREAD_STRUCT。
通常开发人员不需要知道TCB的细节。但某些情况下特别是调试时,检查某些字段非常有用。如:
TCB中还有许多其他有用的字段,包括堆栈指针、时间片值和优先级。开发人员可以检查TCB的成员,但严禁修改它们。
没有显式的值来指示线程当前是否正在执行。在给定的时间只有一个线程执行,ThreadX在其他地方跟踪当前执行的线程。
注意,正在执行的线程的tx_state的值是TX_READY。
就绪列表其实是一个容量为32(表示32个优先级)的链表数组,数组的每个元素都是线程TCB链表头,存储该优先级已经就绪的线程TCB结点。
该数据结构由common/inc/tx_thread.h
文件定义:
THREAD_DECLARE TX_THREAD * _tx_thread_priority_list[TX_MAX_PRIORITIES];
除此之外,还定义了一个ULONG类型的变量,作为每个优先级是否有线程就绪的标志。
如上图,ULONG变量一共32位,每位表示一个优先级,若该位为1,则表示此优先级有就绪线程。
该数据结构由common/inc/tx_thread.h
文件定义:
THREAD_DECLARE ULONG _tx_thread_priority_maps[TX_MAX_PRIORITIES/32];
UINT tx_thread_create(TX_THREAD *thread_ptr,
CHAR *name_ptr,
VOID (*entry_function)(ULONG id),
ULONG entry_input,
VOID *stack_start,
ULONG stack_size,
UINT priority,
UINT preempt_threshold,
ULONG time_slice,
UINT auto_start)
创建线程:
- 不允许在中断服务程序中调用,只可以在初始化和线程中调用。
- 使用抢占阈值将禁用时间片。合法的时间片值范围是 1 到 0xFFFFFFFF(包括 0)。值为
TX_NO_TIME_SLICE(值为 0)将禁用此线程的时间片。- 使用时间分片会导致少量系统开销。由于时间片仅在多个线程共享相同优先级的情况下才有用,因此,具有唯一优先级的线程不要分配时间片。
UINT tx_thread_delete(TX_THREAD *thread_ptr)
删除线程:
- 不允许在中断服务程序中调用,只可以在线程和定时器组中调用。
- 只能删除处于 Terminated 终止态或者 Completed 完成态的线程。因此也就不可以在要删除的线程
中调用此函数,也就是自己删除自己。
#define tx_thread_identify _tx_thread_identify
TX_THREAD *_tx_thread_identify(VOID)
返回正在执行线程:
UINT tx_thread_info_get( TX_THREAD *thread_ptr,
CHAR **name,
UINT *state,
ULONG *run_count,
UINT *priority,
UINT *preemption_threshold,
ULONG *time_slice,
TX_THREAD **next_thread,
TX_THREAD **next_suspended_thread)
获取线程信息:
UINT tx_thread_preemption_change( TX_THREAD *thread_ptr,
UINT new_threshold,
UINT *old_threshold)
更改线程抢占阈值:
UINT tx_thread_priority_change(TX_THREAD *thread_ptr,
UINT new_priority,
UINT *old_priority)
更改线程优先级:
- 允许在线程和定时器组里面调用。
- 如果要同时更改抢占阈值,则必须在更改优先级完成后再更改抢占阈值。
VOID tx_thread_relinquish(VOID)
转让CPU控制权:
线程可以通过tx_thread_ relinquish自愿地将控制权让给另一个线程。
执行此操作通常是为了实现一种循环调度形式。这个动作是当前正在执行的线程发出的一个协作调用,它暂时放弃对处理器的控制,从而允许具有相同或更高优先级的其他线程执行。
这种技术有时称为协作多线程。
UINT tx_thread_resume(TX_THREAD *thread_ptr)
唤醒线程:
允许在中断,线程,定时器组和初始化中调用。
UINT tx_thread_sleep(ULONG timer_ticks)
线程延迟:
不允许在中断中调用,仅可以在线程中调用
UINT tx_thread_suspend(TX_THREAD *thread_ptr)
线程无条件挂起:
- 允许在中断,线程,定时器组和初始化中调用。
- 如果线程已有条件挂机,则本次无条件挂起将被保存,直到有条件挂起恢复,再执行线程的无条件挂起。若线程已无条件挂起,则请求无效。
UINT tx_thread_terminate(TX_THREAD *thread_ptr)
线程无条件终止:
- 不允许在中断中调用,仅可以在线程和定时器组中调用,可以终止自身。
- 终止后,必须调用函数 tx_thread_reset 复位线程以使其再次执行。
- 应用程序有责任确保线程处于适合终止的状态。例如,线程不应在关键应用程序处理期间或在其他
中间件组件内部终止,否则可能会使这种处理处于未知状态。
UINT _tx_thread_time_slice_change( TX_THREAD *thread_ptr,
ULONG new_time_slice,
ULONG *old_time_slice)
更改线程时间片:
- 如果已经指定了抢占阈值,那么将禁用该线程的时间片。