static const u8 tcp_conntracks[2][6][TCP_CONNTRACK_MAX] = { { /* ORIGINAL */ /* sNO, sSS, sSR, sES, sFW, sCW, sLA, sTW, sCL, sS2 */ /*syn*/ { sSS, sSS, sIG, sIG, sIG, sIG, sIG, sSS, sSS, sS2 }, /* ... };以下是状态机转换公式:
static struct tcp_states_t tcp_states [] = { /* INPUT */ /* sNO, sES, sSS, sSR, sFW, sTW, sCL, sCW, sLA, sLI, sSA */ /*syn*/ {{sSR, sES, sES, sSR, sSR, sSR, sSR, sSR, sSR, sSR, sSR }}, /*fin*/ {{sCL, sCW, sSS, sTW, sTW, sTW, sCL, sCW, sLA, sLI, sTW }}, /*ack*/ {{sCL, sES, sSS, sES, sFW, sTW, sCL, sCW, sCL, sLI, sES }}, /*rst*/ {{sCL, sCL, sCL, sSR, sCL, sCL, sCL, sCL, sLA, sLI, sSR }}, ... };可见和ip_conntrack的极其类似!这种数组所包含的信息量极大,几乎把索引下标,数组维数全部都用来存储数据了,这样就形成了一个多维的存储结构,一旦两个维度的信息不再正交,那么数组元素本身它就有了状态,而这正是实现状态机的关键。
我这里给出一个例子,下图是一个一般的状态机
以下就需要用该状态机构造一个数组,填充每一个元素,最终把这个静态数组写入.h文件中,若计算机自己有智能,那么这个填充工作完全可以在运行时完成,然而计算机没有智能,所以只能程序员写好它。如果非要运行时填充,那么填充代码本身的代码还是回归到了while循环处理状态机了。/*状态定义*/ enum State{ State0=0, StateA=1, StateB=2, StateC=3, }; /*事件定义*/ enum Event{ Einit=0, Evt0A=1, EvtA0=2, EvtAC=3, EvtC0=4, EvtCB=5, EvtBC=6, };我们定义一个二维数组,第一维代表事件,第二维的值代表下一个状态第二维的索引,其实就是下面的公式
StateMachine[][]= { /*Einit*/ {State0,} /*Evt0A*/ {StateA,}, /*EvtA0*/ {stub,State0,}, /*EvtAC*/ {stub,StateC,}, /*EvtC0*/ {stub,stub,stub,State0,}, /*EvtCB*/ {stub,stub,StateB,}, /*EvtBC*/ {stub,stub,StateC,}, }其中的stub表示该处不表示任何状态。
看懂了这个例子以后,再回过头上ip_conntrack以及ipvs的TCP的状态机数组的例子,就更加清晰了,唯一不同的是,ip_conntrack通过一个新的维度来表示数据方向,而ipvs则通过一个base+offset的方式定位到了事件维度数组的索引。最后把上面的过程做成了一张图: