IOT-OS之RT-Thread(六)--- 线程间同步与线程间通信

本文详细介绍了RT-Thread操作系统中线程间同步与通信的各种机制,包括信号量、互斥量、事件集、邮箱和消息队列的管理。通过对这些对象的理解,读者可以掌握如何在RT-Thread中实现线程间的同步与通信,以及如何利用这些机制解决实际问题,如生产者消费者模型。文章还提到了信号对象作为软中断的模拟,用以处理异步事件。
摘要由CSDN通过智能技术生成

之前的博客UCOS任务间同步与通信介绍了信号量、互斥量等任务间同步机制,消息邮箱、消息队列等任务间通信机制的实现原理,本文主要从RT-Thread与UCOS的对比与差异看RT-Thread线程间同步与通信对象管理的实现。

一、IPC对象管理

再回顾下内核对象的派生和继承关系:
内核对象派生继承关系

1.1 IPC对象控制块

前面已经介绍过直接继承自基对象rt_object的定时器对象rt_timer、内存池对象rt_mempool、线程对象rt_thread,下面要介绍线程间的同步与通信,线程间同步对象rt_sem / rt_mutex / rt_event和线程间通信对象rt_mb / rt_mq都直接继承自rt_ipc_object,而IPC对象又继承自基对象rt_object,在介绍线程间同步与通信对象前先介绍派生IPC对象rt_ipc_object,其数据结构如下:

// rt-thread-4.0.1\include\rtdef.h
/**
 * Base structure of IPC object
 */
struct rt_ipc_object
{
   
    struct rt_object parent;                            /**< inherit from rt_object */

    rt_list_t        suspend_thread;                    /**< threads pended on this resource */
};

/**
 * IPC flags and control command definitions
 */
#define RT_IPC_FLAG_FIFO                0x00            /**< FIFOed IPC. @ref IPC. */
#define RT_IPC_FLAG_PRIO                0x01            /**< PRIOed IPC. @ref IPC. */

#define RT_IPC_CMD_UNKNOWN              0x00            /**< unknown IPC command */
#define RT_IPC_CMD_RESET                0x01            /**< reset IPC object */

#define RT_WAITING_FOREVER              -1              /**< Block forever until get resource. */
#define RT_WAITING_NO                   0               /**< Non-block. */

IPC对象rt_ipc_object继承自基对象rt_object,rt_ipc_object.parent.flag用以标识实际IPC对象的处理方式,根据上面的宏定义总结如下:

flag位 0 1 备注
bit0 RT_IPC_FLAG_FIFO:按消息队列先进先出的方式处理. RT_IPC_FLAG_PRIO:按线程优先级的方式处理,即哪个线程的优先级高则哪个先操作 IPC处理方式

rt_ipc_object继承自基对象rt_object的另外几个成员根据具体对象类型取值有所不同,等介绍具体对象类型时再介绍。

rt_ipc_object唯一的私有对象rt_ipc_object.suspend_thread是挂起等待线程链表节点,这些挂起等待线程共同构成了事件等待链表,事件挂起链表的组织顺序跟前面介绍的flag有关,如果设置RT_IPC_FLAG_FIFO则挂起线程链表按先入先出排序,如果设置RT_IPC_FLAG_PRIO则挂起线程按优先级排序。

1.2 IPC对象接口函数

派生IPC对象rt_ipc_object由于只有一个私有成员suspend_thread,对其的操作主要也就是链表初始化、链表节点插入/移除等,IPC对象的初始化函数与挂起线程函数实现代码如下:

// rt-thread-4.0.1\src\ipc.c

/**
 * This function will initialize an IPC object
 *
 * @param ipc the IPC object
 *
 * @return the operation status, RT_EOK on successful
 */
rt_inline rt_err_t rt_ipc_object_init(struct rt_ipc_object *ipc)
{
   
    /* init ipc object */
    rt_list_init(&(ipc->suspend_thread));

    return RT_EOK;
}

/**
 * This function will suspend a thread to a specified list. IPC object or some
 * double-queue object (mailbox etc.) contains this kind of list.
 *
 * @param list the IPC suspended thread list
 * @param thread the thread object to be suspended
 * @param flag the IPC object flag,
 *        which shall be RT_IPC_FLAG_FIFO/RT_IPC_FLAG_PRIO.
 *
 * @return the operation status, RT_EOK on successful
 */
