rt-thread的IPC机制之互斥锁源码分析

互斥锁是管理临界资源的一种有效手段。因为互斥锁是独占的,所以在一个时刻只允许一个线程占有互斥锁,利用这个性质来实现共享资源的互斥锁保护。任何时刻只允许一个线程获得互斥量对象,未能够获得互斥量对象的线程被挂起在该互斥量的等待线程队列上。


1 互斥锁控制块

/**
 * Mutual exclusion (mutex) structure
 */
struct rt_mutex
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object *///派生自IPC对象

    rt_uint16_t          value;                         /**< value of mutex *///此互斥锁的值

    rt_uint8_t           original_priority;             /**< priority of last thread hold the mutex *///原始优先级,即此互斥锁拥有者线程的优先级
    rt_uint8_t           hold;                          /**< numbers of thread hold the mutex *///此互斥锁当前已被几个线程(同一线程)take的次数

    struct rt_thread    *owner;                         /**< current owner of mutex *///此互斥锁的拥有者线程
};
typedef struct rt_mutex *rt_mutex_t;
由上述源码可知,互斥锁控制块也是派生自IPC对象,而IPC对象又派生自内核对象.

value大于1时表示此互斥锁可用,小于或等于0都表示此互斥锁当前不可用.

original_priority保存着互斥锁拥有者的初始优先级,因此rt-thread为了处理互斥锁导致线程优先级翻转的问题,可能会提升互斥锁拥有者线程的优先级,这里保存原先优先级便于恢复.

hold表示此互斥锁被同一线程成功take的次数,一般情况下一个线程只会take一次互斥锁,但rt-thread也允许线程重复take同一线程,此时hold的值就用来做记录线程重复take互斥锁的次数,以便在realse同样多次时才去唤醒互斥锁上的挂起的线程.

而owner表示此互斥锁的拥有者线程,rt-thread只允许同一时间只有一个线程拥有这个互斥锁,此参数就是用来记录此线程的.

2 初始化及创建互斥锁

2.1 初始化互斥锁

/**
 * This function will initialize a mutex and put it under control of resource
 * management.
 *
 * @param mutex the mutex object
 * @param name the name of mutex
 * @param flag the flag of mutex
 *
 * @return the operation status, RT_EOK on successful
 */
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)
{
    RT_ASSERT(mutex != RT_NULL);

    /* init object */
    rt_object_init(&(mutex->parent.parent), RT_Object_Class_Mutex, name);//初始化互斥锁的内核对象

    /* init ipc object */
    rt_ipc_object_init(&(mutex->parent));//初始化互斥锁的IPC对象

    mutex->value = 1;//设置互斥锁值初始化为1
    mutex->owner = RT_NULL;//初始化互斥锁当前没有拥有者
    mutex->original_priority = 0xFF;//初始化互斥锁原始优先级为255,即拥有者线程的优先级
    mutex->hold  = 0;//初化为当前互斥锁的被take的次数为0(同一线程)

    /* set flag */
    mutex->parent.parent.flag = flag;//设置互斥锁的内核对象标志

    return RT_EOK;
}

注意互斥锁在初始化时其值value被初始化为1,即默认情况下其是可用的.在其被take之后value值减1变为0时才不可用.

2.2 创建互斥锁

/**
 * This function will create a mutex from system resource
 *
 * @param name the name of mutex
 * @param flag the flag of mutex
 *
 * @return the created mutex, RT_NULL on error happen
 *
 * @see rt_mutex_init
 */
rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)
{
    struct rt_mutex *mutex;

    RT_DEBUG_NOT_IN_INTERRUPT;//确保此函数不是在ISR中使用

    /* allocate object */
    mutex = (rt_mutex_t)rt_object_allocate(RT_Object_Class_Mutex, name);//动态分配一个互斥锁
    if (mutex == RT_NULL)
        return mutex;

    /* init ipc object */
    rt_ipc_object_init(&(mutex->parent));//初始化此互斥锁的内核IPC对象

    mutex->value              = 1;//初始化互斥锁的值为1
    mutex->owner              = RT_NULL;//无拥有者
    mutex->original_priority  = 0xFF;//互斥锁原始优先级为255
    mutex->hold               = 0;//take此互斥锁的线程个数(同一线程)次数为0

    /* set flag */
    mutex->parent.parent.flag = flag;//设置互斥锁内核对象标志

    return mutex;
}

