2021年12月7日23点54分: 目前这个调度器已更新
https://blog.csdn.net/qq_42907191/article/details/121772005.
线程调度器H_TS[H_ThreadScheduler]
源码
二话不说,先上代码
链接: https://gitee.com/H0x9DEFA478/H_TS.git.
源码中包含了H_TS以及一个例子,例子使用的单片机型号为STM32F730R8T6
之前我实现过一个简陋版本:链接: https://blog.csdn.net/qq_42907191/article/details/116611128.
特性
- 最低只占用一个可软件触发的中断和一个定时器中断。
- 可选择使能CPU占用统计,堆栈占用统计。(其中CPU占用统计需另外占用一个定时器(需要比Sysictk更高的频率))
- 抢占型,只有就绪的最高优先级的线程会运行
- H_TS全程不屏蔽、不关闭系统的中断,不影响中断的响应。对于中断来说,H_TS就像裸机的主循环一样。
- 线程可休眠(这玩意肯定得要有)
- 互斥锁
- 信号量
- 消息队列(固定每个消息2个指针)
H_TS实现
对资源占用
使用H_TS,系统需提供:
(必须)1:
一个可由软件触发的中断,该中断要能处于最低优先级,并且在该中断正 在运行时该中断可以以相同方式再次触发,使得中断退出之后再次进入该中断。该中断被H_TS完全占用 (STM32中可以使用PendSV)
(必须)2:
一个定时器中断,用于给H_TS提供时基。该中断可以不被H_TS完全占用,只需要在中断中调用H_TS的Systick处理函数即可 (STM32中的Systick)
(可选)3:
如果需要使能CPU占用率计算功能,还需要一个计时器(使得H_TS能获取一个时间)
内核
内核这个词有点高大上了,H_TS似乎并没有什么东西是能称作内核的。
结构
公开结构
公开是指仅包含 H_ThreadScheduler.h 这个文件就能使用到的对象。
公开的结构体有H_TS_Thread(线程定义),H_TS_Lock(互斥锁定义),H_TS_Semaphore (信号量定义),H_TS_MessageQueue(消息队列定义)。
内部结构
内部是指仅包含 H_ThreadScheduler.h 这个文件也不能能使用到的对象,这些对象只在H_TS内部被使用。
内部结构体有 H_TS(H_TS的核心句柄定义)
H_TS文件说明
H_TS有2个文件夹,分别为
H_ThreadScheduler->LL
H_ThreadScheduler->API
API存放的是一些用户接口
LL存放的是与底层相关的文件
其中
H_ThreadScheduler->LL->H_ThreadScheduler_Memory.c是与内存分配相关的的文件,其内部的几个函数需要被实现
H_ThreadScheduler->LL->H_ThreadScheduler_Config.h 配置H_TS的功能
其他的几个文件实现了其他的底层接口
H_TS的使用
移植
- 将文件夹H_ThreadScheduler添加到自己的工程文件后,一般来说只需要修改H_ThreadScheduler->LL目录下的文件就可以了。下面说明文件如何修改
H_ThreadScheduler_Config.h
#ifndef __H_ThreadScheduler_Config_H_
#define __H_ThreadScheduler_Config_H_
//最底优先级 32位有符号整型最大值为 0x7FFFFFFF
#define vH_TS_ThreadMaxPriorityValue 0x7FFFFFFF
//堆栈指针增长方向 0:地址减少方向 其他:地址增加方向
#define vH_TS_StackPtrDirection 0
//是否使能堆栈占用统计 0:关闭 其他:开启
#define vH_TS_isEnableStackuUsageStatistics 1
//阻塞链表数 设置为 sqrt(阻塞任务数) 为最佳 (实际上阻塞任务随着程序运行是不定的 根据实际情况确定)
#define vH_TS_BlockThreadListNum 4
//tick计数使用的类型
typedef int H_TS_Tick;
//是否使用cpu占用率统计 0:关闭 其他:开启
#define vH_TS_isEnableCPU_Utilization 0
#endif //__H_ThreadScheduler_Config_H_
说明
- 注释已有说明
- 其中
vH_TS_ThreadMaxPriority的定义需要根据机器定义,此段表示线程优先级的最大值(对应最小优先级 优先级的值越大优先级越低)
vH_TS_BlockThreadListNum字段说明:
由于H_TS不关中断,并且其也不使用类似于
strex,ldrex的用于互斥范围的指令,当阻塞列表很长时(很多线程都在阻塞也不是稀罕事),位于列表尾部的线程解阻塞时,线程切换中断会遍历整个链表,降低线程切换速度。当线程切换频繁时(甚至远高于Systick中断频率),遍历链表会带来一些性能损耗(或许并不高,有谁会开一大堆线程呢)。
对此,H_TS使用了一个策略,阻塞链表不止有一个,对阻塞的线程进行分批管理,线程解阻塞时只会遍历对应的链表。
而vH_TS_BlockThreadListNum代表的就是阻塞链表条数。一般设置为sqrt(阻塞任务数),使得达到这个任务数时,阻塞链表的长度都为vH_TS_BlockThreadListNum,就像一个正方形。
由于寻找阻塞的线程在哪个链表也需要时间,vH_TS_BlockThreadListNum不宜太大,线程切换中断会对一个长度为vH_TS_BlockThreadListNum的数组遍历(如果有线程解阻塞)。
H_ThreadScheduler_Memory.c
- 里面定义了一些H_TS会调用的一些关于内存管理的接口。H_TS本身不实现内存管理,在这个文件中可以将自己的内存管理方法实现在这个文件中。如果没有内存管理方法,可以使用H_Lib下的H_Malloc,这是我实现的一个内存管理算法
#include "H_Malloc.h"
#define vH_TS_MemSize (16*1024)
//__attribute__((aligned(H_Malloc_Align)))
__align(H_Malloc_Align) static Hbyte Mem[vH_TS_MemSize];
/**
* @brief 分配内存
* @param Size 目标大小
* @return 内存指针
*/
void* H_TS_Malloc(Hsize Size){
return H_Malloc(Mem,Size);
}
/**
* @brief 释放内存
* @param v 要释放的内存指针
* @return 无
*/
void H_TS_Free(void* v){
H_Free(Mem,v);
}
/**
* @brief 获取内存信息
* @param FreeSize 用于返回剩余栈空间
* @param AllSize 返回所有的栈空间
* @return 无
*/
void H_TS_MemoryGetInfo(Hsize* FreeSize,Hsize* AllSize){
H_Malloc_Info_Def info;
H_Malloc_GetInfo(Mem,&info);
*AllSize=vH_TS_MemSize;
*FreeSize=info.FreeSize;
}
/**
* @brief 初始化内存 此方法在H_TS初始化时被调用(H_TS_Init())
* @return 无
*/
void H_TS_MemoryInit(){
H_Malloc_Init(Mem,vH_TS_MemSize);
}
/**
* @brief 内存错误回调
* @return 无
*/
void H_TS_MemoryErrorCallback(){
H_Malloc_Info_Def info;
H_Malloc_GetInfo(Mem,&info);
if(info.Result==0){
return;
}
for(;;){}
}
说明
- 注释以对各个方法有所说明,如果H_TS获取到了为NULL的内存,
void H_TS_MemoryErrorCallback()将被调用(不会影响到H_TS运行 例如创建线程时获取到NULL了则相关API会返回创建失败)。 void H_TS_MemoryGetInfo(Hsize* FreeSize,Hsize* AllSize)是用于获取内存信息,如果不使用H_TS获取剩余内存的API,此方法可以不实现(返回1即可,AllSize不建议返回0,会导致除0计算)
void H_TS_MemoryErrorCallback()其实是提供给用户的,用于在申请到空内存是执行一些用户代码,甚至可以在其中加入死循环,阻塞H_TS(Debug我喜欢这么用)
H_ThreadScheduler_LL.h
#ifndef __H_ThreadScheduler_LL_H_
#define __H_ThreadScheduler_LL_H_
#include "H_ThreadScheduler.h"
//用户包含 H_ThreadScheduler_LL.c H_ThreadScheduler.c会包含H_ThreadScheduler_LL.h
#include "Peripheral.h"
//栈标记值
#define vH_TS_StackMarkValue 0x55AA55AAU
//指针最大值 (不能为0)
#define vH_TS_VoidPtrMaxValue ((void*)0xFFFFFFFFU)
//如果使能了cpu占用统计(通过vH_TS_isEnableCPU_Utilization来设置) 该段表示每运行多少时间后更新CPU占用值(时间由H_TS_GetDT来提供)
#define vH_TS_CPU_Utilization_T 400000
//触发PendSV
#define oH_TS_CallPendSV() do{\
__schedule_barrier();\
SCB->ICSR|=SCB_ICSR_PENDSVSET_Msk;\
__schedule_barrier();\
}while(0)
//挂起任务调度
#define oH_TS_SchedulerSuspend() do{\
__schedule_barrier();\
H_TS_Core->SchedulerSuspend=-1;\
__schedule_barrier();\
}while(0)
//释放任务调度
#define oH_TS_ResumeScheduler_0() do{\
__schedule_barrier();\
H_TS_Core->SchedulerSuspend=0;\
if(H_TS_Core->SchedulerForIrq){\
H_TS_Core->SchedulerForIrq=0;\
oH_TS_CallPendSV();\
}\
__schedule_barrier();\
}while(0)
//释放任务调度 并且固定触发一次PendSV
#define oH_TS_ResumeScheduler_1() do{\
__schedule_barrier();\
H_TS_Core->SchedulerSuspend=0;\
if(H_TS_Core->SchedulerForIrq){\
H_TS_Core->SchedulerForIrq=0;\
}\
oH_TS_CallPendSV();\
__schedule_barrier();\
}while(0)
//挂起SysTick
#define oH_TS_SysTickSuspend() do{\
__schedule_barrier();\
H_TS_Core->SysTickSuspend=-1;\
__schedule_barrier();\
}while(0)
//恢复SysTick
#define oH_TS_ReusmeSysTick() do{\
__schedule_barrier();\
H_TS_Core->SysTickSuspend=0;\
__schedule_barrier();\
}while(0)
//中断中触发PendSV
#define oH_TS_ISR_CallPendSV() do{\
__schedule_barrier();\
if(H_TS_Core->SchedulerSuspend!=0){\
H_TS_Core->SchedulerForIrq=-1;\
}else{\
oH_TS_CallPendSV();\
}\
__schedule_barrier();\
}while(0)
#endif //__H_ThreadScheduler_LL_H_
说明
- 其内部定义了一些宏定义。
__schedule_barrier();是用于防止优化的,保证生成的汇编代码是按照顺序执行的。 - 一般只需要修改
oH_TS_CallPendSV()(设置PendSV悬起位)将里面的寄存器操作替换为对应平台的。 - 当CPU统计使能时,宏定义
vH_TS_CPU_Utilization_T生效,代表计算周期,单位与H_ThreadScheduler_LL.c中的H_TS_Tick H_TS_GetDT()方法返回的数值单位一致。
H_ThreadScheduler_LL.c
#include "H_ThreadScheduler_Core.h"
#include "H_ThreadScheduler_LL.h"
extern H_TS* volatile H_TS_Core;
extern void* H_TS_StartFirstThread;
void* volatile * volatile H_TS_RunThreadPtr;
void* H_TS_PendSV_Call;
#if vH_TS_isEnableCPU_Utilization != 0
/**
* @brief 返回上次调用该函数是多少时间之前 该方法在使能cpu占用率统计后被使用
* 根据平台修改
* @return 上次调用该函数是多少时间之前
*/
H_TS_Tick H_TS_GetDT(){
static H_TS_Tick lastT=0;
H_TS_Tick nowT;
H_TS_Tick r;
nowT=(H_TS_Tick)TIM5->CNT;
r=nowT-lastT;
lastT=nowT;
return r;
}
#endif
/**
* @brief 设置Systick回调 并且使能
* 根据平台修改
* @param callback 使能SysTick回调
* @param v 回调传入参数
* @return 无
*/
void H_TS_EnableTickCallback(void (*callback)(void*),void* v){
//该方法在开始调度时被调用
//在这里将callback设置为SysTick回调 使得每次Systick中断callback都会被调用一次
TimeTick_Set_IRQ_Callback(callback,v);//Systick回调设置
}
/**
* @brief 底层初始化
* 根据平台修改
* @return 无
*/
void H_TS_LL_Init(){
//该方法在开始调度时被调用
FPU->FPCCR|=FPU_FPCCR_ASPEN_Msk|FPU_FPCCR_LSPEN_Msk;//使能control自动设置与fpu惰性压栈
HAL_NVIC_SetPriority(PendSV_IRQn, 15U, 0U);//设置PendSV优先级为最低
//H_TS内部配置 无需修改
H_TS_PendSV_Call=(void*)&H_TS_StartFirstThread;
H_TS_RunThreadPtr=H_TS_Core->ReadyThreads->StackPointer;
H_TS_Core->RunThreadPtr=&H_TS_RunThreadPtr;
}
/**
* @brief 初始化堆栈
* 根据平台修改
* @param StackPtr 为堆栈内存首地址指针的指针 初始化完毕后用于返回初始化好的堆栈指针
* @param StackSize 堆栈大小
* @param Code 线程执行的代码
* @param v 线程传入参数
* @param ReturnCallback 线程方法返回后调用的方法
* @return 无
*/
void H_TS_ThreadStackInit(void* StackPtr,int StackSize,int (*Code)(void*),void* v,void (*ReturnCallback)(int)){
//该方法在线程开始时被调用
#if vH_TS_StackPtrDirection == 0
unsigned int mStackPtr;
//栈指针指向末尾(刚好超出栈内存空间 代表栈为空)
((void**)StackPtr)[0]=(void*)&((Hbyte_ptr)*((void**)StackPtr))[StackSize];
mStackPtr=((unsigned int*)StackPtr)[0];
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=(void*)0x01000000U;//xPSR
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=Code;//PC
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=ReturnCallback;//LR 当线程使用return返回 跳转到ReturnCallback
#if 0
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=ReturnCallback;//R12
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=ReturnCallback;//R3
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=ReturnCallback;//R2
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=ReturnCallback;//R1
mStackPtr-=sizeof(void*);
#else
//R1 R2 R3 R12
mStackPtr-=5*sizeof(void*);
#endif
(*((void**)mStackPtr))=v;//R0
//下面模拟被保护的现场(实际线程还没开始运行 虚构一个现场)为了线程切换出栈用
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=(void*)0xFFFFFFFDU;//LR 退回到线程模式 使用PSP
#if 0
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=(void*)0;//R11
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=(void*)0;//R10
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=(void*)0;//R9
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=(void*)0;//R8
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=(void*)0;//R7
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=(void*)0;//R6
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=(void*)0;//R5
mStackPtr-=sizeof(void*);
(*((void**)mStackPtr))=(void*)0;//R4
#else
mStackPtr-=8*sizeof(void*);
#endif
((unsigned int*)StackPtr)[0]=mStackPtr;
#else
#error "未编写"
#endif
}
/**
* @brief 线程通过return退出时会触发调用的方法 (如果机器不支持return转到函数 此方法可能不可用)
* @param retVal 线程的返回值
* @return 无
*/
void ThreadReturnCallback(int retVal){
H_TS_Thread* _this;
//_this=(*H_TS_Core->RunThreadPtr)[1];
//可以在此截获线程退出信息
H_TS_ThreadExit();
}
说明
- 注释已有说明
- 此文件实现了一些底层接口供H_TS调用
- 在
void H_TS_EnableTickCallback(void (*callback)(void*),void* v)中,需要将Systick使能(或许已经使能了),使得在此方法执行后或执行时,后面Systick中断要调用传进来的callback。(这个设计能让H_TS决定什么时候开始使用Systick)void H_TS_LL_Init()一般无需修改void H_TS_ThreadStackInit(void* StackPtr,int StackSize,int (*Code)(void*),void* v,void (*ReturnCallback)(int))一般无需修改void ThreadReturnCallback(int retVal)线程使用return后的退出调用(相当于线程调用了这个方法) 用户可在此添加一些代码,可以通过_this=(*H_TS_Core->RunThreadPtr)[1]来获取当前线程句柄。最后必须调用H_TS_ThreadExit()结束线程(因为线程此时实际上没有结束)
H_ThreadScheduler_LL_AMS.s
;底层汇编文件
;移植时,需要处理
;根据平台修改任务切换汇编代码
;将此文件的PendSV_Handler设置为PendSV中断调用
AREA |.text|,CODE,READONLY
THUMB
PRESERVE8
;对外接口
EXPORT PendSV_Handler
EXPORT H_TS_StartFirstThread
;引入声明
EXTERN H_TS_PendSV_Call
EXTERN H_TS_RunThreadPtr
EXTERN H_TS_RunThreadPtr_Refresh
H_TS_ThreadSwitch PROC
LDR R0,=H_TS_RunThreadPtr
LDR R12,[R0] ;读取栈指针地址
PUSH {R12,LR}
BL H_TS_RunThreadPtr_Refresh ;切换Thread
POP {R12,LR}
CMP R0,#0 ;结果是否为0
BXEQ LR ;如果为0 返回 不做任务切换
;开始任务切换 保存和释放堆栈
MRS R0,PSP ;读线程栈指针到R0
;判断线程是否使用了FPU 如果使用了 保存浮点寄存器
TST LR,#0x10
VSTMDBEQ R0!, {S16-S31}
STMDB R0!, {R4-R11,LR} ;保存现场
STR R0,[R12] ;保存栈指针
LDR R0,=H_TS_RunThreadPtr
LDR R0,[R0] ;读取栈指针地址
LDR R0,[R0] ;读取栈指针
LDMIA R0!,{R4-R11,LR} ;恢复现场
;判断线程是否使用了FPU 如果使用了 恢复浮点寄存器
TST LR,#0x10
VLDMIAEQ R0!,{S16-S31}
MSR PSP,R0 ;恢复堆栈指针
BX LR
ENDP
H_TS_StartFirstThread PROC
LDR R0,=H_TS_PendSV_Call
LDR R1,=H_TS_ThreadSwitch
STR R1,[R0]
MOV R0,#0
MSR CONTROL,R0 ;设置CONTROL 特权级,使用MSP (主要是为了清除FPCA)
LDR R1,=H_TS_RunThreadPtr ;加载Thread地址
LDR R1,[R1] ;读取栈指针地址
LDR R0,[R1] ;读取栈指针
LDMIA R0!,{R4-R11,LR} ;恢复现场
MSR PSP,R0 ;恢复堆栈指针
BX LR
ENDP
;需要将下面的方法设为PendSV中断调用
PendSV_Handler PROC
LDR R0,=H_TS_PendSV_Call
LDR R0,[R0]
BX R0
ENDP
END
说明
- 注释已对部分代码说明
- 其中PendSV中断要能直接调用
PendSV_Handle(例如stm32,要将xxx_it.c中的PendSV_Handle()要注释掉)
- 对于 Cortex-M4 Cortex-CM7 (都带FPU) 这些处理器,本文件的线程切换代码无需修改
- 像Cortex-M3这种无FPU的处理器,
H_TS_ThreadSwitch中的
;判断线程是否使用了FPU 如果使用了 保存浮点寄存器
TST LR,#0x10
VSTMDBEQ R0!, {S16-S31}
和
;判断线程是否使用了FPU 如果使用了 恢复浮点寄存器
TST LR,#0x10
VLDMIAEQ R0!,{S16-S31}
可以去掉
使用
移植完成之后,如果不调用它的API,那它就相当于空气(甚至编译出的代码都没有它)。…似乎PendSV中断是会在里面的
如何在代码中开始它
可以参照例子中的代码,下面通过例子的代码来说明。(其实不一定要这么干)
#include "main.h"
#include "Peripheral.h"
extern void Entry(void);
int main(void){
H_TS_Init(NULL,512);
Entry();
H_TS_StartScheduler();
for(;;){
}
}
这个示例中的主要代码只有三行,H_TS_Init(NULL,512);与H_TS_StartScheduler();都是H_TS的API 一个负责初始化H_TS,一个用于开始线程调度。中间的Entry();用于初始化用户的一些东西,新建线程等,这个方法在另外一个文件实现。
#include "Peripheral.h"
int LED_Thread(void* v){
RCC->AHB1ENR|=RCC_AHB1ENR_GPIOCEN;
oIP_GPIO_Config(GPIOC,6,vIP_GPIO_Mode_OUT,vIP_GPIO_OutType_PP,vIP_GPIO_Speed_LOW,vIP_GPIO_PuPd_NP);
for(;;){
H_TS_ThreadSleep(100);
oIP_GPIO_OutputBitReverse(GPIOC,6);
}
}
int Thread_Start(void* v){
//初始化外设
Peripherals_Init();
H_TS_StartNewThread(LED_Thread,NULL,256,0,0);
//结束线程
return 0;
}
static void IdleProcess(void* v){
__WFI();
}
/**
* @brief 初始化入口
* @return 无
*/
void Entry(){
//需要在此将系统初始化到可以开始任务调度的状态
//此时H_TS已经完成了基本初始化(已经使用了H_TS_Init()) 此时可以新建线程 但线程不会立即运行
//此函数退出后H_TS_StartScheduler()被调用 开始任务调度
//建议此时不要开启其他涉及到H_TS的中断 在开始任务调度后再打开这些中断
//传入的函数会在H_TS空闲时被循环调用
H_TS_SetIdleProcess(IdleProcess);
//系统初始化
Peripherals_SysInit();
//开始一个线程
H_TS_StartNewThread(Thread_Start,NULL,512,0,0);
}
这个例子只用到了1个LED(板子上就一个,我有什么办法。有兴趣的可以自己再加个线程在用一个),建议使用H_TS时,像例子中的一样,先初始化部分外设(时钟 Systick这种必须的,例子中相关代码是Peripherals_SysInit();),然后新建一个线程(例子中的int Thread_Start(void* v),在新线程中调用其他外设的初始化方法(Peripherals_Init();)。
这样可以使得Peripherals_Init();初始化的外设的中断可以安全的使用信号量,消息队列(信号量,消息队列的创建要早于中断到来)。
其他的API,在H_ThreadScheduler.h文件中有详细注释。如果有用过其他rtos(例如FreeRTOS)很容易上手
一些注意事项(如果使用H_TS必看)
- 除了新建线程API(
H_TS_StartNewThread()等),设置空闲调用API(H_TS_SetIdleProcess()),其他API不能在H_TS_Init()与H_TS_StartScheduler()之间使用。 - 中断中能使用的API只有
int H_TS_MessageQueueSend_ISR(H_TS_MessageQueue* MessageQueue,void* v0,void* v1)(如果队列满,返回非0)
和
int H_TS_SemaphoreGive_ISR(H_TS_Semaphore* Semaphore)
这两个。(如果信号量满,返回非0)
组件的一些注意事项
- 中断只能访问到信号量和消息队列,并且每个只有1个对于的API。(上面已提到)
- 对于互斥锁(
H_TS_LOCK),它只能被线程访问(没有中断使用的API,中断说来就来,说走就走。H_TS无法限制中断(设计也不允许))- 对于信号量(
H_TS_Semaphore)和消息队列(H_TS_MessageQueue),其实这两者差不多,只是消息队列附带了信息,是个带信息版的信号量(但信息队列的容量没有信号量严格,申请到的信号量最大容纳个数与创建它是传入的数量一致,但消息队列最大容纳量比申请时传入的数量少1个(消息队列实际占用总空间却没有少,因为队列是1个FIFO,如果填满,标记的状态将会与FIFO空的状态一致,所以限制FIFO不能填满,但多出来的空间不是浪费的,FIFO的空间是循环使用的)。信号量不需要空间,没有这方面的顾虑)。
对于它们,有一些共同特性:
- 同一时间只能被一个线程接收(Receive,Take),一般也只会这么干。如果多个线程对同一个
信号量/消息队列进行接收会出现不可预料的结果。- 向
信号量/消息队列进行释放信号量\发送消息必须遵守以下两条中的其中一条
- 1个
信号量/消息队列只有一个中断向其进行释放信号量\发送消息,线程不能对其释放信号量\发送消息- 1个
信号量/消息队列有多个线程向其进行释放信号量\发送消息,中断不能对其释放信号量\发送消息
H_TS的信号量/消息队列的使用是有限制的,并且对于线程来说比较自由。
这些特性(限制)是不采用关中断等方法所带来的牺牲。(实际上就向1个信号量/消息队列进行释放信号量\发送消息的中断只能有1个这个限制比较麻烦,多个中断就需要多个信号量,也需要多个线程来接收。)。
实际上,如果能保证多个中断不会同时触发(对同一个信号量/消息队列的释放信号量\发送消息方法不被重入(不被更高优先级的中断重入)),信号量/消息队列是可以接收多个中断的消息的。(然而想让中断不产生抢占是几乎不可能的,简单的分时使能中断相当于关中断。)
本文档详细介绍了开源线程调度器H_TS的特性、源码结构、移植步骤及使用注意事项。H_TS占用资源少,支持抢占式调度、互斥锁、信号量和消息队列,适用于嵌入式系统。移植时需要修改配置文件、内存管理、底层接口等,确保与目标平台兼容。此外,文章还提供了示例代码以帮助理解如何启动和使用H_TS。
3699

被折叠的 条评论
为什么被折叠?



