跨编译或为CPU架构本地编译

4

在编写特定于 CPU 架构的软件时,例如运行在 x86 或 ARM CPU 上的 C 代码,通常有两种编译这些代码的方式,一种是交叉编译到 ARM CPU 架构(例如在 x86 系统上开发),或将代码复制到本机架构 CPU 系统并进行本地编译。

我想知道本地方法与交叉编译方法之间是否有优劣之处?我注意到 Fedora ARM 团队正在使用一组慢速/低功耗 ARM 设备的构建服务器集群来“本地”编译他们的 Fedora ARM 版本... 肯定由 Red Hat 支持的项目可以访问一些运行 x86 CPU 的强大构建服务器,可以在一半的时间内完成工作... 那么他们的选择是为什么?通过交叉编译软件,我是否错过了什么重要的东西?


如果你认为C代码是CPU体系结构相关的,那么你肯定错过了什么。我认为你会发现许多优秀的开发者会尝试将与实现相关的代码与可移植、严格符合要求的代码分离,并且通常会尽可能地编写可移植的代码。 - autistic
1
@undefinedbehaviour 我认为他的问题实质上是关于在构建复杂软件包时是否会出现遗漏的情况。 - Ahmed Masud
2
我的猜测是他们想测试机器是否能够为自己构建工作二进制文件。如果你需要购买一个Linux机器,却发现你还需要购买一台PC来构建/更新内核和二进制文件,那将会很麻烦。这也是测试所构建的操作系统稳定性的一种非常好的方式,完整的编译可以测试多个系统。 - Joachim Isaksson
针对此问题,我特别指的是与CPU架构相关的软件,例如gcc -march=armv6 - SnakeDoc
@JoachimIsaksson 说得好;虽然你可以使用 qemu 进行测试。但是这时候过程的缓慢就会成为一个问题。 - artless noise
5个回答

4
主要优点是在本地运行时,所有的./configure脚本都不需要进行调整。如果您使用的是阴影rootfs,那么仍然需要运行uname来检测CPU类型等配置。例如,请参见this questionpkgconfig和其他工具尝试简化交叉编译,但软件包通常先在x86上正确进行本机构建,然后可能在ARM上进行本机构建交叉编译可能很痛苦,因为每个软件包可能需要单独进行调整。

最后,如果您正在进行基于配置文件的优化并运行测试套件根据Joachim,在交叉构建环境中几乎不可能完成此操作。

编译速度在ARM上比人工软件包构建者快得多,读取configure,编辑configure,重新运行configure、编译和链接循环。
这也非常适合持续集成策略。各种软件包,特别是,可以快速构建/部署/测试。测试可能涉及数百个依赖软件包。当升级和修补基本库时,Arm Linux分发通常需要原型更改,这些更改可能有数百个依赖软件包,至少需要重新测试。由计算机完成的慢周期总是比快速编译后进行手动干预要好。

2
编译本地代码的唯一好处是不需要将程序转移到目标平台,因为它已经在那里了。
然而,考虑到大多数目标平台与现代x86 PC相比性能都极低,这并不是一个很大的优势。在PC上,更多的内存,更快的CPU和尤其是更快的磁盘使得编译时间比目标平台快得多。以至于本地编译的优势实际上已经不再是优势了。

那么最终的结果本质上是相同的二进制文件吗?所以我不必担心尝试寻找一个ARM构建设备来编译我的软件,因为除了能够说“我的软件在ARM上本地编译”之外,没有其他好处。 - SnakeDoc
2
@SnakeDoc 如果交叉编译器设置正确,生成的二进制文件将完全没有任何差异。 - Some programmer dude