此函数与初始化类似,直接跳过.

3 脱离或删除互斥锁

3.1 脱离互斥锁

/**
 * This function will detach a mutex from resource management
 *
 * @param mutex the mutex object
 *
 * @return the operation status, RT_EOK on successful
 *
 * @see rt_mutex_delete
 */
rt_err_t rt_mutex_detach(rt_mutex_t mutex)
{
    RT_ASSERT(mutex != RT_NULL);

    /* wakeup all suspend threads */
    rt_ipc_list_resume_all(&(mutex->parent.suspend_thread));//唤醒所有挂起的线程

    /* detach semaphore object */
    rt_object_detach(&(mutex->parent.parent));//脱离互斥锁的内核对象

    return RT_EOK;
}

脱离互斥锁只要是唤醒挂起的线程及脱离其内核对象,没有什么好说明的.

3.2 删除互斥锁

/**
 * This function will delete a mutex object and release the memory
 *
 * @param mutex the mutex object
 *
 * @return the error code
 *
 * @see rt_mutex_detach
 */
rt_err_t rt_mutex_delete(rt_mutex_t mutex)
{
    RT_DEBUG_NOT_IN_INTERRUPT;//确保此函数不是在ISR中使用

    RT_ASSERT(mutex != RT_NULL);

    /* wakeup all suspend threads */
    rt_ipc_list_resume_all(&(mutex->parent.suspend_thread));//唤醒所有挂起的线程

    /* delete semaphore object */
    rt_object_delete(&(mutex->parent.parent));//删除互斥锁的内核对象

    return RT_EOK;
}

除了唤醒挂起的线程还需要删除其内核对象.

3.3 获取互斥锁

/**
 * This function will take a mutex, if the mutex is unavailable, the
 * thread shall wait for a specified time.
 *
 * @param mutex the mutex object
 * @param time the waiting time
 *
 * @return the error code
 */
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
{
    register rt_base_t temp;
    struct rt_thread *thread;

    /* this function must not be used in interrupt even if time = 0 */
    RT_DEBUG_NOT_IN_INTERRUPT;//确保此函数不是在ISR中使用

    RT_ASSERT(mutex != RT_NULL);

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();//关中断

    /* get current thread */
    thread = rt_thread_self();//获取当前线程

    RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mutex->parent.parent)));

    RT_DEBUG_LOG(RT_DEBUG_IPC,
                 ("mutex_take: current thread %s, mutex value: %d, hold: %d\n",
                  thread->name, mutex->value, mutex->hold));

    /* reset thread error */
    thread->error = RT_EOK;//设置当前线程的错误标志为RT_EOK

    if (mutex->owner == thread)//如果当前互斥锁的拥有者就是当前线程,即当前线程重复take同一互斥锁,则当前互斥锁的take次数加1
    {
        /* it's the same thread */
        mutex->hold ++;
    }
    else//如果当前互斥锁的拥有者不是本身线程
    {
        /* The value of mutex is 1 in initial status. Therefore, if the
         * value is great than 0, it indicates the mutex is avaible.
         */
        if (mutex->value > 0)//当前互斥锁的值大于0,则说明此互斥锁可用
        {
            /* mutex is available */
            mutex->value --;//将互斥锁的值减1

            /* set mutex owner and original priority */
            mutex->owner             = thread;//设置互斥锁的拥有者为当前线程
            mutex->original_priority = thread->current_priority;//设置互斥锁的原始优先级为当前线程的优先级
            mutex->hold ++;//互斥锁被成功take,take次数加1
        }
        else//如果当前互斥锁不可用
        {
            /* no waiting, return with timeout */
            if (time == 0)//如果等待参数为0,则应立即返回错误
            {
                /* set error as timeout */
                thread->error = -RT_ETIMEOUT;//设置超时错误

                /* enable interrupt */
                rt_hw_interrupt_enable(temp);//开中断

                return -RT_ETIMEOUT;//返回超时错误
            }
            else//如果时间参数不为0
            {
                /* mutex is unavailable, push to suspend list */
                RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_take: suspend thread: %s\n",
                                            thread->name));

                /* change the owner thread priority of mutex */
                if (thread->current_priority < mutex->owner->current_priority)//如果当前线程的优先级比互斥锁拥有者线程大的话(优先级值越小,优先级越高)
                {
                    /* change the owner thread priority */
                    rt_thread_control(mutex->owner,//则修改当前互斥锁拥有者线程的优先级降为当前线程的优先级,如果不这么做的话,则如果拥有者线程永远不释放,则当前线程永远不可能获取此互斥锁,这样有可以会造成问题
                                      RT_THREAD_CTRL_CHANGE_PRIORITY,//这样拥有者线程和当前线程优先级相同,系统采用时间片轮转方式,当前线程就有了获得互斥锁的可能了
                                      &thread->current_priority);
                }

                /* suspend current thread */
                rt_ipc_list_suspend(&(mutex->parent.suspend_thread),//挂起当前线程
                                    thread,
                                    mutex->parent.parent.flag);

                /* has waiting time, start thread timer */
                if (time > 0)//如果时间参数大于0
                {
                    RT_DEBUG_LOG(RT_DEBUG_IPC,
                                 ("mutex_take: start the timer of thread:%s\n",
                                  thread->name));

                    /* reset the timeout of thread timer and start it */
                    rt_timer_control(&(thread->thread_timer),//设置定时器
                                     RT_TIMER_CTRL_SET_TIME,
                                     &time);
                    rt_timer_start(&(thread->thread_timer));//启动定时器
                }

                /* enable interrupt */
                rt_hw_interrupt_enable(temp);//开中断

                /* do schedule */
                rt_schedule();//立即重新调度线程
                
                if (thread->error != RT_EOK)//等待时间已到,但还是未成功获得互斥锁时,thread->error的值会被设为-RT_ETIMEOUT,见thread.c源文件中的rt_thread_timeout函数
                {
                    /* return error */
                    return thread->error;
                }
                else//如果在另一线程执行rt_mutex_release函数唤醒此线程,则此线程在在处被唤醒时即已经获得互斥锁,这一过程中thread->error的值都未被修改,还是保持为RT_EOK
                {
                    /* the mutex is taken successfully. */
                    /* disable interrupt */
                    temp = rt_hw_interrupt_disable();//关中断
                }
            }
        }
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);//开中断

    RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mutex->parent.parent)));

    return RT_EOK;
}

关于上述的末尾部分的代码,其实在thread.c源文件中的rt_thread_timeout函数中有将超时线程的thread->error = -RT_ETIMEOUT,即当当前线程获取互斥锁的时间已到还是未成功时,当前线程的thread->error将在定时器的超时回调函数中设置为-RT_ETIMEOUT,而如果另一线程在释放互斥锁rt_mutex_release函数中如果唤醒当前阻塞的线程,在这个过程中thread->error的值将一直保持原样RT_EOK.这就是为什么在这里可以通过判断thread->error的值来得知当前线程是否已经获得互斥锁了.


注:使用互斥量会导致的一个潜在问题就是线程优先级翻转。所谓优先级翻转问题即当一个高优先级线程通过互斥量机制访问共享资源时,该互斥量已被一低优先级线程占有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢先,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证。例如:有优先级为A、B和C的三个线程,优先级A >B > C,线程A,B处于挂起状态,等待某一事件的发生,线程C正在运行,此时线程C开始使用某一共享资源S。在使用过程中,线程A等待的事件到来,线程A转为就绪态,因为它比线程C优先级高,所以立即执行。但是当线程A要使用共享资源S时,由于其正在被线程C使用,因此线程A被挂起切换到线程C运行。如果此时线程B等待的事件到来,则线程B转为就绪态。由于线程B的优先级比线程C高,因此线程B开始运行,直到其运行完毕,线程C才开始运行。只有当线程C释放共享资源S后,线程A才得以执行。在这种情况下,优先级发生了翻转,线程B先于线程A运行。这样便不能保证高优先级线程的响应时间。

在RT-Thread中实现的是优先级继承算法。优先级继承通过在线程A被阻塞期间提升线程C的优先级到线程A的优先级别从而解决优先级翻转引起的问题。这防止了C(间接地防止A)被B抢占。通俗地说,优先级继承协议使一个拥有资源的线程以等待该资源的线程中优先级最高的线程的优先级执行。当线程释放该资源时,它将返回到它正常的或标准的优先级。因此,继承优先级的线程避免了被任何中间优先级的线程抢占。(参见上面修改互斥锁拥有者线程优先级处的代码部分).


