线程调度器H_TS(可以说是一个RTOS了)

本文档详细介绍了开源线程调度器H_TS的特性、源码结构、移植步骤及使用注意事项。H_TS占用资源少,支持抢占式调度、互斥锁、信号量和消息队列,适用于嵌入式系统。移植时需要修改配置文件、内存管理、底层接口等,确保与目标平台兼容。此外,文章还提供了示例代码以帮助理解如何启动和使用H_TS。

2021年12月7日23点54分: 目前这个调度器已更新
https://blog.csdn.net/qq_42907191/article/details/121772005.

源码

二话不说,先上代码
链接: https://gitee.com/H0x9DEFA478/H_TS.git.
源码中包含了H_TS以及一个例子,例子使用的单片机型号为STM32F730R8T6
之前我实现过一个简陋版本:链接: https://blog.csdn.net/qq_42907191/article/details/116611128.

特性

  1. 最低只占用一个可软件触发的中断和一个定时器中断。
  2. 可选择使能CPU占用统计,堆栈占用统计。(其中CPU占用统计需另外占用一个定时器(需要比Sysictk更高的频率))
  3. 抢占型,只有就绪的最高优先级的线程会运行
  4. H_TS全程不屏蔽、不关闭系统的中断,不影响中断的响应。对于中断来说,H_TS就像裸机的主循环一样。
  5. 线程可休眠(这玩意肯定得要有)
  6. 互斥锁
  7. 信号量
  8. 消息队列(固定每个消息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个信号量/消息队列只有一个中断向其进行释放信号量\发送消息,线程不能对其释放信号量\发送消息
  2. 1个信号量/消息队列有多个线程向其进行释放信号量\发送消息,中断不能对其释放信号量\发送消息
    H_TS的信号量/消息队列的使用是有限制的,并且对于线程来说比较自由。
    这些特性(限制)是不采用关中断等方法所带来的牺牲。(实际上就向1个信号量/消息队列进行释放信号量\发送消息的中断只能有1个这个限制比较麻烦,多个中断就需要多个信号量,也需要多个线程来接收。)。
    实际上,如果能保证多个中断不会同时触发(对同一个信号量/消息队列释放信号量\发送消息方法不被重入(不被更高优先级的中断重入)),信号量/消息队列是可以接收多个中断的消息的。(然而想让中断不产生抢占是几乎不可能的,简单的分时使能中断相当于关中断。)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值