0%

软件实现状态机要点

在比较复杂一点的软件系统建模的时候,有时需要理清楚系统的状态。这个时候画个系统
的状态机出来就很有帮助。注意对于一个系统,可能同时又好几个状态机,之所以这样,是
因为每个状态机关注的主题是不一样,每个独立的状态机只能保证关注的主题逻辑上是自恰
的。

我们以一个例子说明下。假设我们要为一个设备写一个Linux内核驱动,在驱动里这个设备
被抽象为:

1
2
3
4
5
6
struct test_dev {
int state;
int a;
int b;
int c;
};

这个设备需要初始化,然后配置下才能正常工作,这个设备出错的情况下需要复位这个设备。
所以我们给出的test_dev这个设备的状态有:初始化状态,正常工作状态,复位状态。在
判断可能的状态的时候,如果没有必要就不要新增加状态进来,一个状态机里每新增加一个
状态以后都会成为负担。

有了第一个状态,就可以找可以进入这个状态的激励(event), 比如,这里可能是:设备初始化。
基于第一个状态,分析在这个状态可能接收到的所有激励, 看看在这个状态的test_dev接收
到某个激励后对test_dev采取的动作(action)应该是怎么样的。比如这里对于初始化状态,
可能接收到的event可能有,1. 对test_dev配置,这个动作会使其进入正常工作状态;2.
用户还可以做初始化的逆操作,把这个设备释放掉。

重复对每一个状态做上面的分析。我们可以大概得到一个这样的状态机:
一个状态机示例

接下来我们要针对每一个状态分析是否还有其他的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的时候加锁。