5 释放互斥锁

/**
 * This function will release a mutex, if there are threads suspended on mutex,
 * it will be waked up.
 *
 * @param mutex the mutex object
 *
 * @return the error code
 */
rt_err_t rt_mutex_release(rt_mutex_t mutex)
{
    register rt_base_t temp;
    struct rt_thread *thread;
    rt_bool_t need_schedule;

    need_schedule = RT_FALSE;//需要重新调试标志初始化为不需要

    /* get current thread */
    thread = rt_thread_self();//获取当前线程

    /* disable interrupt */
    temp = rt_hw_interrupt_disable();//关中断

    RT_DEBUG_LOG(RT_DEBUG_IPC,
                 ("mutex_release:current thread %s, mutex value: %d, hold: %d\n",
                  thread->name, mutex->value, mutex->hold));

    RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mutex->parent.parent)));

    /* mutex only can be released by owner */
    if (thread != mutex->owner)//如果当前线程非拥有者,则说明有问题
    {
        thread->error = -RT_ERROR;//设置当前线程有错误

        /* enable interrupt */
        rt_hw_interrupt_enable(temp);//开中断

        return -RT_ERROR;//返回错误
    }

    /* decrease hold */
    mutex->hold --;//互斥锁的阻塞线程个数减1
    /* if no hold */
    if (mutex->hold == 0)//如果互斥锁当前已将全部take次数释放
    {
        /* change the owner thread to original priority */
        if (mutex->original_priority != mutex->owner->current_priority)//如果互斥锁的原始优先级不等于拥有者当前的优先级
        {
            rt_thread_control(mutex->owner,//则需要将拥有者线程的优先级还原为原始优先级
                              RT_THREAD_CTRL_CHANGE_PRIORITY,
                              &(mutex->original_priority));
        }

        /* wakeup suspended thread */
        if (!rt_list_isempty(&mutex->parent.suspend_thread))//如果互斥锁的挂起链表不为空
        {
            /* get suspended thread */
            thread = rt_list_entry(mutex->parent.suspend_thread.next,//获取挂起线程
                                   struct rt_thread,
                                   tlist);

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

            /* set new owner and priority */
            mutex->owner             = thread;//设置互斥锁的拥有者为挂起的线程
            mutex->original_priority = thread->current_priority;//设置互斥锁的原始优先级为此挂起线程的优先级
            mutex->hold ++;//互斥锁的take次数加1(由于被唤醒的线程即将获取此互斥锁)

            /* resume thread */
            rt_ipc_list_resume(&(mutex->parent.suspend_thread));//唤醒此线程

            need_schedule = RT_TRUE;//设置需要调度
        }
        else//如果挂起线程链表为空
        {
            /* increase value */
            mutex->value ++;//互斥锁的值加1

            /* clear owner */
            mutex->owner             = RT_NULL;//互斥锁的拥有者为空
            mutex->original_priority = 0xff;//互斥锁的原始优先级设为255
        }
    }

    /* enable interrupt */
    rt_hw_interrupt_enable(temp);//开中断

    /* perform a schedule */
    if (need_schedule == RT_TRUE)//如需要调度则调度线程
        rt_schedule();

    return RT_EOK;
}

只有在确保互斥锁再没有线程take时,即hold=0时,才去唤醒阻塞在此互斥锁上的第一个线程,并将此互斥锁的拥有者线程设置为此被唤醒的线程,并记录唤醒线程的优先级,因为唤醒线程即将成功拥有互斥锁,所以互斥锁的hold值加1.

6 互斥锁控制

/**
 * This function can get or set some extra attributions of a mutex object.
 *
 * @param mutex the mutex object
 * @param cmd the execution command
 * @param arg the execution argument
 *
 * @return the error code
 */
rt_err_t rt_mutex_control(rt_mutex_t mutex, rt_uint8_t cmd, void *arg)
{
    return -RT_ERROR;//互斥锁目前暂不能控制
}


你可能感兴趣的:(rt-thread的IPC机制之互斥锁源码分析)