【STM32F4】【银杏科技ARM+FPGA】iCore3移植RT-Thread--内核之互斥量

邮差的信 提交于 2020-08-11 09:01:53

一、互斥量的引入

  互斥量类似于ATM取款机:当有客户进入的时候,将取款机门锁住,其他客户在外面等候。当里面的客户出来时,将门打开,下一个客户才可以进入。

  由此,互斥量与信号量的工作机制就比较相似,其实,互斥量是特殊的二进制信号量。信号量用于线程的同步,好比交通灯,线程只有在获得许可的时候才可以运行,强调的是步骤;互斥量用于线程的互斥,就像一把锁,只有获取钥匙的线程才可以运行,强调的是许可和权限。

  互斥量只有两种状态,开锁或闭锁。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的持有权。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它。 

二、互斥量和信号量的相同之处

1,创建时,线程阻塞排序均可以选择优先级或先进先出方式。

2,线程阻塞时间均可以选择直接返回、挂起一段时间、永久等待。

三、互斥量和信号量的区别

1.互斥量用于线程的互斥,信号量用于线程的同步。

2.互斥量值只能为0或1,信号量值为非负整数。一个互斥量只能允许一个资源访问。信号量可以实现多个同类资源的多线程互斥和同步。

3.信号量可以由任何线程释放,但互斥锁只有获得了其控制权的线程才可以释放,即:只有“锁上”它的那个线程才有“钥匙”打开它。

4.信号量可能导致线程优先级反转,而互斥锁可通过优先级继承的方法解决优先级反转问题。

  创建一个信号量并对他进行初始化,当一个线程需要使用一个共享资源时,他必须先申请得到这个信号量。在这个过程中即使有优先级更高的线程进入了就绪态,因为无法得到信号量,也不能使用共享资源。简单的来说,就是高优先级任务必须等待低优先级任务的完成。

四、使用互斥量的好处

  在多线程的系统中,适当使用互斥量,可以使系统实时性更高,因为它内部有优先级继承算法。通过提高某个占用共享资源的低优先级的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相同,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。

五、线程控制块

每个互斥量都有自己的互斥量控制块,其中包含互斥量的所有信息:

struct rt_mutex
{
    struct rt_ipc_object parent;                        /*继承自ipc_object 类*/

    rt_uint16_t          value;                         /* 互斥量的值 */

    rt_uint8_t           original_priority;             /* 原始优先级,即此互斥量拥有者线程的优先级 */
    rt_uint8_t           hold;                          /*被同一个线程获取的次数 */

    struct rt_thread    *owner;                         /*拥有互斥量的线程*/
};
typedef struct rt_mutex *rt_mutex_t;

参数:

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

original_priority:保存持有互斥量线程的初始优先级, rt-thread为了解决线程优先级翻转的问题,将提升持有互斥量线程的优先级,这里便于恢复原始优先级。

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

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

六、接口函数

6.1、创建动态互斥量:当调用此函数时,系统完成对该控制块的初始化工作。

rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag);

(1)入口参数:

 name:互斥量的名称。

 flag:互斥量标志,它可以取如下数值:RT_IPC_FLAG_FIFO 或RT_IPC_FLAG_PRIO。当设定为 RT_IPC_FLAG_FIFO(先进先出)时,等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的信号量;当设定为RT_IPC_FLAG_PRIO(优先级等待)时,等待线程队列将按照优先级进行排队,优先级高的等待线程将先获得等待的信号量。

(2)返回值:

互斥量句柄:创建成功。

RT_NULL:创建失败。

6.2、删除动态互斥量:当删除一个互斥量时,所有等待此互斥量的线程都将被唤醒,等待线程获得的返回值是-RT_ERROR。然后系统将该互斥量从内核对象管理器链表中删除并释放互斥量占用的内存空间。

rt_err_t rt_mutex_delete(rt_mutex_t mutex);

(1)入口参数:

 mutex:要删除的动态互斥量对象的句柄。

(2)返回值:

 RT_EOK:删除成功。

6.3、创建静态互斥量:即初始化互斥量,静态互斥量对象的内存是在系统编译时由编译器分配的,一般放于读写数据段或未初始化数据段中

rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag);

(1)入口参数:

 mutex:互斥量对象的句柄,它由用户提供,并指向互斥量对象的内存块。

 name:互斥量的名称。

 flag:互斥量标志,它可以取如下数值:RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO

(2)返回值:

 RT_EOK:创建成功。

6.4 删除静态互斥量:使用该函数接口后,内核先唤醒所有挂在该互斥量上的线程(线程的返回值是RT_ERROR),然后系统将该互斥量从内核对象管理器中脱离。