2
在 .c -> .o -> a.out(或其他)的环境下进行交叉编译并不会缺少任何技术上的东西;交叉编译将会给您提供与本地编译器相同的二进制文件(版本等除外)。
“优点”来自于编译后的测试和复杂系统的管理。
1)如果我可以在编译后快速运行单元测试,那么我可以快速找到任何错误/问题,这个周期可能比交叉编译周期更短;
2)如果我正在编译一些使用第三方库的目标软件,那么在本地平台上构建、部署然后使用它们来构建我的目标可能会更容易;我不想处理那些由疯狂的猴子编写的构建过程使得交叉编译变得困难的库的交叉编译构建。
通常情况下,大多数人都会尝试先进行基本构建,然后本地编译其余部分。除非我的交叉编译器非常快,我节省的时间值得花费在使其余事物(例如单元测试和依赖项管理)更容易的设置上。
至少这是我的想法。

1
这很大程度上取决于编译器。工具链如何处理本地和交叉编译之间的差异。它是否仅仅是工具链始终认为正在构建交叉编译器,但一种构建方法是让配置脚本自动检测主机而不是手动执行(并自动设置前缀等)?
不要假设仅因为它被构建为本地编译器就真的是本地的。有许多情况下,发行版会将其本地编译器(以及内核和其他二进制文件)降级,以便该发行版在更广泛的系统上运行。例如,在ARMv6系统上,您可能正在运行默认为ARMv4的编译器。
这引出了类似于您自己的问题,如果我使用一个默认架构来构建工具链,然后指定另一个架构,是否与为目标架构构建工具链不同?
理想情况下,经过大部分调试的编译器/工具链应该能够在本地或交叉编译时给出相同的结果,并且与默认架构无关。现在我已经看到过旧版的llvm上,当在64位主机上运行llvm-gcc进行arm交叉编译时,所有的int都会被编译成64位,从而增加了很多代码量。同样的编译器版本,在32位主机上编译相同的源代码会得到不同的结果(32位int)。基本上,-m32开关在llvm-gcc中不起作用(当时),我不知道现在是否仍然是这种情况,因为我在做llvm工作时切换到了clang,从未回头看过llvm-gcc...例如,llvm/clang几乎总是一个交叉编译器,只有链接器似乎是特定于主机的,您可以使用现成的llvm为任何目标在任何主机系统上编译(前提是您的构建没有禁用任何支持的目标,当然)。

1

虽然很多人认为“本地编译”更有益或者至少与“交叉编译”没有区别,但事实恰恰相反。

对于从事较低级别工作的人,比如Linux内核,他们通常会遭受复制编译平台的困扰。以x86和ARM为例,直接想法是构建ARM编译基础,但这是一个不好的想法。

二进制文件有时是不同的,例如,

# diff hello_x86.ko hello_arm.ko
Binary files hello_x86.ko and hello_arm.ko differ
# diff hello_x86_objdump.txt hello_arm_objdump.txt
2c8
< hello_x86.ko:     file format elf64-littleaarch64
---
> hello_arm.ko:     file format elf64-littleaarch64
26,27c26,27
<    8: 91000000        add     x0, x0, #0x0
<    c: 910003fd        mov     x29, sp
---
>    8: 910003fd        mov     x29, sp
>    c: 91000000        add     x0, x0, #0x0

通常较高级的应用程序可以同时使用两者,建议在较低级别(与硬件相关)的工作中使用 x86“交叉编译”,因为它具有更好的工具链。

总之,编译是关于 GCC Glibc 和 lib.so 的工作,如果熟悉这些,任何一种方式都应该很容易。

PS:以下是源代码

# cat hello.c
#include <linux/module.h>      /* Needed by all modules */
#include <linux/kernel.h>      /* Needed for KERN_ALERT */
#include <linux/init.h>        /* Needed for the macros */



static int hello3_data __initdata = 3;


static int __init hello_3_init(void)
{
   printk(KERN_ALERT "Hello, world %d\n", hello3_data);
   return 0;
}


static void __exit hello_3_exit(void)
{
   printk(KERN_ALERT "Goodbye, world 3\n");
}


module_init(hello_3_init);
module_exit(hello_3_exit);

MODULE_LICENSE("GPL"); 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接