对于多任务,甚至多核的操作系统,需要访问共同的系统资源。共享资源包括软件资源和硬件资源,软件共享资源主要是共享内存,包括共享变量、共享队列等等,硬件共享资源包括一些硬件设备的访问,例如:输入/输出设备。为了避免多个任务访问共享资源时相互影响甚至冲突,需要对共享资源进行保护,有下列几种处理方式:开关中断、信号量(semphore)、互斥量(mutex)。
开关中断:一般用于单核平台多任务之间的互斥,通过关闭任务的调度,从而达到单任务访问共享资源的目的。缺点是会影响中断响应时间。
信号量:多任务可以通过获取信号量来获取访问共享资源,可以配置信号量的数目,让多个任务同时获取信号量,当信号量无法获取时,相关任务会按照优先级排序等待信号量释放,并让出CPU资源;缺点是存在高低任务优先级反转的问题。
互斥量:任务也是通过获取mutex来获取访问共享资源的门禁,但是单次只有一个任务能获取到该互斥量。互斥量通过动态调整任务的优先级来解决高低优先级反转的问题。
本章节介绍AliOS Thngs上的信号量接口。
API 列表
aos_sem_new()
创建信号量对象
aos_sem_free()
删除信号量对象
aos_sem_wait()
请求一个信号量
aos_sem_signal()
释放一个信号量
aos_sem_is_valid()
判断信号量对象是否有效
aos_sem_signal_all()
释放信号量,并唤醒所有阻塞在该信号量上的任务
使用
添加该组件
信号量是AliOS Things 默认添加的组件,开发者无需再手动添加。
包含头文件
#include
使用示例
示例说明:当前任务创建一个信号量和子任务,并等待子任务释放信号量。
static aos_sem_t g_sem_taskexit_sync;
unsigned int stack_size = 1024;
int ret = -1;
....
static void task1(void *arg)
{
....
/*释放信号量*/
aos_sem_signal(&g_sem_taskexit_sync);
....
}
/*当前任务:创建信号量,信号量初始count为0*/
ret = aos_sem_new(&g_sem_taskexit_sync, 0);
if (ret != 0) {
printf("sem create failed\r\n");
...
}
....
/*判断信号量是否可用*/
ret = aos_sem_is_valid(&g_sem_taskexit_sync);
if (ret == 0) {
printf("sem is invalid\r\n");
...
}
/*创建新任务task1*/
ret = aos_task_new("task1", task1, NULL, stack_size);
if (ret != 0) {
printf("timer create failed\r\n");
...
}
....
/*获取信号量,由于初始值为0,这里获取不到信号量,当前任务进入睡眠并发生切换。
参数 AOS_WAIT_FOREVER 表示永久等待,知道获得信号量 */
aos_sem_wait(&g_sem_taskexit_sync, AOS_WAIT_FOREVER);
/*获取到信号量,当前任务继续执行下去*/
printf("task1 exit!\r\n");
....
/*删除信号量*/
aos_sem_free(&g_sem_taskexit_sync);
API 详情
aos_sem_new()
创建信号量对象。
函数原型
int aos_sem_new(aos_sem_t *sem, int count);
输入参数
sem
信号量句柄,需要用户定义一个aos_sem_t结构体变量
count
信号量初始个数
1
返回参数
0表示成功,其他值表示失败。具体的返回值见本文档返回参数定义小节。
调用示例
aos_sem_t sem_handle;
int status;
status = aos_sem_new(&sem_handle, 1);
aos_sem_free()
删除信号量对象。
函数原型
void aos_sem_free(aos_sem_t *sem);
输入参数
sem
信号量句柄
返回参数
无
调用示例
aos_sem_t sem_handle;
int status;
status = aos_sem_new(&sem_handle, 1);
……
aos_sem_free(&sem_handle);
aos_sem_signal()
释放一个信号量。
函数原型
void aos_sem_signal(aos_sem_t *sem);
输入参数
sem
信号量句柄
返回参数
无
调用示例
aos_sem_t sem_handle;
int status;
status = aos_sem_new(&sem_handle, 1);
……
aos_sem_signal(&sem_handle);
aos_sem_signal_all()
释放信号量,并唤醒所有阻塞在该信号量上的任务。
函数原型
void aos_sem_signal_all(aos_sem_t *sem);
输入参数
sem
信号量句柄
返回参数
无
调用示例
aos_sem_t sem_handle;
int status;
status = aos_sem_new(&sem_handle, 1);
……
aos_sem_signal_all(&sem_handle);
aos_sem_wait()
请求一个信号量,若获取不到且超时时间不为0,则任务将被阻塞。
函数原型
int aos_sem_wait(aos_sem_t *sem, unsigned int timeout);
输入参数
sem
信号量句柄
timeout
等待超时时间。0表示不超时,立即返回;AOS_WAIT_FOREVER表示永久等待;其他数值表示超时时间,单位ms
返回参数
0表示成功,其他值表示失败。具体的返回值见本文档返回参数定义小节。
调用示例
aos_sem_t sem_handle;
int status;
status = aos_sem_new(&sem_handle, 1);
……
status = aos_sem_wait(&sem_handle, AOS_WAIT_FOREVER);
aos_sem_is_valid()
判断信号量对象是否有效。
函数原型
int aos_sem_is_valid(aos_sem_t *sem);
输入参数
sem
信号量句柄
返回参数
1表示有效,0表示无效。
调用示例
extern aos_sem_t sem_handle;
int status;
/* 信号量句柄在别的文件定义,因此在使用前先判断一下是否有效 */
status = aos_sem_is_valid(&sem_handle);
if (status == 0) {
printf("sem is invalid\r\n");
...
}
其他
返回参数定义
返回值定义在core/rhino/include/k_err.h文件中。该文件为内部文件,出错时可根据返回值查阅该文件确认出错原因。
typedef enum
{
RHINO_SUCCESS = 0u,
RHINO_SYS_FATAL_ERR,
RHINO_SYS_SP_ERR,
RHINO_RUNNING,
RHINO_STOPPED,
RHINO_INV_PARAM,
RHINO_NULL_PTR,
RHINO_INV_ALIGN,
RHINO_KOBJ_TYPE_ERR,
RHINO_KOBJ_DEL_ERR,
RHINO_KOBJ_DOCKER_EXIST,
RHINO_KOBJ_BLK,
RHINO_KOBJ_SET_FULL,
RHINO_NOTIFY_FUNC_EXIST,
RHINO_MM_POOL_SIZE_ERR = 100u,
RHINO_MM_ALLOC_SIZE_ERR,
RHINO_MM_FREE_ADDR_ERR,
RHINO_MM_CORRUPT_ERR,
RHINO_DYN_MEM_PROC_ERR,
RHINO_NO_MEM,
RHINO_RINGBUF_FULL,
RHINO_RINGBUF_EMPTY,
RHINO_SCHED_DISABLE = 200u,
RHINO_SCHED_ALREADY_ENABLED,
RHINO_SCHED_LOCK_COUNT_OVF,
RHINO_INV_SCHED_WAY,
RHINO_TASK_INV_STACK_SIZE = 300u,
RHINO_TASK_NOT_SUSPENDED,
RHINO_TASK_DEL_NOT_ALLOWED,
RHINO_TASK_SUSPEND_NOT_ALLOWED,
RHINO_TASK_CANCELED,
RHINO_SUSPENDED_COUNT_OVF,
RHINO_BEYOND_MAX_PRI,
RHINO_PRI_CHG_NOT_ALLOWED,
RHINO_INV_TASK_STATE,
RHINO_IDLE_TASK_EXIST,
RHINO_NO_PEND_WAIT = 400u,
RHINO_BLK_ABORT,
RHINO_BLK_TIMEOUT,
RHINO_BLK_DEL,
RHINO_BLK_INV_STATE,
RHINO_BLK_POOL_SIZE_ERR,
RHINO_TIMER_STATE_INV = 500u,
RHINO_NO_THIS_EVENT_OPT = 600u,
RHINO_BUF_QUEUE_INV_SIZE = 700u,
RHINO_BUF_QUEUE_SIZE_ZERO,
RHINO_BUF_QUEUE_FULL,
RHINO_BUF_QUEUE_MSG_SIZE_OVERFLOW,
RHINO_QUEUE_FULL,
RHINO_QUEUE_NOT_FULL,
RHINO_SEM_OVF = 800u,
RHINO_SEM_TASK_WAITING,
RHINO_MUTEX_NOT_RELEASED_BY_OWNER = 900u,
RHINO_MUTEX_OWNER_NESTED,
RHINO_MUTEX_NESTED_OVF,
RHINO_NOT_CALLED_BY_INTRPT = 1000u,
RHINO_TRY_AGAIN,
RHINO_WORKQUEUE_EXIST = 1100u,
RHINO_WORKQUEUE_NOT_EXIST,
RHINO_WORKQUEUE_WORK_EXIST,
RHINO_WORKQUEUE_BUSY,
RHINO_WORKQUEUE_WORK_RUNNING,
RHINO_TASK_STACK_OVF = 1200u,
RHINO_INTRPT_STACK_OVF,
RHINO_STATE_ALIGN = INT_MAX /* keep enum 4 bytes at 32bit machine */
} kstat_t;
使用注意事项
1)在中断中禁止信号量获取检测 中断服务程序的执行不能被阻塞,因此不能在中断中调用请求信号量的接口。有些内核将这种判断处理交由上层软件进行判断和使用,AliOS Things的内核会在请求信号量时进行检测,如果是中断上下文,则直接返回失败。在中断上下文可以释放信号量。
2) 请求信号量时的非等待、永远等待、延时的区别 上层应用在获取信号量时,需要按照实际的需求来安排信号量获取策略。aos_sem_wait传入延时为0时,当获取不到信号量会立即返回并报失败;超时时间为AOS_WAIT_FOREVER时,会永久等待,直到获取到信号量,可能会造成该任务无法继续运行;其他值表示最大延迟的时间上限,达到上限时,即使未获取到信号量,任务也会被唤醒,并返回状态为超时。
3) 信号量优先级反转问题 优先级反转问题出现在高、低两个优先级任务同时使用信号量访问互斥资源时。当高优先级任务请求的信号量被低优先级任务已经占用时,高优先级任务会被阻塞。此时如果有一个中优先级任务,那么中优先级任务能抢占低优先级任务而得到CPU资源。这个时候出现了一种情况,由于资源被低优先级任务占用但由于低优先级任务得不到CPU资源而没有机会运行,最终导致高优先级任务得不到调度。
互斥信号量支持解决优先级反转问题,其方法是动态提高占用信号量的任务的运行优先级。