工具与资源核心
帮忙开发者更加高效的工作,提供围绕开发者全生命周期的工具与资源
https://developer.aliyun.com/...

1、概述

本文将剖析互斥信号量的源码。

互斥信号量与信号量有相似之处,却又有很大的不同。次要的几个不同点为:

(1)任意时刻互斥信号量最多只能被一个线程取得,它不像信号量那样能够有多个。

(2)只有取得互斥信号量的工作能力开释互斥信号量,所以中断上下文中不能开释互斥信号量。

(3)反对嵌套申请,即取得互斥信号量的工作可再次申请该互斥信号量。若嵌套申请信号量,则每申请一次将耗费一个信号量。

(4)反对解决优先级反转问题。优先级反转问题是操作系统设计的一个经典问题,曾导致过重大软件事变,有趣味的读者能够理解一下。因为互斥信号量源码中有不少代码是为了解决这个问题,因而有必要先解释一下。

假如有三个工作taskhigh、taskmid、tasklow,其中taskhigh优先级最高,taskmid其次,tasklow优先级最低。假如tasklow曾经取得了互斥信号量Mutex,那么当taskhigh申请Mutex时将被阻塞,它要始终等到tasklow开释Mutex后能力持续运行。然而若taskmid就绪,它将抢占tasklow运行,tasklow开释互斥信号量要等taskmid让出CPU。若零碎中还有taskmid2、taskmid3等工作,那么taskhigh还得等他们先执行。也就是说,高优先级工作因期待被低优先级工作占用的互斥信号量而得不到调度。这便是优先级反转问题。

AliOS Things采纳优先级继承策略解决优先级反转问题:当高优先级工作申请互斥信号量阻塞时,将晋升占用互斥信号量的工作的优先级。在上述例子中,当taskhigh申请Mutex阻塞时,将把tasklow的优先级晋升到与taskhigh雷同,这样就能够防止因为taskmid抢占tasklow而导致taskhigh得不到调度。

上面咱们来解读互斥信号量源码,剖析上述不同点是如何实现的。

互斥信号量源码地位:core/rhino/k_mutex.c

互斥信号量头文件地位:core/rhino/include/k_mutex.h

2、互斥信号量构造体kmutex_t

