什么会导致 Linux 中的通用 x86 用户态应用程序出现 SIGBUS(总线错误)?我能在网上找到的所有讨论都是关于内存对齐错误的,据我所知,这并不真正适用于 x86。
(我的代码在Geode上运行,以防那里有任何相关的特定于处理器的怪癖。)
SIGBUS
除了内存对齐错误之外,在 Linux 中可能会发生很多原因 - 例如,如果您尝试访问mmap
超出映射文件末尾的区域。
您是否使用类似mmap
、共享内存区域或类似的东西?
如果打开未对齐访问陷阱,则可以从未对齐访问中获取 SIGBUS,但通常在 x86 上是关闭的。如果出现某种错误,您也可以通过访问内存映射设备来获取它。
您最好的选择是使用调试器来识别错误指令(SIGBUS 是同步的),并尝试查看它试图做什么。
x86(包括 x86_64)Linux 上的 SIGBUS 是一种罕见的野兽。它可能出现在尝试访问mmap
ed 文件末尾之后,或者 POSIX 描述的其他一些情况下。
但是由于硬件故障,要获得 SIGBUS 并不容易。也就是说,来自任何指令的非对齐访问——无论是否是 SIMD——通常都会导致 SIGSEGV。堆栈溢出导致 SIGSEGV。即使访问非规范形式的地址也会导致 SIGSEGV。所有这一切都是由于#GP 被提出,它几乎总是映射到 SIGSEGV。
现在,由于 CPU 异常,这里有一些获取 SIGBUS 的方法:
启用 AC 位EFLAGS
,然后通过任何内存读取或写入指令进行非对齐访问。有关详细信息,请参阅此讨论。
通过堆栈指针寄存器(rsp
或rbp
)执行规范违规,生成#SS。这是 GCC 的示例(使用 编译gcc test.c -o test -masm=intel
):
主函数() { __asm__("mov rbp,0x400000000000000\n" "mov rax,[rbp]\n" "ud2\n"); }
哦,是的,还有另一种获取 SIGBUS 的奇怪方法。
如果内核由于内存压力(必须禁用 OOM 杀手)或失败的 IO 请求而无法在代码页中分页,SIGBUS.
这在上面被简单地称为“失败的 IO 请求”,但我将对其进行一些扩展。
一个常见的情况是当您使用 ftruncate 懒惰地增长文件,将其映射到内存中,开始写入数据,然后用完文件系统中的空间。映射文件的物理空间在页面错误时分配,如果没有剩余空间,则进程接收 SIGBUS。
如果您需要您的应用程序正确地从这个错误中恢复,那么在 mmap 之前使用 fallocate 显式保留空间是有意义的。在 fallocate 调用之后处理 errno 中的 ENOSPC 比处理信号要简单得多,尤其是在多线程应用程序中。
You may see SIGBUS when you're running the binary off NFS (network file system) and the file is changed. See https://rachelbythebay.com/w/2018/03/15/core/.
如果您请求由带有mmap
和MAP_HUGETLB
标志的大页面支持的映射,您可以获得SIGBUS
内核是否用完分配的大页面,因此无法处理页面错误。
In this case, you'll need to raise the number of allocated huge pages via
/sys/kernel/mm/hugepages/hugepages-<size>/nr_hugepages
or/sys/devices/system/node/nodeX/hugepages/hugepages-<size>/nr_hugepages
on NUMA systems.x86 Linux 上总线错误的一个常见原因是试图取消引用不是真正指针的东西,或者是野指针。例如,未能初始化指针,或将任意整数分配给指针然后尝试取消引用它通常会产生分段错误或总线错误。
对齐确实适用于 x86。即使 x86 上的内存是可字节寻址的(因此您可以有一个指向任何地址的 char 指针),但如果您有一个指向 4 字节整数的指针,则该指针必须对齐。
您应该在 gdb 中运行您的程序并确定哪个指针访问正在生成总线错误以诊断问题。
这有点偏离常规,但您可以从未对齐的 SSE2 (m128) 负载中获取 SIGBUS。