Raw-OS源码分析之空闲事件

        分析的内核版本截止到2014-04-15,基于1.05正式版,blogs会及时跟进最新版本的内核开发进度,若源码注释出现”???”字样,则是未深究理解部分。

        Raw-OS官方网站:http://www.raw-os.org/

        Raw-OS托管地址:https://github.com/jorya/raw-os/

 

        在真正介绍状态机编程之前,还是先介绍一下一些Raw-OS关于状态机编程的基本的东西,做做前戏是很有必要的,让后来来得更有感觉~嗯嗯~就是这样的~

        那这篇就先介绍一下Raw-OS的空闲事件的内核执行过程,据Raw-OS作者txj大大的介绍,状态机编程结合事件触发机制,双剑合璧才能发挥非常非常好的功效,但是小弟我到现在还不能很好的领悟Raw-OS中状态机编程的精髓啊~精啊~啊~

 

        所谓的事件触发机制,在Raw-OS内核表现出来的就是一套用户APIs,其实这里就讲解一下API的执行原理而已,为下一节开始的状态机编程(fsm、hsm)打打预防针,嗯嗯~就是这样的

 

一、事件触发原理

        首先,先来看看一张图

 


 

        这张图就是事件触发的在Raw-OS执行的流程图,这里就先了解一下概念,是了解哈~亲~

 

        首先就是图上的OBJ、OBJ是什么呢?记住一个名词,活动对象,只是先记住“活动对象”这个名词,所谓的状态机编程,就是对活动对象定义和编写状态函数,那什么又是状态函数呢,就是事件的处理过程函数,处理的过程函数好理解,就是具体的执行操作嘛,那尼玛什么又是事件?事件嘛,其实就是一个信号,尼玛什么信号啊,给老子具体点。

 

        好好,举个例子,

 


 

        本屌是一个程序员,程序员就是本屌,那么本屌就可以抽象成一个活动对象

        那么状态机编程呢?就是定义和编写状态过程,例如本屌比较正常的一天的工作就是,写代码,然后累的时候去抽烟,领导不在的时候就偷懒,刷糗百的时候选择拉翔,最后空闲的时候就去文员办公室调剂一下,调戏为数不多的小妹妹们

        对应于事件触发的机制来讲一讲~

        有时候本屌的责任心就是比较强,爱岗敬业,经常督促自己完成当日的代码编写工作,那么本屌就会给自己发送一个:尼玛,赶紧干活的事件消息;再比如说项目组的文员经常有事没事就会要你跟一下项目进度,或者一些琐碎的事情,那么文员就会给你发送一个:妈蛋,给老娘滚过来报告进度的事件消息;再比如说,老大有时候无聊会找你组队抽个烟,吐槽一下公司的垃圾方面,那就就会给你发送一个:来来来,走一个的事件信号......

        然后,本屌会接收到很多的事件信号,这些事件都是要处理的,都是有优先级的,比如我先会去文员那里报告进度,得罪女人很麻烦的,你懂的,然后才会去跟老大“走一个”,最后才会去完成本屌的本质工作:写代码。所以,在接收信号的时候,我自己会对事件信号排序,然后存在我的脑子里,在抽象框图就是活动对象的buffer中,到这里可以稍微理解一点没有。

        事件调度就是在我脑子里接收并排序好的事件信号,一个个去完成它,拿到一个事件信号,就去执行对应的工作,也就是执行活动对象的状态函数,状态机编程具体就是干这个东西,怎么去定义状态,怎么样去编写状态执行函数的方法论,完事。

 