头文件k_mutex.h定义了互斥信号量构造体kmutex_t。互斥信号量相干的函数都基于该构造体,所以咱们首先剖析一下该构造体,其具体定义如下:
typedef struct mutex_s {

/**<

  • Manage blocked tasks
  • List head is this mutex, list node is task, which are blocked in this mutex
    */
    blk_obj_t blk_obj;

ktask_t mutex_task; /< Pointer to the owner task /

/**<

  • Manage mutexs owned by the task
  • List head is task, list node is mutex, which are owned by the task
    */
    struct mutex_s *mutex_list;

mutex_nested_t owner_nested;

if (RHINO_CONFIG_KOBJ_LIST > 0)

klist_t mutex_item; /*< kobj list for statistics /

endif

uint8_t mm_alloc_flag; /*< buffer from internal malloc or caller input /

} kmutex_t;

成员阐明:

(1)blk_obj这是内核的一个根底构造体,用于治理内核构造体的根本信息。用面向对象的思维来看,它相当于kmutex_t的父类。它的次要域有:blk_list阻塞队列,name对象名字,blk_policy阻塞队列期待策略(次要有优先级(PRI)和先入先出(FIO)两种),obj_type构造体类型;

(2)mutex_task 指向取得该互斥信号量的工作;

(3)mutex_list 一个工作可能取得多个互斥信号量,用该域构建工作取得的互斥信号量链表。入下图所示,工作取得了三个互斥信号量:

(4)owner_nested记录同一个工作的申请嵌套次数;

(5)mutex_item是一个链表节点,用来把互斥信号量插入到全局链表,次要用作调试、统计;

(6)mm_alloc_flag是一个内存标记,用来示意该构造体的内存是动态调配的还是动态分配的。

3、创立互斥信号量函数mutex_create

创立互斥信号量的外围函数是mutex_create,它的原型如下:

kstat_t mutex_create(kmutex_t mutex, const name_t name, uint8_t mm_alloc_flag);

参数含意:

mutex:互斥信号量构造体指针;

name:互斥信号量名字,用户能够为本人的互斥信号量指定名字,以便于调试辨别;

mm_alloc_flag:内存类型,即入参mutex指向的内存是动态调配的还是动态分配的。若为动态分配,则在删除互斥信号量时须要开释mutex指向的构造体内存;

比照信号量创立接口,这里没有信号量个数的入参,这是因为互斥信号量最多只能被一个工作取得,且初始状态互斥信号量闲暇,即相当于初始值为1。

在该函数内将初始化互斥信号量构造体,几个要害信息是:

(1)blk_obj.blk_policy初始化为BLK_POLICY_PRI,示意采纳基于优先级的阻塞策略,意思是当多个工作阻塞在该互斥信号量上时,高优先级工作优先取得该互斥信号量。另外一种策略是BLK_POLICY_FIFO,即先阻塞的工作优先取得互斥信号量;

(2)mutex_task初始化NULL,示意以后没有工作占用该互斥信号量;

(3)用RHINO_CRITICAL_ENTER()/RHINO_CRITICAL_EXIT()临界区语句爱护的链表插入操作将互斥信号量构造体插入全局链表。在调试时,能够通过g_kobj_list.mutex_head链表取得零碎中所有互斥信号量;

(4)blk_obj.obj_type初始化为RHINO_MUTEX_OBJ_TYPE,示意该构造体类型是互斥信号量。

函数krhino_mutex_create()和krhino_mutex_dyn_create()是创立互斥信号量的对外接口,两者的差异是前者是动态创立(K_OBJ_STATIC_ALLOC),即kmutex_t构造体的内存由内部传入。后者是动态创建(K_OBJ_DYN_ALLOC),该函数内将调用krhino_mm_alloc动态分配kmutex_t构造体的内存,并通过入参mutex把创立的构造体对象传回给调用者,所以krhino_mutex_dyn_create函数入参mutex的类型是kmutex_t **。

4、申请互斥信号量krhino_mutex_lock

创立互斥信号量后,就能够调用krhino_mutex_lock申请互斥信号量了,其原型如下:

kstat_t krhino_mutex_lock(kmutex_t *mutex, tick_t ticks);

参数阐明:

(1)mutex 指向互斥信号量构造体的指针;

(2)ticks 阻塞工夫。当互斥信号量曾经被占用时,发动申请的工作将被阻塞,ticks用来指定阻塞工夫。其中,两个非凡值是:(a)RHINO_NO_WAIT,不期待,当不能取得互斥信号量时间接返回;(b) RHINO_WAIT_FOREVER,始终阻塞期待,直到取得互斥信号量为止。

在该函数内:

(1)函数入口做一些入参查看。语句RHINO_CRITICAL_ENTER()用于进入临界区;

(2)条件语句g_active_task[cur_cpu_num] == mutex->mutex_task用来判断互斥信号量是否曾经被当前任务取得。若曾经取得,则互斥信号量外部嵌套计数mutex->owner_nested加1。而后返回,这里实现了嵌套取得互斥信号量;

(3)条件语句mutex_task == NULL用来判断互斥信号量是否闲暇,若该条件成立,将占用该互斥信号量,占用的次要操作是mutex_task赋值为g_active_task[cur_cpu_num],即以后申请互斥信号量的工作。这里将返回胜利;

(4)如果上述两个条件不成立,阐明互斥信号量曾经被占用了,如果入参ticks等于RHINO_NO_WAIT,阐明调用者不想期待,间接返回失败。如果g_sched_lock[cur_cpu_num] > 0,阐明零碎以后关调度了,那么工作不能被阻塞了,因为阻塞将触发调度,所以也返回失败;

(5)执行到这里,工作将被阻塞。调用pend_to_blk_obj将置工作为非就绪状态,RHINO_CRITICAL_EXIT_SCHED()将退出临界区并触发调度。当工作被唤醒后,继续执行,将调用pend_state_end_proc函数,用来判断是什么起因被唤醒的,次要是两个(a)超时工夫到;(b)取得了互斥信号量;

调用pend_to_blk_obj前,用预编译宏RHINO_CONFIG_MUTEX_INHERIT管制的区域还有一个判断语句。这个判断语句就是用来解决优先级反转问题的:

判断g_active_task[cur_cpu_num]->prio < mutex_task->prio,阐明以后申请互斥信号量的工作优先级比取得互斥信号量的工作高(值越小优先级越高),这个时候将调用ask_pri_change动静晋升取得互斥信号量工作的优先级。

5、开释互斥信号量krhino_mutex_unlock

开释互斥信号量的函数原型为:

kstat_t krhino_mutex_unlock(kmutex_t *mutex)

在该函数内:

(1)函数入口先查看入参和进入临界区;

(2)调用mutex_release把互斥信号量从工作的互斥信号量链表删除。若该工作的优先级被动静调整过,则复原工作的优先级。当然,这个函数还要思考工作可能占用着其余互斥信号量,篇幅起因,不具体开展了;

(3)开释互斥信号量后,判断以后是否有工作阻塞在该互斥信号量上,若没有则间接返回;若有,则从阻塞链表取一个阻塞工作,调用pend_task_wakeup唤醒该工作,而后将互斥信号量的信息更新为被新工作占用。

6、删除互斥信号量krhino_mutex_del/ krhino_mutex_dyn_del

krhino_mutex_del用来删除krhino_mutex_create创立的互斥信号量。krhino_mutex_dyn_del用来删除krhino_mutex_dyn_create创立的互斥信号量。这两组函数必须配套应用,否则开释将失败。

krhino_mutex_dyn_del相比krhino_mutex_de多了开释互斥信号量构造体的操作,这里咱们仅剖析krhino_mutex_del函数。

在该函数内:

(1)首先查看入参,进入临界区,而后判断类型是否正确,是否动态调配;

(2)若该互斥信号量以后被工作占用,则调用mutex_release把互斥信号量从工作的互斥信号量链表删除。若该工作的优先级被动静调整过,则复原工作的优先级;

(3)若有工作阻塞在该互斥信号量上,则全副唤醒;

(4)语句klist_rm(&mutex->mutex_item)把互斥信号量从g_kobj_list.mutex_head链表删除。与mutex_create中的插入操作绝对;

(5)退出临界区并返回。

7、应用示例

上面是一个接口应用示例,工作1和工作2通过互斥信号量互斥拜访全局变量a,并判断是否呈现互斥失败。

include <k_api.h>
/ 定义测试工作参数 /

define TEST_TASK1_NAME "task_test1"
define TEST_TASK2_NAME "task_test2"
define TEST_TASK1_PRI 34
define TEST_TASK2_PRI 34
define TEST_TASK_STACKSIZE (512)
/ 定义互斥信号量构造体 /

kmutex_t mutex_test;

/ 定义工作相干资源 /

ktask_t test_task1_tcb;

cpu_stack_t test_task1_stack[TEST_TASK_STACKSIZE];

ktask_t test_task2_tcb;

cpu_stack_t test_task2_stack[TEST_TASK_STACKSIZE];

/ 前向申明工作入口函数 /

static void test_task1(void *arg);

static void test_task2(void *arg);

/ 定义互斥拜访的全局变量 /

int a = 0;

/ 入口函数 /

int application_start(int argc, char *argv[])

{

/ 动态创立互斥信号量,初始个数为0 /

krhino_mutex_create(&mutex_test, "mutex_test");

/ 创立两个测试工作 /

krhino_task_create(&test_task1_tcb, TEST_TASK1_NAME, 0, TEST_TASK1_PRI, 50,

              test_task1_stack, TEST_TASK_STACKSIZE, test_task1, 0);

krhino_task_create(&test_task2_tcb, TEST_TASK2_NAME, 0, TEST_TASK2_PRI, 50,

              test_task2_stack, TEST_TASK_STACKSIZE, test_task2, 0);

}

/ 工作1的入口 /

static void test_task1_entry(void *arg)

{

int b;

while (1) {

krhino_mutex_lock(&mutex_test, RHINO_WAIT_FOREVER);

b = a;

a = a + 1;

if (a != b + 1) {

   printf("task 1 data process error\r\n");

}

krhino_mutex_unlock(&mutex_test);
}

}

/ 工作2的入口 /

static void test_task2(void *arg)

{

int b;

while(1) {

krhino_mutex_lock(&mutex_test, RHINO_WAIT_FOREVER);

b = a;

a = a + 1;

if (a != b + 1) {

   printf("task 2 data process error\r\n");

}

krhino_mutex_unlock(&mutex_test);
}

}