关于操作系统:高薪秘诀跟着AliOS-Things轻松入门操作系统互斥信号量

7次阅读

共计 5995 个字符,预计需要花费 15 分钟才能阅读完成。

工具与资源核心
帮忙开发者更加高效的工作,提供围绕开发者全生命周期的工具与资源
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);
}

}

正文完
 0