基于SNL的狀態(tài)機

snl是面向狀態(tài)的語言,作為一門語言雾鬼,當(dāng)然有自己的詞法語法解析萌朱,這不是本文的范疇。同時策菜,基于snl語言和epics-base開發(fā)的sequencer晶疼,集成到epics support中,給epics應(yīng)用的流程控制提供支持又憨。sequencer在ca框架下翠霍,屬于client的部分,能獨立運行蠢莺,需要創(chuàng)建上下文寒匙、信道等慣例操作。

基本概念

  • program
    一個流程控制的完整程序躏将,也可能是按功能模塊劃分后的獨立完整程序锄弱。

  • state set(ss)
    通常一個program由一個或多個ss狀態(tài)集組成考蕾,這是一種邏輯上的劃分。在seq啟動的時候会宪,一個ss會分配一個線程肖卧,分別獨立的運行下去。

  • state
    ss內(nèi)部包含一個或多個state掸鹅,所有狀態(tài)組合形成了一個ss塞帐,在任何時候,一個ss內(nèi)只會停留在一個state狀態(tài)河劝,和普通狀態(tài)機相同。

基本語法

本文只針對sequencer結(jié)構(gòu)矛紫,詳盡的語法可以參考網(wǎng)站https://www-csr.bessy.de/control/SoftDist/sequencer/index.html赎瞎。

嵌入式代碼

sequencer是基于snl語法的,是一種c-like的代碼颊咬,而且可以調(diào)用嵌入的c代碼务甥,形式上是包含在百分號+括號內(nèi),如下所示喳篇。

{
    #action代碼
    %{
        printError(ErrId, ErrLevel, FILE_AND_LINE_STRING, ": LayerSwitch\n");
    }%
}

同時也可以先聲明后調(diào)用:

%{
    epicsInt32 transINT32(double value, double norminal)
    {
        return (epicsInt32)(((pow(2, 30) - 1) * value) / norminal);
    }
}%

{
    #action代碼
    val = transINT32(6, 2.0);
}

需要特別注意的是敞临,涉及pv的操作,要傳入?yún)?shù)ssid麸澜,表示這個操作是在哪個狀態(tài)集中完成的挺尿。ssid可以通過pvIndex獲取,該調(diào)用不能嵌入到c代碼中炊邦。
官方宣傳是可以任何c/c++代碼的编矾,但本人在實際運用過程中,發(fā)現(xiàn)部分高級的是不支持的馁害,索性直接用native的c代碼好了窄俏,歡迎批評。

狀態(tài)機

作為狀態(tài)機碘菜,基本的要素包括:狀態(tài)凹蜈、事件、行為忍啸,簡單的描述就是在某種狀態(tài)仰坦,遇到某件事情,觸發(fā)某個動作计雌,并轉(zhuǎn)移到另一狀態(tài)缎岗。
下面的代碼簡單描述了snl狀態(tài)機。

ss state_set_name
{
    state state1
    {
        entry
        {
            //action
        }
        
        when (event)
        {
            //action
        } state state2
        
        when (event)
        {
            //action
        } state state3
        
        exit
        {
            //action
        }
    }

    state state2
    {
        ...
    }
}
  • state 括號內(nèi)包含完成狀態(tài)描述白粉,在該狀態(tài)下需要監(jiān)聽的事件传泊,以及需要完成的動作鼠渺;
  • entry 進入狀態(tài)時的行為,一般作為條件的初始化眷细;
  • exit 退出狀態(tài)之前的行為拦盹,對應(yīng)的完成資源的釋放等;
  • when 條件判定溪椎,所有的when會順序執(zhí)行普舆,直到某個條件滿足跳轉(zhuǎn)到對應(yīng)state;
  • event 需要判定的條件校读,可以使某個變量沼侣,某個pv值,某個evtflag歉秫;
  • action 具體的行為蛾洛,可調(diào)用sequencer提供的各種接口;
事件綁定

