嵌入式系统底层软件的复杂性
问题
经常有人问,为什么嵌入式系统的底层软件,出问题后解决起来,耗时长。
确实,底层软件处理的都是很常见很成熟的设备,比如Flash、以太网、SD卡。 看起来应该不难。
可是大多数项目,又都有前面提到的问题。这是一个难以回答得问题。
结论
先说结论。最重要的原因有三个。第一是代码量非常非常大,第二是没有深入研究,第三是潜在的硬件、协议、软件问题。这三个问题,导致运气好时没有问题,大家都很开心; 万一有问题,就不知道怎么处理,花很长的时间才解决问题。
底层软件
底层软件种类多
底层软件包括的种类多,通常包括boot软件,比如Xilinx FSBL和U-Boot; 还包括Linux内核、设备树、和文件系统。
这些软件,各自有不同的环境、语法。比如在Linux内核中,内存有连续内存和分页内存; 有高端(高地址)内存和低端(低地址)内存,有保留内存和系统内存,有cacheable内存和non-cacheable内存,有hugepage内存。内存的地址,物理地址和虚地址。内存使用方法和standalone的软件,就完全不一样。 同样的设备,在standalone、U-Boot、Linux内核下,需要不同的驱动。
底层软件代码量庞大
底层软件的代码量很大。Xilinx PetaLinux 2022.2中的U-Boot 2022.1,会实际编译大约950个文件,约30万行代码。Xilinx PetaLinux 2022.2中的Linux内核 5.15,会实际编译大约2600个C文件,约200万行代码。后面加载的文件系统,更是包含几千个应用程序,代码量更加庞大。
这种规模的代码量,远大于很多产品的业务软件的代码量。
代码量大后,配置选项也多,很多组合甚至一直没有测试。比如编译Linux内核中的miniconfig,就会出错。
利用开源软件,虽然不用投入人力资源开发这些代码,但是维护这些代码,也需要相当的专业经验,需要相当的工作量和人力资源。
底层软件代码协议复杂
Linux需要适配不同的硬件,不同的应用场景。不管你是否使用,大型机、云计算的代码都在里面。比如嵌入式系统里基本不使用IOMMU,Linux的里也包含IOMMU的代码。
做外设驱动时,经常需要刷新cache。有些硬件系统能由硬件管理cache 一致性;有些硬件系统又没有这个能力。Linux的外设驱动,要能在这两种情况都能工作。
底层软件人力资源少
嵌入式系统的开发,芯片厂商会提供相关的底层软件。项目的底层软件人员根据自己单板的修改,对厂商提供的底层软件进行修改。芯片厂商的turnkey方案,降低了开发难度,各个项目组也减少了分给底层软件的资源,包括人力和时间。迫于压力,底层软件人员只看修改部分,对相关芯片、相关软件的了解不够深入。
底层软件工具少
由于底层软件的开发人员较少,相关软件大部分是开源软件,缺乏商业投资,所以能用于底层软件调试的工具,也比较少,比较贵。
软件成熟度
有些软件特性使用的人少,或者刚刚推出,没有经过实际环境中的大规模、长时间测试,有可能隐藏有问题。 比如Xilinx的Zynq-7000和MPCoC,经过多年上千个项目的验证,目前在客户的使用过程中,问题少,开发都比较顺利。
没有深入研究
大多数项目,只有1-3个工程师负责底层软件。底层软件代码量大,协议多,所以导致没有深入研究相关代码。
硬件接口的工作时序与协议
底层软件正常工作,需要硬件和软件的配合。从严谨的角度出发,开发人员应该先了解硬件接口工作时序、协议,还要了解相关软件环境(U-Boot或Linux内核)提供的资源与限制,最后才研究具体的软件。这些工作都需要大量的人力和时间。比如很多系统使用USB,却几乎没有人读过USB的规范。比如很多系统使用RGMII,也很少有人了解RGMII的时序。如果软件工程师、硬件工程师都没有了解时序与协议,彼此都觉得自己的部分没有问题,找不到解决问题的突破口。
曾经有单板在两个CPU之间使用RGMII链接两个MAC。最后调试的时候,网络一直不能工作。经过长时间调试后,才发现双方都不支持调整RGMII时钟和数据之间delay,才导致通信有问题。
硬件问题
很多时候的软件问题,是有硬件设计或者信号质量导致的问题。有些硬件开发人员既不了解时序,也不测试波形。几年前有项目遇到USB问题2个月后才测试USB的波形,发现波形不符合规范。之前一直希望以软件解决问题,浪费了很多时间。
还有的软件问题,是外设导致的问题。曾经的一个项目,用的是已经在其它项目量产两年的Flash芯片,一直觉得Flash没有问题。后来才发现是Flash芯片的问题。软件清除状态寄存器时,Flash芯片的控制寄存器也被清除,导致Flash的工作模式不对。之前使用这款Flash芯片,只启动,没有写操作,所以没有暴露问题。
思维差异
硬件工程师、底层软件工程师、FPGA工程师面对的工作内容有差异,各自形成了一些思维惯性。
曾经有FPGA工程师说DMA每秒能完成百万次传输。而客户拿到相关设计后,每秒只能能完成12万次传输。开会讨论后,发现FPGA工程师说的是以硬件状态机连续做DMA传输,能达到每秒百万次。客户在Linux下使用DMA,Linux的中断处理延时在4us左右。再加上后续处理和线程调度,每次dma操作需要8us处理。在Linux或者其它OS下,软件即使是休眠0us,大多数也有1-5us的延时,有时更大。评估软件性能,不能以硬件状态机的思维来计算。
曾经有项目发现PCIe的数据传输性能很差。经过仔细检查后,发现软件代码的一个循环里一直查询某个PCIe设备的寄存器,每次查询之间没有其它操作,没有延时。在循环中,增加延时,数据传输的性能恢复正常。软件读本地设备的寄存器很快,但是读外部PCIe、USB设备的寄存器开销很大。这是软件工程师经常忽略的一个地方。
建议
CPU可以说是最复杂的硬件设计之一; Linux是最复杂的软件设计之一; 需要给底层软件足够的重视和资源。
即使不做任何新代码开发,1-3个工程师要想彻底搞清楚200万行代码,也是非常困难的。所以只能针对性的研究。
建议在做单板之前,把需要用到的外设,进行详细、全面的测试。
建议有问题后,软件工程师、硬件工程师坦诚合作,既研究硬件接口工作时序、协议,测量波形,也研究软件工作流程,全面的分析问题。
感言
工作后第一份工作,有幸进入了最严谨的公司。软件工程师会不断做Code review, 硬件工程师主动测试信号测试,彼此之间也会做培训和讲解。 项目开始时有很长时间预研,结束的时候有总结分享,强迫大家深入研究,想偷懒也不行。
当时获得的经验,现在看起来还是非常宝贵,非常有用。