二、Raw-OS事件触发相关概念

        然后现在具体到Raw-OS的事件触发的具体代码里面。

        在Raw-OS中,可以定义最多64个活动对象,就好比一个公司有众多屌丝程序员一样,然后每个活动对象都有自己的优先级,就好比屌丝中也有累不死然后喜欢天天加班的屌,他们优先级是很高的,通常分配到他们的资源是非常的多的,好比我所在的公司,组长尼玛都是天天加班在办公室里面做码神,但是好比我这样的可有可无的屌丝就不会干这事,经常一下班就开溜,项目也不多,钱也拿得少,所以优先级很低。那么在Raw-OS中的,活动对象的优先级大小是按照定义时的顺序先后排列的。

 



        也就是说,先定义OBJ0,那么OBJ0就是优先级最高,为0;比如我先定义OBJ63,那么OBJ63就是最高优先级的活动对象,总之,先定义的活动对象比后定义的活动对象的优先级高,所有定义的活动对象都放在active_idle_task[]全局数组里面,Raw-OS在事件触发系统初始化时,就会初始化这些定义的活动对象。


        每个活动对象,在内核中由两部分组成

 



        活动对象包含:

        1.关于其自身的状态机,也就是说,包含属于这个活动对象的可能的所有状态和状态函数的定义

        2.一个用于接收事件信号的消息队列(Queue)

 

        至于内核代码,就是抽象成几个变量

 


 

        现在再利用上面的例子拓展一下,对于项目组的文员,也可以抽象成活动对象,然后老大也可以抽象成活动对象,领导也是一个鸟样,同样是活动对象

 


 

        那么对于事件消息,活动对象可以给活动对象发送事件消息,另外硬件中断也可以给活动对象发送事件消息~硬件中断怎么理解,妈蛋现在写单片机程序你说你理解不了中断,卧槽,你TM是在逗我吗?

 

        好好,接下来再说说,有几个Raw-OS系统默认可以发送的事件信息,一个是入口事件ENTRY,一个是初始化事件INIT,一个是出口事件EXIT,最后一个是超时事件TIMEOUT。

        这几个信号在下节进入状态机具体编程的时候再讲,这里先记住就可以了~

 

        举个超时事件TIMEOUT的例子

 



        比如刚才项目管理组的文员叫本屌去汇报进度,但是本屌在忙其他的事情,距离叫本屌去汇报进度已经过去了一两个小时,文员拍案而起,给本屌发了条消息:“狗日的,老娘日理万机,还不给老娘滚过来汇报一下”,得罪女人很麻烦的~你懂的,于是我给自己发送一个超时信号,是时候要去安抚一下那帮面目狰狞的女汉子暴躁的情绪了,超时信号就是这样,活动对象等待一个特定的事件发生,然后我们给他定义了一段等待事件,如果在等待事件里面特性的时间还没发生,就给自己发送一个timeout的事件,执行timeout的状态函数。

        而等待超时的活动对象,都会放到idle的tick list中,这个和之前讲过Raw-OS内核的软件定时器tick list是一样的原理

 

        最后讲讲queue这个东东,之前讲过的所有关于queue的原理在这里同样适用,这里仅仅介绍发送事件消息时,可以发往消息queue的前端和后端,发送到queue前端的消息,在活动对象激活时会首先被处理,所以,消息按优先级大小排列就是根据发送给queue的事件消息的顺序

 


 

三、事件触发系统API代码详解

        接下来就直接看注释过的代码了~按照上面的概念去分析代码,结合注释,很容易看懂

 

活动对象初始化

void idle_event_init(void)
{
	ACTIVE_EVENT_STRUCT *temp;
	RAW_U8 i;

	/* 初始化idle任务队列中的所有活动对象,根据Raw-OS内核规定,活动对象的最大数为64 */
	for (i = 0; i <= MAX_IDLE_EVENT_TASK - 1; i++) {
		temp = active_idle_task[i].act;

		RAW_ASSERT(temp != 0);

		temp->prio = i;
		temp->head    = 0;
		temp->tail    = 0;
		temp->nUsed   = 0;

		/*
		 * priority_bit_y定义为组别优先级
		 * priority_bit_x定义为每个组别活动对象优先级
		 * 分8个组别,每个组别含8个活动对象
		 */
		temp->priority_x = i & 0x7;
		temp->priority_y = i >> 3;

		temp->priority_bit_y = (RAW_U8 )(1 << temp->priority_y);
		temp->priority_bit_x = (RAW_U8 )(1 << temp->priority_x);

	}

	/* 初始化idle的tick链表 */
	list_init(&raw_idle_tick_head);

}


发送事件消息到queue前端

RAW_U16 idle_event_front_post(ACTIVE_EVENT_STRUCT *me, RAW_U16 sig, void *para)
{

	#if (CONFIG_RAW_ZERO_INTERRUPT > 0)
	/* 开启task 0后,消息由task 0转发??? */
	if (raw_int_nesting && raw_sched_lock) {
		return int_msg_post(RAW_TYPE_IDLE_FRONT_EVENT_POST, me, para, sig, 0, 0);
	}
	#endif

	/* 向活动对象的queue发送消息,这里选择发送到queue的前端,紧急消息 */
	return event_post(me, sig, para, SEND_TO_FRONT);
}

发送事件到queue后端

RAW_U16 idle_event_end_post(ACTIVE_EVENT_STRUCT *me, RAW_U16 sig, void *para)
{

	#if (CONFIG_RAW_ZERO_INTERRUPT > 0)
	/* 开启task 0后,消息由task 0转发??? */
	if (raw_int_nesting && raw_sched_lock) {
		return int_msg_post(RAW_TYPE_IDLE_END_EVENT_POST, me, para, sig, 0, 0);
	}
	#endif

	/* 向活动对象的queue发送消息,这里选择发送到queue的末端,一般消息 */
	return event_post(me, sig, para, SEND_TO_END);
}

事件发送核心代码