rt_inline rt_err_t rt_ipc_list_suspend(rt_list_t        *list,
                                       struct rt_thread *thread,
                                       rt_uint8_t        flag)
{
   
    /* suspend thread */
    rt_thread_suspend(thread);

    switch (flag)
    {
   
    case RT_IPC_FLAG_FIFO:
        rt_list_insert_before(list, &(thread->tlist));
        break;

    case RT_IPC_FLAG_PRIO:
        {
   
            struct rt_list_node *n;
            struct rt_thread *sthread;

            /* find a suitable position */
            for (n = list->next; n != list; n = n->next)
            {
   
                sthread = rt_list_entry(n, struct rt_thread, tlist);

                /* find out */
                if (thread->current_priority < sthread->current_priority)
                {
   
                    /* insert this thread before the sthread */
                    rt_list_insert_before(&(sthread->tlist), &(thread->tlist));
                    break;
                }
            }

            /*
             * not found a suitable position,
             * append to the end of suspend_thread list
             */
            if (n == list)
                rt_list_insert_before(list, &(thread->tlist));
        }
        break;
    }

    return RT_EOK;
}

函数rt_ipc_list_suspend有一个参数flag正好表示前面介绍的IPC处理方式,RT_IPC_FLAG_FIFO比较简单快速,RT_IPC_FLAG_PRIO能实现更好的实时性,用户根据需求选择传入的flag值。

有挂起自然有唤醒,考虑到IPC对象可能会被删除或脱离,此时挂起在该IPC对象上的所有线程都需要被唤醒,因此还提供了唤醒全部挂起线程的函数,实现代码如下:

// rt-thread-4.0.1\src\ipc.c

/**
 * This function will resume the first thread in the list of a IPC object:
 * - remove the thread from suspend queue of IPC object
 * - put the thread into system ready queue
 *
 * @param list the thread list
 *
 * @return the operation status, RT_EOK on successful
 */
rt_inline rt_err_t rt_ipc_list_resume(rt_list_t *list)
{
   
    struct rt_thread *thread;

    /* get thread entry */
    thread = rt_list_entry(list->next, struct rt_thread, tlist);

    RT_DEBUG_LOG(RT_DEBUG_IPC, ("resume thread:%s\n", thread->name));

    /* resume it */
    rt_thread_resume(thread);

    return RT_EOK;
}

/**
 * This function will resume all suspended threads in a list, including
 * suspend list of IPC object and private list of mailbox etc.
 *
 * @param list of the threads to resume
 *
 * @return the operation status, RT_EOK on successful
 */
rt_inline rt_err_t rt_ipc_list_resume_all(rt_list_t *list)
{
   
    struct rt_thread *thread;
    register rt_ubase_t temp;

    /* wakeup all suspend threads */
    while (!rt_list_isempty(list))
    {
   
        /* disable interrupt */
        temp = rt_hw_interrupt_disable();

        /* get next suspend thread */
        thread = rt_list_entry(list->next, struct rt_thread, tlist);
        /* set error code to RT_ERROR */
        thread->error = -RT_ERROR;

        /*
         * resume thread
         * In rt_thread_resume function, it will remove current thread from
         * suspend list
         */
        rt_thread_resume(thread);

        /* enable interrupt */
        rt_hw_interrupt_enable(temp);
    }

    return RT_EOK;
}

二、线程间同步对象管理

同步是指按预定的先后次序进行运行,线程同步是指多个线程通过特定的机制(如互斥量,事件对象,临界区)来控制线程之间的执行顺序,也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间将是无序的。

线程的同步方式有很多种,其核心思想都是:在访问临界区的时候只允许一个 (或一类) 线程运行。进入 / 退出临界区的方式有很多种:

  1. 调用 rt_hw_interrupt_disable() 进入临界区,调用 rt_hw_interrupt_enable()
    退出临界区;
  2. 调用 rt_enter_critical() 进入临界区,调用 rt_exit_critical() 退出临界区

2.1 信号量对象管理

信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。

信号量工作示意图如下图所示,每个信号量对象都有一个信号量值和一个线程等待队列,信号量的值对应了信号量对象的实例数目、资源数目,假如信号量值为 5,则表示共有 5 个信号量实例(资源)可以被使用,当信号量实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)。
信号量工作示意图

  • 信号量控制块

在 RT-Thread 中,信号量控制块是操作系统用于管理信号量的一个数据结构,由结构体 struct rt_semaphore 表示。另外一种 C 表达方式 rt_sem_t,表示的是信号量的句柄,在 C 语言中的实现是指向信号量控制块的指针。信号量控制块结构的详细定义如下:

// rt-thread-4.0.1\include\rtdef.h
/**
 * Semaphore structure
 */
struct rt_semaphore
{
   
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    rt_uint16_t          value;                         /**< value of semaphore. */
    rt_uint16_t          reserved;                      /**< reserved field */
};
typedef struct rt_semaphore *rt_sem_t;

rt_semaphore 对象从 rt_ipc_object 中派生,rt_semaphore.parent.parent.type值为RT_Object_Class_Semaphor,rt_semaphore.parent.parent.list为已初始化/创建信号量链表节点,组织成一个信号量链表(IPC对象不像定时器或线程对象分好几种状态,只维护一个IPC对象链表即可)。

