状态机进阶(smp 状态机介绍)

来源:互联网 发布:sqlserver 错误1326 编辑:程序博客网 时间:2024/06/17 14:01

状态机进阶(smp 状态机介绍)

在Bluedroid中的线程介绍 那篇文章中,我们介绍了最简单的一种状态机实现方式。这个状态机的缺点也很明显,如果事件和状态都很多的话,那么状态转换表会很大(事件个数),状态转换表也要很多个(状态个数)。
在smp模块中,我们可以看到如何设计实现状态和事件都比较多的状态机。

smp状态机需求分析

  • smp状态机的状态很多(17个状态)
  • smp状态机的事件很多(41个事件)
  • BLE分为master和slave role,smp状态机需要同时支持这两个role的功能。
  • smp的每个状态处理的事件很少,状态之间跳转简单(流程简单一步一步往下走,但是步骤很多)

smp状态机设计思路

状态很多,每个状态处理的事件很少。可以把每个状态拆分成小的状态转换表,只需要当前状态要处理的事件的项。不需要每个状态实现一个大表(包含所有的事件的状态转换表),因为大表的话,很多项都是无用的项,还占内存。
状态机的三要素就是:状态,事件,处理函数。
原先大表的话,事件这一列可以省略只有action和next state两列。拆成小表后就需要事件这一列了。
处理函数还是跟前面一样,通过枚举值查对应的函数;
拆分后的状态表的查询比较简单,通过当前状态和role(区分master和slave),就可以获取状态转换表;
最关键的地方是如何把事件映射到小表的对应列上。映射做好后,就可以使用小表做事件处理和状态跳转了。下面结合代码详细介绍。

代码分析

stack/smp/smp_main.cc

action list

enum {  SMP_PROC_SEC_REQ,  SMP_SEND_PAIR_REQ,  SMP_SEND_PAIR_RSP,  SMP_SEND_CONFIRM,  SMP_SEND_PAIR_FAIL,  SMP_SEND_RAND,  SMP_SEND_ENC_INFO,  SMP_SEND_ID_INFO,  SMP_SEND_LTK_REPLY,  SMP_PROC_PAIR_CMD,  SMP_PROC_PAIR_FAIL,  SMP_PROC_CONFIRM,  SMP_PROC_RAND,  ...}static const tSMP_ACT smp_sm_action[] = {    smp_proc_sec_req,    smp_send_pair_req,    smp_send_pair_rsp,    smp_send_confirm,    smp_send_pair_fail,    smp_send_rand,    smp_send_enc_info,    smp_send_id_info,    smp_send_ltk_reply,    smp_proc_pair_cmd,    smp_proc_pair_fail,    smp_proc_confirm,    smp_proc_rand,    ...}

上面的代码我们看到action和前面介绍的一样,enum和处理函数一一对应,在状态转换表中填enum的值,可以通过这个值来找到对应的处理函数。

状态表

static const uint8_t smp_master_idle_table[][SMP_SM_NUM_COLS] = {    /* Event                  Action               Next State */    /* L2C_CONN */    {SMP_SEND_APP_CBACK, SMP_SM_NO_ACTION, SMP_STATE_WAIT_APP_RSP},    /* SEC_REQ */    {SMP_PROC_SEC_REQ, SMP_SEND_APP_CBACK, SMP_STATE_WAIT_APP_RSP},    /* L2C_DISC */    {SMP_IDLE_TERMINATE, SMP_SM_NO_ACTION, SMP_STATE_IDLE},    /* AUTH_CMPL */    {SMP_PAIRING_CMPL, SMP_SM_NO_ACTION, SMP_STATE_IDLE},    /* CR_LOC_SC_OOB_DATA */    {SMP_CREATE_PRIVATE_KEY, SMP_SM_NO_ACTION,SMP_STATE_CREATE_LOCAL_SEC_CONN_OOB_DATA}};static const tSMP_SM_TBL smp_state_table[][2] = {    /* SMP_STATE_IDLE */    {smp_master_idle_table, smp_slave_idle_table},    /* SMP_STATE_WAIT_APP_RSP */    {smp_master_wait_for_app_response_table,    ¦smp_slave_wait_for_app_response_table},    /* SMP_STATE_SEC_REQ_PENDING */    {NULL, smp_slave_sec_request_table},    /* SMP_STATE_PAIR_REQ_RSP */    {smp_master_pair_request_response_table,    ¦smp_slave_pair_request_response_table},    /* SMP_STATE_WAIT_CONFIRM */    {smp_master_wait_for_confirm_table, smp_slave_wait_confirm_table},    /* SMP_STATE_CONFIRM */    {smp_master_confirm_table, smp_slave_confirm_table},    /* SMP_STATE_RAND */    {smp_master_rand_table, smp_slave_rand_table},    /* SMP_STATE_PUBLIC_KEY_EXCH */    {smp_master_public_key_exchange_table, smp_slave_public_key_exch_table},    ...}static const uint8_t smp_all_table[][SMP_SM_NUM_COLS] = {    /* Event                  Action             Next State */    /* PAIR_FAIL */    {SMP_PROC_PAIR_FAIL, SMP_PAIRING_CMPL, SMP_STATE_IDLE},    /* AUTH_CMPL */    {SMP_SEND_PAIR_FAIL, SMP_PAIRING_CMPL, SMP_STATE_IDLE},    /* L2C_DISC */    {SMP_PAIR_TERMINATE, SMP_SM_NO_ACTION, SMP_STATE_IDLE}};

