【STM32F429】第20章 ThreadX互斥信号量

2021-08-06 14:36:53 浏览数 (1)

20.1 互斥信号量

20.1.1 互斥信号量的概念及其作用

互斥信号量的主要作用是对资源实现互斥访问,使用二值信号量也可以实现互斥访问的功能,不过互斥信号量与二值信号量有区别。下面我们先举一个通过二值信号量实现资源独享,即互斥访问的例子,让大家有一个形象的认识,进而引出要讲解的互斥信号量。

运行条件:

  • 让两个任务Task1和Task2都运行串口打印函数printf,这里我们就通过二值信号量实现对函数printf的互斥访问。如果不对函数printf进行互斥访问,串口打印容易出现乱码。
  • 用计数信号量实现二值信号量只需将计数信号量的初始值设置为1即可。

代码实现:

  • 创建二值信号量
代码语言:javascript复制
TX_SEMAPHORE Semaphore;

/*
*********************************************************************************************************
*    函 数 名: AppObjCreate
*    功能说明: 创建任务通信机制
*    形    参: 无
*    返 回 值: 无
*********************************************************************************************************
*/
static void AppObjCreate (void)
{    
    /* 创建信号量,初始值为1,用于二值信号量 */
    tx_semaphore_create(&Semaphore, "Semaphore", 1);
}
  • 通过二值信号量实现对printf函数互斥访问的两个任务
代码语言:javascript复制
/*
*********************************************************************************************************
*    函 数 名: AppTaskLED
*    功能说明: 实现对串口的互斥访问
*    形    参: thread_input是在创建该任务时传递的形参
*    返 回 值: 无
*   优 先 级: 2  
*********************************************************************************************************
*/
static void AppTaskLED(ULONG thread_input)
{    
    while(1)
    {
        /* 通过二值信号量实现资源互斥访问,永久等待直到资源可用 */
        tx_semaphore_get(&Semaphore, TX_WAIT_FOREVER);
        
        printf("任务AppTaskLED在运行rn");
        bsp_LedToggle(1);
        bsp_LedToggle(4);
        
        tx_semaphore_put(&Semaphore);
        
        tx_thread_sleep(100); 
    } 
}

/*
*********************************************************************************************************
*    函 数 名: AppTaskMsgPro
*    功能说明: 实现对串口的互斥访问
*    形    参: thread_input是在创建该任务时传递的形参
*    返 回 值: 无
*   优 先 级: 3  
*********************************************************************************************************
*/
static void AppTaskMsgPro(ULONG thread_input)
{    
    while(1)
    {
         /* 通过二值信号量实现资源互斥访问,永久等待直到资源可用 */
        tx_semaphore_get(&Semaphore, TX_WAIT_FOREVER);
        
        printf("任务AppTaskMsgPro在运行rn");
        bsp_LedToggle(1);
        bsp_LedToggle(4);
        
        tx_semaphore_put(&Semaphore);
        
        tx_thread_sleep(100); 
    }
}

有了上面二值信号量的认识之后,互斥信号量与二值信号量又有什么区别呢?互斥信号量可以防止优先级翻转,而二值信号量不支持,下面我们就讲解一下优先级翻转问题。

20.1.2 优先级翻转问题

下面我们通过如下的框图来说明一下优先级翻转的问题,让大家有一个形象的认识。

运行条件:

  • 创建3个任务Task1,Task2和Task3,优先级分别为3,2,1。也就是Task1的优先级最高。
  • 任务Task1和Task3互斥访问串口打印printf,采用二值信号实现互斥访问。
  • 起初Task3通过二值信号量正在调用printf,被任务Task1抢占,开始执行任务Task1,也就是上图的起始位置。

运行过程描述如下:

  • 任务Task1运行的过程需要调用函数printf,发现任务Task3正在调用,任务Task1会被挂起,等待Task3释放函数printf。
  • 在调度器的作用下,任务Task3得到运行,Task3运行的过程中,由于任务Task2就绪,抢占了Task3的运行。优先级翻转问题就出在这里了,从任务执行的现象上看,任务Task1需要等待Task2执行完毕才有机会得到执行,这个与抢占式调度正好反了,正常情况下应该是高优先级任务抢占低优先级任务的执行,这里成了高优先级任务Task1等待低优先级任务Task2完成。所以这种情况被称之为优先级翻转问题。
  • 任务Task2执行完毕后,任务Task3恢复执行,Task3释放互斥资源后,任务Task1得到互斥资源,从而可以继续执行。

上面就是一个产生优先级翻转问题的现象。

20.1.3 ThreadX互斥信号量的实现

ThreadX互斥信号量是怎么实现的呢?其实相对于二值信号量,互斥信号量就是解决了一下优先级翻转的问题。下面我们通过如下的框图来说明一下ThreadX互斥信号量的实现,让大家有一个形象的认识。