rt_semaphore对象有两个私有成员,但一个保留未用,相当于只有一个私有成员rt_semaphore.value,信号量的最大值是 65535,有成员类型rt_uint16_t决定。

  • 信号量接口函数

信号量控制块中含有信号量相关的重要参数,在信号量各种状态间起到纽带的作用。信号量相关接口如下图所示,对一个信号量的操作包含:创建 / 初始化信号量、获取信号量、释放信号量、删除 / 脱离信号量。
信号量接口函数
先看信号量的构造/析构函数原型(依然分静态对象与动态对象):

// rt-thread-4.0.1\include\rtdef.h
/**
 * This function will initialize a semaphore and put it under control of
 * resource management.
 *
 * @param sem the semaphore object
 * @param name the name of semaphore
 * @param value the init value of semaphore
 * @param flag the flag of semaphore
 *
 * @return the operation status, RT_EOK on successful
 */
rt_err_t rt_sem_init(rt_sem_t    sem,
                     const char *name,
                     rt_uint32_t value,
                     rt_uint8_t  flag);

/**
 * This function will detach a semaphore from resource management
 *
 * @param sem the semaphore object
 *
 * @return the operation status, RT_EOK on successful
 *
 * @see rt_sem_delete
 */
rt_err_t rt_sem_detach(rt_sem_t sem);

/**
 * This function will create a semaphore from system resource
 *
 * @param name the name of semaphore
 * @param value the init value of semaphore
 * @param flag the flag of semaphore
 *
 * @return the created semaphore, RT_NULL on error happen
 *
 * @see rt_sem_init
 */
rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag);

/**
 * This function will delete a semaphore object and release the memory
 *
 * @param sem the semaphore object
 *
 * @return the error code
 *
 * @see rt_sem_detach
 */
rt_err_t rt_sem_delete(rt_sem_t sem);

再看信号量的获取rt_sem_take与释放rt_sem_release函数原型:

// rt-thread-4.0.1\include\rtdef.h
/**
 * This function will take a semaphore, if the semaphore is unavailable, the
 * thread shall wait for a specified time.
 *
 * @param sem the semaphore object
 * @param time the waiting time
 *
 * @return the error code
 */
rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time);

/**
 * This function will try to take a semaphore and immediately return
 *
 * @param sem the semaphore object
 *
 * @return the error code
 */
rt_err_t rt_sem_trytake(rt_sem_t sem);

/**
 * This function will release a semaphore, if there are threads suspended on
 * semaphore, it will be waked up.
 *
 * @param sem the semaphore object
 *
 * @return the error code
 */
rt_err_t rt_sem_release(rt_sem_t sem);

最后看信号量控制函数rt_sem_control,前面介绍IPC对象时也提到了两个命令宏定义,IPC对象支持的控制函数命令只有一种RT_IPC_CMD_RESET,对于RT_IPC_CMD_UNKNOWN可能是保留备用吧。rt_sem_control的作用是重新设置信号量值,函数原型如下:

// rt-thread-4.0.1\include\rtdef.h
/**
 * This function can get or set some extra attributions of a semaphore object.
 *
 * @param sem the semaphore object
 * @param cmd the execution command
 * @param arg the execution argument
 *
 * @return the error code
 */
rt_err_t rt_sem_control(rt_sem_t sem, int cmd, void *arg);

信号量是一种非常灵活的同步方式,可以运用在多种场合中。形成锁、同步、资源计数等关系,也能方便的用于线程与线程、中断与线程间的同步中。中断与线程间的互斥不能采用信号量(锁)的方式,而应采用开关中断的方式。

一般资源计数类型多是混合方式的线程间同步,因为对于单个的资源处理依然存在线程的多重访问,这就需要对一个单独的资源进行访问、处理,并进行锁方式的互斥操作。

2.2 互斥量对象管理

互斥量又叫相互排斥的信号量,是一种特殊的二值信号量。互斥量和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。

互斥量的状态只有两种,开锁或闭锁(两种状态值)。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它,持有该互斥量的线程也能够再次获得这个锁而不被挂起,如下图时所示。这个特性与一般的二值信号量有很大的不同:在信号量中,因为已经不存在实例,线程递归持有会发生主动挂起(最终形成死锁)。
互斥量工作机制
使用信号量会导致的另一个潜在问题是线程优先级翻转问题,优先级反转在博客UCOS任务间同步与通信中介绍过,这里不再赘述了,RT-Thread与UCOS类似,都是采用优先级继承算法来解决优先级翻转问题的。在获得互斥量后,请尽快释放互斥量,并且在持有互斥量的过程中,不得再行更改持有互斥量线程的优先级。

  • 互斥量控制块

在 RT-Thread 中,互斥量控制块是操作系统用于管理互斥量的一个数据结构,由结构体 struct rt_mutex 表示。另外一种 C 表达方式 rt_mutex_t,表示的是互斥量的句柄,在 C 语言中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流云IoT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值