请解释一下。如果我知道每个CPU都有不同的编程模型。让我们假设,我有可以执行XYZ汇编指令的CPU。然后我将编译程序并将其发送给我的朋友,其中有没有XYZ程序集指令的CPU。尽管如此,我认为该计划将有效。如何编译程序一般独立于硬件架构?
答案 0 :(得分:3)
当然二进制文件只能在它编译的体系结构的机器上执行。例如,您无法在x86上运行ARM可执行文件。大多数情况下,程序加载器甚至不会让你尝试。
我们能够成功分发二进制版本的软件(对于特定的体系结构,如x86-64),因为处理器必须支持一组基本指令。您的Intel CPU和您的伙伴的AMD CPU仍然实现相同的基本指令集。默认情况下,GCC只会发出属于此类别的说明。
此外,可以允许GCC发出更多专用指令,这些指令属于特定指令集扩展。例如,SSE扩展有各种迭代,它们执行向量数学运算。因为并非所有CPU都实现这些指令,所以必须明确告诉GCC允许它们使用它们。您可以使用-march
和-mtune
标志向GCC执行此操作。 -march=native
将编译一个程序以适合您的计算机,但如果您尝试在另一台具有不同CPU的计算机上运行相同的二进制文件,则可能无效。
虽然如果要编译代码,该策略仍然有效,但我们可以实现一种混合方法。这涉及使用CPUID
指令来确定(在运行时)当前CPU上可用的哪些特征。然后,基于该信息,我们可以调用使用不同指令集扩展的不同函数。这些通常是在汇编中手工编码,或者在C中使用built-ins用于相关架构。
Linux内核实际上也是在运行时执行此操作,尽管通常以更令人印象深刻的方式。关于x86的几个例子:
在SMP系统中,某些指令需要x86 LOCK
前缀,以防止多个CPU同时修改相同的变量。然而,在单处理器(UP)系统上,该指令是不必要的并且减慢了速度。如果为SMP编译的内核确定它在UP系统上运行,它实际上将NOP
- 输出LOCK
指令,使其无效。也就是说,它在顶部写了一条“无操作”指令。
可以为字符串指令提供REP
前缀,以便在内存区域上运行。例如,REP STOSB
可用于实现memset
,而REP MOVSB
可用于memcpy
,依此类推。多年来,各种英特尔处理器在内部实现这些指令的方式大不相同。因此,Linux内核将确定它运行的CPU是否具有“良好”实现(同样基于CPUID
)。如果它是好的,它将使用REP ...
指令。如果没有,则使用备用例程。
使用paravirtualization支持编译的内核也可以为特权指令执行此操作。在裸机上运行的内核能够执行LGDT
指令,而在Xen下运行的内核必须向管理程序提供hypercall才能完成相同的操作。
所有这些功能都通过flags :
中的/proc/cpuinfo
行公开。这些日子很长。