Fork me on GitHub

RT-Thread学习笔记2-互斥量与信号量


1. 临界区保护

临界区是仅允许一个线程访问的共享资源。它可以是一个具体的硬件设备,也可以是一个变量、一个缓冲区。多个线程必须互斥的对他们进行访问

1.1 方法一:关闭系统调度保护临界区

禁止调度

/* 调度器商锁,上锁后不再切换到其他线程,仅响应中断 */
rt_enter_critical();
/* 临界操作 */
rt_exit_critical();

关闭中断:因为所有的线程的调度都是建立在中断的基础上的,所以当关闭中断后系统将不能再进行调度,线程自身也自然不会被其他线程抢占了

rt_base_t level;
/* 关闭中断 */
level = rt_hw_interrupt_disable();
/* 临界操作 */
rt_hw_interrupt_enable(level);

1.2 方法二:互斥特性保护临界区

信号量、互斥量

2. 信号量

嵌入式系统运行的代码主要包括线程和ISR,在它们的运行过程中,它们的运行步骤有时需要同步(按照预定的先后次序运行),有时访问的资源需要互斥(一个时刻只允许一个线程访问资源),有时也需要比本次交换数据。这些机制成为进程间通信IPC。RT-Thread中的IPC机制包括信号量、互斥量、事件、邮箱、消息队列。通过IPC,可以协调多个线程(包括ISR)默契的工作。信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。每个信号量对象都有一个信号量值和一个线程等待队列。信号量的值对应信号量对象的实例数目(资源数目),如果信号量值N,则表示有N个信号量实例(资源)可被使用。当值为0时,再请求该信号量的的线程,就会被挂起在该信号量的等待队列上。

2.1 信号量的定义

struct rt_semaphore
{
    struct rt_ipc_object parent; /* IPC对象继承而来 */
    rt_uint16_t value; /* 信号量的值 */
}

静态信号量:struct rt_semaphore static_sem
动态信号量:rt_sem_t dynamic_sem

typedef struct rt_semaphore *rt_sem_t;

2.2 信号量的操作

  1. 初始化与脱离
静态信号量:
rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag)
rt_err_t rt_sem_detach(rt_sem_t sem) //将不用的静态信号量从系统中脱离
  1. 创建与删除
判断一下返回值是不是RT_NULL
动态信号量:
rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag) //等待队列中的线程,当有资源的时候: RT_IPC_FLAG_FIFO先来先服务,先后顺序排列 RT_IPC_FLAG_PRIO按照线程优先级排列
rt_err_t rt_sem_delete(rt_sem_t sem)//释放系统资源
  1. 获取信号量
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time) //RT_WAITING_FOREVER = -1, 以系统滴答时钟为单位。即100HZ,等待10ms的倍数。如果超时则返回-RT_ETIMEOUT.切忌该函数不可在中断中调用,因为它会导致线程被挂起。只能在线程调用
rt_err_t rt_sem_trytake(rt_sem_t sem) //时间参数为0,一秒钟都不等待
  1. 释放信号量
rt_err_t rt_sem_release(rt_sem_t sem) // 既可以在线程,也可以在中断中调用。因为它不会导致线程被挂起

3. 生产者、消费者问题

两个线程,一个生产者线程和一个消费者线程,两个线程共享一个初始为空、固定大小为n的缓存区。生产者的工作是生产一段数据,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待,如此反复。只有缓冲区非空时,消费者才能从中取出数据,一次消费一段数据,否则必须等待。问题的核心是:

  1. 保证不让生产者在缓存还是满的时候仍然要向内写数据
  2. 不让消费者试图从空的缓存中取出数据

解决生产者消费者问题实际上是要解决线程间互斥关系和同步关系问题。由于缓冲区是临界资源,一个时刻只允许一个生产者放入消息,或者一个消费者从中取出消息。这里需要解决一个互斥访问的问题。同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,所以还需要解决一个同步的问题
BballV.png

/* 生成者线程入口 */
void producer_thread_entry(void* parameter)
{
    int cnt = 0;

    /* 运行100次 */
    while( cnt < 100)
    {
        /* 获取一个空位 */
        rt_sem_take(&sem_empty, RT_WAITING_FOREVER);

        /* 修改array内容,上锁 */
        rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
        array[set%MAXSEM] = cnt + 1;
        rt_kprintf("the producer generates a number: %d\n", array[set%MAXSEM]);
        set++;
        rt_sem_release(&sem_lock);

        /* 发布一个满位 */
        rt_sem_release(&sem_full);
        cnt++;

        /* 暂停一段时间 */
        rt_thread_delay(50);
    }

    rt_kprintf("the producer exit!\n");
}

