軟體實作狀态機要點
-v0.1 2019.5.3 Sherlock init
在比較複雜一點的軟體系統模組化的時候,有時需要理清楚系統的狀态。這個時候畫個系統
的狀态機出來就很有幫助。注意對于一個系統,可能同時又好幾個狀态機,之是以這樣,是
因為每個狀态機關注的主題是不一樣,每個獨立的狀态機隻能保證關注的主題邏輯上是自恰
的。
我們以一個例子說明下。假設我們要為一個裝置寫一個Linux核心驅動,在驅動裡這個裝置
被抽象為:
struct test_dev {
int state;
int a;
int b;
int c;
};
這個裝置需要初始化,然後配置下才能正常工作,這個裝置出錯的情況下需要複位這個裝置。
是以我們給出的test_dev這個裝置的狀态有:初始化狀态,正常工作狀态,複位狀态。在
判斷可能的狀态的時候,如果沒有必要就不要新增加狀态進來,一個狀态機裡每新增加一個
狀态以後都會成為負擔。
有了第一個狀态,就可以找可以進入這個狀态的激勵(event), 比如,這裡可能是:裝置初始化。
基于第一個狀态,分析在這個狀态可能接收到的所有激勵, 看看在這個狀态的test_dev接收
到某個激勵後對test_dev采取的動作(action)應該是怎麼樣的。比如這裡對于初始化狀态,
可能接收到的event可能有,1. 對test_dev配置,這個動作會使其進入正常工作狀态;2.
使用者還可以做初始化的逆操作,把這個裝置釋放掉。
重複對每一個狀态做上面的分析。我們可以大概得到一個這樣的狀态機:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIwczX0xiRGZkRGZ0Xy9GbvNGL2EzXlpXazxSPJhVZplTMkZnSzkFbKhVWq50MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnLzAzM4IjNzMjMzATNwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
接下來我們要針對每一個狀态分析是否還有其他的event會發生,比如,我們有可能發現
這個裝置驅動對應的fd檔案上有ioctl,那就需要分析在每一種狀态上,這個ioctl進來的
時候,test_dev是怎麼變化的。當然,在某種狀态的時候,我們是可以拒絕執行某種event
的。注意這裡分析的中心還是test_dev的變化,因為我們這裡要建立的是test_dev的狀态機,
我們不能脫離test_dev而去分析比如fd的變化。再比如,我們這個裝置驅動還mmap了一段
mmio空間到使用者态去,使用者态程式可以直接讀寫硬體寄存器,在test_dev的狀态機中也不應
該去考慮使用者态程式讀寫硬體寄存器這樣的event,因為test_dev的狀态機根本處理不了
這樣的情況。針對使用者态程式讀寫硬體寄存器這樣的event,我們要單獨分析。test_dev
狀态機需要考慮的是test_dev結構裡各個成員的變化。
完成對所有狀态在全部event下的分析,我們的狀态機就基本上建完了。剩下就是具體實作
的問題。實作中,要求所有對test_dev的action是原子的,也就是說狀态變遷的過程是不能
被打斷的,不然,可以看到我們會陷入各種各樣同步帶來的問題中。
還是看上面的圖,從初始狀态到正常工作狀态的切換如果不是原子的,那麼在這個過程中,
如果reset event來了,整個系統的狀态将如何切換?如果,configure action裡包括很多
步驟,而reset event可以在任何步驟到來,那麼這個切換的分析工作将變得非常複雜。
狀态切換變成原子操作将避免這樣的情況發生,使得整個系統的狀态變得可控。狀态切換
是原子行為時,如果test_dev正從初始狀态切換到工作狀态,那麼中間到來的reset event
将不得不排隊,之後再在一個明确的狀态響應reset event。
保證原子操作,一個簡單的辦法就是執行action的時候加鎖。