对于嵌入式开发团队来说,更快的嵌入式应用上市时间确实是一个重要的考虑因素。但是,如何开发一种方法来更快地交付应用程序,同时将质量和安全性作为优先事项?在这种情况下,OTA软件更新经理Mender.io背后的产品工程团队经历了为开发Mender的嵌入式客户端和服务器部分选择最佳编程语言的过程。在这个评估过程中,Go、C和C++入围候选名单。本文解释了从这个评估过程中吸取的一些教训,以及为什么最终选择Go开发Mender的客户端嵌入式应用程序。
虽然这可能很主观,但Go对于嵌入式开发来说是一种非常有效的语言。当涉及到网络编程时,尤其如此,网络编程在某种程度上是每个连接的设备或应用程序的一部分。Go是谷歌为满足谷歌开发者的需求而创建的,它的开发主要是为了解决其生态系统中的复杂性爆炸问题。因此,高效编译、高效执行和易于编程是开发Go背后的三个主要原则,因为这三个原则以前并不是在同一种主流语言中都可用。
然而,我们要强调的是,Go不能被视为C的替代品,因为在很多地方都需要C,例如在开发实时操作系统或设备驱动程序时。
嵌入式开发的严格要求
建立架构后,修补程序产品工程团队评估哪种语言最适合开发修补程序应用程序。该系统应该由运行在嵌入式设备上的客户端和作为客户端连接的中心点的服务器组成。因此,我们对语言有几个要求:
由于客户端应用程序将在嵌入式设备上运行,编译后的二进制文件需要尽可能小。
它需要与Linux的嵌入式发行版Yocto一起工作。
安装客户端的复杂性应该很低,从而限制外部依赖性和库。
因为它可以在不同的设备上运行,所以该语言必须在不同的架构上编译。
运行修补程序客户端应用程序的设备将是物联网或联网设备,因此该语言需要访问联网库。
选择新语言的要求还包括一些非功能性要求:
我们组织中尽可能多的程序员需要能够理解这种语言。
在用C编写的现有应用程序之间共享和重用现有代码以及在客户端和服务器应用程序之间重用代码必须尽可能容易。
开发速度也是考虑因素之一——我们面临着快速添加新功能的持续压力。
比较Go与C
Go还因为其极其丰富的标准库而被选中进行嵌入式开发,这使得开发速度更快,尤其是与C相比。Go继承了C、C++和Python的许多元素,包括表达式语法、控制流语句、数据结构、接口、指针、按值传递概念、参数解析语法、字符串处理和垃圾收集。通过其优化的编译器,Go可以在嵌入式设备上自然运行。
Go确实有一些缺点,它们的分量不足以克服它的优势,比如开发速度和语言构建的简易性,但它们确实是我们决策的考虑因素。
Go与C的大小比较
就大小而言,Go比C更笨重,这是它的几个缺点之一。如果你把“helloworld”作为最小的应用程序,你可以在Go中编写并使用内置的println函数,在去掉调试符号后,它的大小刚好超过600kB。如果包含fmt包及其依赖项,那么大小将增加到1.5MB。
与C相比,如果你正在构建一个动态链接库,那么它仅为8kB。如果是静态的,那么大小会增加到800KB以上,这比Go二进制文件还要大得惊人。
Go与C的速度比较
编译的Go代码通常比C可执行文件慢。Go是完全垃圾收集的,这本身会减慢速度。使用C,你可以精确地决定要为变量分配内存的位置,以及是在堆栈上还是在堆上。在Go中,编译器试图对变量的分配位置做出明智的决定。例如,你可以看到变量将被分配到哪里(go build-gcflags-m),但不能强制编译器仅使用堆栈。
然而,说到速度,我们不能忘记编译速度和嵌入式开发人员速度。Go提供了极快的编译速度;例如,15000行Go客户端代码需要1.4秒才能编译。Go非常适合并发执行(goroutines和channel),前面提到的丰富标准库涵盖了大多数基本需求,因此开发速度更快。
汇编和交叉汇编
有两个Go编译器可以使用:最初的一个叫做gc。它是默认安装的一部分,由Google编写和维护。第二个叫做gccgo,是GCC的前身。使用gccgo,编译速度极快,大型模块可以在几秒内编译完成。如前所述,Go默认情况下是静态编译的,因此只创建一个二进制文件,而不需要额外的依赖关系或虚拟机。可以使用-linkshared标志创建和使用共享库。通常,Go不需要构建文件,可以通过一个简单的“Go build”命令进行构建,但也可以将Makefile用于更高级的构建过程。
除此之外,在交叉编译方面,Go支持大量的操作系统和体系结构。
Go中的调试和测试
许多开发人员为了在程序执行时了解程序内部的情况,将使用GDB。对于高度并发的Go应用程序,GDB在调试它们时遇到了一些问题。幸运的是,有一个名为Delve的专用Go调试器更适合于此目的。只熟悉GDB的嵌入式开发人员应该能够在大多数情况下使用它而不会出现任何问题。
将测试和单元测试添加到Go代码中非常容易。测试内置于该语言中,只需创建一个带有“test”后缀的文件,向函数添加测试前缀,导入测试包,然后运行“go test”即可。所有测试都将自动从源代码中提取并相应执行。
并发支持
Go中很好地支持并发。它有两个内置机制:goroutine和channel。goroutine是轻量级线程,大小只有2kB。创建goroutine非常简单:只需在函数前面添加“go”关键字,它就会同时执行。Go有自己的内部调度器,goroutine可以根据需要复用到OS线程中。通道是在gorroutine之间交换消息的“管道”,可以是阻塞的,也可以是非阻塞的。
Go利大于弊
我们使用Go的经验显示了优点和缺点。从负面来说,社区中有很多外部库,但质量参差不齐,你需要非常小心地使用哪种库。随着语言的成熟和被采用,这一点正在大大提高。
当你的Go代码中有一些C绑定时,事情通常会变得更加复杂,特别是交叉编译,如果不导入整个C交叉编译基础结构,就无法轻松完成。
使用Go进行嵌入式开发仍然有很多好处。从C和/或Python转换到Go是快速而简单的,并在几天内实现高效。Go提供了非常好的工具和100多个包的标准库。你可以轻松地设置和控制运行时设置,例如要使用多少OS线程(GOMAXPROCS),或者是否要打开或关闭垃圾收集(GOGC=off)。库和代码可以很容易地在服务器和客户端开发团队之间共享。