目录
1、信号量
1.1、信号量机制
信号量值使用 16位的无符号整数表示。
1.2、信号量的使用场合
信号量是一种非常灵活的同步方式,可以运用在多种场合中。形成锁、同步、资源计数等关系,也能方便的用于线程与线程、中断与线程间的同步中。
1.2.1、线程同步
线程同步是信号量最简单的一类应用。例如,使用信号量进行两个线程之间的同步,信号量的值初始化成 0,表示具备 0 个信号量资源实例;而尝试获得该信号量的线程,将直接在这个信号量上进行等待。
当持有信号量的线程完成它处理的工作时,释放这个信号量,可以把等待在这个信号量上的线程唤醒,让它执行下一部分工作。这类场合也可以看成把信号量用于工作完成标志:持有信号量的线程完成它自己的工作,然后通知等待该信号量的线程继续下一部分工作。
1.2.2、锁
锁,单一的锁常应用于多个线程间对同一共享资源(即临界区)的访问。信号量在作为锁来使用时,通常应将信号量资源实例初始化成 1,代表系统默认有一个资源可用,因为信号量的值始终在 1 和 0 之间变动,所以这类锁也叫做二值信号量。
注:二值信号量不适合用作锁,因为存在优先级反转的问题。
1.2.3、中断与线程的同步
信号量也能够方便地应用于中断与线程间的同步,例如一个中断触发,中断服务例程需要通知线程进行相应的数据处理。这个时候可以设置信号量的初始值是 0,线程在试图持有这个信号量时,由于信号量的初始值是 0,线程直接在这个信号量上挂起直到信号量被释放。当中断触发时,先进行与硬件相关的动作,例如从硬件的 I/O 口中读取相应的数据,并确认中断以清除中断源,而后释放一个信号量来唤醒相应的线程以做后续的数据处理。
注:中断与线程间的互斥不能采用信号量(锁)的方式,而应采用开关中断的方式。
1.2.4、资源计数
信号量也可以认为是一个递增或递减的计数器,需要注意的是信号量的值非负。例如:初始化一个信号量的值为 5,则这个信号量可最大连续减少 5 次,直到计数器减为 0。资源计数适合于线程间工作处理速度不匹配的场合,这个时候信号量可以做为前一线程工作完成个数的计数,而当调度到后一线程时,它也可以以一种连续的方式一次处理多个事件。例如,生产者与消费者问题中,生产者可以对信号量进行多次释放,而后消费者被调度到时能够一次处理多个信号量资源。
注:一般资源计数类型多是混合方式的线程间同步,因为对于单个的资源处理依然存在线程的多重访问,这就需要对一个单独的资源进行访问、处理,并进行锁方式的互斥操作。
1.3、信号量控制块
信号量控制块是操作系统用于管理信号量的一个数据结构,由结构体 structrt_semaphore 表示。
struct rt_semaphore
{
struct rt_ipc_object parent; /* ipc对象 */
rt_uint16_t value; /* 信号量值 */
rt_uint16_t reserved; /* 保留字段 */
};
typedef struct rt_semaphore *rt_sem_t;
1.4、函数
1.4.1、rt_sem_init()函数
此函数将初始化一个信号量,并将其交由对象容器管理。
1)初始化信号量对象
2)初始化IPC信号
3)设置信号量值和标志(RT_IPC_FLAG_FIFO/RT_IPC_FLAG_PRIO)
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); //断言sem != RT_NULL
RT_ASSERT(value < 0x10000U); //断言value < 0x10000U(65536)
rt_object_init(&(sem->parent.parent), RT_Object_Class_Semaphore, name); //初始化信号量对象
rt_ipc_object_init(&(sem->parent)); //初始化ipc对象
sem->value = (rt_uint16_t)value; //设置初始值
sem->parent.parent.flag = flag; //设置信号量标志
return RT_EOK; //返回RT_EOK
}
1.4.2、rt_sem_create()函数
此函数将从内存中分配一个信号量。
1)分配信号量对象
2)初始化IPC信号
3)设置信号量值和标志(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)
{
rt_sem_t sem;
RT_DEBUG_NOT_IN_INTERRUPT; //断言不是在ISR中调用
RT_ASSERT(value < 0x10000U); //断言value < 0x10000U(65536)
sem = (rt_sem_t)rt_object_allocate(RT_Object_Class_Semaphore, name); //分配信号量对象
if (sem == RT_NULL) //sem等于RT_NULL
return sem; //返回RT_NULL
rt_ipc_object_init(&(sem->parent)); //初始化信号量ipc对象
sem->value = value; //设置信号量初始值
sem->parent.parent.flag = flag; //设置信号量的标志
return sem; //返回信号量
}
1.4.3、rt_sem_detach()函数
此函数用于卸载信号量。
1)唤醒所有等待此信号量的线程
2)脱离信号量对象
rt_err_t rt_sem_detach(rt_sem_t sem)
{
/* 参数检查 */
RT_ASSERT(sem != RT_NULL); //断言sem != RT_NULL
RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore); //断言对象是信号量
RT_ASSERT(rt_object_is_systemobject(&sem->parent.parent)); //断言信号量对象是静态的
rt_ipc_list_resume_all(&(sem->parent.suspend_thread)); //唤醒等待此信号量的线程
rt_object_detach(&(sem->parent.parent)); //脱离信号量对象
return RT_EOK; //返回RT_EOK
}
1.4.4、rt_sem_delete()函数
此函数将删除一个信号量对象并释放内存。
1)唤醒所有等待此信号量的线程
2)删除信号量对象
rt_err_t rt_sem_delete(rt_sem_t sem)
{
RT_DEBUG_NOT_IN_INTERRUPT; //断言不在ISR中调用
/* 参数检查 */
RT_ASSERT(sem != RT_NULL); //断言sem != RT_NULL
RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore); //断言对象是信号量
RT_ASSERT(rt_object_is_systemobject(&sem->parent.parent) == RT_FALSE); //断言信号量对象不是静态的
rt_ipc_list_resume_all(&(sem->parent.suspend_thread)); //唤醒所有等待此信号量的线程
rt_object_delete(&(sem->parent.parent)); //删除信号量对象
return RT_EOK; //返回RT_EOK
}
1.4.5、rt_sem_take()函数
这个函数将接受一个信号量,如果这个信号量不可用,线程将等待指定的时间。
1)调用钩子回调函数rt_object_trytake_hook
2)信号量大于0,则信号量减1后直接返回
3)非阻塞调用,则直接返回-RT_ETIMEOUT
4)获取当前线程,并将线程的错误码设置为RT_EOK
5)挂起线程
6)设置线程定时器(线程超时函数会将线程从挂起链表移除,并将线程加入到就绪链表中)
7)调度
8)调用钩子回调函数rt_object_take_hook
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); //断言sem != RT_NULL
RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore); //断言对象是信号量
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) //信号量值大于0
{
sem->value --; //信号量值减1
rt_hw_interrupt_enable(temp); //开中断
}
else
{
if (time == 0) //非阻塞调用
{
rt_hw_interrupt_enable(temp); //开中断
return -RT_ETIMEOUT; //返回-RT_ETIMEOUT
}
else
{
RT_DEBUG_IN_THREAD_CONTEXT; //当前上下文检查
thread = rt_thread_self(); //获取当前线程
thread->error = RT_EOK; //将线程的错误码设置为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) //线程错误码不等于RT_EOK
{
return thread->error; //返回线程错误码
}
}
}
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(sem->parent.parent))); //调用回调函数
return RT_EOK; //返回RT_EOK
}
1.4.6、rt_sem_trytake()函数
此函数将尝试获取一个信号量并立即返回。
rt_err_t rt_sem_trytake(rt_sem_t sem)
{
return rt_sem_take(sem, 0); //超时时间参数为0
}
1.4.7、rt_sem_release()函数
此函数将释放一个信号量,如果有线程挂起这个信号量,它将被唤醒。
1)调用钩子回调函数rt_object_put_hook
2)如果挂起链表非空,则唤醒挂起链表中第一个线程
3)如果挂起链表为空,信号量加1
4)如果需要进行调度,则进行调度
rt_err_t rt_sem_release(rt_sem_t sem)
{
register rt_base_t temp;
register rt_bool_t need_schedule;
/* 参数检查 */
RT_ASSERT(sem != RT_NULL); //断言sem != RT_NULL
RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore); //断言对象是信号量
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(sem->parent.parent))); //调用回调函数
need_schedule = RT_FALSE; //设置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; //返回RT_EOK
}
1.4.8、rt_sem_control()函数
此函数可以获取或设置信号量对象的一些额外属性,目前只有重置信号量这一个功能。
rt_err_t rt_sem_control(rt_sem_t sem, int cmd, void *arg)
{
rt_ubase_t level;
/* 参数检查 */
RT_ASSERT(sem != RT_NULL); //断言sem != RT_NULL
RT_ASSERT(rt_object_get_type(&sem->parent.parent) == RT_Object_Class_Semaphore); //断言对象是信号量
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; //返回RT_EOK
}
return -RT_ERROR; //返回-RT_ERROR
}
2、互斥量
2.1、互斥量机制
互斥量又叫相互排斥的信号量,是一种特殊的二值信号量。可以解决二值信号量存在的优先级反转问题。
2.1.1、优先级反转
当一个高优先级线程试图通过信号量机制访问共享资源时,如果该信号量已被一低优先级线程持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证。
如下图所示:有优先级为 A、 B 和 C 的三个线程,优先级 A> B > C。线程A, B 处于挂起状态,等待某一事件触发,线程 C 正在运行,此时线程 C 开始使用某一共享资源 M。在使用过程中,线程 A 等待的事件到来,线程 A 转为就绪态,因为它比线程 C 优先级高,所以立即执行。但是当线程 A 要使用共享资源 M 时,由于其正在被线程 C 使用,因此线程 A 被挂起切换到线程 C 运行。如果此时线程 B 等待的事件到来,则线程 B 转为就绪态。由于线程 B 的优先级比线程 C 高,因此线程 B 开始运行,直到其运行完毕,线程 C 才开始运行。只有当线程 C 释放共享资源 M 后,线程 A 才得以执行。在这种情况下,优先级发生了翻转:线程 B 先于线程 A 运行。这样便不能保证高优先级线程的响应时间。
2.2、优先级继承算法
在RT-Thread操作系统中,互斥量可以解决优先级翻转问题,实现的是优先级继承算法。优先级继承是通过在线程 A 尝试获取共享资源而被挂起的期间内,将线程 C 的优先级提升到线程 A 的优先级别,从而解决优先级翻转引起的问题。这样能够防止 C(间接地防止 A)被 B 抢占,如下图所示。优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。
2.3、互斥量的使用场合
互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量永远都处于开锁的状态,而被线程持有的时候则立刻转为闭锁的状态。
2.4、互斥量控制块
互斥量控制块是操作系统用于管理互斥量的一个数据结构,由结构体 struct rt_mutex 表示。
struct rt_mutex
{
struct rt_ipc_object parent; /* ipc对象 */
rt_uint16_t value; /* 互斥量值 */
rt_uint8_t original_priority; /* 拥有互斥量线程原先的优先级 */
rt_uint8_t hold; /* 线程持有互斥量的数量 */
struct rt_thread *owner; /* 当前拥有互斥量的线程 */
};
typedef struct rt_mutex *rt_mutex_t;
2.5、函数
2.5.1、rt_mutex_init()函数
此函数将初始化互斥锁,并将交由对象容器管理。
1)初始化IPC互斥量对象
2)初始化互斥量对象参数
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)
{
RT_ASSERT(mutex != RT_NULL); //断言mutex != RT_NULL
rt_object_init(&(mutex->parent.parent), RT_Object_Class_Mutex, name); //初始化互斥量对象
rt_ipc_object_init(&(mutex->parent)); //初始化ipc对象
mutex->value = 1; //设置互斥量值为1
mutex->owner = RT_NULL; //设置互斥量owner为RT_NULL
mutex->original_priority = 0xFF; //设置互斥量original_priority为0xff
mutex->hold = 0; //设置欲持有互斥锁的线程数为0
mutex->parent.parent.flag = flag; //设置互斥量的标志
return RT_EOK; //返回RT_EOK
}
2.5.2、rt_mutex_create()函数
此函数将从内存中分配一个互斥锁对象。
1)分配互斥量对象
2)初始化IPC互斥量对象
3)初始化互斥量对象参数
rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)
{
struct rt_mutex *mutex;
RT_DEBUG_NOT_IN_INTERRUPT; //断言不是在ISR中调用
mutex = (rt_mutex_t)rt_object_allocate(RT_Object_Class_Mutex, name); //分配互斥量对象
if (mutex == RT_NULL) //mutex等于RT_NULL
return mutex; //返回mutex
rt_ipc_object_init(&(mutex->parent)); //初始化互斥量ipc对象
mutex->value = 1; //设置互斥量值为1
mutex->owner = RT_NULL; //设置互斥量拥有者为RT_NULL
mutex->original_priority = 0xFF; //设置原优先级值为0xff
mutex->hold = 0; //设置欲持有互斥锁的线程数为0
mutex->parent.parent.flag = flag; //设置互斥量对象标志
return mutex; //返回互斥量
}
2.5.3、rt_mutex_detach()函数
此函数将把互斥锁从资源管理中分离出来。
1)唤醒所有等待此互斥量的线程
2)脱离互斥量对象
rt_err_t rt_mutex_detach(rt_mutex_t mutex)
{
/* 参数检查 */
RT_ASSERT(mutex != RT_NULL); //断言mutex != RT_NULL
RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex); //断对象为互斥量
RT_ASSERT(rt_object_is_systemobject(&mutex->parent.parent)); //断言对象为静态对象
rt_ipc_list_resume_all(&(mutex->parent.suspend_thread)); //唤醒所有等待此互斥量的线程
rt_object_detach(&(mutex->parent.parent)); //脱离互斥量对象
return RT_EOK; //返回RT_EOK
}
2.5.4、rt_mutex_delete()函数
此函数将删除一个互斥对象并释放内存。
1)唤醒所有等待此互斥量的线程
2)删除互斥量对象
rt_err_t rt_mutex_delete(rt_mutex_t mutex)
{
RT_DEBUG_NOT_IN_INTERRUPT; //断言不是在ISR中调用
/* 参数检查 */
RT_ASSERT(mutex != RT_NULL); //断言mutex != RT_NULL
RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex); //断言对象为互斥量
RT_ASSERT(rt_object_is_systemobject(&mutex->parent.parent) == RT_FALSE); //断言对象不是静态的
rt_ipc_list_resume_all(&(mutex->parent.suspend_thread)); //唤醒所有等待此互斥量的线程
rt_object_delete(&(mutex->parent.parent)); //删除互斥量对象
return RT_EOK; //返回RT_EOK
}
2.5.5、rt_mutex_take()函数
此函数将获得一个互斥锁,如果互斥锁不可用,线程将等待指定的时间。
1)获取当前线程
2)调用钩子回调函数rt_object_trytake_hook
3)将线程错误码设置为RT_EOK
4)线程已拥有互斥量,互斥量的hold成员加1(支持当前线程获取多次互斥量)
5)互斥量可用时,设置互斥量的owner成员为调用线程,设置互斥量的original_priority 成员为调用线程当前优先级,互斥量的hold成员加1
6)互斥量不可用,非阻塞调用。 设置调用线程错误码为-RT_ETIMEOUT,并返回-RT_ETIMEOUT;
7)互斥量不可用,阻塞调用。如果持有互斥量的线程优先级小于当前优先级,设置持有互斥量的线程优先级为调用线程优先级,以解决优先级反转的问题
8)挂起调用线程
9)设置调用线程定时器
10)调度
11)线程错误码等于-RT_EINTR则重新尝试获取互斥量
12)线程错误码不等于RT_EOK和-RT_EINTR,则直接返回错误码
13)调用钩子回调函数rt_object_take_hook
rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
{
register rt_base_t temp;
struct rt_thread *thread;
RT_DEBUG_IN_THREAD_CONTEXT; //断言不是在ISR中调用
/* 参数检查 */
RT_ASSERT(mutex != RT_NULL); //断言mutex != RT_NULL
RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex); //断言对象是互斥量
thread = rt_thread_self(); //获取当前线程
temp = rt_hw_interrupt_disable(); //关中断
RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(mutex->parent.parent))); //调用回调函数
RT_DEBUG_LOG(RT_DEBUG_IPC,
("mutex_take: current thread %s, mutex value: %d, hold: %d\n",
thread->name, mutex->value, mutex->hold)); //打印信息
thread->error = RT_EOK; //将线程错误码设置为RT_EOK
if (mutex->owner == thread) //线程已拥有互斥量
{
mutex->hold ++; //互斥量hold加1
}
else
{
__again:
if (mutex->value > 0) //互斥量值大于0
{
mutex->value --; //互斥量计数器减1
mutex->owner = thread; //设置互斥量owner调用线程
mutex->original_priority = thread->current_priority; //保存线程当前优先级
mutex->hold ++; //互斥量hold加1
}
else
{
if (time == 0) //不等待,返回超时
{
thread->error = -RT_ETIMEOUT; //设置错误为超时
rt_hw_interrupt_enable(temp); //开中断
return -RT_ETIMEOUT; //返回-RT_ETIMEOUT
}
else
{
RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_take: suspend thread: %s\n",
thread->name)); //打印信息
/* 如果持有互斥量的线程优先级小于当前优先级,设置持有互斥量的线程优先级为当前优先级,以解决优先级反转的问题 */
if (thread->current_priority < mutex->owner->current_priority)
{
rt_thread_control(mutex->owner,
RT_THREAD_CTRL_CHANGE_PRIORITY,
&thread->current_priority); //改变持有互斥量线程的优先级
}
rt_ipc_list_suspend(&(mutex->parent.suspend_thread),
thread,
mutex->parent.parent.flag); //挂起当前线程
if (time > 0)
{
RT_DEBUG_LOG(RT_DEBUG_IPC,
("mutex_take: start the timer of thread:%s\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) //线程错误码不等于RT_EOK
{
/* 被信号中断,重新尝试 */
if (thread->error == -RT_EINTR) goto __again; //goto到标签__again
return thread->error; //返回线程错误码
}
else
{
temp = rt_hw_interrupt_disable(); //关中断
}
}
}
}
rt_hw_interrupt_enable(temp); //开中断
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(mutex->parent.parent))); //调用回调函数
return RT_EOK; //返回RT_EOK
}
2.5.6、rt_mutex_release()函数
此函数将释放互斥锁,如果有线程挂起了互斥锁,它将被唤醒。
1)调用回调钩子函数rt_object_put_hook
2)调用线程不是互斥量持有线程,直接返回-RT_ERROR(互斥量只能被持有互斥量的线程释放!)
3)互斥量hold成员减1
4)如果mutex->hold为0,将持有该互斥量的线程的优先级恢复为原来的优先级
5)如果有等待此互斥量的线程,唤醒第一个挂起的线程,并持有此互斥量。
6)如果需要调度,则进行调度
rt_err_t rt_mutex_release(rt_mutex_t mutex)
{
register rt_base_t temp;
struct rt_thread *thread;
rt_bool_t need_schedule;
/* 参数检查 */
RT_ASSERT(mutex != RT_NULL); //断言mutex != RT_NULL
RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex); //断言对象为互斥量
need_schedule = RT_FALSE; //设置need_schedule为RT_FALSE
RT_DEBUG_IN_THREAD_CONTEXT; //上下文检查
thread = rt_thread_self(); //获取当前线程
temp = rt_hw_interrupt_disable(); //关中断
RT_DEBUG_LOG(RT_DEBUG_IPC,
("mutex_release:current thread %s, mutex value: %d, hold: %d\n",
thread->name, mutex->value, mutex->hold)); //打印信息
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(mutex->parent.parent))); //调用回调函数
/* 互斥量只能被持有互斥量的线程释放! */
if (thread != mutex->owner) //调用此函数的线程不是持有互斥量的线程
{
thread->error = -RT_ERROR; //设置错误码为-RT_ERROR
rt_hw_interrupt_enable(temp); //开中断
return -RT_ERROR; //返回-RT_ERROR
}
mutex->hold --; //互斥量hold减1
if (mutex->hold == 0) //互斥量hold等于0
{
/* 将持有该互斥量的线程的优先级恢复为原来的优先级 */
if (mutex->original_priority != mutex->owner->current_priority)
{
rt_thread_control(mutex->owner,
RT_THREAD_CTRL_CHANGE_PRIORITY,
&(mutex->original_priority)); //改变回原来的优先级
}
/* 唤醒挂起的线程 */
if (!rt_list_isempty(&mutex->parent.suspend_thread))
{
thread = rt_list_entry(mutex->parent.suspend_thread.next,
struct rt_thread,
tlist); //获取挂起线程
RT_DEBUG_LOG(RT_DEBUG_IPC, ("mutex_release: resume thread: %s\n",
thread->name)); //打印
/* 设置新的持有线程和优先级 */
mutex->owner = thread; //设置持有线程为当前线程
mutex->original_priority = thread->current_priority; //保存当前线程优先级
mutex->hold ++; //hold加1
rt_ipc_list_resume(&(mutex->parent.suspend_thread)); //唤醒线程
need_schedule = RT_TRUE; //需要调度
}
else
{
mutex->value ++; //互斥量值加1
mutex->owner = RT_NULL; //设置互斥量owner为RT_NULL
mutex->original_priority = 0xff; //设置互斥量original_priority设置为0xff
}
}
rt_hw_interrupt_enable(temp); //开中断
if (need_schedule == RT_TRUE) //是否需要调度
rt_schedule(); //调度
return RT_EOK; //返回RT_EOK
}
2.5.7、rt_mutex_control()函数
此函数可以获取或设置互斥对象的一些额外属性,当前没有功能。
rt_err_t rt_mutex_control(rt_mutex_t mutex, int cmd, void *arg)
{
/* 参数检查 */
RT_ASSERT(mutex != RT_NULL); //断言mutex != RT_NULL
RT_ASSERT(rt_object_get_type(&mutex->parent.parent) == RT_Object_Class_Mutex); //断言对象为互斥量
return -RT_ERROR; //返回-RT_ERROR
}
3、事件集
事件集也是线程间同步的机制之一,一个事件集可以包含多个事件,利用事件集可以完成一对多,多对多的线程间同步。
3.1、事件集机制
使用 32 位的无符号整数来标识事件集,它的每一位代表一个事件,因此一个事件集对象可同时等待接收 32 个事件,内核可以通过指定选择参数 “逻辑与” 或 “逻辑或” 来选择如何激活线程,使用 “逻辑与” 参数表示只有当所有等待的事件都发生时才激活线程,而使用 “逻辑或” 参数则表示只要有一个等待的事件发生就激活线程。
3.1.1、事件集特点
1)事件只与线程相关,事件间相互独立:每个线程可拥有 32 个事件标志,采用一个 32 bit 无符号整型数进行记录,每一个 bit 代表一个事件;
2)事件仅用于同步,不提供数据传输功能;
3)事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。
3.2、事件集控制块
事件集控制块是操作系统用于管理事件的一个数据结构,由结构体 struct rt_event表示。
struct rt_event
{
struct rt_ipc_object parent; /* ipc对象 */
rt_uint32_t set; /* 事件集 */
};
typedef struct rt_event *rt_event_t;
3.3、函数
3.3.1、rt_event_init()函数
此函数将初始化一个事件,并将其交由对象容器的管理。
rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag)
{
/* 参数检查 */
RT_ASSERT(event != RT_NULL); //断言event != RT_NULL
rt_object_init(&(event->parent.parent), RT_Object_Class_Event, name); //初始化事件集对象
event->parent.parent.flag = flag; //设置事件集标志
rt_ipc_object_init(&(event->parent)); //初始化事件集ipc对象
event->set = 0; //初始化事件集
return RT_EOK; //返回RT_EOK
}
3.3.2、rt_event_create()函数
此函数将从内存中分配一个事件对象。
rt_event_t rt_event_create(const char *name, rt_uint8_t flag)
{
rt_event_t event;
RT_DEBUG_NOT_IN_INTERRUPT; //断言不是在ISR中调用
event = (rt_event_t)rt_object_allocate(RT_Object_Class_Event, name); //分配事件集对象
if (event == RT_NULL) //分配失败
return event; //返回RT_NULL
event->parent.parent.flag = flag; //设置事件集标志
rt_ipc_object_init(&(event->parent)); //初始化事件集ipc对象
event->set = 0; //初始化事件集
return event; //返回event
}
3.3.3、rt_event_detach()函数
此函数将把事件对象从资源管理中分离出来。
rt_err_t rt_event_detach(rt_event_t event)
{
/* 参数检查 */
RT_ASSERT(event != RT_NULL); //断言event != RT_NULL
RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event); //断言对象是事件集
RT_ASSERT(rt_object_is_systemobject(&event->parent.parent)); //事件集对象是静态的
rt_ipc_list_resume_all(&(event->parent.suspend_thread)); //唤醒因事件集而挂起的所有线程
rt_object_detach(&(event->parent.parent)); //脱离事件集对象
return RT_EOK; //返回RT_EOK
}
3.3.4、rt_event_delete()函数
此函数将删除一个事件对象并释放内存。
rt_err_t rt_event_delete(rt_event_t event)
{
/* 参数检查 */
RT_ASSERT(event != RT_NULL); //断言event != RT_NULL
RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event); //断言对象是事件集对象
RT_ASSERT(rt_object_is_systemobject(&event->parent.parent) == RT_FALSE); //断言事件集对象不是静态的
RT_DEBUG_NOT_IN_INTERRUPT; //断言不在ISR中调用
rt_ipc_list_resume_all(&(event->parent.suspend_thread)); //唤醒因获取事件集而挂起的所有线程
rt_object_delete(&(event->parent.parent)); //删除事件集对象
return RT_EOK; //返回RT_EOK
}
3.3.5、rt_event_send()函数
发送事件函数可以发送事件集中的一个或多个事件。
rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set)
{
struct rt_list_node *n;
struct rt_thread *thread;
register rt_ubase_t level;
register rt_base_t status;
rt_bool_t need_schedule;
/* 参数检查 */
RT_ASSERT(event != RT_NULL); //断言event != RT_NULL
RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event); //断言对象是事件集
if (set == 0) //设置的事件为空
return -RT_ERROR; //返回-RT_ERROR
need_schedule = RT_FALSE; //设置need_schedule为RT_FALSE
level = rt_hw_interrupt_disable(); //关中断
event->set |= set; //设置事件
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(event->parent.parent))); //调用回调
if (!rt_list_isempty(&event->parent.suspend_thread)) //是否有线程等待事件
{
n = event->parent.suspend_thread.next; //设置n等于event->parent.suspend_thread.next
while (n != &(event->parent.suspend_thread)) //是否有因事件集而挂起的线程
{
thread = rt_list_entry(n, struct rt_thread, tlist); //获取线程
status = -RT_ERROR; //设置status为-RT_ERROR
if (thread->event_info & RT_EVENT_FLAG_AND) //事件集为与事件
{
if ((thread->event_set & event->set) == thread->event_set) //事件集全部满足条件
{
status = RT_EOK; //设置status为RT_EOK
}
}
else if (thread->event_info & RT_EVENT_FLAG_OR) //事件集为或事件
{
if (thread->event_set & event->set) //事件集部分满足条件
{
/* 保存接收到的事件集 */
thread->event_set = thread->event_set & event->set; //保存满足事件集的条件
status = RT_EOK; //设置status为RT_EOK
}
}
n = n->next; //设置n为n->next
if (status == RT_EOK) //status等于RT_EOK
{
if (thread->event_info & RT_EVENT_FLAG_CLEAR) //需要清除事件
event->set &= ~thread->event_set; //清除事件
rt_thread_resume(thread); //唤醒线程
need_schedule = RT_TRUE; //需要调度
}
}
}
rt_hw_interrupt_enable(level); //开中断
if (need_schedule == RT_TRUE) //是否需要调度
rt_schedule(); //调度
return RT_EOK; //返回RT_EOK
}
3.3.6、rt_event_recv()函数
此函数将从事件对象接收事件,如果事件不可用,线程将等待指定的时间。
rt_err_t rt_event_recv(rt_event_t event,
rt_uint32_t set,
rt_uint8_t option,
rt_int32_t timeout,
rt_uint32_t *recved)
{
struct rt_thread *thread;
register rt_ubase_t level;
register rt_base_t status;
RT_DEBUG_IN_THREAD_CONTEXT; //断言不在ISR中调用
/* 参数检查 */
RT_ASSERT(event != RT_NULL); //断言event != RT_NULL
RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event); //断言对象是事件集
if (set == 0) //接收的事件集是否为空
return -RT_ERROR; //返回-RT_ERROR
status = -RT_ERROR; //初始化状态
thread = rt_thread_self(); //获取当前线程
thread->error = RT_EOK; //设置线程错误码为RT_EOK
RT_OBJECT_HOOK_CALL(rt_object_trytake_hook, (&(event->parent.parent))); //调用回调函数
level = rt_hw_interrupt_disable(); //关中断
/* 检查事件 */
if (option & RT_EVENT_FLAG_AND) //事件集为或事件
{
if ((event->set & set) == set) //事件集全部满足条件
status = RT_EOK; //设置status为RT_EOK
}
else if (option & RT_EVENT_FLAG_OR) //事件集为与事件
{
if (event->set & set) //事件集部分满足条件
status = RT_EOK; //设置status为RT_EOK
}
else
{
RT_ASSERT(0); //断言option参数错误
}
if (status == RT_EOK) //status是否等于RT_EOK
{
if (recved) //recved不为空
*recved = (event->set & set); //保存接收到的事件
if (option & RT_EVENT_FLAG_CLEAR) //是否需要清除事件
event->set &= ~set; //清除事件
}
else if (timeout == 0) //超时时间为0
{
thread->error = -RT_ETIMEOUT; //设置线程错误码为-RT_ETIMEOUT
}
else
{
thread->event_set = set; //设置线程事件集
thread->event_info = option; //设置线程事件信息
rt_ipc_list_suspend(&(event->parent.suspend_thread),
thread,
event->parent.parent.flag); //挂起线程
/* 需要等待,启动线程定时器 */
if (timeout > 0)
{
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&timeout); //设置线程定时器超时时间
rt_timer_start(&(thread->thread_timer)); //启动线程定时器
}
rt_hw_interrupt_enable(level); //开中断
rt_schedule(); //调度
if (thread->error != RT_EOK) //线程错误码不等于RT_EOK
{
return thread->error; //返回线程错误码
}
level = rt_hw_interrupt_disable(); //关中断
if (recved) //recved不为空
*recved = thread->event_set; //保存接收到的事件
}
rt_hw_interrupt_enable(level); //开中断
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(event->parent.parent))); //调用回调函数
return thread->error; //返回线程错误码
}
3.3.7、rt_event_control()函数
此函数可以获取或设置事件集对象的一些额外属性。
rt_err_t rt_event_control(rt_event_t event, int cmd, void *arg)
{
rt_ubase_t level;
/* 参数检查 */
RT_ASSERT(event != RT_NULL); //断言event != RT_NULL
RT_ASSERT(rt_object_get_type(&event->parent.parent) == RT_Object_Class_Event); //断言对象是事件集
if (cmd == RT_IPC_CMD_RESET)
{
level = rt_hw_interrupt_disable(); //关中断
rt_ipc_list_resume_all(&event->parent.suspend_thread); //唤醒因获取事件集而挂起的所有线程
event->set = 0; //初始化事件集
rt_hw_interrupt_enable(level); //开中断
rt_schedule(); //调度
return RT_EOK; //返回RT_EOK
}
return -RT_ERROR; //返回-RT_ERROR
}