通常聲明變量雁芙、pv關(guān)聯(lián)轧膘、事件關(guān)聯(lián)等操作如下:

int reqpowersupply; //聲明變量
assign reqpowersupply to "G-ACS-PS:REQ-MRW"; //綁定pv
monitor reqpowersupply; //監(jiān)視pv值,及時改變變量值
evflag powersupplyevt; //聲明事件
sync reqpowersupply powersupplyevt; //綁定事件

這樣一個關(guān)聯(lián)pv的事件就定義好了兔甘,pv變量值得改變會直接改變事件變量谎碍,從而用efTest判定事件達成與否。相應(yīng)地洞焙,efSet可以直接改變事件變量的值蟆淀,efClear清除變量的值。

以上采用sync的方式綁定事件和變量澡匪,如果對于變化較快的pv值扳碍,可能無法及時處理事件的變化。因此使用syncQ將每次改變暫存在隊列仙蛉,需要的時候pvGetQ從隊列頭依次取出笋敞。

連接管理

通常在執(zhí)行證實邏輯前,需要判定pv連接情況荠瘪,包括分配數(shù)量夯巷、連接數(shù)量等。例如:

when (pvConnectCount() == pvAssignCount())
{
  ...
}
when (pvConnectCount() == pvChannelCount())
{
  ...
}

基本流程

編譯過程

預(yù)編譯

snc編譯器讀取.st或.stt文件哀墓,進行詞法趁餐、語法解析,編譯生成.c代碼篮绰。本文不對編譯過程和原理進行剖析后雷,僅對生成結(jié)果進行解析。

  • program,整個sequencer的主體結(jié)構(gòu)臀突,一切要素的入口勉抓。
/* Program table (global) */
seqProgram psctrl = {
    /* magic number */      2002005,
    /* program name */      "psctrl",
    /* channels */          seqg_chans,
    /* num. channels */     5829,
    /* state sets */        seqg_statesets,
    /* num. state sets */   2,
    /* user var size */     0,
    /* param */             "",
    /* num. event flags */  0,
    /* encoded options */   (0 | OPT_CONN | OPT_NEWEF),
    /* init func */         seqg_init,
    /* entry func */        0,
    /* exit func */         0,
    /* num. queues */       0
};

  • 變量聲明
/* Variable declarations */
# 全局變量
# line 29 "../psctrl.st"
static  char tempstr[100];

# ss局部變量
struct seqg_vars_pscheck {
# line 1418 "../psctrl.st"
    int i;
} seqg_vars_pscheck;

# state局部變量
struct seqg_vars_psctrl {
# line 266 "../psctrl.st"
    struct {
# line 603 "../psctrl.st"
        int initall_dcon;
# line 604 "../psctrl.st"
        int initall_start;
# line 605 "../psctrl.st"
        int initall_done;
    } seqg_vars_st_InitAll;
} seqg_vars_psctrl;
  • channel表(綁定到pv的變量)
/* Channel table */
static seqChan seqg_chans[] = {
    /* chName, offset, varName, varType, count, eventNum, efId, monitored, queueSize, queueIndex */
    {"", (size_t)&temppointlist[0], "temppointlist[0]", P_INT, 1, 1, 0, 0, 0, 0},
    {"G-ACS-SNS:RNUM-MRW", (size_t)&room_number, "room_number", P_INT, 1, 5002, 0, 1, 0, 0},
};
  • ss表,包含名字候学、狀態(tài)數(shù)量藕筋,以及指向狀態(tài)表的指針。
/* State set table */
static seqSS seqg_statesets[] = {
    {
    /* state set name */    "psctrl",
    /* states */            seqg_states_psctrl,
    /* number of states */  16
    },

    {
    /* state set name */    "pscheck",
    /* states */            seqg_states_pscheck,
    /* number of states */  2
    },
};
  • 狀態(tài)表梳码,包含狀態(tài)名隐圾、以及經(jīng)典的狀態(tài)機三要素。
