对于多任务,甚至多核的操作系统,需要访问共同的系统资源。共享资源包括软件资源和硬件资源,软件共享资源主要是共享内存,包括共享变量、共享队列等等,硬件共享资源包括一些硬件设备的访问,例如:输入/输出设备。为了避免多个任务访问共享资源时相互影响甚至冲突,需要对共享资源进行保护,有下列几种处理方式:开关中断、信号量(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 <aos/kernel.h>

使用示例

示例说明:当前任务创建一个信号量和子任务,并等待子任务释放信号量。

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 详情

信号量的应用层API说明请参考include/aos/kernel.h

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资源而没有机会运行,最终导致高优先级任务得不到调度。

互斥信号量支持解决优先级反转问题,其方法是动态提高占用信号量的任务的运行优先级。