分析的内核版本截止到2014-04-15,基于1.05正式版,blogs会及时跟进最新版本的内核开发进度,若源码注释出现”???”字样,则是未深究理解部分。
Raw-OS官方网站:http://www.raw-os.org/
Raw-OS托管地址:https://github.com/jorya/raw-os/
一、事件标志使用例子
原理:
先假设事件标志用一个usigned int变量表示,即32bit的一个变量,例如有一个任务X监控着事件标志的第3位,那么如果第3位被置位1,那么就说任务x满足事件标志;又如果任务X同时监控事件标志的第3、4位,那么,如果仍然是第3位被置位,那么如果X是“或监控状态”,那么说任务X满足事件标志,如果X是“与监控状态”,那么说X不满足事件标志。
多个任务监控若干位事件标志也是同样分析。
事件标志的使用也是很简单,先看看用法:
一个事件标志的阻塞例子:
1.首先任务不能满足事件标志而阻塞在事件标志控制块的阻塞链表上
2.某个时刻有代码设置了时间标志的某个或某些位
3.系统进行调度
4.此时被阻塞的任务可能满足新的事件标志,得以唤醒
5.执行满足时间标志的任务
二、Raw-OS创建事件标志
创建事件标志与创建信号量的过程非常之类似
具体看看代码即可:
RAW_U16 raw_event_create(RAW_EVENT *event_ptr, RAW_U8 *name_ptr, RAW_U32 flags_init)
{
/* 检查要创建的事件标志控制块是否有实体定义,没有定义时返回 */
#if (RAW_EVENT_FUNCTION_CHECK > 0)
if (event_ptr == 0) {
return RAW_NULL_OBJECT;
}
#endif
/* 初始化事件标志控制块内的阻塞链表 */
list_init(&event_ptr->common_block_obj.block_list);
/* 事件标志阻塞方式:默认优先级排序 */
event_ptr->common_block_obj.block_way = RAW_BLOCKED_WAY_PRIO;
/* 事件标志控制块名称 */
event_ptr->common_block_obj.name = name_ptr;
/* 事件标志初始化值,创建事件标志时一般设置为0 */
event_ptr->flags = flags_init;
/* 事件标志类型:事件集类型 */
event_ptr->common_block_obj.object_type = RAW_EVENT_OBJ_TYPE;
/* trace调试系统??? */
TRACE_EVENT_CREATE(raw_task_active, event_ptr, name_ptr, flags_init);
return RAW_SUCCESS;
}
三、判断满足事件标志
获取事件标志的过程也与获取信号量的过程非常类似,只不过当获取信号量时,信号量计数器-1,而满足事件标志时,会根据设置的操作方式是否将满足事件标志的对应位清零
同样和获取信号量时类似,内核做出类似的操作:
0.如果满足事件标志,返回
1.将任务设置成RAW_PEND或者按照阻塞超时按超时时间大小插入到tick_list
2.将任务从运行队列中移除
3.将任务按优先级大小加入到信号量的阻塞链表中
4.执行系统调度
获取事件标志执行代码
RAW_U16 raw_event_get(RAW_EVENT *event_ptr, RAW_U32 requested_flags, RAW_U8 get_option, RAW_U32 *actual_flags_ptr, RAW_TICK_TYPE wait_option)
{
/* 错误状态变量,用来存放可能发生不满足事件标志时任务被唤醒的错误标识 */
RAW_U16 error_status;
RAW_U8 status;
/* 定义CPU状态机字变量 */
RAW_SR_ALLOC();
/* 检查事件标志控制块是否存在,不存在返回 */
#if (RAW_EVENT_FUNCTION_CHECK > 0)
if (event_ptr == 0) {
return RAW_NULL_OBJECT;
}
/* 检查中断嵌套,即中断内不能调用此函数,即中断内不能获取事件标志,但中断内可以设置事件标志 */
if (raw_int_nesting) {
return RAW_NOT_CALLED_BY_ISR;
}
/* 检查Raw-OS支持的事件标志操作:与,或,与后清除标志,或后清除标志,不是这四种情况,错误返回 */
if ((get_option != RAW_AND) && (get_option != RAW_OR) && (get_option != RAW_AND_CLEAR) && (get_option != RAW_OR_CLEAR)) {
return RAW_NO_THIS_OPTION;
}
#endif
/* 保存CPU状态字 */
RAW_CRITICAL_ENTER();
/* 检查操作传入的事件标志控制块,控制块内标识不是事件标志对象类型时,返回 */
if (event_ptr->common_block_obj.object_type != RAW_EVENT_OBJ_TYPE) {
RAW_CRITICAL_EXIT();
return RAW_ERROR_OBJECT_TYPE;
}
/* 判断获取事件标志设定的事件操作是否是Raw-OS定义的与操作:RAW_AND或者RAW_AND_CLEAR之一 */
if (get_option & RAW_FLAGS_AND_MASK) {
/*
* 判断事件标志控制块中发生的事件标志是否等于请求判断的事件标志,相等时设置true状态
*
* 与操作要求发生的事件标志和请求判断的事件标志完全一致
*/
if ((event_ptr->flags & requested_flags) == requested_flags) {
status = RAW_TRUE;
}
/* 否则请求判断的事件标志与事件标志控制块内发生的事件标志不相等,设置为false状态 */
else {
status = RAW_FALSE;
}
}
/* 如果之前判断的不是与操作,即获取事件标志设定的是或操作:RAW_OR或者RAW_OR_CLEAR之一 */
else {
/* 或操作要求发生的事件标志和请求判断的事件标志其中某一个符合即可 */
if (event_ptr->flags & requested_flags) {
status = RAW_TRUE;
}
else {
status = RAW_FALSE;
}
}
/* 通过之前的判断,满足事件标志时执行 */
if (status == RAW_TRUE) {
/* 指向事件标志控制块内部的事件标志变量 */
*actual_flags_ptr = event_ptr->flags;
/*
* 当事件标志控制块内设定的是RAW_OR_CLEAR或者RAW_AND_CLEAR时,清除对应请求判断的事件标志位
*
* 例如事件控制块内的事件标志为10001010,请求判断的事件标志是00001010,判断类型为AW_AND_CLEAR
* 那么满足事件标志,在这里会将控制块内的事件标志对应请求判断位清除,清除后为10000000
*/
if (get_option & RAW_FLAGS_CLEAR_MASK) {
event_ptr->flags &= ~requested_flags;
}
RAW_CRITICAL_EXIT();
/* trace调试系统??? */
TRACE_EVENT_GET(raw_task_active, event_ptr);
/* 满足事件标志时在这里返回 */
return RAW_SUCCESS;
}
/* 当不满足事件标志时,如果用户不设置等待,那么直接返回 */
if (wait_option == RAW_NO_WAIT) {
RAW_CRITICAL_EXIT();
return RAW_NO_PEND_WAIT;
}
/* 当系统系统上锁时,任务不能被阻塞,直接返回 */
SYSTEM_LOCK_PROCESS();
/* 将获取事件标志方式存入当前运行任务TCB */
raw_task_active->raw_suspend_option = get_option;
/* 将请求获取的事件标志存入当前运行任务TCB */
raw_task_active->raw_suspend_flags = requested_flags;
/* 将此时不满足事件标志的事件标志控制块内的标志位存入当前运行任务TCB */
raw_task_active->raw_additional_suspend_info = actual_flags_ptr;
/* 因为满足事件标志后在上面会返回,这里同semaphore一样,根据wait_option条件阻塞任务 */
raw_pend_object((RAW_COMMON_BLOCK_OBJECT *)event_ptr, raw_task_active, wait_option);
RAW_CRITICAL_EXIT();
/* trace调试系统??? */
TRACE_EVENT_GET_BLOCK(raw_task_active, event_ptr, wait_option);
/* 阻塞当前活动任务后,进行系统调度,唤醒新的最高优先级任务 */
raw_sched();
/*
* 执行到这里,是因为某些条件唤醒新的最高优先级任务,根据之前阻塞的情况,
* 判断之前阻塞的情况,置位新任务TCB阻塞标志,目的是得知是什么操作唤醒任务
*/
error_status = block_state_post_process(raw_task_active, 0);
/*
* 如果唤醒新的任务后,任务阻塞标志不是RAW_SUCCESS,那么可能是阻塞超时,用户调用abort,任务删除
* 引起的结果,这些都识得任务不能正确满足事件标志,发生错误,在这里返回错误状态提示
*
* 正常满足事件标志后被系统唤醒的任务是不会发生错误的
*/
if (error_status != RAW_SUCCESS) {
return error_status;
}
RAW_CRITICAL_ENTER();
/* 新任务满足事件标志被唤醒后,根据是否设置了RAW_OR_CLEAR或者RAW_AND_CLEAR
* 清除事件标志控制块中对应请求判断的事件标志位
*/
if (get_option & RAW_FLAGS_CLEAR_MASK) {
event_ptr->flags &= ~requested_flags;
}
RAW_CRITICAL_EXIT();
return RAW_SUCCESS;
}
阻塞部分代码:
RAW_U16 raw_pend_object(RAW_COMMON_BLOCK_OBJECT *block_common_obj, RAW_TASK_OBJ *task_ptr, RAW_TICK_TYPE timeout)
{
/*
* 因为在调用raw_pend_object之前已经处理过timeout为0时的情况,即wait_option==RAW_NO_WAIT
* 所以这里再次发生timeout==0就会不正确,所以系统检测到timeout==0时,系统down掉
*
* 例如raw_semaphore_get中会先检查wait_option == RAW_NO_WAIT的阻塞情况
*/
if (timeout == 0u) {
RAW_ASSERT(0);
}
/* 将阻塞任务对象传入到任务阻塞对象指针,作为记录 */
task_ptr->block_obj = block_common_obj;
/* 如果阻塞类型是永久阻塞,设置任务状态为阻塞态 */
if (timeout == RAW_WAIT_FOREVER) {
task_ptr->task_state = RAW_PEND;
}
/* 如果不是永久阻塞态,就会带有阻塞超时参数,那么就插入到tick list中,等待超时 */
else {
/* 将阻塞任务插入到tick list中,等待超时 */
tick_list_insert(task_ptr,timeout);
/* 加入到tick list时设置任务状态是阻塞超时态 */
task_ptr->task_state = RAW_PEND_TIMEOUT;
}
/* 发生阻塞时将此任务从就绪队列的对应位置中移除 */
remove_ready_list(&raw_ready_queue, task_ptr);
/* 如果,阻塞对象为FIFO机制(先入先出)时,则将被阻塞对象加入到阻塞对象的阻塞链表的末端 */
if (block_common_obj->block_way == RAW_BLOCKED_WAY_FIFO) {
list_insert(&block_common_obj->block_list, &task_ptr->task_list);
}
/* 如果不是FIFO机制,则按优先级大小插入到阻塞对象的阻塞链表的适当位置 */
else {
add_to_priority_list(&block_common_obj->block_list, task_ptr);
}
return RAW_SUCCESS;
}
四、设置事件标志
同样与释放信号量类似,当任务因不满足事件标志阻塞后,在别的代码位置设置了新的事件标志,然后判断阻塞在事件标志控制块中的阻塞任务是否满足新的事件标志,然后重新唤醒任务
1.任务或中断ISR设置新的事件标志
2.根据唤醒选择唤醒阻塞链表中的单个最高优先级或者整个阻塞链表中的任务
3.执行系统调度
RAW_U16 raw_event_set(RAW_EVENT *event_ptr, RAW_U32 flags_to_set, RAW_U8 set_option)
{
/* 检查要设置的事件标志控制块是否存在,不存在时返回 */
#if (RAW_EVENT_FUNCTION_CHECK > 0)
if (event_ptr == 0) {
return RAW_NULL_OBJECT;
}
/*
* 检查Raw-OS支持的事件标志方式,与操作和或操作,设置事件标志没有清除的说法
* 所以不存在RAW_OR_CLEAR和RAW_AND_CLEAR
*/
if ((set_option != RAW_AND) && (set_option != RAW_OR)) {
return RAW_NO_THIS_OPTION;
}
#endif
/* 当开启raw-os的0中断特性时,由0中断任务转发??? */
#if (CONFIG_RAW_ZERO_INTERRUPT > 0)
if (raw_int_nesting) {
return int_msg_post(RAW_TYPE_EVENT, event_ptr, 0, 0u, flags_to_set, set_option);
}
#endif
/* 调用事件标志内核API,置位事件标志位 */
return event_set(event_ptr, flags_to_set, set_option);
}
在raw_event_set中调用内核事件标志API
RAW_U16 event_set(RAW_EVENT *event_ptr, RAW_U32 flags_to_set, RAW_U8 set_option)
{
LIST *iter;
LIST *event_head_ptr;
LIST *iter_temp;
RAW_TASK_OBJ *task_ptr;
RAW_U8 status;
/* 定义CPU状态字变量 */
RAW_SR_ALLOC();
/* */
status = RAW_FALSE;
/* 保存CPU状态字变量 */
RAW_CRITICAL_ENTER();
/* 如果操作传入的事件标志控制块,控制块内标识不是事件标志对象类型时,错误返回 */
if (event_ptr->common_block_obj.object_type != RAW_EVENT_OBJ_TYPE) {
RAW_CRITICAL_EXIT();
return RAW_ERROR_OBJECT_TYPE;
}
/* 获取传入事件标志控制块的阻塞链表头 */
event_head_ptr = &event_ptr->common_block_obj.block_list;
/*
* 如果设置事件标志时为与操作,那么将存入的事件标志和控制块的事件标志相与后返回
* 即无论此前控制块内的事件标志是什么值,都会变成新的传入的事件标志值
*
* 但是到这是设置完新的事件标志后就立即返回了,就是说AND操作是不会执行阻塞任务唤醒的!!!
*/
if (set_option & RAW_FLAGS_AND_MASK) {
event_ptr->flags &= flags_to_set;
RAW_CRITICAL_EXIT();
return RAW_SUCCESS;
}
/* 如果进行的是或操作,那么将原控制块内事件标志值和传入的事件标志值相或 */
else {
event_ptr->flags |= flags_to_set;
}
/*
* 找到控制块的阻塞链表的第一个任务,因为发生阻塞时任务插入到控制块阻塞链表是按优先级大小
* 进行的,所以第一个任务也是阻塞的最高优先级任务
*/
iter = event_head_ptr->next;
/* 历遍整个阻塞链表 */
while (iter != event_head_ptr) {
/* 通过阻塞链表内的task_list信息找到阻塞任务的具体TCB地址 */
task_ptr = list_entry(iter, RAW_TASK_OBJ, task_list);
/* iter_temp保存再下一个阻塞链表中的task_list元素 */
iter_temp = iter->next;
/*
* 因为任务阻塞时会将获取事件标志方式(RAW_AND、RAW_AND_CLEAR、RAW_OR、RAW_OR_CLEAR)
* 和请求判断的事件标志存在任务的TCB中,这里读取设置方式进行判断
*
* raw_suspend_option存放获取事件标志方式
* raw_suspend_flags请求判断的事件标志
* 这里如果任务进行的是与操作(RAW_AND、RAW_AND_CLEAR)
*/
if (task_ptr->raw_suspend_option & RAW_FLAGS_AND_MASK) {
/* 如果新的控制块事件标志满足阻塞任务等待的事件标志后,设置true状态 */
if ((event_ptr->flags & task_ptr ->raw_suspend_flags) == task_ptr ->raw_suspend_flags) {
status = RAW_TRUE;
}
/* 否则设置flase状态 */
else {
status = RAW_FALSE;
}
}
/* 如果不是与操作(RAW_AND、RAW_AND_CLEAR),这里就执行或操作(RAW_OR、RAW_OR_CLEAR) */
else {
/* 如果新设置的事件标志某一位满足阻塞任务等待事件标志,设置true状态 */
if (event_ptr->flags & task_ptr ->raw_suspend_flags) {
status = RAW_TRUE;
}
/* 否则设置flase状态 */
else {
status = RAW_FALSE;
}
}
/* true状态,表明阻塞链表中存在满足事件标志的阻塞任务 */
if (status == RAW_TRUE) {
/* 将新的任务控制内的事件标志存入到阻塞任务TCB的额外阻塞信息中 */
(*(RAW_U32 *)(task_ptr->raw_additional_suspend_info)) = event_ptr->flags;
/* true状态,唤醒阻塞在事件控制块中阻塞链表的任务 */
raw_wake_object(task_ptr);
/* trace调试系统??? */
TRACE_EVENT_WAKE(raw_task_active, task_ptr);
}
/* 更新到下一个阻塞链表中的task_list,继续历遍阻塞链表 */
iter = iter_temp;
}
RAW_CRITICAL_EXIT();
/* 执行系统调度 */
raw_sched();
return RAW_SUCCESS;
}
五、阻塞任务唤醒
当设置新的事件标志后,唤醒任务机制跟获取释放信号量的机制完全一致,唤醒任务会根据任务TCB中阻塞状态标识变量用以下3种操作组合唤醒任务
三种操作分别为:
1.从信号量阻塞链表中删除
2.加入到就绪队列
3.带timeout模式的阻塞唤醒时从tick_list中删除
static RAW_U16 pend_task_wake_up(RAW_TASK_OBJ *task_ptr)
{
/* 对于不同任务状态的阻塞任务进行唤醒 */
switch (task_ptr->task_state) {
/* 阻塞、超时阻塞态 */
case RAW_PEND:
case RAW_PEND_TIMEOUT:
/* 阻塞的任务中task_list记录着阻塞对象的阻塞链表信息,唤醒时要从阻塞对象的阻塞链表中移除 */
list_delete(&task_ptr->task_list);
/* 简单阻塞唤醒后加入到就绪队列中,等待调度 */
add_ready_list(&raw_ready_queue, task_ptr);
/* 唤醒后设置任务状态为就绪态 */
task_ptr->task_state = RAW_RDY;
break;
/* 阻塞挂起、超时阻塞挂起态 */
case RAW_PEND_SUSPENDED:
case RAW_PEND_TIMEOUT_SUSPENDED:
/* 唤醒后还有挂起状态存在,仅从阻塞对象的阻塞链表中移除任务 */
list_delete(&task_ptr->task_list);
/* 设置任务状态为挂起态 */
task_ptr->task_state = RAW_SUSPENDED;
break;
default:
RAW_ASSERT(0);
}
/*
* 阻塞态的任务被唤醒后,从tick_list中删除有关的休眠任务
*
* 这里,在阻塞时或者其他操作引起休眠时才会在tick_list中添加休眠任务的休眠信息
*
* 例如,以永久等待时间形式调用用户API信号量获取函数、互斥锁获取函数、事件获取函数等等的操作时
* 因为在tick_list_remove会检查任务中是否存在有时间链表头tick_head(当tick_head有值时说明在
* tick_list中有任务休眠信息,也等同于说,任务是以有限等待时间阻塞的)
*
* 永久等待时间不会引起任务添加到tick_list的操作,仅仅将任务状态设置为RAW_PEND,并且唤醒时进行到
* 此函数也不会引发tick_list的删除操作,因为在阻塞任务控制块中也不存在tick_list信息
*/
tick_list_remove(task_ptr);
/* 设置任务阻塞状态 */
task_ptr->block_status = RAW_B_OK;
/* 阻塞任务唤醒后,阻塞对象就设置为NULL */
task_ptr->block_obj = 0;
return RAW_SUCCESS;
}