【STM32F4】【银杏科技ARM+FPGA】iCore3移植RT-Thread--内核之信号量
一、信号量的引入
以车辆进出停车场为例。例如停车场有五个车位,起初五个车位都为空,此时来了七辆车,门卫允许其中五辆进入,然后放下车拦,剩下的车则必须在入口处等待,此后来的车也只能在入口处等待。这时,有一辆车离开停车场,门卫得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。
在这个停车场系统中,车位是公共资源,每辆车就是一个线程,门卫起的就是信号量的作用。
二、信号量的描述
信号量是一个非负整数(车位数),所有通过它的线程/进程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。在信号量上我们定义两种操作: Wait(等待) 和 Release(释放)。当一个线程调用Wait操作时,它要么得到资源然后将信号量减一,要么一直等下去(指放入阻塞队列),直到信号量大于等于一时。Release(释放)实际上是在信号量上执行加操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为释放了由信号量守护的资源。信号量是用来解决线程同步和互斥的通用工具,但信号量比较简单、小巧灵活,不能解决优先级翻转问题。
在实际操作系统中,信号量常应用与两种场景:
- 从外设中断中释放信号量,在任务中获取信号量。这种场景在串口接收中最为常见。
- 任务与任务之间的同步,一个任务通过信号量通知另一个任务。例如生产者与消费者场景。
三、 信号量控制块
信号量控制块与线程控制相似,每个信号量都有自己的信号量控制块,信号量控制块中包含了信号量的所有信息:信号量的状态信息、使用情况等。
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红绿循环闪烁。