【STM32F4】【银杏科技ARM+FPGA】iCore3移植RT-Thread--内核之信号量

一、信号量的引入

  以车辆进出停车场为例。例如停车场有五个车位,起初五个车位都为空,此时来了七辆车,门卫允许其中五辆进入,然后放下车拦,剩下的车则必须在入口处等待,此后来的车也只能在入口处等待。这时,有一辆车离开停车场,门卫得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。

在这个停车场系统中,车位是公共资源,每辆车就是一个线程,门卫起的就是信号量的作用。

二、信号量的描述

  信号量是一个非负整数(车位数),所有通过它的线程/进程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。在信号量上我们定义两种操作: Wait(等待) 和 Release(释放)。当一个线程调用Wait操作时,它要么得到资源然后将信号量减一,要么一直等下去(指放入阻塞队列),直到信号量大于等于一时。Release(释放)实际上是在信号量上执行加操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为释放了由信号量守护的资源。信号量是用来解决线程同步和互斥的通用工具,但信号量比较简单、小巧灵活,不能解决优先级翻转问题。

  在实际操作系统中,信号量常应用与两种场景:

  1. 从外设中断中释放信号量,在任务中获取信号量。这种场景在串口接收中最为常见。
  2. 任务与任务之间的同步,一个任务通过信号量通知另一个任务。例如生产者与消费者场景。

 

三、 信号量控制块

  信号量控制块与线程控制相似,每个信号量都有自己的信号量控制块,信号量控制块中包含了信号量的所有信息:信号量的状态信息、使用情况等。

struct rt_semaphore
{
    struct rt_ipc_object parent;                        /**< 继承自 ipc_object 类*/

    rt_uint16_t          value;                         /**< 信号量的值,最大为65535 */
};

 

四、 信号量接口函数

1、创建动态信号量函数:当调用此函数时,系统完成对该控制块的初始化工作。信号量标志参数flag决定了多个线程等待的排队方式,当设定为 RT_IPC_FLAG_FIFO(先进先出)时,等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的信号量;当设定为RT_IPC_FLAG_PRIO(优先级等待)时,等待线程队列将按照优先级进行排队,优先级高的等待线程将先获得等待的信号量。

rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag);

(1)入口参数:

  name:信号量名称。

  value:信号量初始值。

  flag:信号量标志,它可以取如下数值:RT_IPC_FLAG_FIFO 或RT_IPC_FLAG_PRIO。

(2)返回值

  RT_NULL:创建失败。

  信号量的控制块指针:创建成功。

2、删除动态信号量函数:此函数将使系统删除动态信号量,如果此时有线程正在等待该信号量,那么删除操作会先唤醒这些线程(等待线程的返回值是- RT_ERROR,表明异常唤醒),然后再释放信号量的内存资源。

rt_err_t rt_sem_delete(rt_sem_t sem);

(1)入口参数:

  sem:创建的信号量对象。

(2)返回值:

  RT_EOK:删除成功。

3、创建静态信号量函数:创建静态信号量也就是初始化信号量。对于静态信号量(它的内存空间在编译时期就被编译器分配出来,放在读写数据段或未初始化数据段上)就不再需要使用 rt_sem_create 接口来创建它,只需在使用前对它进行初始化即可。

rt_err_t rt_sem_init(rt_sem_t    sem,

                             const char *name,

                             rt_uint32_t value,

                             rt_uint8_t  flag);

(1)入口参数:

 sem:信号量对象的句柄。

 name:信号量名称。

 value:信号量初始值。

 flag:信号量标志,它可以取如下数值:RT_IPC_FLAG_FIFO(先进先出) 或 RT_IPC_FLAG_PRIO(优先级等待)。

(2)返回值:

 RT_EOK:初始化成功。

4、删除静态信号量函数:即脱离信号量,就是让信号量对象从内核对象管理器中脱离。此时内核先唤醒所有挂在该信号量等待队列上的线程,然后将该信号量从内核对象管理器中脱离,原来挂起在信号量上的等待线程将获得-RT_ERROR 的返回值。

rt_err_t rt_sem_detach(rt_sem_t sem);

(1)入口参数:

 sem:信号量对象的句柄。

(2)返回值:

 RT_EOK:脱离成功。

5、获取信号量函数:线程通过此函数获取信号量,当信号量值大于零时,线程将获得信号量,并且相应的信号量值会减 1。如果信号量的值等于零,此时当前信号量资源不可用,申请该信号量的线程将根据 time 参数的情况选择直接返回、或挂起等待一段时间、或永久等待,直到其他线程或中断释放该信号量。如果在参数 time 指定的时间内依然得不到信号量,线程将超时返回,返回值是-RT_ETIMEOUT。

rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time);

(1)入口参数

 sem:信号量对象的句柄。

 time:指定的等待时间,单位是操作系统时钟节拍(OS Tick)。

(2)返回值

 RT_EOK:成功获得信号量。

 RT_ETIMEOUT:超时依然未获得信号量。

 RT_ERROR:其他错误。

6、无等待获取信号量:当用户不想在申请的信号量上挂起线程进行等待时,可以使用无等待方式获取信号量,它的作用是和rt_sem_take(sem, 0) 一样的,即当线程申请的信号量资源实例不可用的时候,它不会等待在该信号量上,而是直接返回RT_ETIMEOUT。

rt_err_t rt_sem_trytake(rt_sem_t sem);

(1)入口参数:

  sem:信号量对象的句柄。

(2)返回值:

 RT_EOK:成功获得信号量。

 RT_ETIMEOUT:获取失败。

7、释放信号量函数:释放信号量可以唤醒挂起在该信号量上的线程。当信号量的值等于零时,并且有线程等待这个信号量时,释放信号量将唤醒等待在该信号量线程队列中的第一个线程,由它获取信号量,同时将把信号量的值加 1。

rt_err_t rt_sem_release(rt_sem_t sem);

(1)入口参数:

 sem:信号量对象的句柄。

(2)返回值:

 RT_EOK:成功释放信号量。

五、 信号量实例

下面通过RT-Thread的信号量来完成一个简单的应用:ARM_LED交替闪烁。

  信号量是进程间通信的媒介,在此定义两个线程thread1和thread2。Thread1首先获取一个信号量,然后点亮LED0,延迟500ms过后熄灭LED0,随即释放一个信号量。Thread2以RT_WAITING_FOREVER的方式一直保持获取信号量,当Thread1释放信号量之后,Thread2获取到一个信号量开始运行,点亮LED1,延迟500ms过后熄灭,随即释放一个信号量。

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>

#define LED0_PIN    GET_PIN(I, 5)
#define LED1_PIN    GET_PIN(I, 6)
#define LED2_PIN    GET_PIN(I, 7)

static rt_sem_t dynamic_sem = RT_NULL;

static rt_thread_t tid1,tid2;

void semaphore_led_init(void)
{
    rt_pin_mode(LED0_PIN,PIN_MODE_OUTPUT);
    rt_pin_mode(LED1_PIN,PIN_MODE_OUTPUT);
}

void rt_thread_entry1(void *parameter)
{
    rt_err_t result = RT_NULL;
    while(1)
    {    
        result = rt_sem_take(dynamic_sem,RT_WAITING_FOREVER); //获取信号量,模式为一直等待
        if(result != RT_EOK)
        {
            return;
        }
        rt_kprintf("thread1 take 1 sem\n");
        rt_pin_write(LED0_PIN,0);
        rt_kprintf("led0-on in thread1 \n");
        rt_thread_delay(RT_TICK_PER_SECOND/2);       //延时500ms;
        rt_pin_write(LED0_PIN,1);
        rt_kprintf("led0-off in thread1 \n");
        rt_kprintf("thread1 release 1 sem\n");
        rt_sem_release(dynamic_sem);                //释放信号量
    }
}
 
void rt_thread_entry2(void *parameter)
{
    rt_err_t result = RT_NULL;
    while(1)
    {
        result = rt_sem_take(dynamic_sem,RT_WAITING_FOREVER);
        if(result != RT_EOK)
        {
            return;
        }
        rt_kprintf("thread2 take 1 sem\n");
        rt_pin_write(LED1_PIN,0);
        rt_kprintf("led1-on in thread2 \n");
        rt_thread_delay(RT_TICK_PER_SECOND/2);
        rt_pin_write(LED1_PIN,1);
        rt_kprintf("led1-off in thread2 \n");
        rt_kprintf("thread2 release 1 sem\n");
        rt_sem_release(dynamic_sem);
    }
}

int semaphore_led_sample_init(void *parameter)
{
    semaphore_led_init();
    
    dynamic_sem = rt_sem_create("dsem",               //信号量名字
                                1,                    //信号量初始值
                                RT_IPC_FLAG_FIFO);    //先进先出
    if(dynamic_sem == RT_NULL)
    {
        rt_kprintf("Failed to create dynamic semaphore! \n");
        return 1;
    }
    
    tid1 = rt_thread_create("thread1",rt_thread_entry1,RT_NULL,512,5,10);
    if(tid1 == RT_NULL)
    {
        rt_kprintf("Failed to create thread1\n");
        return 1;
    }
    rt_thread_startup(tid1);            //线程1创建成功,则启动线程
    
    tid2 = rt_thread_create("thread2",rt_thread_entry2,RT_NULL,512,6,10);
    if(tid2 == RT_NULL)
    {
        rt_kprintf("Failed to create thread2\n");
        return 1;
    }
    rt_thread_startup(tid2);           //线程2创建成功,则启动线程
    return 0;
}
 
#if defined (RT_SAMPLES_AUTORUN) && defined(RT_USING_COMPONENTS_INIT)
    INIT_APP_EXPORT(semaphore_led_sample_init);
#endif
 
MSH_CMD_EXPORT(semaphore_led_sample_init, semaphore led sample);

 六、实验现象

当线程1得到一个信号量是,led0闪烁一下释放信号量,由线程2得到信号量,led1闪烁一下释放信号量,再由线程1得到信号量,依次往复,ARM_LED红绿循环闪烁。

posted @ 2020-06-04 09:44  XiaomaGee  阅读(274)  评论(0编辑  收藏  举报