/* State table for state set "pscheck" */
static seqState seqg_states_pscheck[] = {
    {
    /* state name */        "st_Init",
    /* action function */   seqg_action_pscheck_1_st_Init,
    /* event function */    seqg_event_pscheck_1_st_Init,
    /* entry function */    seqg_entry_pscheck_1_st_Init,
    /* exit function */     0,
    /* event mask array */  seqg_mask_pscheck_1_st_Init,
    /* state options */     (0)
    },
    {
    /* state name */        "st_Monitor",
    /* action function */   seqg_action_pscheck_1_st_Monitor,
    /* event function */    seqg_event_pscheck_1_st_Monitor,
    /* entry function */    seqg_entry_pscheck_1_st_Monitor,
    /* exit function */     0,
    /* event mask array */  seqg_mask_pscheck_1_st_Monitor,
    /* state options */     (0)
    },
};
  • 狀態(tài)處理函數(shù)
    seqg_entry_進入到某個狀態(tài)時執(zhí)行掰茶,對應(yīng)語法為entry暇藏。其中SS_ID參數(shù)為當(dāng)前所在ss的id,程序內(nèi)部自動獲取濒蒋,用戶不應(yīng)關(guān)心盐碱。對應(yīng)的有exit的處理函數(shù),本文未使用啊胶。
/* Entry function for state "st_Init" in state set "pscheck" */
static void seqg_entry_pscheck_1_st_Init(SS_ID seqg_env)
{
# line 1421 "../psctrl.st"
    printf("start ps check sequence\n");
}

seqg_event_執(zhí)行條件檢查時需要甸各,對應(yīng)語法為when垛贤。其中seqg_ptrn表示將要執(zhí)行action的第幾個case焰坪,seqg_pnst表示將要跳轉(zhuǎn)到第幾個狀態(tài)。均為按照出現(xiàn)先后聘惦,從0計數(shù)某饰。每個if為每個when的條件判定,從語法上也可以推斷出各個條件之間有先后順序善绎。

/* Event function for state "st_Init" in state set "pscheck" */
static seqBool seqg_event_pscheck_1_st_Init(SS_ID seqg_env, int *seqg_ptrn, int *seqg_pnst)
{
# line 1424 "../psctrl.st"
    if (seq_pvConnectCount(seqg_env) == seq_pvAssignCount(seqg_env))
    {
        *seqg_pnst = 1;
        *seqg_ptrn = 0;
        return TRUE;
    }
# line 1427 "../psctrl.st"
    if (seq_delay(seqg_env, 1.0))
    {
        *seqg_pnst = 0;
        *seqg_ptrn = 1;
        return TRUE;
    }
    return FALSE;
}

seqg_action為各個條件下所需要執(zhí)行的行為黔漂,各個case對應(yīng)上文event不同的if分支。

/* Action function for state "st_Init" in state set "pscheck" */
static void seqg_action_pscheck_1_st_Init(SS_ID seqg_env, int seqg_trn, int *seqg_pnst)
{
    switch(seqg_trn)
    {
    case 0:
        {
        }
        return;
    case 1:
        {
        }
        return;
    }
}
編譯

標準的gcc編譯流程禀酱,不在贅述炬守。

