信号量(Semaphore)是操作系统上极为常用的一种同步机制。本篇文章通过解析AliOS Things内核源码来学习信号量机制。
在AliOS Things中信号量的源码路径如下
信号量源码位置:core/rhino/k_sem.c
信号量头文件位置:core/rhino/include/k_sem.h
1、信号量结构体ksem_t
k_sem.h头文件定义了信号量结构体ksem_t。信号量相关的函数都基于该结构体,所以我们首先分析一下该结构体,其具体定义如下:
typedef struct sem_s {
blk_obj_t blk_obj;
sem_count_t count;
sem_count_t peak_count;
if (RHINO_CONFIG_KOBJ_LIST 0)klist_t sem_item; /* kobj list for statistics /
endifuint8_t mm_alloc_flag; /* buffer from internal malloc or caller input /
} ksem_t;
成员说明:
(1)blk_obj 这是内核的一个基础结构体,用于管理内核结构体的基本信息。用面向对象的思想来看,它相当于ksem_t的父类。它的主要域有:blk_list阻塞队列,name对象名字,blk_policy阻塞队列等待策略(主要有优先级(PRI)和先入先出(FIO)两种),obj_type结构体类型;
(2)count记录了信号的个数;
(3)peak_count是一个统计值,记录了系统运行过程中该信号量的最高信号个数。
(4)sem_item是一个链表节点,用来把信号量插入到全局链表,主要用作调试、统计。
(5)mm_alloc_flag是一个内存标记,用来表示该结构体的内存是静态分配的还是动态分配的。
2、创建信号量函数sem_create
创建信号量的核心函数是sem_create,它的原型如下:
static kstat_t sem_create(ksem_t sem, const name_t name, sem_count_t count,
uint8_t mm_alloc_flag);
参数含义:
sem:信号量结构体指针;
name:信号量名字,用户可以为自己的信号量指定名字,以便于调试区分;
count:初始信号个数;
mm_alloc_flag:内存类型,即sem指向的内存是静态分配的还是动态分配的。若为动态分配,则在删除信号量时需要释放sem指向的结构体内存。
在该函数内:
(1)首先用语句CPSR_ALLOC()定义一个内部变量,该变量被临界区语句RHINO_CRITICAL_ENTER()/RHINO_CRITICAL_EXIT()使用。在单核上临界区用关中断保护,因此CPSR_ALLOC()其实就是定义一个保存中断状态的变量。RHINO_CRITICAL_ENTER()将读取当前中断状态并保存下来,然后关中断。RHINO_CRITICAL_EXIT()将恢复中断状态。所以按如下方式进入/退出临界区:
CPSR_ALLOC();
RHINO_CRITICAL_ENTER(); //进入临界区
…… //临界区
RHINO_CRITICAL_EXIT(); //退出临界区
在sem_create函数中,用来保护对全局链表的访问。
klist_insert( (g_kobj_list.sem_head), sem- sem_item); //把信号量结构体插入全局链表
(2)NULL_PARA_CHK()宏用来做指针非空检查。若发现传入的sem或name指针为NULL,将直接返回。
(3)接下来将初始化信号量结构体。阻塞策略被初始化为BLK_POLICY_PRI,意思是当有多个任务阻塞在信号量上时,高优先级任务优先获得信号量。另外一种策略是BLK_POLICY_FIFO,即先阻塞的任务优先获得信号量。ksem_t被初始化的类型为RHINO_SEM_OBJ_TYPE。
函数krhino_sem_create()和krhino_sem_dyn_create()是创建信号量的对外接口,两者的差别是前者是静态创建(K_OBJ_STATIC_ALLOC),即ksem_t结构体的内存由外部传入。后者是动态创建(K_OBJ_DYN_ALLOC),该函数内将调用krhino_mm_alloc动态分配ksem_t结构体的内存,并通过入参sem把创建的结构体对象传给调用者,所以入参sem的类型是ksem_t **。
分析信号量创建函数的源码,我们可以得出信号量的第一个特点:创建信号量时可以指定初始信号量个数。
3、请求信号量函数krhino_sem_take
函数krhino_sem_take用于请求信号量。该函数原型为:
kstat_t krhino_sem_take(ksem_t *sem, tick_t ticks);
参数说明:
(1)sem 指向信号量结构体的指针;
(2)ticks 阻塞时间。如果当前没有信号量,任务最多阻塞ticks个系统时钟。两个特殊值是:(a)RHINO_NO_WAIT,若当前没有信号量则直接返回;(2) RHINO_WAIT_FOREVER,一直阻塞任务直到获得信号量为止。
在该函数内:
(1)NULL_PARA_CHK(sem);检查入参sem是否为NULL,如果为NULL直接退出函数;
(2)RHINO_CRITICAL_ENTER();用于进入临界区,因为多个任务可能同时访问sem结构体,所以需要临界区保护;
(3)调用cpu_cur_get()用来获得当前核号,这是为了支持多核架构。单核处理器上,这个函数返回0;
(4)TASK_CANCEL_CHK(sem)用来检查当前任务是否已经被终止。INTRPT_NESTED_LEVEL_CHK()用来检查是否在中断上下文。中断处理函数不允许被阻塞,所以不能调用krhino_sem_take();
(5)条件判断语句if (sem- blk_obj.obj_type != RHINO_SEM_OBJ_TYPE)用来检查sem类型是否为RHINO_SEM_OBJ_TYPE。这可以避免信号量结构体被删除后误用;
(6)现在正式进入到申请信号量的逻辑。如果sem- count大于0,说明当前有信号量,那么sem- count减1后返回即可,申请成功。当然,返回前要用语句RHINO_CRITICAL_EXIT();退出临界区;
(7)如果当前没有信号量,且等待时间为RHINO_NO_WAIT,那么直接返回,申请失败;
(8)剩下的情况是:当前没有信号量,调用者想等待一段时间,直到超时或者获得信号量。条件判断g_sched_lock[cur_cpu_num] 0u用来检查当前是否允许调度。由于后续代码将挂起当前任务,并调度到其他任务。因此若g_sched_lock[cur_cpu_num] 0u(不允许调度)那么就不能执行下面的代码了,直接退出临界区并返回;
(9)语句pend_to_blk_obj将置当前任务为非就绪态;
(10)语句RHINO_CRITICAL_EXIT_SCHED();将退出临界区并触发调度。这里将切换到其他任务,直到超时或者有任务释放信号量并唤醒当前任务;
(11)语句pend_state_end_proc用来做唤醒后的处理,主要是判断因为什么原因被唤醒:获得了信号量或超时到期或信号量被删除等。调用krhino_sem_take的地方可以根据返回值决定后续操作;
4、释放信号量函数sem_give
这个接口也被称为发送信号量。函数原型如下:
static kstat_t sem_give(ksem_t *sem, uint8_t opt_wake_all)
参数说明:
(1)sem 指向信号量结构体的指针;
(2)opt_wake_all 唤醒一个等待任务(WAKE_ONE_SEM)还是唤醒所有任务(WAKE_ALL_SEM)。
在该函数内:
(1)RHINO_CRITICAL_ENTER();用于进入临界区,因为多个任务可能同时访问sem结构体,所以需要临界区保护。
(2)条件判断语句if (sem- blk_obj.obj_type != RHINO_SEM_OBJ_TYPE)用来检查sem类型是否为RHINO_SEM_OBJ_TYPE。这可以避免信号量结构体被删除后误用。
(3)函数cpu_cur_get()用来获得当前核号,这是为了支持多核架构。单核处理器上,这个函数返回0。
(4)条件语句if (is_klist_empty(blk_list_head))用来判断阻塞队列是否为空,即当前没有任务阻塞在该信号量上。这种情况下,处理比较简单,信号量个数加1即可。
这里先做了一个错误检查,如果sem- count == (sem_count_t)-1说明系统出现问题了。如果没有问题,则信号量个数加1:
sem- count++;
如果sem- count大于sem- peak_count将更新sem- peak_count,以记录历史最高信号量个数。
(5)如果有任务正在等待该信号量,那么不用对sem- count加1了,直接唤醒任务消耗掉该信号量即可。这里又分为两种情况:如果opt_wake_all不为0(WAKE_ALL_SEM),则调用pend_task_wakeup唤醒所有任务。否则只唤醒一个任务。
对外接口函数krhino_sem_give/krhino_sem_give_all都调用sem_give释放信号量,两者的差别是,前者只唤醒一个阻塞任务,后者将唤醒所有阻塞任务。
分析信号量释放函数的源码,我们可以得出信号量的另外两个特点:
第二个特点:信号量请求和释放不需要成对出现,没有获得信号量也可以释放信号量。所以中断处理函数可以释放信号量,这常用在中断与任务之间的同步。
第三个特点:可以唤醒所有阻塞的在该信号量上的任务。
5、删除信号量函数krhino_sem_del/krhino_sem_dyn_del
krhino_sem_dyn_del用来删除krhino_sem_dyn_create创建的信号量。krhino_sem_del用来删除krhino_sem_create创建的信号量。这两组函数必须配套使用,否则将出现严重问题。
krhino_sem_dyn_del相比krhino_sem_del多了一步释放信号量结构体的操作,这里我们分析krhino_sem_dyn_del函数:
(1)函数入口处和前面的函数处理类似,主要是做一些正确性检查;
(2)sem- blk_obj.obj_type = RHINO_OBJ_TYPE_NONE设置结构体的类型为NONE。该语句的好处是,若信号量释放后被误用,能被检测出来。
(3)循环语句调用pend_task_rm函数唤醒所有阻塞在该信号量上的任务。如果没有这步操作,一旦信号量被删除,等待该信号量的任务将永远都不会被唤醒了。
(4)语句klist_rm( sem- sem_item);用于把信号量结构体从全局g_kobj_list.sem_head链表删除;
(5)最后退出临界区,并调用krhino_mm_free释放信号量结构体的内存空间。
分析信号量删除函数的源码,我们可以看到使用信号量的一个注意点:
若任务调用krhino_sem_take()被阻塞了,那么当删除信号量时将唤醒该任务,所以krhino_sem_take()返回时不代表一定获得了信号量,应判断返回值是否为RHINO_SUCCESS。
6、示例
在该示例中,任务2每隔1秒发送一个信号量。任务1请求信号量,收到信号量后打印一行输出。
/ 定义信号量结构体/
ksem_t sem_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 application_start(int argc, char *argv[])
{
/ 静态创建信号量,初始个数为0 /
krhino_sem_create( sem_test, "sem_test", 0);
/ 创建两个测试任务 /
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);
本文转自网络,原文链接:https://developer.aliyun.com/article/785220
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!
在Python语言中有如下3种方法: 成员方法 类方法(classmethod) 静态方法(staticm...
信息化2.0时代提出开展智慧教育创新发展行动。2019年2月,中共中央、国务院印发...
前提条件 请您在购买前确保已完成注册和充值。详细操作请参见 如何注册公有云管...
本文整理自直播《Hologres 数据导入/导出实践-王华峰(继儒)》 视频链接: https:/...
摘要 元旦期间 订单业务线 告知 推送系统 无法正常收发消息,作为推送系统维护者...
2021年3月24日,主题为《数据的世界,世界的数据》的星环科技2021春季新品发布会...
建站 什么 虚拟主机 够用?这要看搭建的是什么类型的网站。比如个人博客类型的网...
从 10.0.0 版开始,异步迭代器就出现在 Node 中了,在本文中,我们将讨论异步迭...
Docker生成新镜像版本的两种方式 There are two ways Docker can generate new m...
【51CTO.com快译】 数据可视化工具不断发展,提供更强大的功能,同时改善可访问...