zebra中FSM编写总结
说明:
本文主要通过例子的分析,说明一般状态机的编写方法
在ospf里面有两个状态机:邻居状态机和接口状态机
zebra里有相应的程序
目 录
ZEBRA中FSM编写总结...1
1 邻居状态机模块的分析... 1
1.1 状态和事件的定义...1
1.1.1 宏定义状态机的各种状态...1
1.1.2 宏定义状态转移的触发条件...2
1.2 状态机中的函数操作...3
1.2.1 函数的分类...3
1.2.2 总调函数...4
1.3 邻居状态转移表...4
1.3.1 怎样编写状态转移表...4
1.3.2 空函数...8
2 接口状态机模块的分析... 8
ospf_nsm.h里面说明
其中定义了两个表示范围的状态. NSM_DependUpon = 0 ;OSPF_NSM_STATE_MAX = 9, 这两个状态是没有使用到的,只是为了 编程的方便而设置的. 其他的各种状态按照一定的顺序定义为从1-8. 为了编程的方便和阅读的习惯,较高级的状态相应得标号就比较高。
下面是实际上不存在的状态:
#defineNSM_DependUpon 0
#defineOSPF_NSM_STATE_MAX 9
下面是协议中规定的状态:
#define NSM_Down 1
#defineNSM_Attempt 2
#define NSM_Init 3
#defineNSM_TwoWay 4
#defineNSM_ExStart 5
#defineNSM_Exchange 6
#defineNSM_Loading 7
#define NSM_Full 8
在ospf邻居状态机里面就是各种邻居事件. 同状态的定义的道理,也定义两个用于限定范围的。邻居事件没有什么高级和初级的分别,所以编号的分配也就随便了,以方便为原则。
下面这两个就是额外设置的两个没有使用的事件。
#defineNSM_NoEvent 0
#defineOSPF_NSM_EVENT_MAX 14
下面是协议中规定的事件:
#defineNSM_HelloReceived 1
#defineNSM_Start 2
#defineNSM_TwoWayReceived 3
#defineNSM_NegotiationDone 4
#defineNSM_ExchangeDone 5
#defineNSM_BadLSReq 6
#defineNSM_LoadingDone 7
#defineNSM_AdjOK 8
#defineNSM_SeqNumberMismatch 9
#defineNSM_OneWayReceived 10
#defineNSM_KillNbr 11
#defineNSM_InactivityTimer 12
#defineNSM_LLDown 13
//*************************************
nsm_timer_set
//**************************************
邻居事件激起的操作函数
//**************************************
FSM中有一些操作是根据从特定的状态转移应该执行的动作,和邻居事件没有直接的联系,而且可能好多邻居事件都能有同样的执行。比如,通过不同的路线到达一个状态时,都要执行某些操作,那么这个操作就放在nsm_change_state()里面。
感觉有点像OPNET里面的 状态进入或者跳出执行函数,示意图:
整个圆表示一个状态,in表示进入该状态要执行的函数,out表示离开该状态要执行的函数。(进一步的了解,需要对OPNET里有限状态机编程有简单的了解。)
有限状态机中函数的执行分为两种,一种是只和特定的状态转移有关,一种只和特殊的触发事件相关。
Ø 和特定的状态或状态转移有关
这种情况又分为两种:
一种是和以前的状态无关,只和本状态有关。比如,只要进入本状态,就要执行一定的操作,或者只要离开本状态,就要执行一定的操作。也有可能两者兼有,离开和进入本状态都必须执行同样的某些操作。例如在nsm_change_state()函数里面就可以发现进出full状态都回执行特定的操作。
另外一种就是和转移的路线相关,比如要判断从哪个状态进入本状态和从本状态跳出到哪个状态,从而根据不同情况执行不同的操作。像这些操作,可以集中放在一个函数里面,在本例中,就像nsm_change_state()。
在OPNET里面,一直使用的是进入和跳出某个状态才执行某些操作,如果要判断从哪个状态进入本状态和从本状态跳出到哪个状态,就要用FSM全局变量标记以确认。
在zebra程序中,这一大类的所有操作都封装在nsm_change_state()里面,在c程序编程中,这是一个值得参考和采用的方法。
Ø 和特殊的触发事件相关
和状态完全无关。只要发生了某些触发事件,就要执行特定操作。在zebra中,这些操作是分开成各个单独的函数实现的。如:
nsm_hello_received()
nsm_twoway_received()
最后在状态转移表中和特定的事件通过函数指针挂钩。
(特别注意的是,改变状态是在该类函数里实现的)
最后,zebra里面还有一个总调函数ospf_nsm_event(),主要是两个功能:
Ø 执行挂钩的事件触发函数。
Ø 如果状态发生了改变,调用状态或状态转移的函数看看有没有需要执行的操作。
这是一个结构数组,用来指明状态的跳转和特殊的触发事件相关的挂钩函数。维度是:状态的个数*事件的个数。
下面试一个例子:邻居状态机的装移表
例子里面,第一小段也是为编程而需要的空操作,没有什么实际的意义。以第2小段为例说明。
这是down状态的说明表,描述的是该状态下遇到所有的邻居事件执行的操作和下一个状态。像这一行:
{ nsm_hello_received, NSM_Init }, /* HelloReceived */
前面的nsm_hello_received是要执行的操作,NSM_Init发生此事件发生后装移到的下一个状态。注释部分说明这个是HelloReceived事件对应的条项。
struct {
int(*func) ();
intnext_state;
} NSM[OSPF_NSM_STATE_MAX][OSPF_NSM_EVENT_MAX] =
{
{
/* DependUpon: dummy state. */
{nsm_ignore, NSM_DependUpon}, /* NoEvent */
{nsm_ignore, NSM_DependUpon}, /* HelloReceived */
{nsm_ignore, NSM_DependUpon}, /* Start */
{nsm_ignore, NSM_DependUpon}, /* 2-WayReceived */
{nsm_ignore, NSM_DependUpon}, /* NegotiationDone */
{nsm_ignore, NSM_DependUpon}, /* ExchangeDone */
{nsm_ignore, NSM_DependUpon}, /* BadLSReq */
{nsm_ignore, NSM_DependUpon}, /* LoadingDone */
{nsm_ignore, NSM_DependUpon}, /* AdjOK? */
{nsm_ignore, NSM_DependUpon}, /* SeqNumberMismatch */
{nsm_ignore, NSM_DependUpon}, /* 1-WayReceived */
{nsm_ignore, NSM_DependUpon}, /* KillNbr */
{nsm_ignore, NSM_DependUpon}, /* InactivityTimer */
{nsm_ignore, NSM_DependUpon}, /* LLDown */
},
{
/* Down: */
{nsm_ignore, NSM_DependUpon}, /* NoEvent */
{nsm_hello_received, NSM_Init }, /* HelloReceived */
{nsm_start, NSM_Attempt }, /* Start */
{nsm_ignore, NSM_Down }, /* 2-WayReceived */
{nsm_ignore, NSM_Down }, /* NegotiationDone */
{nsm_ignore, NSM_Down }, /* ExchangeDone */
{nsm_ignore, NSM_Down }, /* BadLSReq */
{nsm_ignore, NSM_Down }, /* LoadingDone */
{nsm_ignore, NSM_Down }, /* AdjOK? */
{nsm_ignore, NSM_Down }, /* SeqNumberMismatch */
{nsm_ignore, NSM_Down }, /* 1-WayReceived */
{nsm_kill_nbr, NSM_Down }, /* KillNbr */
{nsm_inactivity_timer, NSM_Down }, /* InactivityTimer */
{nsm_ll_down, NSM_Down }, /* LLDown */
},
{
/* Attempt: */
{nsm_ignore, NSM_DependUpon}, /* NoEvent */
{nsm_hello_received, NSM_Init }, /* HelloReceived */
{nsm_ignore, NSM_Attempt }, /* Start */
{ nsm_ignore, NSM_Attempt }, /* 2-WayReceived */
{nsm_ignore, NSM_Attempt }, /* NegotiationDone */
{nsm_ignore, NSM_Attempt }, /* ExchangeDone */
{nsm_ignore, NSM_Attempt }, /* BadLSReq */
{nsm_ignore, NSM_Attempt }, /* LoadingDone */
{nsm_ignore, NSM_Attempt }, /* AdjOK? */
{nsm_ignore, NSM_Attempt }, /* SeqNumberMismatch */
{nsm_ignore, NSM_Attempt }, /* 1-WayReceived */
{nsm_kill_nbr, NSM_Down }, /* KillNbr */
{nsm_inactivity_timer, NSM_Down }, /* InactivityTimer */
{nsm_ll_down, NSM_Down },/* LLDown */
},
{
/* Init: */
{nsm_ignore, NSM_DependUpon}, /* NoEvent */
{nsm_hello_received, NSM_Init }, /* HelloReceived */
{nsm_ignore, NSM_Init }, /* Start */
{nsm_twoway_received, NSM_DependUpon}, /* 2-WayReceived */
{nsm_ignore, NSM_Init }, /* NegotiationDone */
{nsm_ignore, NSM_Init }, /* ExchangeDone */
{nsm_ignore, NSM_Init }, /* BadLSReq */
{nsm_ignore, NSM_Init }, /* LoadingDone */
{nsm_ignore, NSM_Init }, /* AdjOK? */
{nsm_ignore, NSM_Init }, /* SeqNumberMismatch */
{nsm_ignore, NSM_Init }, /* 1-WayReceived */
{nsm_kill_nbr, NSM_Down }, /* KillNbr */
{nsm_inactivity_timer, NSM_Down }, /* InactivityTimer */
{nsm_ll_down, NSM_Down }, /* LLDown */
},
{
/* 2-Way: */
{nsm_ignore, NSM_DependUpon}, /* NoEvent */
{nsm_hello_received, NSM_TwoWay }, /* HelloReceived */
{nsm_ignore, NSM_TwoWay }, /* Start */
{nsm_ignore, NSM_TwoWay }, /* 2-WayReceived */
{nsm_ignore, NSM_TwoWay }, /* NegotiationDone */
{nsm_ignore, NSM_TwoWay }, /* ExchangeDone */
{nsm_ignore, NSM_TwoWay }, /* BadLSReq */
{nsm_ignore, NSM_TwoWay }, /* LoadingDone */
{nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */
{nsm_ignore, NSM_TwoWay }, /* SeqNumberMismatch */
{nsm_oneway_received, NSM_Init }, /* 1-WayReceived */
{nsm_kill_nbr, NSM_Down }, /* KillNbr */
{nsm_inactivity_timer, NSM_Down }, /* InactivityTimer */
{nsm_ll_down, NSM_Down }, /* LLDown */
},
{
/* ExStart: */
{nsm_ignore, NSM_DependUpon}, /* NoEvent */
{nsm_hello_received, NSM_ExStart }, /* HelloReceived */
{ nsm_ignore, NSM_ExStart }, /* Start */
{nsm_ignore, NSM_ExStart }, /* 2-WayReceived */
{nsm_negotiation_done, NSM_Exchange }, /*NegotiationDone */
{nsm_ignore, NSM_ExStart }, /* ExchangeDone */
{nsm_ignore, NSM_ExStart }, /* BadLSReq */
{nsm_ignore, NSM_ExStart }, /* LoadingDone */
{nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */
{nsm_ignore, NSM_ExStart }, /* SeqNumberMismatch */
{nsm_oneway_received, NSM_Init }, /* 1-WayReceived */
{nsm_kill_nbr, NSM_Down }, /* KillNbr */
{nsm_inactivity_timer, NSM_Down },/* InactivityTimer */
{nsm_ll_down, NSM_Down }, /* LLDown */
},
{
/* Exchange: */
{nsm_ignore, NSM_DependUpon}, /* NoEvent */
{nsm_hello_received, NSM_Exchange }, /*HelloReceived */
{nsm_ignore, NSM_Exchange }, /* Start */
{nsm_ignore, NSM_Exchange }, /*2-WayReceived */
{nsm_ignore, NSM_Exchange }, /*NegotiationDone */
{nsm_exchange_done, NSM_DependUpon}, /* ExchangeDone */
{nsm_bad_ls_req, NSM_ExStart }, /* BadLSReq */
{nsm_ignore, NSM_Exchange }, /*LoadingDone */
{nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */
{nsm_seq_number_mismatch, NSM_ExStart }, /* SeqNumberMismatch */
{nsm_oneway_received, NSM_Init }, /* 1-WayReceived */
{nsm_kill_nbr, NSM_Down }, /* KillNbr */
{nsm_inactivity_timer, NSM_Down }, /* InactivityTimer */
{nsm_ll_down, NSM_Down }, /* LLDown */
},
{
/* Loading: */
{nsm_ignore, NSM_DependUpon}, /* NoEvent */
{nsm_hello_received, NSM_Loading }, /* HelloReceived */
{nsm_ignore, NSM_Loading }, /* Start */
{nsm_ignore, NSM_Loading }, /* 2-WayReceived */
{nsm_ignore, NSM_Loading }, /* NegotiationDone */
{nsm_ignore, NSM_Loading }, /* ExchangeDone */
{nsm_bad_ls_req, NSM_ExStart }, /* BadLSReq */
{nsm_ignore, NSM_Full }, /* LoadingDone */
{nsm_adj_ok, NSM_DependUpon }, /* AdjOK? */
{nsm_seq_number_mismatch, NSM_ExStart }, /* SeqNumberMismatch */
{nsm_oneway_received, NSM_Init }, /* 1-WayReceived */
{nsm_kill_nbr, NSM_Down }, /* KillNbr */
{nsm_inactivity_timer, NSM_Down }, /* InactivityTimer */
{nsm_ll_down, NSM_Down }, /* LLDown */
},
{/* Full: */
{nsm_ignore, NSM_DependUpon}, /* NoEvent */
{nsm_hello_received, NSM_Full }, /* HelloReceived */
{nsm_ignore, NSM_Full }, /* Start */
{nsm_ignore, NSM_Full }, /* 2-WayReceived */
{nsm_ignore, NSM_Full },/* NegotiationDone */
{nsm_ignore, NSM_Full }, /* ExchangeDone */
{nsm_bad_ls_req, NSM_ExStart }, /* BadLSReq */
{nsm_ignore, NSM_Full }, /* LoadingDone */
{nsm_adj_ok, NSM_DependUpon}, /* AdjOK? */
{nsm_seq_number_mismatch, NSM_ExStart }, /* SeqNumberMismatch */
{nsm_oneway_received, NSM_Init }, /* 1-WayReceived */
{nsm_kill_nbr, NSM_Down },/* KillNbr */
{nsm_inactivity_timer, NSM_Down }, /* InactivityTimer */
{nsm_ll_down, NSM_Down }, /* LLDown */
},
};
仔细看一下,在邻居状态转移表里面有一个空函数nsm_ignore(),这个是为了某些情况下不执行任何操作而设定的,很明显,这是编程的需要。
(未完)
状态事件返回新的状态值为什么总是0。
什么情况下返回的不是0?
nsm_twoway_received返回的不是0。而是根据情况有两种结果。猜测是其他的a状态发生b事件就一定转移到c状态。这种情况下发生的b事件就应改特殊处理。
发现在nsm里面,执行玩nsm_twoway_received后,发现下一个状态是NSM_DependUpon。这个说明,这其实是一种新的小类别函数,以前没有注意。
后面发现nsm_exchange_done也是这样。
这些函数的产生是这样的原因。
比如在通常情况下,a状态发生b事件就一定转移到c状态。但是有个例外,比如说这个节点是个DR,或者链路是p2p的,那么就会直接跳转到另外一个特殊的状态。所以这个时候,在nsm里面的next state值是NSM_DependUpon。b事件的执行函数就会返回不同的next state值。而其他的函数就只会返回0。在总的调度函数里面,如果发现返回值是0,那么就按照nsm表取得next state值,否则按照函数返回的next state值。
然后,将这node和next state值输入给状态转移函数,于是完成整个的状态跳转动作。
另外注意的一点就是在状态机中直接调用的那些函数通常没有什么复杂的参数。复杂的参数也不容易通过线程结构传递。一般也就2个,一个node,也就是状态机本身,一个是event,也就是事件的编号。
另外有一点要明确的就是:在状态机相关里面执行的函数只是为了驱动状态机本身,而不是包办各种事情,比如:在ospf里面的nsm_hello_received函数,并不是真正处理了hello包,只是设置了一些timer,驱动状态机的跳转。
真正的包处理过程是这样的:
接到协议包之后系统调用ospf_read,分析出是hello包之后,调用ospf_hello。进行各种处理,其中就将nsm_hello_received函数加入到线程链表。而且由于通常不只影响一个状态机,所以也会调用别的状态机的同样的函数。在这里面完成hello包处理的主要操作,而不是在nsm_hello_received里面。nsm_hello_received只是一个状态机的驱动event而已。
还有一个问题,线程什么时候执行.
第一种方法是立刻执行操作, 如: OSPF_NSM_EVENT_EXECUTE.这个是立刻就执行了.另外的一种是
while (thread_fetch (master,&thread))
thread_call (&thread);
实现的机制就是, 启动第一个操作, 用thread_call执行. 这个操作会执行别的东西, 别的东西也会加入到thread.这样, 使用thread_fetch会得到源源不断的thread.
其中有两重循环保证thread不会断.
1. 在thread_fetch里面使用while(1)来取得线程执行. 也就是说, 如果没有返回线程, 那么thread_fetch是会一直跑下去.
2. while(thread_fetch (master, &thread))保证了只要有线程返回, 那么一定会执行.
新的问题: 在ospf中, 是由外来的包进行中断, 调用ISR, 这样会改变thread master. 所以在ospf里面可以一直等. 但是如果是单机模拟的话, 是没有中断的, 也无法跳出死循环. 当然, 在多节点运行的过程中采用这个运行模式是不错的选择.
问题的解决:
双线程操作. 改变while(thread_fetch (master,&thread)). 先检查thread_fetch,如果没有, 就执行预先设定要的操作, 暂时命名为fix_thread. 执行完fix_thread又进行thread_fetch. 造成的效果就是, 执行一个预设的操作,
在目前的汇编实现中,操作必然是在某个状态下执行的。
比如check_cts。Make cts. 但是根据普通的状态机的写法,在zebra中,象hello_received,是在down状态都到的,在nsm_hello_received这个函数执行完之前,系统明显属于down状态。但是在目前的实现中,make cts和make ack都是在对应的发送状态里面完成的。所以不能放在特殊的触发事件相关的挂钩函数里面,而是放在change state函数里面。而特殊的触发事件相关的挂钩函数的执行实际上是在当前状态,而不是下一个状态。而change state里面可以实现状态的进入,退出函数,以及中间执行。
以在idle状态收到RTS为例。汇编程序里面在idle状态完全实现了基本的RTS接收。初步的分析。然后跳转到tx_cts状态,开始准备cts packet,并且检查是否发送。所以,在对应的C code里面,准备cts packet不能放在特殊的触发事件相关的挂钩函数里面,而是只能在changestate函数里面实现。在node的节点状态变化已经完成的阶段,由于change state的执行结果,需要转移到新的状态。结果出现新的trigger。而在ospf程序里面,决定下一个状态的往往是触发事件相关的挂钩函数决定下一个状态。在mesh的C实现中,触发事件相关的挂钩函数的内容非常的少,一方面,触发事件很明晰。比如收RTS,收到了一个合格的RTS,系统才认为是一个RTS_IN事件。也就是说,只有系统已经检查完这个RTS,才会出发RTS_IN引起状态转移,而且这个转移是立刻执行的,导致触发事件的挂钩函数根本没有什么事情可以做。而且不能让该类函数对触发时间是否合格而决定下一个状态的跳转,否则所有的next state全是MSM_DependUpon。所以这类函数所能够做的事情就是当外部的函数已经判定合法的触发事件之后,根据这个合法的触发事件还有一些分析,并且影响到下一个状态。而下一个状态所能做的事情,该类函数是不能够执行的。而在目前的mesh DSP实现里面很少有这种情况出现,所以基本上触发事件的挂钩函数基本上都是空函数。
如果在从一个状态转移到另外一个状态的时候,并不是100%的机会,而是有一些别的可能,就不能认为是无条件转移。
就像在mesh中从TX_CTS到RX_DATA,默认没有问题就会这样转移,可是还是有一个可能是check fail,这个时候就是从TX_CTS到IDLE。所以,check OK也应该算是一种事件。
或者更广义一点,check cts作为触发事件。在触发事件的挂钩函数里面进行实际的检查,决定跳转到idle状态还是rx_data状态。
mesh_rx
not every time the box can receive packets, only the rf is inlistening mode.
Seems a tiny state machine is in idle state.
由于在DSP code当中,大部分得操作都在state得label下进行的,使得实际上大部分的操作都在change_state函数里面执行, 状态转移函数几乎为空。
在zebra里面,比如接到hello message,是在上一个状态判断好的,然后决定转移。而在mesh里,跳转到rx hello状态之后才准备接收信息。
没有任何显式表明下一个状态是什么, 通过在函数里的复杂控制. 这点和OSPF的状态机比起来, 显得凌乱, 不够规范, 相比较很容易出现bug. 所以状态机的写法不采用RSTP中的方法. 当然在RSTP的程序中也有不少可取的地方.
另外, 以后不得用##表达函数, 严重影响source insight工作, 即使是搜索也很费劲, 不容易找到函数.
1.3.2.2 状态转移表的ignore
在A状态, 有些事件会发生, 有些事件根本不会发生. 那么根本不会发生的对应的肯定是ignore. 即使有些事件会发生, 但是在A状态完全不受影响, 可以忽略. 也应该对应的是ignore.
无条件转移的写法.
主要的全局变量在main文件里面定义, 如果其他文件想要引用, 但是又不方便include main.h. 那么使用extern就行了.