ZEBRA中FSM编写总结
zebra中FSM编写总结
说明:
)
本文主要通过样例的分析,说明一般状态机的编写方法
在ospf里面有两个状态机:邻居状态机和接口状态机
zebra里有对应的程序
目 录
1 邻居状态机模块的分析
1.1 状态和事件的定义
ospf_nsm.h里面说明
1.1.1 宏定义状态机的各种状态
当中定义了两个表示范围的状态. 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
1.1.2 宏定义状态转移的触发条件
在ospf邻居状态机里面就是各种邻居事件. 同状态的定义的道理。也定义两个用于限定范围的。
邻居事件没有什么高级和0基础的分别,所以编号的分配也就随便了。以方便为原则。
以下这两个就是额外设置的两个没有使用的事件。
#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
//**************************************
邻居事件激起的操作函数
//**************************************
1.2 状态机中的函数操作
1.2.1 函数的分类
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()
最后在状态转移表中和特定的事件通过函数指针挂钩。
(特别注意的是,改变状态是在该类函数里实现的)
1.2.2 总调函数
最后,zebra里面另一个总调函数ospf_nsm_event(),主要是两个功能:
Ø 运行挂钩的事件触发函数。
Ø 假设状态发生了改变。调用状态或状态转移的函数看看有没有须要运行的操作。
1.3 邻居状态转移表
1.3.1 如何编写状态转移表
这是一个结构数组。用来指明状态的跳转和特殊的触发事件相关的挂钩函数。维度是:状态的个数*事件的个数。
以下试一个样例:邻居状态机的装移表
样例里面,第一小段也是为编程而须要的空操作。没有什么实际的意义。以第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 */
},
};
1.3.2 空函数
细致看一下,在邻居状态转移表里面有一个空函数nsm_ignore(),这个是为了某些情况下不运行不论什么操作而设定的,非常明显,这是编程的须要。
2 接口状态机模块的分析
(未完)
3 OSM编写记录
状态事件返回新的状态值为什么总是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值输入给状态转移函数,于是完毕整个的状态跳转动作。
另外注意的一点就是在状态机中直接调用的那些函数通常没有什么复杂的參数。复杂的參数也不easy通过线程结构传递。一般也就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状态之后才准备接收信息。
1.3.3 在RSTP的程序中是怎样得到下一个状态的?
没有不论什么显式表明下一个状态是什么, 通过在函数里的复杂控制. 这点和OSPF的状态机比起来, 显得凌乱, 不够规范, 相比較非常easy出现bug. 所以状态机的写法不採用RSTP中的方法. 当然在RSTP的程序中也有不少可取的地方.
另外, 以后不得用##表达函数, 严重影响source insight工作, 即使是搜索也非常费劲, 不easy找到函数.
1.3.2.2 状态转移表的ignore
在A状态, 有些事件会发生, 有些事件根本不会发生. 那么根本不会发生的相应的肯定是ignore. 即使有些事件会发生, 可是在A状态全然不受影响, 能够忽略. 也应该相应的是ignore.
无条件转移的写法.
基本的全局变量在main文件中面定义, 假设其它文件想要引用, 可是又不方便include main.h. 那么使用extern即可了.