初始化流程

  • iocsh解析
    sequencer通過ioc命令行啟動,執(zhí)行seq programName即可剂跟,可直接在ioc啟動時執(zhí)行减途。

    程序啟動時,調(diào)用注冊到iocsh的seqCallFunc曹洽,解析第一個參數(shù)為program/threadID鳍置,并且正式調(diào)用seq函數(shù),傳入包含該名字的seqProgram結(jié)構(gòu)送淆。

  • 注冊Program
    首先會向sequencerProgram結(jié)構(gòu)體注冊當(dāng)前ProgramseqProgram,并創(chuàng)建Program的實例program_instanceprogram_instance可以看做seqProgram的具象肄满,不僅包含前者的靜態(tài)數(shù)據(jù),還包含運行時分配的動態(tài)數(shù)據(jù)撞羽。例如動態(tài)assign數(shù)量、連接數(shù)量梧兼、monitor數(shù)量放吩、讀寫請求隊列等。

  • 初始化
    首先是給evFlags分配空間羽杰,bitMask類型的數(shù)組渡紫,用于事件達成的判定。然后給綁定的變量通道syncedChans分配空間考赛,對于通過syncQ方式綁定的事件惕澎,還會創(chuàng)建相應(yīng)數(shù)量的隊列。

    緊接著颜骤,給狀態(tài)集數(shù)組分配空間唧喉,有多少個ss就分配幾倍的state_set空間。分配好之后忍抽,就需要對每個狀態(tài)集進行初始化八孝。主要是對每個channel讀/寫請求的結(jié)構(gòu)創(chuàng)建,以及對應(yīng)的數(shù)據(jù)空間pv_meta_data(包括時間戳鸠项、狀態(tài)干跛、嚴重程度以及錯誤消息)的創(chuàng)建。當(dāng)然祟绊,channel本身的空間CHAN也是需要分配和初始化的楼入。

    在初始化過程中,有三個事件id非常重要牧抽。一個是用于時間同步的信號量嘉熊,一個是所有通道連接已建立的標志,一個是ss退出的標記扬舒。

  • 主線程
    初始化工作完成以后阐肤,會激發(fā)一個線程啟動主工作線程,其入口函數(shù)是sequencer讲坎。

    首先孕惜,將program添加到program列表(一般而言,一個足以)衣赶。然后創(chuàng)建ca上下文诊赊,這點類似于epics框架下的client。

    接著府瞄,會觸發(fā)當(dāng)前這個program的initFunc碧磅,該函數(shù)在snc編譯時自動生成碘箍。如下:

/* Program init func */
static void seqg_init(PROG_ID seqg_env)
{
}

接著,會對ss狀態(tài)集中每個變量進行初始化鲸郊。其中snc生成的變量按各個state區(qū)分開的丰榴,如下:

struct seqg_vars_psctrl {
# line 266 "../psctrl.st"
    int i;
# line 266 "../psctrl.st"
    int j;
# line 266 "../psctrl.st"
    int x;
    struct {
# line 603 "../psctrl.st"
        int initall_dcon;
# line 604 "../psctrl.st"
        int initall_start;
# line 605 "../psctrl.st"
        int initall_done;
    } seqg_vars_st_InitAll;
}seqg_vars_psctrl;

其中ij秆撮,x聲明在ss四濒,initall_dconinitall_start职辨,initall_done聲明在狀態(tài)st_InitAll里面盗蟆,作用域會有所不同。

接著舒裤,對所有的pv進行connect喳资,這步操作過程可參考《CA工作機制》。而線程會循環(huán)等待腾供,直到所有結(jié)果返回仆邓。如果狀態(tài)錯誤,那么會進入退出sequencer流程伴鳖,斷掉連接节值,釋放資源等。

接著榜聂,如果program有entryFunc就會執(zhí)行搞疗,不過一般entryFuncexitFunc均為空。當(dāng)然也可以利用這一時機峻汉,處理很多邏輯上的資源分配贴汪、釋放問題脐往。

最后休吠,為每個ss激發(fā)線程,使其在獨立線程中運行业簿,而主線程會直接接管第一個ss瘤礁,并跳轉(zhuǎn)到入口ss_entry。這是整個狀態(tài)機的主循環(huán)梅尤,處理ss所有的事件信息柜思。

主循環(huán)

