3.318. 无价之宝——一个设计仿写的例子
最近评审了一个OS和BIOS的接口设计,我感觉基础定义不合理。我大致总结一下这些我感受不合理的地方:
它使用一个寄存器传递参数的二进制接口,然后用描述C函数的方式描述这个接口,但这个二进制接口有两个返回值(我们知道C语言函数调用只有一个返回值)。我感觉这两个二进制作为返回值的寄存器,其实有一个是用作C函数的返回值,另一个类似Unix的errno,用来统一表示错误分类的。
但在一个检查某个服务是否被支持的接口中——我们这里假设叫check_available(service_id)吧,它的返回值是0,而用错误码表示这个服务是否被支持,这又违背了errno的使用pattern。
errno这样的设计导致了所有错误码都统一编码,但相应的错误在每个请求中其实不共享。比如请求CPU复位的错误,和取版本号这个错误,错误码是在一个空间里面编码的,这样毫无意义。
Unix系统中使用errno有它的道理,因为Unix系统大部分时候errno确实是在同一个空间中使用的。比如你调用malloc,malloc调用了futex,那么malloc报的错可能是它自己报的,也可能是futex报的,使用一个统一的编码就可以定位到错误出现在哪一层了。你一个OS和BIOS间的接口又没有这样的关系,为什么需要这样定义?
我把这些问题提出来的时候,有人告诉我,其实这个接口是仿OpenSBI的,OpenSBI的设计就是这样的。
于是我去看了一下OpenSBI,我的调查结果是这样的:
OpenSBI确实是用了一个C的接口,但它真的明确的基于C的ABI来定义的。它的参数就是a0-a7,和RISCV的Call Convension是一样的。只是补充了a6,a7用来做扩展名和扩展中的功能参数。返回值也是按C标准叫a0,但扩展了a1,以便返回errno。所以OpenSBI确实可以用C的方法来描述接口,因为我们可以直接用C的表达直接对应到二进制接口的全部行为上。
OpenSBI确实用了errno的模式,这个我认为其实是不合理的。我个人认为长远维护下去,维护者估计也会看着不爽。不过一般来说OS-BIOS接口没几个人用,感受到不爽的人也不会太多。这算是个小瑕疵吧。
OpenSBI那个检查服务是否支持的功能叫sbi_probe_extension(),人家确实是用返回值来表示服务是否存在,而不是用errno来表示服务是否存在的。更关键的一个问题是,RISCV是一个以大量扩展为基础的方案,所以它这类检查必须以扩展为单位。而我们现在谈的这个方案完全不是这回事,这里用服务ID再分功能的方式,其实没什么道理。
其实我看完这个OpenSBI的接口后,我确实发现我评审的这个方案几乎是像素级在复刻OpenSBI的设计。但我依然认为OpenSBI的设计算不上太好,但还是质量在线的。而上面那个设计的质量,我在直观感觉上认为是不在线的。
“质量不在线”是个“感受”而不是理性的逻辑分析。作为一个经常对人家的设计有一堆意见的架构师,我希望通过写这个总结反思一下,我是不是过于苛刻了。
从直觉想逻辑,我觉得作为一个架构师,如果我允许一个这样设计放在系统中,那么后面的设计也会复刻前面设计的风格,而这种风格在整个系统中一传递,这个系统后面就很难维护了。
那么,这个“风格”是什么呢?
我觉得主要还是“无目标导致的无规律”。比如,它也用r6和r7作为扩展ID和功能ID,但OpenSBI用的是a6和a7啊大哥,a6和a7是RISCV ABI接口中的X16和X17啊。人家是有目的的,选择了所有的输入参数的最后两个作为输入。你这里用顺序的第六和第七个寄存器作为输入,这个选择就莫名其妙了。
然后就是前面说的这个检查服务是否存在的调用,设计成那样(用错误码当作返回值用),明显就是没有搞清楚errno的真正作用所以才是这样的。
这样,你从这个接口上看不到设计选择的明确目的,那么以后补充其他设计的时候,后面的人就不知道如何取舍了。
每个设计决定,本质都是一个限制(“第一个参数放在R0中”,就拒绝了“第一个参数放在R1中”这个选择,所以设计本质是限制),所以每个设计决定都是一个代价(失去自由的代价)。我们把很多的设计决定综合在一起的时候,需要综合出所有设计决定的自由空间的交集,这就会产生冲突,要消弭冲突,就要取舍。这时,代价成本就是这些设计决定“谁后退,谁保持”的判断依据了。没有设计目的的设计决定,我称为“无价之宝”,又叫“铁索横江”,你不敢动他,因为复杂了以后你根本不知道会导致什么问题。系统中有一堆这种“无价之宝”,这个系统就没法维护了。
所以,一旦在设计初期就有大量这种原因都搞不清楚,就是在简单复刻别人的设计的设计,它就是危险的,我本能会反对它。
这样想一想,我这个直觉还是有逻辑的。
只要不侵犯版权,而且可以达成设计目标,复刻别人的设计是个安全的做法,这我认为应该鼓励。但复刻设计是复刻经验(所达成的目标),不能复刻样子。