rt-thread操作系统的IPC(Inter-Process Communication,进程间通信)包含有信号量,互斥锁,事件,邮箱,消息队列.
本文主要针对信号量.信号量是用来解决线程同步和互斥的通用工具,和互斥量类似,信号量也可用作资源互斥访问,但信号量没有所有者的概念,在应用上比互斥量更广泛。信号量比较简单,不能解决优先级翻转问题,但信号量是一种轻量级的对象,比互斥量小巧、灵活。因此在很多对互斥要求不严格的系统中(或者不会造成优先级翻转的情况下),经常使用信号量来管理互斥资源。
/** * Semaphore structure */ struct rt_semaphore { struct rt_ipc_object parent; /**< inherit from ipc_object *///派生自IPC对象 rt_uint16_t value; /**< value of semaphore. *///信号量计数器 }; typedef struct rt_semaphore *rt_sem_t;
value为信号计数器,此信号量多次被释放时将会累加,在被获取时则将减1,当其值为0时,再请求获取的线程将会被挂起到挂起链表中。
parent为一rt_ipc_object即IPC对象,其定义如下:
/** * Base structure of IPC object */ struct rt_ipc_object { struct rt_object parent; /**< inherit from rt_object *///可知其派生自内核对象 rt_list_t suspend_thread; /**< threads pended on this resource *///线程挂起链表 };
从rt_ipc_object的定义结构可知其派生自rt_object结构,即内核对象的定义(参考http://blog.csdn.net/flydream0/article/details/8568463),而其它IPC,如互斥锁,事件,邮箱,消息队列都是从rt_ipc_object派生。
另外,IPC对象还包含一挂起链表,用来保存因此IPC对象而挂起的线程.
/** * This function will initialize a semaphore and put it under control of * resource management. * * @param sem the semaphore object * @param name the name of semaphore * @param value the init value of semaphore * @param flag the flag of semaphore * * @return the operation status, RT_EOK on successful */ 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); /* init object */ rt_object_init(&(sem->parent.parent), RT_Object_Class_Semaphore, name);//初始化信号量的内核对象 /* init ipc object */ rt_ipc_object_init(&(sem->parent));//初始化信号量的IPC对象 /* set init value */ sem->value = value;//设置信号量计数器的值 /* set parent */ sem->parent.parent.flag = flag;//设置信号量的内核对象的标志 return RT_EOK; }
/** * @addtogroup IPC */ /*@{*/ /** * This function will initialize an IPC object * * @param ipc the IPC object * * @return the operation status, RT_EOK on successful */ rt_inline rt_err_t rt_ipc_object_init(struct rt_ipc_object *ipc) { /* init ipc object */ rt_list_init(&(ipc->suspend_thread));//初始化线程挂起链表 return RT_EOK; }初始化及创建信号量很简单,一个是静态初始化,一个是动态分配的然后再初始化,不做过多解释.
/** * This function will create a semaphore from system resource * * @param name the name of semaphore * @param value the init value of semaphore * @param flag the flag of semaphore * * @return the created semaphore, RT_NULL on error happen * * @see rt_sem_init */ 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;//确保此函数不是在中断中使用 /* allocate object */ sem = (rt_sem_t)rt_object_allocate(RT_Object_Class_Semaphore, name);//动态分配内核对象 if (sem == RT_NULL) return sem; /* init ipc object */ rt_ipc_object_init(&(sem->parent));//初始化信号量的IPC对象 /* set init value */ sem->value = value;//初始化信号量的计数器值 /* set parent */ sem->parent.parent.flag = flag;//设置信号量的内核对象的标志 return sem; }
/** * This function will detach a semaphore from resource management * * @param sem the semaphore object * * @return the operation status, RT_EOK on successful * * @see rt_sem_delete */ rt_err_t rt_sem_detach(rt_sem_t sem) { RT_ASSERT(sem != RT_NULL); /* wakeup all suspend threads */ rt_ipc_list_resume_all(&(sem->parent.suspend_thread));//唤醒所有信号量内挂起的线程 /* detach semaphore object */ rt_object_detach(&(sem->parent.parent));//脱离信号量的内核对象 return RT_EOK; }
脱离信号量时被将挂起链表中的所有线程都唤醒,其中rt_ipc_list_resume_all函数如下:
/** * This function will resume all suspended threads in a list, including * suspend list of IPC object and private list of mailbox etc. * * @param list of the threads to resume * * @return the operation status, RT_EOK on successful */ rt_inline rt_err_t rt_ipc_list_resume_all(rt_list_t *list) { struct rt_thread *thread; register rt_ubase_t temp; /* wakeup all suspend threads */ while (!rt_list_isempty(list))//遍历线程挂起链表 { /* disable interrupt */ temp = rt_hw_interrupt_disable();//关中断 /* get next suspend thread */ thread = rt_list_entry(list->next, struct rt_thread, tlist);//获得线程 /* set error code to RT_ERROR */ thread->error = -RT_ERROR;//设置线程的错误码为-RT_ERROR /* * resume thread * In rt_thread_resume function, it will remove current thread from * suspend list */ rt_thread_resume(thread);//唤醒此线程 /* enable interrupt */ rt_hw_interrupt_enable(temp);//开中断 } return RT_EOK; }需要注意地是,被脱离的信号量时唤醒的线程的error值将会被设置为-RT_ERROR,以此标志此线程是被异常唤醒,并不是正常获取到信号量而被唤醒,这在take函数中将会以线程的error值来进行判断.
/** * This function will delete a semaphore object and release the memory * * @param sem the semaphore object * * @return the error code * * @see rt_sem_detach */ rt_err_t rt_sem_delete(rt_sem_t sem) { RT_DEBUG_NOT_IN_INTERRUPT;//确保此函数不是在中断中使用 RT_ASSERT(sem != RT_NULL); /* wakeup all suspend threads */ rt_ipc_list_resume_all(&(sem->parent.suspend_thread));//唤醒所有挂起的线程 /* delete semaphore object */ rt_object_delete(&(sem->parent.parent));//删除信号量内核对象 return RT_EOK; }删除信号量与脱离信号量类似,说明路过。
/** * This function will take a semaphore, if the semaphore is unavailable, the * thread shall wait for a specified time. * * @param sem the semaphore object * @param time the waiting time * * @return the error code */ 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))); /* disable interrupt */ 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)//如果此信号量的计数器的值大于0,说明有信号,则应该立即返回 { /* semaphore is available */ sem->value --;//则将信号量的计数器的值减1 /* enable interrupt */ rt_hw_interrupt_enable(temp);//开中断 } else//如果此信号量的计数器的值小于或等于0,说明此时还未有信号 { /* no waiting, return with timeout */ if (time == 0)//如果等待时间参数为0,则立即返回超时错误 { rt_hw_interrupt_enable(temp);//开中断 return -RT_ETIMEOUT;//返回超时错误 } else//等待信号 { /* current context checking */ RT_DEBUG_NOT_IN_INTERRUPT;//确保此时不在中断中使用 /* semaphore is unavailable, push to suspend list */ /* get current thread */ thread = rt_thread_self();//获取当前正在运行的线程 /* reset thread error number */ thread->error = RT_EOK;//设置当前线程的错误代码为RT_EOK,需要注意这里 RT_DEBUG_LOG(RT_DEBUG_IPC, ("sem take: suspend thread - %s\n", thread->name)); /* suspend thread */ rt_ipc_list_suspend(&(sem->parent.suspend_thread),//挂起当前线程到信号量中的断起线程链表 thread, sem->parent.parent.flag); /* has waiting time, start thread timer */ if (time > 0)//如果时间参数大于0 { RT_DEBUG_LOG(RT_DEBUG_IPC, ("set thread:%s to timer list\n", thread->name)); /* reset the timeout of thread timer and start it */ rt_timer_control(&(thread->thread_timer),//设置定时器 RT_TIMER_CTRL_SET_TIME, &time); rt_timer_start(&(thread->thread_timer));//启动定时器开始计时 } /* enable interrupt */ rt_hw_interrupt_enable(temp);//开中断 /* do schedule */ rt_schedule();//当前线程已挂起,需要重新调试线程 //rt_schedule之后再执行到这里,只有两种可能,一是当前线程被挂起后时间已到达,此时,定时器的超时回调处理函数会将此线程的err值设为-RT_ETIMEOU,见thread.c源文件中的rt_thread_timeout函数;另一种情况是,有信号量到来,当前线程被rt_sem_release函数唤醒,此时,此线程的err值将一直保持原样不变,因此可以下面可能通过判断线程的err值来判断当前线程是否已被接收到信号量 if (thread->error != RT_EOK)//如果当前线程的错误代码不为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)了。
/** * This function will try to take a semaphore and immediately return * * @param sem the semaphore object * * @return the error code */ rt_err_t rt_sem_trytake(rt_sem_t sem) { return rt_sem_take(sem, 0); }
/** * This function will release a semaphore, if there are threads suspended on * semaphore, it will be waked up. * * @param sem the semaphore object * * @return the error code */ 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;//默认情况下设置不需要重新调度标记 /* disable interrupt */ 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))//挂起线程不为空 { /* resume the suspended thread */ rt_ipc_list_resume(&(sem->parent.suspend_thread));//唤醒第一个挂起的线程 need_schedule = RT_TRUE;//需要重新调度 } else sem->value ++; /* increase value *///信号量计数器加1 /* enable interrupt */ rt_hw_interrupt_enable(temp);//开中断 /* resume a thread, re-schedule */ if (need_schedule == RT_TRUE)//如果需要重新调度线程,则重新调度 rt_schedule(); return RT_EOK; }
释放信号量只对将信号时的value值加1。
其中函数rt_ipc_list_resume只会唤醒信号量中第一个挂起的线程,其源码如下:
/** * This function will resume the first thread in the list of a IPC object: * - remove the thread from suspend queue of IPC object * - put the thread into system ready queue * * @param list the thread list * * @return the operation status, RT_EOK on successful */ rt_inline rt_err_t rt_ipc_list_resume(rt_list_t *list) { struct rt_thread *thread; /* get thread entry */ thread = rt_list_entry(list->next, struct rt_thread, tlist);//获取线程 RT_DEBUG_LOG(RT_DEBUG_IPC, ("resume thread:%s\n", thread->name)); /* resume it */ rt_thread_resume(thread);//唤醒此线程 return RT_EOK; }
这里需要注意地是,释放信号量的过程不会修改线程的error值,即error原持原值RT_EOK不变.
/** * This function can get or set some extra attributions of a semaphore object. * * @param sem the semaphore object * @param cmd the execution command * @param arg the execution argument * * @return the error code */ 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; /* get value */ value = (rt_uint32_t)arg; /* disable interrupt */ level = rt_hw_interrupt_disable();//关中断 /* resume all waiting thread */ rt_ipc_list_resume_all(&sem->parent.suspend_thread);//唤醒信号量上所有挂起的线程 /* set new value */ sem->value = (rt_uint16_t)value;//设置信号时的计数器值 /* enable interrupt */ rt_hw_interrupt_enable(level);//开中断 rt_schedule();//立即重新调试 return RT_EOK; } return -RT_ERROR; }
只支持重置信号量,此时若其存在挂起线程,则将其全部唤醒再次重新调度。此时在rt_ipc_list_resume_all函数中会将所有挂起的线程的error值设置为-RT_ERROR.