在多线程实时系统中,一项工作的完成往往可以通过多个线程协调的方式共同来完成,而通过这三种方式:信号量、互斥量、事件集可以保证多个线程之间同步。
线程的同步方式有很多种,其核心思想都是:在访问临界区的时候只允许一个 (或一类) 线程运行。进入 / 退出临界区的方式有很多种:
1)调用 rt_hw_interrupt_disable() 进入临界区,调用 rt_hw_interrupt_enable() 退出临界区;详见《中断管理》的全局中断开关内容。
2)调用 rt_enter_critical() 进入临界区,调用 rt_exit_critical() 退出临界区。
线程同步是指多个线程通过特定的机制(如互斥量,事件对象,临界区)来控制线程之间的执行顺序
以生活中的停车场为例来理解信号量的概念:
①当停车场空的时候,停车场的管理员发现有很多空车位,此时会让外面的车陆续进入停车场获得停车位;
②当停车场的车位满的时候,管理员发现已经没有空车位,将禁止外面的车进入停车场,车辆在外排队等候;
③当停车场内有车离开时,管理员发现有空的车位让出,允许外面的车进入停车场;待空车位填满后,又禁止外部车辆进入。
在此例子中,管理员就相当于信号量,管理员手中空车位的个数就是信号量的值(非负数,动态变化);停车位相当于公共资源(临界区),车辆相当于线程。车辆通过获得管理员的允许取得停车位,就类似于线程通过获得信号量访问公共资源。
信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。
信号量控制块是操作系统用于管理信号量的一个数据结构
struct rt_semaphore
{
struct rt_ipc_object parent; //继承ipc_object结构体{rt_object(name、type、flag); suspend_thread}
rt_uint16_t value; //信号量的数值(例如空车位数量)
};
typedef struct rt_semaphore *rt_sem_t;
第一个parent rt_ipc_object
{struct rt_object parent;
rt_list_t suspend_thread;
}
第二个parent rt_object
{
char name[RT_NAME_MAX];
rt_uint8_t type;
rt_uint8_t flag;
}
Kernel中的 ipc.c
函数顺序按照ipc.c中顺序(内核代码顺序是按照创作顺序书写的)
#ifdef RT_USING_SEMAPHORE
信号量的开端,在rtconfig.h中是否宏定义了,才可使用信号量函数(一般都是定义的)
初始化(多是对系统静态句柄操作)
rt_err_t rt_sem_init(rt_sem_t sem,//信号量对象的句柄(停车场地址)
const char *name,//信号量名称(停车场名字)
rt_uint32_t value,//信号量初始值(停车场车位数量)
rt_uint8_t flag)//信号量标志等待线程队列RT_IPC_FLAG_FIFO(按顺序) 或 RT_IPC_FLAG_PRIO(按优先级)(排队顺序还是优先级顺序进出)
{
RT_ASSERT(sem != RT_NULL);//信号量对象的句柄是否存在
/* init object */
rt_object_init(&(sem->parent.parent), RT_Object_Class_Semaphore, name);
//sem->parent.parent的初始化(见1.2信号量控制块)
/* init ipc object */
rt_ipc_object_init(&(sem->parent));
//sem->parent的初始化(见1.2信号量控制块)
/* set init value */
sem->value = value;//赋值
/* set parent */
sem->parent.parent.flag = flag;//设置标志位RT_IPC_FLAG_FIFO(按顺序) 或 RT_IPC_FLAG_PRIO(按优先级)
return RT_EOK;
}
RTM_EXPORT(rt_sem_init);
脱离(内存继续存在,所以是脱离-内存还存在)
有初始化必有脱离
rt_err_t rt_sem_detach(rt_sem_t sem)
{
/* parameter check */
RT_ASSERT(sem != RT_NULL);//信号量对象的句柄是否存在
RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);//rt_object名字是否为信号量并返回对象类型
RT_ASSERT(rt_object_is_systemobject(&sem->parent.parent));
//判断对象是否为系统对象
/* 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;
}
RTM_EXPORT(rt_sem_detach);
创建
和初始化差别:没有已存在的信号量句柄,要自己创建,要自己给内存
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));
//sem->parent的初始化(见1.2信号量控制块)
/* set init value */
sem->value = value;//赋值
/* set parent */
sem->parent.parent.flag = flag;//设置状态
return sem;
}
RTM_EXPORT(rt_sem_create);
删除
和脱离差别:要自己释放内存
rt_err_t rt_sem_delete(rt_sem_t sem)
{
RT_DEBUG_NOT_IN_INTERRUPT;
/* parameter check */
RT_ASSERT(sem != RT_NULL);//信号量对象的句柄是否存在
RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);
//rt_object名字是否为信号量并返回对象类型
RT_ASSERT(rt_object_is_systemobject(&sem->parent.parent) == RT_FALSE);
//判断对象是否为系统对象
/* 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;
}
RTM_EXPORT(rt_sem_delete);
#endif
申请信号量,信号量无余值则定时等待,不等待返回错误
rt_err_t rt_sem_take(rt_sem_t sem, //信号量对象的句柄(停车场地址)
rt_int32_t time)//指定的等待时间(等待时间)
{
register rt_base_t temp;//中断标志
struct rt_thread *thread;//线程指针
/* parameter check */
RT_ASSERT(sem != RT_NULL);//信号量对象的句柄是否存在
RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);
//rt_object名字是否为信号量并返回对象类型
RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(sem->parent.parent)));
/* disable interrupt */
temp = rt_hw_interrupt_disable();//关中断1
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)//有车位
{
/* semaphore is available */
sem->value --;//车位减一
/* enable interrupt */
rt_hw_interrupt_enable(temp);//开中断1
}
else
{
/* no waiting, return with timeout */
if (time == 0)//(重点)这是定时器到了(再次申请车位),没等到车位,不等了,返回错误
{
rt_hw_interrupt_enable(temp);//开中断1
return -RT_ETIMEOUT;//超时依然未获得信号量
}
else
{
/* current context checking */
RT_DEBUG_IN_THREAD_CONTEXT;
/* semaphore is unavailable, push to suspend list */
/* get current thread */
thread = rt_thread_self();//读取当前线程
/* reset thread error number */
thread->error = RT_EOK;//设置线程错误码为ok
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)//设置的定时器值为正
{
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);//开中断1
/* do schedule */
rt_schedule();//运行调度器
if (thread->error != RT_EOK)
{
return thread->error;
}
}
}
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(sem->parent.parent)));
return RT_EOK;
}
RTM_EXPORT(rt_sem_take);
无等待获取信号量
time为0的获取函数
rt_err_t rt_sem_trytake(rt_sem_t sem)
{
return rt_sem_take(sem, 0);
}
RTM_EXPORT(rt_sem_trytake);
释放信号量可以唤醒挂起在该信号量上的线程
1.有挂起线程:唤醒线程
2.无挂起线程:信号量value加一
rt_err_t rt_sem_release(rt_sem_t sem)
{
register rt_base_t temp;//中断标志位
register rt_bool_t need_schedule;//调度标志位
/* parameter check */
RT_ASSERT(sem != RT_NULL);//信号量对象的句柄是否存在
RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore);
//rt_object名字是否为信号量并返回对象类型
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(sem->parent.parent)));
need_schedule = RT_FALSE;
/* disable interrupt */
temp = rt_hw_interrupt_disable();//关中断1
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))//有挂起线程,唤醒挂起线程,调度标志位为true
{
/* resume the suspended thread */
rt_ipc_list_resume(&(sem->parent.suspend_thread));
need_schedule = RT_TRUE;
}
else
sem->value ++; /* increase value */
/* enable interrupt */
rt_hw_interrupt_enable(temp);//开中断
/* resume a thread, re-schedule */
if (need_schedule == RT_TRUE)//如果唤醒了线程,调度标志位为true,就启动调度器
rt_schedule();
return RT_EOK;
}
RTM_EXPORT(rt_sem_release);
该例程创建了一个动态信号量,初始化两个线程,一个线程发送信号量,一个线程接收到信号量后,执行相应的操作。
#include
#define THREAD_PRIORITY 25
#define THREAD_TIMESLICE 5
/* 指向信号量的指针 */
static rt_sem_t dynamic_sem = RT_NULL;
ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void rt_thread1_entry(void *parameter)
{
static rt_uint8_t count = 0;
while(1)
{
if(count <= 100)
{
count++;
}
else
return;
/* count 每计数 10 次,就释放一次信号量 */
if(0 == (count % 10))
{
rt_kprintf("t1 release a dynamic semaphore.\n");
rt_sem_release(dynamic_sem);
}
}
}
ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
static void rt_thread2_entry(void *parameter)
{
static rt_err_t result;
static rt_uint8_t number = 0;
while(1)
{
/* 永久方式等待信号量,获取到信号量,则执行 number 自加的操作 */
result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);
if (result != RT_EOK)
{
rt_kprintf("t2 take a dynamic semaphore, failed.\n");
rt_sem_delete(dynamic_sem);
return;
}
else
{
number++;
rt_kprintf("t2 take a dynamic semaphore. number = %d\n" ,number);
}
}
}
/* 信号量示例的初始化 */
int semaphore_sample(void)
{
/* 创建一个动态信号量,初始值是 0 */
dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_PRIO);
if (dynamic_sem == RT_NULL)
{
rt_kprintf("create dynamic semaphore failed.\n");
return -1;
}
else
{
rt_kprintf("create done. dynamic semaphore value = 0.\n");
}
rt_thread_init(&thread1,
"thread1",
rt_thread1_entry,
RT_NULL,
&thread1_stack[0],
sizeof(thread1_stack),
THREAD_PRIORITY, THREAD_TIMESLICE);
rt_thread_startup(&thread1);
rt_thread_init(&thread2,
"thread2",
rt_thread2_entry,
RT_NULL,
&thread2_stack[0],
sizeof(thread2_stack),
THREAD_PRIORITY-1, THREAD_TIMESLICE);
rt_thread_startup(&thread2);
return 0;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(semaphore_sample, semaphore sample);
结果
\ | /
- RT - Thread Operating System
/ | \ 3.1.0 build Aug 27 2018
2006 - 2018 Copyright by rt-thread team
msh >semaphore_sample
create done. dynamic semaphore value = 0.
msh >t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 1
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 2
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 3
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 4
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 5
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 6
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 7
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 8
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 9
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 10
如上面运行结果:线程 1 在 count 计数为 10 的倍数时(count 计数为 100 之后线程退出),发送一个信号量,线程 2 在接收信号量后,对 number 进行加 1 操作。
信号量的另一个应用例程如下所示,本例程将使用 2 个线程、3 个信号量实现生产者与消费者的例子。其中:
3 个信号量分别为:①lock:信号量锁的作用,因为 2 个线程都会对同一个数组 array 进行操作,所以该数组是一个共享资源,锁用来保护这个共享资源。②empty:空位个数,初始化为 5 个空位。③full:满位个数,初始化为 0 个满位。
2 个线程分别为:①生产者线程:获取到空位后,产生一个数字,循环放入数组中,然后释放一个满位。②消费者线程:获取到满位后,读取数组内容并相加,然后释放一个空位。
本例子引用于官网文档