RAW_U16 event_post(ACTIVE_EVENT_STRUCT *me, RAW_U16 sig, void *para, RAW_U8 opt_send_method)
{

	ACTIVE_EVENT_STRUCT_CB *acb;

 	RAW_SR_ALLOC();
 	/* 根据活动对象的优先级,获取其在idle队列中的活动对象控制块 */
	acb = &active_idle_task[me->prio];

	RAW_CRITICAL_ENTER();

	/*
	 * nUsed变量用来存放活动对象中queue中存在的消息数量
	 * and变量表示的是活动对象中queue的大小
	 *
	 * 这里判断当活动对象的存在消息数量超过活动对象queue存放消息数量的大小关系,溢出返回
	 */
	if (me->nUsed == acb->end) {

		RAW_CRITICAL_EXIT();
		return RAW_IDLE_EVENT_EXHAUSTED;
	}

	/*
	 * 在这里,回顾raw_queue_buffer这个模块,活动对象存放消息的queue也是一个环形buffer
	 *
	 * 发送消息到活动对象queue的末端
	 */
	if (opt_send_method == SEND_TO_END) {
		/* 向buffer头指针位置写入活动对象信号 */
		acb->queue[me->head].sig = sig;
		/* 向buffer头指针位置写入活动对象信号参数 */
		acb->queue[me->head].para = para;
		/* 当head指针到达buffer的末端时,重置回到buffer起始位置 */
		me->head++;
		if (me->head == acb->end) {
			me->head = 0;
		}
	}

	else {
		/* 当tail指针到达buffer的起始位置时,重置回到buffer末端 */
		if (me->tail == 0) {
			me->tail = acb->end;
		}
		me->tail--;
		/* 向buffer尾指针位置写入活动对象信号 */
		acb->queue[me->tail].sig = sig;
		/* 向buffer尾指针位置写入活动对象信号参数 */
		acb->queue[me->tail].para = para;
	}
	/* 消息数量+1 */
	++me->nUsed;

	/*
	 * 当活动对象存在消息时,活动对象就会得到活动对象优先级大小raw_rdy_tbl[]和组别优先级大小raw_idle_rdy_grp信息
	 *
	 * 存在消息的活动对象就可以根据raw_rdy_tbl[]和raw_idle_rdy_grp查表得到存在消息的最高优先级活动对象
	 */
	if (me->nUsed == 1) {
		raw_idle_rdy_grp |= acb->act->priority_bit_y;
		raw_rdy_tbl[acb->act->priority_y] |= acb->act->priority_bit_x;
	}

	RAW_CRITICAL_EXIT();

	return RAW_SUCCESS;
}

给活动对象指定超时时间

/*
 * 这个函数相当于给活动对象创建一个软件定时器,但是简化很多raw_timer的工作
 * 但是这个活动对象的定时器只有一次超时的功能,超时会向创建活动对象软件定时器定义的活动对象发送超时信号
 */
RAW_U16 idle_tick_arm(ACTIVE_EVENT_STRUCT *me, RAW_TICK_TYPE ticks)
{
	RAW_U16 tick_ret;

	RAW_SR_ALLOC();
	/* 中断中不能创建活动对象的定时器 */
	if (raw_int_nesting) {
		return RAW_NOT_CALLED_BY_ISR;
	}
	/* 创建活动对象定时器时,超时时间大小不能为0 */
	if (ticks == 0) {
		return RAW_IDLE_TICK_ADD_FAILED;
	}

	RAW_CPU_DISABLE();
	/* 将定时器信息写入到活动对象中,并加入到idle_tick_list链表,在系统tick中断中调用idle_tick_isr()等待超时 */
	if (me->tick_ctr == 0) {
		me->tick_ctr = ticks;
		list_insert(&raw_idle_tick_head, &me->idle_tick_list);
		tick_ret = RAW_SUCCESS;
	}

	else {
		tick_ret = RAW_IDLE_TICK_ADD_FAILED;
	}

	RAW_CPU_ENABLE();

	return tick_ret;
}

在系统时间中计算超时时间

/*
 * 如果raw_time_tick()函数一样,在系统tick isr中调用,更新idle_tick_list链表,用来计算活动对象超时时间
 */
void idle_tick_isr(void)
{
	ACTIVE_EVENT_STRUCT *a;

	LIST *head;
	LIST *iter;
	LIST *iter_temp;

	head = &raw_idle_tick_head;
	iter = head->next;

	/* 历遍idle_tick_list,判断idle_tick_list中有没有等待超时的活动对象 */
 	while (iter != head) {

		a =  list_entry(iter, ACTIVE_EVENT_STRUCT, idle_tick_list);
		iter_temp =  iter->next;
		/* 更新活动对象超时时间 */
		if (a->tick_ctr) {
			--a->tick_ctr;
			/* 活动对象超时时,向活动对象发送timeout信号,并以0参数传入活动对象 */
			if (a->tick_ctr == 0) {
				/* 这里说明活动对象的超时是一次超时,超时后删除活动对象的软件定时器 */
				list_delete(iter);
				idle_event_end_post(a, STM_TIMEOUT_SIG, 0);
			}
		}

		iter = iter_temp;
 	}
}