上面代码中,smp_master_idle_table是role master在idle状态时的转换表。可以看到这张表有3列,分别是Event, Action, Next State。数组的行数就是对应状态下要处理的event的数目,每个表处理的event数目都不太一样。类似的状态转换表有很多,最终每个状态下的都会查类似的状态表来做处理和跳转动作。
smp_state_table是查找状态子表的入口数组。有17行(状态数),2列(分别为master和slave)。通过当前状态和role就可以查到当前状态下的状态转换子表,类似smp_master_idle_table的表。
smp_all_table是另外一张状态表,通过mask查,不需要通过role来查。看起来master和slave的处理是相同的合并到一起。

映射表

static const tSMP_ENTRY_TBL smp_entry_table[] = {smp_master_entry_map,  smp_slave_entry_map};/************ SMP Master FSM State/Event Indirection Table **************/static const uint8_t smp_master_entry_map[][SMP_STATE_MAX] = {    /* state name: */    /* Idle, WaitApp Rsp, SecReq Pend, Pair ReqRsp, Wait Cfm, Confirm, Rand,    ¦  PublKey Exch, SCPhs1 Strt, Wait Cmtm, Wait Nonce, SCPhs2 Strt, Wait    ¦  DHKChk, DHKChk, Enc Pend, Bond Pend, CrLocSc OobData */    /* PAIR_REQ */    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},    /* PAIR_RSP */    {0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},    /* CONFIRM */    {0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},    /* RAND */    {0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0},    /* PAIR_FAIL */    {0, 0x81, 0, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81,    ¦0x81, 0, 0x81, 0},    /* ENC_INFO */    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0},    /* MASTER_ID */    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0},    /* ID_INFO */    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0},    /* ID_ADDR */    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0},    /* SIGN_INFO */    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0},    ... ...}

smp_entry_table是映射表的入口数组,里面有两个元素,分别是master 和slave的映射表。
smp_master_entry_map是master的映射表。有41行(状态机事件的总数)17列(状态机的状态总数)。数组里面元素的值跟状态子表的行号(event)有对应关系,通过元素的值,可以把event映射到子表去查询事件对应的action和next state。

处理流程

1 void smp_sm_event(tSMP_CB* p_cb, tSMP_EVENT event, void* p_data) {2    uint8_t curr_state = p_cb->state;3    tSMP_SM_TBL state_table;4    uint8_t action, entry, i;5    tSMP_ENTRY_TBL entry_table = smp_entry_table[p_cb->role];6    /* look up the state table for the current state */7    /* lookup entry /w event & curr_state */8    /* If entry is ignore, return.9     * Otherwise, get state table (according to curr_state or all_state) */10    if ((event <= SMP_MAX_EVT) &&11     ((entry = entry_table[event - 1][curr_state]) != SMP_SM_IGNORE)) {12        if (entry & SMP_ALL_TBL_MASK) {13            entry &= ~SMP_ALL_TBL_MASK;14            state_table = smp_all_table;15        } else16            state_table = smp_state_table[curr_state][p_cb->role];17     } else {18        return;19    }20    /* Get possible next state from state table. */21    smp_set_state(state_table[entry - 1][SMP_SME_NEXT_STATE]);22    /* If action is not ignore, clear param, exec action and get next state.23     * The action function may set the Param for cback.24     * Depending on param, call cback or free buffer. */25    /* execute action */26    /* execute action functions */27    for (i = 0; i < SMP_NUM_ACTIONS; i++) {28        action = state_table[entry - 1][i];29        if (action < SMP_SM_NO_ACTION) {30            (*smp_sm_action[action])(p_cb, (tSMP_INT_DATA*)p_data);31        } else {32            break;33        }34    }35}
  • 第2行,获取保存的当前状态
  • 第5行,根据设备的role,获取对应的映射表
  • 第11行,根据event值和当前状态,获取映射表元素的值。(状态子表的行数)
  • 第12行,根据是否有mask,选择不同的状态转换表
  • 第14行,选择的是master和slave共用的状态转换表
  • 第16行,根据当前state和role选择对应的状态转换表
  • 第21行,从状态转换子表中查找到next state的状态值保存下来,处理完后就是下次处理前的当前状态(第2行)。
  • 第28行,从状态转换表中查action的enum值。
  • 第30行,根据enum值,查找并执行对应的action函数。

在映射表中,0表示忽略这个事件,需要映射的值从1开始,但是状态转换表的数组是从0开始的,所以在21和28行要做entry-1的操作。
这个状态机的event定义是从1开始的,但是映射表数组是从0开始的,所以也需要做减一操作,第11行。


smp的状态机框架就介绍到这里了。

原创粉丝点击