运行条件:

  • 创建2个任务Task1和Task2,优先级分别为1和3,也就是任务Task2的优先级最高。
  • 任务Task1和Task2互斥访问串口打印printf。
  • 使用ThreadX的互斥信号量实现串口打印printf的互斥访问。

运行过程描述如下:

  • 低优先级任务Task1执行过程中先获得互斥资源printf的执行。此时任务Task2抢占了任务Task1的执行,任务Task1被挂起。任务Task2得到执行。
  • 任务Task2执行过程中也需要调用互斥资源,但是发现任务Task1正在访问,此时任务Task1的优先级会被提升到与Task2同一个优先级,也就是优先级3,这个就是所谓的优先级继承(Priority inheritance),这样就有效地防止了优先级翻转问题。任务Task2被挂起,任务Task1有新的优先级继续执行。
  • 任务Task1执行完毕并释放互斥资源后,优先级恢复到原来的水平。由于互斥资源可以使用,任务Task2获得互斥资源后开始执行。

上面就是一个简单的ThreadX互斥信号量的实现过程。

20.1.4 ThreadX中断方式互斥信号量的实现

互斥信号量仅支持用在ThreadX的任务中,中断函数中不可使用。

20.2 互斥信号量创建函数tx_mutex_create

函数原型:

代码语言:javascript复制
UINT tx_mutex_create(
    TX_MUTEX *mutex_ptr,
    CHAR *name_ptr, 
    UINT priority_inherit);

函数描述:

此函数用于创建互斥信号量。

1、 第1个参数是互斥信号量控制块。

2、 第2个参数是互斥信号量名字。

3、 第3个参数用于设置是否使能优先级继承

TX_INHERIT使能优先级继承,即支持优先级反转。

TX_NO_INHERIT关闭优先级继承。

4、 返回值

TX_SUCCESS (0x00) 互斥信号量创建成功。

TX_MUTEX_ERROR (0x1C) 无效的互斥信号量控制块或者互斥信号量已经创建。

TX_CALLER_ERROR (0x13) 无效调用。

TX_INHERIT_ERROR (0x1F) 无效的优先级继承参数。

注意事项:

  • 可以在初始化和任务中调用。

使用举例:

代码语言:javascript复制
static  TX_MUTEX   AppPrintfSemp;    /* 用于printf互斥 */

/* 创建互斥信号量 */
tx_mutex_create(&AppPrintfSemp,"AppPrintfSemp",TX_INHERIT);

20.3 互斥信号量获取函数tx_mutex_get

函数原型:

代码语言:javascript复制
UINT tx_mutex_get(
    TX_MUTEX *mutex_ptr, 
    ULONG wait_option);

函数描述:

此函数用于获取互斥信号量,如果互斥信号量已经被调用的任务所拥有,再次调用会触发所有权计数变量加1操作。如果互斥信号量被一个低优先级任务所拥有,并且在创建互斥信号量的时候使能了优先级继承,那么高优先级任务也要获取互斥信号量时,这个低优先级任务的优先级将提升到和高优先级任务一个等级。

1、 第1个参数是互斥信号量控制块。

2、 第2个参数是等待选项:

如果互斥信号量被一个任务锁拥有,这个形参将派上用场:

  • TX_NO_WAIT (0x00000000),表示不管是否获取成功,立即返回。如果在初始化阶段调用,必须要设置成这个参数。
  • TX_WAIT_FOREVER (0xFFFFFFFF),表示永久等待,直到有互斥信号量可用)。
  • 等待时间,范围0x00000001 到 0xFFFFFFFE,单位系统时钟节拍、

3、 返回值

  • TX_SUCCESS(0x00)设置成功。
  • TX_DELETED (0x01) 任务挂起阶段,消息队列被删除。
  • TX_NO_AVAILABLE (0x1D)等待了指定时间后, 依然无法获取信号量。
  • TX_WAIT_ABORTED (0x1A) 挂起被其它任务,定时器组或者中断服务程序终止。
  • TX_MUTEX _ERROR (0x1C) 无效的互斥信号量控制块。
  • TX_WAIT_ERROR (0x04) 无效调用,主要是在非常任务代码中使用TX_NO_WAIT 以外的形参。比如在中断服务程序里面设置等待。
  • TX_CALLER_ERROR (0x13)无效调用

注意事项:

  • 可以在初始化,任务,定时器组里面调用。

使用举例:

代码语言:javascript复制
static  TX_MUTEX   AppPrintfSemp;    /* 用于printf互斥 */

/*
*********************************************************************************************************
*    函 数 名: App_Printf
*    功能说明: 线程安全的printf方式                        
*    形    参: 同printf的参数。
*             在C中,当无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
*    返 回 值: 无
*********************************************************************************************************
*/
static  void  App_Printf(const char *fmt, ...)
{
    char  buf_str[200   1]; /* 特别注意,如果printf的变量较多,注意此局部变量的大小是否够用 */
    va_list   v_args;


    va_start(v_args, fmt);
   (void)vsnprintf((char       *)&buf_str[0],
                   (size_t      ) sizeof(buf_str),
                   (char const *) fmt,
                                  v_args);
    va_end(v_args);

/* 互斥操作 */
    tx_mutex_get(&AppPrintfSemp, TX_WAIT_FOREVER);

    printf("%s", buf_str);

    tx_mutex_put(&AppPrintfSemp);                     
}

20.4 互斥信号量释放函数tx_mutex_put

函数原型:

UINT tx_semaphore_put(TX_SEMAPHORE *semaphore_ptr);

函数描述:

此函数用于释放互斥信号量,当互斥信号量所有权计数变量为0时(每次嵌套调用,此变量都会执行加1操作,释放时执行减1操作),互斥资源才可用。

1、 第1个参数是信号量控制块。

2、 返回值

TX_SUCCESS (0x00) 互斥信号量创建成功。

TX_NOT_OWNED(0x1E) 互斥信号量不被调用者所拥有。

TX_MUTEX_ERROR (0x1C) 无效的互斥信号量控制块或者互斥信号量已经创建。

TX_CALLER_ERROR (0x13) 无效调用。

使用举例:

代码语言:javascript复制
static  TX_MUTEX   AppPrintfSemp;    /* 用于printf互斥 */

/*
*********************************************************************************************************
*    函 数 名: App_Printf
*    功能说明: 线程安全的printf方式                        
*    形    参: 同printf的参数。
*             在C中,当无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
*    返 回 值: 无
*********************************************************************************************************
*/
static  void  App_Printf(const char *fmt, ...)
{
    char  buf_str[200   1]; /* 特别注意,如果printf的变量较多,注意此局部变量的大小是否够用 */
    va_list   v_args;


    va_start(v_args, fmt);
   (void)vsnprintf((char       *)&buf_str[0],
                   (size_t      ) sizeof(buf_str),
                   (char const *) fmt,
                                  v_args);
    va_end(v_args);

/* 互斥操作 */
    tx_mutex_get(&AppPrintfSemp, TX_WAIT_FOREVER);

    printf("%s", buf_str);

    tx_mutex_put(&AppPrintfSemp);                     
}

20.5 实验例程

配套例子:

V6-3015_ThreadX Semaphore

实验目的:

  1. 学习ThreadX互斥信号量

实验内容:

1、共创建了如下几个任务,通过按下按键K1可以通过串口或者RTT打印任务堆栈使用情况

========================================================

CPU利用率 = 0.89%

任务执行时间 = 0.586484645s

空闲执行时间 = 85.504470575s

中断执行时间 = 0.173225395s

系统总执行时间 = 86.264180615s

=======================================================

任务优先级 任务栈大小 当前使用栈 最大栈使用 任务名

Prio StackSize CurStack MaxStack Taskname

2 4092 303 459 App Task Start

5 4092 167 167 App Msp Pro

4 4092 167 167 App Task UserIF

5 4092 167 167 App Task COM

0 1020 191 191 System Timer Thread

串口软件可以使用SecureCRT或者H7-TOOL RTT查看打印信息。

App Task Start任务 :启动任务,这里用作BSP驱动包处理。

App Task MspPro任务 :消息处理,这里用作信号量获取。

App Task UserIF任务 :按键消息处理。

App Task COM任务 :这里用作LED闪烁。

System Timer Thread任务:系统定时器任务

2、(1) 凡是用到printf函数的全部通过函数App_Printf实现。

(2) App_Printf函数做了信号量的互斥操作,解决资源共享问题。

3、默认上电是通过串口打印信息,如果使用RTT打印信息

(1) MDK AC5,MDK AC6或IAR通过使能bsp.h文件中的宏定义为1即可

#define Enable_RTTViewer 1

(2) Embedded Studio继续使用此宏定义为0, 因为Embedded Studio仅制作了调试状态RTT方式查看。

串口打印信息方式(AC5,AC6和IAR):

波特率 115200,数据位 8,奇偶校验位无,停止位 1

RTT打印信息方式(AC5,AC6和IAR):

Embedded Studio仅支持调试状态RTT打印:

由于Embedded Studio不支持中文,所以中文部分显示乱码,不用管。

程序执行框图:

20.6 总结

本章节主要为大家讲解了另一个重要的资源共享机制-互斥信号量,其中优先级翻转是互斥信号量中一个比较重要的概念,初学者要花些时间去掌握这个知识点。

0 人点赞