主循環(huán)是游戲里面的概念,邏輯上是個死循環(huán)巷燥,在循環(huán)體內(nèi)每次都要更新信息赡盘、處理事件等。在sequencer中缰揪,也是在while大循環(huán)中更新pv數(shù)據(jù)陨享、處理事件請求,并執(zhí)行相應(yīng)的狀態(tài)跳轉(zhuǎn)。

  • ss的狀態(tài)切換成當(dāng)前state抛姑,主要是狀態(tài)掛載的eventMask信息赞厕,這是個多比特位數(shù)據(jù),用于事件觸發(fā)判定定硝;
  • 判定是否有狀態(tài)切換皿桑,并進入狀態(tài)的entry,執(zhí)行入口函數(shù)蔬啡;
  • 對所有未處理完成的pv事件進行一次flush诲侮,即強制發(fā)送所有緩存的pv請求。這樣保證在實際進入狀態(tài)前箱蟆,所有pv已處理完畢浆西;
  • 觸發(fā)同步事件信號,喚醒所有需要同步的變量操作顽腾;
  • 進入小循環(huán)近零,等待evt事件或者超時。一般包括pv的get/put/monitor抄肖、連接改變久信,以及evtFlag的set和clear等,當(dāng)然ss的退出等也會觸發(fā)信號量的而改變漓摩。
    ① 根據(jù)臟標記裙士,將所有變化的pv,從ca通道拷貝到sequencer變量管毙;
    ② 重新設(shè)置超時時間腿椎;
    ③ 檢查當(dāng)前狀態(tài)的when條件是否滿足,打上觸發(fā)標記夭咬,等待執(zhí)行啃炸;
    ④ 重置綁定pv事件的標記,等待新的事件卓舵;
    ⑤ 如果有觸發(fā)標記南用,那么跳出小循環(huán),執(zhí)行后續(xù)操作掏湾;否則繼續(xù)小循環(huán)裹虫;
  • 執(zhí)行觸發(fā)標記對應(yīng)的action,執(zhí)行后跳轉(zhuǎn)到對應(yīng)的state融击;發(fā)生狀態(tài)切換前筑公,會執(zhí)行exit方法,完成可能需要的清理尊浪;
  • 如果有dead標記發(fā)生匣屡,則會退出主循環(huán)涩拙;

總結(jié)下來一句話,基于snl的sequencer耸采,依賴MainLoop處理各種事件和變量的更新兴泥,狀態(tài)機通過狀態(tài)表和指針的切換完成。整體結(jié)構(gòu)和實現(xiàn)細節(jié)虾宇,還是十分值得深入學(xué)習(xí)的搓彻。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者嘱朽。
  • 序言:七十年代末旭贬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子搪泳,更是在濱河造成了極大的恐慌稀轨,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岸军,死亡現(xiàn)場離奇詭異奋刽,居然都是意外死亡,警方通過查閱死者的電腦和手機艰赞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門佣谐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人方妖,你說我怎么就攤上這事狭魂。” “怎么了党觅?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵雌澄,是天一觀的道長。 經(jīng)常有香客問我杯瞻,道長镐牺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任又兵,我火速辦了婚禮任柜,結(jié)果婚禮上卒废,老公的妹妹穿的比我還像新娘沛厨。我一直安慰自己,他們只是感情好摔认,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布逆皮。 她就那樣靜靜地躺著,像睡著了一般参袱。 火紅的嫁衣襯著肌膚如雪电谣。 梳的紋絲不亂的頭發(fā)上秽梅,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音剿牺,去河邊找鬼企垦。 笑死,一個胖子當(dāng)著我的面吹牛晒来,可吹牛的內(nèi)容都是我干的钞诡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼湃崩,長吁一口氣:“原來是場噩夢啊……” “哼荧降!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起攒读,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤朵诫,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后薄扁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剪返,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年邓梅,在試婚紗的時候發(fā)現(xiàn)自己被綠了随夸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡震放,死狀恐怖宾毒,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情殿遂,我是刑警寧澤诈铛,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站墨礁,受9級特大地震影響幢竹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恩静,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一焕毫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧驶乾,春花似錦邑飒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至风科,卻和暖如春撒轮,著一層夾襖步出監(jiān)牢的瞬間乞旦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工题山, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留兰粉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓顶瞳,卻偏偏與公主長得像亲桦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子浊仆,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容