rt-thread操作系统的IPC(Inter-Process Communication,进程间通信)包含有信号量,互斥锁,事件,邮箱,消息队列.
本文主要针对信号量.信号量是用来解决线程同步和互斥的通用工具,和互斥量类似,信号量也可用作资源互斥访问,但信号量没有所有者的概念,在应用上比互斥量更广泛。信号量比较简单,不能解决优先级翻转问题,但信号量是一种轻量级的对象,比互斥量小巧、灵活。因此在很多对互斥要求不严格的系统中(或者不会造成优先级翻转的情况下),经常使用信号量来管理互斥资源。
1 信号量控制块
-
-
-
- struct rt_semaphore
- {
- struct rt_ipc_object parent; //派生自IPC对象
-
- rt_uint16_t value; //信号量计数器
- };
- typedef struct rt_semaphore *rt_sem_t;
value为信号计数器,此信号量多次被释放时将会累加,在被获取时则将减1,当其值为0时,再请求获取的线程将会被挂起到挂起链表中。
parent为一rt_ipc_object即IPC对象,其定义如下:
-
-
-
- struct rt_ipc_object
- {
- struct rt_object parent; //可知其派生自内核对象
-
- rt_list_t suspend_thread; //线程挂起链表
- };
从rt_ipc_object的定义结构可知其派生自rt_object结构,即内核对象的定义(参考http://blog.csdn.net/flydream0/article/details/8568463),而其它IPC,如互斥锁,事件,邮箱,消息队列都是从rt_ipc_object派生。
另外,IPC对象还包含一挂起链表,用来保存因此IPC对象而挂起的线程.
2 信号量的创建与初始化
2.1 初始化
-
-
-
-
-
-
-
-
-
-
-
- rt_err_t rt_sem_init(rt_sem_t sem,
- const char *name,
- rt_uint32_t value,
- rt_uint8_t flag)
- {
- RT_ASSERT(sem != RT_NULL);
-
-
- rt_object_init(&(sem->parent.parent), RT_Object_Class_Semaphore, name);
-
-
- rt_ipc_object_init(&(sem->parent));
-
-
- sem->value = value;
-
-
- sem->parent.parent.flag = flag;
-
- return RT_EOK;
- }
其中rt_object_init已在之前介绍rt-thread的内核对象中相关文章中已有介绍(参见:
http://blog.csdn.net/flydream0/article/details/8568463
),rt_ipc_object_init函数见如下:
-
-
-
-
-
-
-
-
-
-
-
-
-
- rt_inline rt_err_t rt_ipc_object_init(struct rt_ipc_object *ipc)
- {
-
- rt_list_init(&(ipc->suspend_thread));
-
- return RT_EOK;
- }
初始化及创建信号量很简单,一个是静态初始化,一个是动态分配的然后再初始化,不做过多解释.
2.2 创建信号量
-
-
-
-
-
-
-
-
-
-
-
- rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag)
- {
- rt_sem_t sem;
-
- RT_DEBUG_NOT_IN_INTERRUPT;
-
-
- sem = (rt_sem_t)rt_object_allocate(RT_Object_Class_Semaphore, name);
- if (sem == RT_NULL)
- return sem;
-
-
- rt_ipc_object_init(&(sem->parent));
-
-
- sem->value = value;
-
-
- sem->parent.parent.flag = flag;
-
- return sem;
- }
3 脱离及删除信号量
3.1 脱离信号量
-
-
-
-
-
-
-
-
-
- rt_err_t rt_sem_detach(rt_sem_t sem)
- {
- RT_ASSERT(sem != RT_NULL);
-
-
- rt_ipc_list_resume_all(&(sem->parent.suspend_thread));
-
-
- rt_object_detach(&(sem->parent.parent));
-
- return RT_EOK;
- }
脱离信号量时被将挂起链表中的所有线程都唤醒,其中rt_ipc_list_resume_all函数如下:
-
-
-
-
-
-
-
-
- rt_inline rt_err_t rt_ipc_list_resume_all(rt_list_t *list)
- {
- struct rt_thread *thread;
- register rt_ubase_t temp;
-
-
- while (!rt_list_isempty(list))
- {
-
- temp = rt_hw_interrupt_disable();
-
-
- thread = rt_list_entry(list->next, struct rt_thread, tlist);
-
- thread->error = -RT_ERROR;
-
-
-
-
-
-
- rt_thread_resume(thread);
-
-
- rt_hw_interrupt_enable(temp);
- }
-
- return RT_EOK;
- }
需要注意地是,被脱离的信号量时唤醒的线程的error值将会被设置为-RT_ERROR,以此标志此线程是被异常唤醒,并不是正常获取到信号量而被唤醒,这在take函数中将会以线程的error值来进行判断.
3.2 删除线程
-
-
-
-
-
-
-
-
-
- rt_err_t rt_sem_delete(rt_sem_t sem)
- {
- RT_DEBUG_NOT_IN_INTERRUPT;
-
- RT_ASSERT(sem != RT_NULL);
-
-
- rt_ipc_list_resume_all(&(sem->parent.suspend_thread));
-
-
- rt_object_delete(&(sem->parent.parent));
-
- return RT_EOK;
- }
删除信号量与脱离信号量类似,说明路过。
4 获取信号量
4.1 等待信号量
-
-
-
-
-
-
-
-
-
- rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time)
- {
- register rt_base_t temp;
- struct rt_thread *thread;
-
- RT_ASSERT(sem != RT_NULL);
-
- RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(sem->parent.parent)));
-
-
- temp = rt_hw_interrupt_disable();
-
- RT_DEBUG_LOG(RT_DEBUG_IPC, ("thread %s take sem:%s, which value is: %d\n",
- rt_thread_self()->name,
- ((struct rt_object *)sem)->name,
- sem->value));
-
- if (sem->value > 0)
- {
-
- sem->value --;
-
-
- rt_hw_interrupt_enable(temp);
- }
- else
- {
-
- if (time == 0)
- {
- rt_hw_interrupt_enable(temp);
-
- return -RT_ETIMEOUT;
- }
- else
- {
-
- RT_DEBUG_NOT_IN_INTERRUPT;
-
-
-
- thread = rt_thread_self();
-
-
- thread->error = RT_EOK;
-
- RT_DEBUG_LOG(RT_DEBUG_IPC, ("sem take: suspend thread - %s\n",
- thread->name));
-
-
- rt_ipc_list_suspend(&(sem->parent.suspend_thread),
- thread,
- sem->parent.parent.flag);
-
-
- if (time > 0)
- {
- RT_DEBUG_LOG(RT_DEBUG_IPC, ("set thread:%s to timer list\n",
- thread->name));
-
-
- rt_timer_control(&(thread->thread_timer),
- RT_TIMER_CTRL_SET_TIME,
- &time);
- rt_timer_start(&(thread->thread_timer));
- }
-
-
- rt_hw_interrupt_enable(temp);
-
-
- rt_schedule();
-
- if (thread->error != RT_EOK)
- {
- return thread->error;
- }
- }
- }
-
- RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(sem->parent.parent)));
-
- return RT_EOK;
- }
获取信号量函数首先会判断当前是否有信号量(通过value值来判断),如果有则立即成功返回,如果没有,则接下来首先判断是否有时间参数,如果等待时间参数为0,则表示需要立即返回,则立即返回错误。如果等待时间参数大于0,则表示需要延时一段时间,在此延时期间,如何信号量到达,或者信号量被非法脱离,或一直没有等到,则通过判断线程的error值来判断当前是否已经成功获得信号值,因为如果成功获得信号量时,即在另一个线程release信号后,因此这一整个过程并没有修改线程的error值,因此,线程的error值一直保持原先的RT_EOK不变。若是线程一直没有等待到信号量的到达,即产生的定时器超时时(在take过程中会将设置一定时器,然后启动它,再挂起当前线程),在线程定时器的回调超时处理函数中,程序会将线程的error值修改为-RT_ETIMEOUT。另,如果之前讲解脱离线程时,如果在某一线程等待信号量期间,这个信号量被意外脱离了时,在脱离信号量的函数中(见3.1节),程序会将线程的error值修改为-RT_ERROR。
综上所述,程序可以通过线程的error值来对其是否真正获得信号量进行判定,即如果线程的error值一直保持原样即thread->error==RT_EOK时,则为已获取信号量,否则没有获取,要么超时(-RT_ETIMEOUT),要么非法脱离(-RT_ERROR)了。
另外,当当前线程没有等到信号量时,程序会调用rt_ipc_list_suspend让当前线程挂起,这个挂起是指将当前线程加入到信号量的挂起链表中,这里有一个flag参数,即sem->parent.parent.flag(在信号量初始化时设置,见2.1节),其值有两种RT_IPC_FLAG_FIFO,RT_IPC_FLAG_FIFO,前者表示按FIFO的方式放入挂起链表,后者是根据线程本身的优先级等级来决定放入到挂起链表的位置,由于每次释放一个信号量,只会从信号量挂起链表上唤醒第一个线程(见第5章),因此,挂起线程链表上的位置就决定了当信号到达时挂起的线程的唤醒顺序。
rt_ipc_list_suspend的源码如下:
-
-
-
-
-
-
-
-
-
-
-
- rt_inline rt_err_t rt_ipc_list_suspend(rt_list_t *list,
- struct rt_thread *thread,
- rt_uint8_t flag)
- {
-
- rt_thread_suspend(thread);
-
- switch (flag)
- {
- case RT_IPC_FLAG_FIFO:
- rt_list_insert_before(list, &(thread->tlist));
- break;
-
- case RT_IPC_FLAG_PRIO:
- {
- struct rt_list_node *n;
- struct rt_thread *sthread;
-
-
- for (n = list->next; n != list; n = n->next)
- {
- sthread = rt_list_entry(n, struct rt_thread, tlist);
-
-
- if (thread->current_priority < sthread->current_priority)
- {
-
- rt_list_insert_before(&(sthread->tlist), &(thread->tlist));
- break;
- }
- }
-
-
-
-
-
- if (n == list)
- rt_list_insert_before(list, &(thread->tlist));
- }
- break;
- }
-
- return RT_EOK;
- }
4.2 获取无等待信号量
-
-
-
-
-
-
-
- rt_err_t rt_sem_trytake(rt_sem_t sem)
- {
- return rt_sem_take(sem, 0);
- }
由此可见,rt_sem_trytake只是rt_sem_take函数的一种特例,时间参数为0而已.
5 释放信号量
-
-
-
-
-
-
-
-
- rt_err_t rt_sem_release(rt_sem_t sem)
- {
- register rt_base_t temp;
- register rt_bool_t need_schedule;
-
- RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(sem->parent.parent)));
-
- need_schedule = RT_FALSE;
-
-
- temp = rt_hw_interrupt_disable();
-
- RT_DEBUG_LOG(RT_DEBUG_IPC, ("thread %s releases sem:%s, which value is: %d\n",
- rt_thread_self()->name,
- ((struct rt_object *)sem)->name,
- sem->value));
-
- if (!rt_list_isempty(&sem->parent.suspend_thread))
- {
-
- rt_ipc_list_resume(&(sem->parent.suspend_thread));
- need_schedule = RT_TRUE;
- }
- else
- sem->value ++; //信号量计数器加1
-
-
- rt_hw_interrupt_enable(temp);
-
-
- if (need_schedule == RT_TRUE)
- rt_schedule();
-
- return RT_EOK;
- }
释放信号量只对将信号时的value值加1。
其中函数rt_ipc_list_resume只会唤醒信号量中第一个挂起的线程,其源码如下:
-
-
-
-
-
-
-
-
-
- rt_inline rt_err_t rt_ipc_list_resume(rt_list_t *list)
- {
- struct rt_thread *thread;
-
-
- thread = rt_list_entry(list->next, struct rt_thread, tlist);
-
- RT_DEBUG_LOG(RT_DEBUG_IPC, ("resume thread:%s\n", thread->name));
-
-
- rt_thread_resume(thread);
-
- return RT_EOK;
- }
这里需要注意地是,释放信号量的过程不会修改线程的error值,即error原持原值RT_EOK不变.
6 信号量控制
-
-
-
-
-
-
-
-
-
- rt_err_t rt_sem_control(rt_sem_t sem, rt_uint8_t cmd, void *arg)
- {
- rt_ubase_t level;
- RT_ASSERT(sem != RT_NULL);
-
- if (cmd == RT_IPC_CMD_RESET)
- {
- rt_uint32_t value;
-
-
- value = (rt_uint32_t)arg;
-
- level = rt_hw_interrupt_disable();
-
-
- rt_ipc_list_resume_all(&sem->parent.suspend_thread);
-
-
- sem->value = (rt_uint16_t)value;
-
-
- rt_hw_interrupt_enable(level);
-
- rt_schedule();
-
- return RT_EOK;
- }
-
- return -RT_ERROR;
- }
只支持重置信号量,此时若其存在挂起线程,则将其全部唤醒再次重新调度。此时在rt_ipc_list_resume_all函数中会将所有挂起的线程的error值设置为-RT_ERROR.