终止活动对象等待指定信号

/*
 * 取消活动对象的软件定时器
 * 某些时候某些情况发生,我们不需要激活活动对象,所以取消还在等到超时的活动对象的软件定时器
 */
RAW_U16 idle_tick_disarm(ACTIVE_EVENT_STRUCT *me)
{
	RAW_U16 tick_ret;

	RAW_SR_ALLOC();
	/* 中断中不能取消 */
	if (raw_int_nesting) {
		return RAW_NOT_CALLED_BY_ISR;
	}

	RAW_CPU_DISABLE();

	/* 等待超时的活动对象中删除软件定时器信息,并从idle_tick_list链表移除超时信息 */
	if (me->tick_ctr) {
		list_delete(&me->idle_tick_list);
		me->tick_ctr = 0;
		tick_ret = RAW_SUCCESS;
	}

	else {
		tick_ret = RAW_IDLE_TICK_DELETE_FAILED;
	}

	RAW_CPU_ENABLE();

	return tick_ret;
}

事件触发系统调度

void idle_run(void)
{
    ACTIVE_EVENT_STRUCT *a;
	STATE_EVENT temp;

	ACTIVE_EVENT_STRUCT_CB *acb;
	RAW_U8 x;
	RAW_U8 y;
	RAW_U8 idle_high_priority;

	RAW_SR_ALLOC();

	while (1) {

		RAW_CRITICAL_ENTER();

		/* 当任一个活动对象存在消息时,即有idle任务有事件发生时 */
		if (raw_idle_rdy_grp) {

			/* 查找位图表,通过64个优先级的活动对象的分组情况(raw_idle_rdy_grp、raw_rdy_tbl[])逆运算得到活动对象优先级 */
			y = raw_idle_map_table[raw_idle_rdy_grp];
			x = y >> 3;
			idle_high_priority = (y + raw_idle_map_table[raw_rdy_tbl[x]]);

			/* 根据优先级从idle任务自定义的活动对象tcb的结构体数组中取出活动对象tcb */
			acb = &active_idle_task[idle_high_priority];
			/* 从活动对象tcb中取出活动对象结构体 */
			a = active_idle_task[idle_high_priority].act;

			/* 活动对象消息数量-1 */
			--a->nUsed;
			/* 当活动对象所有消息处理完毕时 */
			if (a->nUsed == 0) {
				/* 活动对象没有消息时,清除其raw_rdy_tbl[]中raw_idle_rdy_grp的对应位 */
				raw_rdy_tbl[a->priority_y] &= (RAW_U8)~a->priority_bit_x;
				/* 如果清除活动对象后,所在组别中也无其余活动对象,清楚组别标志位 */
				if (raw_rdy_tbl[a->priority_y] == 0) { /* Clear event grp bit if this was only task pending */
					raw_idle_rdy_grp &= (RAW_U8)~a->priority_bit_y;
				}
			}
			/* 在活动对象控制块的queue中的tail位置取出信号和信号参数 */
			temp.sig = acb->queue[a->tail].sig;
			temp.which_pool = acb->queue[a->tail].para;

			/* queue中的tail指针后移,丢弃取出使用后的消息,并且当移动到queue末端时,重置到queue头部 */
			a->tail++;
			if (a->tail == acb->end) {
			    a->tail = 0;
			}

			RAW_CRITICAL_EXIT();

			/*
			 * 取出消息后,根据系统宏选项执行有限状态机或者层级状态机
			 * 在执行状态机的过程,是根据temp消息执行对应的执行选项
			 */
			#if (RAW_FSM_ACTIVE > 0)
			fsm_exceute(&a->super, &temp);
			#else
			hsm_exceute(&a->super, &temp);
			#endif

		}

		/* 如果所有活动对象都不存在消息时,执行用户自定义的idle事件钩子函数,通常在没有消息处理时进入硬件低功耗休眠 */
		else {
			RAW_CRITICAL_EXIT();
			RAW_CPU_DISABLE();
			if (raw_idle_rdy_grp == 0) {
				/* 用户idle事件钩子函数 */
				idle_event_user();
			}
			RAW_CPU_ENABLE();
		}
    }
}

        在这里为止,事件触发系统,也就是事件触发API介绍完毕,也介绍了一般性的概念,例如活动对象,事件信号,状态执行函数,这一节着重理解概念就可以了,下节开始介绍具体的fsm的状态机编程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值