/* 消费者线程入口 */
void consumer_thread_entry(void* parameter)
{
    rt_uint32_t no;
    rt_uint32_t sum;

    /* 第n个线程,由入口参数传进来 */
    no = (rt_uint32_t)parameter;

    sum = 0;
    while(1)
    {
        /* 获取一个满位 */
        rt_sem_take(&sem_full, RT_WAITING_FOREVER);

        /* 临界区,上锁进行操作 */
        rt_sem_take(&sem_lock, RT_WAITING_FOREVER);
        sum += array[get%MAXSEM];
        rt_kprintf("the consumer[%d] get a number: %d\n", no, array[get%MAXSEM] );
        get++;
        rt_sem_release(&sem_lock);

        /* 释放一个空位 */
        rt_sem_release(&sem_empty);

        /* 生产者生产到100个数目,停止,消费者线程相应停止 */
        if (get == 100) break;

        /* 暂停一小会时间 */
        rt_thread_delay(10);
    }

    rt_kprintf("the consumer[%d] sum is %d \n ", no, sum);
    rt_kprintf("the consumer[%d] exit!\n");
}

int semaphore_producer_consumer_init()
{
    /* 初始化3个信号量 */
    rt_sem_init(&sem_lock , "lock",     1,      RT_IPC_FLAG_FIFO);
    rt_sem_init(&sem_empty, "empty",    MAXSEM, RT_IPC_FLAG_FIFO);
    rt_sem_init(&sem_full , "full",     0,      RT_IPC_FLAG_FIFO);

    /* 创建线程1 */
    producer_tid = rt_thread_create("producer",
                                    producer_thread_entry, RT_NULL, /* 线程入口是producer_thread_entry, 入口参数是RT_NULL */
                                    THREAD_STACK_SIZE, THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    if (producer_tid != RT_NULL)
        rt_thread_startup(producer_tid);
    else
        tc_stat(TC_STAT_END | TC_STAT_FAILED);

    /* 创建线程2 */
    consumer_tid = rt_thread_create("consumer",
                                    consumer_thread_entry, RT_NULL, /* 线程入口是consumer_thread_entry, 入口参数是RT_NULL */
                                    THREAD_STACK_SIZE, THREAD_PRIORITY + 1, THREAD_TIMESLICE);
    if (consumer_tid != RT_NULL)
        rt_thread_startup(consumer_tid);
    else
        tc_stat(TC_STAT_END | TC_STAT_FAILED);

    return 0;
}

4. 互斥量

互斥量控制块是操作系统用于管理互斥量的一个数据结构。

4.1 互斥量控制块

struct rt_mutex
{
    struct rt_ipc_object parent; /* IPC对象继承而来 */
    rt_uint16_t value; /* 只有LOCK和UNLOCK两种值 */
    rt_uint8_t original_priority; /* 上一次获得该锁的线程的优先级 */
    rt_uint8_t hold; /* 该线程获取了多少次该互斥锁 */
    struct rt_thread *owner; /* 当前拥有该锁的线程句柄 */
}

静态互斥量:struct rt_mutex static_mutex;
动态互斥量:rt_mutex_t dynamic_mutex;

4.2 互斥量的操作

  1. 初始化与脱离
静态互斥量
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag); //RT_IPC_FIFO RT_IPC_FLAG_PRIO
rt_err_t rt_mutex_detach(rt_mutex_t mutex);
  1. 创建与删除
动态互斥量
rt_mutex rt_mutex_create(const char *name, rt_uint8_t flag);
rt_err_t rt_mutex_delete(rt_mutex_t mutex);
  1. 获取互斥量
只能在线程中调用,且同一个线程能够take多次同一个互斥量,其成员hold+1
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time) // RT_WAITING_FOREVER = -1
  1. 释放互斥量
只能在线程中调用,不能在中断中调用。必须同一个线程获取的同一个互斥量,才能在该线程释放该互斥量
rt_err_t rt_mutex_release(rt_mutex_t mutex)

4.3 互斥量和信号量的差别

  1. 信号量可以由任何线程(以及中断)释放,它用于同步的时候就像交通灯,线程只有在获得许可的时候,才能运行,强调的是运行步骤;互斥量只能由持有它的线程释放,即只有锁上它的哪个线程,才有钥匙打开它,强调的是许可和权限
  2. 使用信号量可能导致优先级反转,互斥量可通过优先级集成的方法解决优先级反转问题

5. 线程优先级翻转

当一个高优先级线程试图通过某种互斥IPC对象机制访问共享资源时,如果该IPC对象已经被一个低优先级的线程所持有,而且这个低优先级线程运行过程中可能又被其他一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞的情况。导致高优先级的实时性得不到保证

5.1 优先级继承

在RT-Thread中,通过互斥量的优先级继承算法,可有有效解决优先级翻转问题。优先级继承是指提高某个占有某种共享资源的低优先级线程优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,从而得到更快地执行然后释放共享资源,当这个低优先级线程释放该资源时,优先级重新回到初始设定值。继承优先级的线程,避免了系统共享资源被任何中间优先级的线程抢占

优先级翻转线向提醒编程人员对共享资源进行互斥访问的代码段应尽量短。让低优先级线程尽快完成工作,释放共享资源

参考文献

  1. RT-Thread视频中心内核入门
  2. RT-Thread文档中心

本文作者: CrazyCatJack

本文链接: https://www.cnblogs.com/CrazyCatJack/p/14408842.html

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

关注博主:如果您觉得该文章对您有帮助,可以点击文章右下角推荐一下,您的支持将成为我最大的动力!


posted @ 2021-02-18 08:19  CrazyCatJack  阅读(1132)  评论(0编辑  收藏  举报