rt_err_t rt_mutex_detach(rt_mutex_t mutex);

(1)入口参数:

 mutex:要删除的静态互斥量对象的句柄。

(2)返回值:

 RT_EOK:删除成功

6.5、获取互斥量:如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。如果互斥量已经被当前线程线程控制,则该互斥量的持有计数加1,当前线程也不会挂起等待。如果互斥量已经被其他线程占有,则当前线程在该互斥量上挂起等待,直到其他线程释放它或者等待时间超过指定的超时时间。

  当前线程获取互斥量的时间已到还是未成功时,当前线程的thread->error将在定时器的超时回调函数中设置为-RT_ETIMEOUT,而如果另一线程在释放互斥锁rt_mutex_release函数中如果唤醒当前阻塞的线程,在这个过程中thread->error的值将一直保持原样RT_EOK。这就是为什么可以通过判断thread->error的值来得知当前线程是否已经获得互斥量。

rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time);

(1)入口参数:

 mutex:互斥量对象的句柄。

 time:指定等待的时间。

(2)返回值:

 RT_EOK:成功获得互斥量。

 RT_ETIMEOUT:超时。

 RT_ERROR:获取失败。

6.6、释放互斥量:使用该函数接口时,只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的持有计数就减1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该信号量上的线程将被唤醒。如果线程的运行优先级被互斥量提升,那么当互斥量被释放后,线程恢复为持有互斥量前的优先级。只有在确保互斥量再没有线程take时,才去唤醒挂起在此互斥量上的第一个线程,并将此互斥量的拥有者线程设置为此被唤醒的线程,并记录唤醒线程的优先级,因为唤醒线程即将成功拥有互斥量,所以互斥量的hold值再次加1。
rt_err_t rt_mutex_release(rt_mutex_t mutex);

(1)入口参数:

 mutex:互斥量对象的句柄。

(2)返回值:

 RT_EOK :成功。

 七、实例代码

#define THREAD_PRIORITY         8      //优先级
#define THREAD_TIMESLICE        5      //时间片

static rt_mutex_t dynamic_mutex = RT_NULL;  //互斥量句柄

static rt_uint8_t number1, number2 = 0;

static char thread1_stack[1024];     //线程堆栈
static char thread2_stack[1024];

static struct rt_thread thread1;     //静态线程控制块
static struct rt_thread thread2;

ALIGN(RT_ALIGN_SIZE)
static void rt_thread_entry1(void *parameter)
{
    while (1)
    {
        /* 线程1获取到互斥量后,先后对number1、number2进行加1操作,然后释放互斥量 */
        rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
        number1++;
        rt_thread_mdelay(10);
        number2++;
        rt_mutex_release(dynamic_mutex);
    }
}
ALIGN(RT_ALIGN_SIZE)
static void rt_thread_entry2(void *parameter)
{
    while (1)
    {
        /* 线程2获取到互斥量后,检查number1、number2的值是否相同,相同则表示mutex起到了锁的作用 */
        rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);
        if (number1 != number2)
        {
            rt_kprintf("not protect.number1 = %d, mumber2 = %d \n", number1, number2);
        }
        else
        {
            rt_kprintf("mutex protect ,number1 = mumber2 is %d\n", number1);
        }
        number1++;
        number2++;
        rt_mutex_release(dynamic_mutex);
        if (number1 >= 50)
            return;
    }
}
/* 互斥量示例的初始化 */
int mutex_sample(void)
{
    /* 创建一个动态互斥量 */
    dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_FIFO);
    if (dynamic_mutex == RT_NULL)
    {
        rt_kprintf("create dynamic mutex failed.\n");
        return -1;
    }
    rt_thread_init(&thread1,
                   "thread1",
                   rt_thread_entry1,
                   RT_NULL,
                   &thread1_stack[0],
                   sizeof(thread1_stack),
                   THREAD_PRIORITY, THREAD_TIMESLICE);
    rt_thread_startup(&thread1);
    rt_thread_init(&thread2,
                   "thread2",
                   rt_thread_entry2,
                   RT_NULL,
                   &thread2_stack[0],
                   sizeof(thread2_stack),
                   THREAD_PRIORITY - 1, THREAD_TIMESLICE);
    rt_thread_startup(&thread2);
    return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mutex_sample, mutex sample);

  七、实验现象

  打开putty,选择正确的端口号和波特率,程序编译成功后下载,输入mutex_sample指令运行,如果number1和number2值相同,则表示mutex起到了互斥量的作用。

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!