sigaction(7):siginfo_t 的 si_code 成员的语义

sigaction(7):siginfo_t 的 si_code 成员的语义

我有一个长时间运行的程序(通过 daemon(3) 调用成为守护进程),经常在信号 11(分段违规)时退出。我不知道为什么。因此,我编写了一个 SIGSEGV 处理程序,使用sigaction()系统调用进行设置。我设置了处理函数,使其具有以下原型:void (*sa_sigaction)(int, siginfo_t *, void *)这意味着它获取一个指向siginfo_t结构的指针作为形式参数。

在出现神秘的 SIGSEGV 时,si_code的元素siginfo_t值为 0x80,根据 sigaction 手册页,这意味着“内核”发送了信号。这是在 Red Hat RHEL 系统上:Linux blahblah 2.6.18-308.20.1.el5 #1 SMP Tue Nov 6 04:38:29 EST 2012 x86_64 x86_64 x86_64 GNU/Linux

为什么内核会发送 SIGSEGV?这是来自著名的 OOM-Killer,还是存在获取 SIGSEGV 的其他原因?作为这个系统上的一个普通用户,我看不到/var/log/message,而且系统管理员也有点冷漠,可能是因为他们来自 Windows 背景。

故意生成的 SIGSEGV(取消引用 NULL 指针)不会获得si_code值 0x80,而是获得 0x1,这意味着“地址未映射到对象”。

答案1

si_code = SI_KERNELwith的未记录语义si_errno = 0是,

  1. 处理器特定的陷阱
  2. 内核段内存违规(信号量访问除外)
  3. ELF 文件格式违规,以及
  4. 堆栈违规。

所有其他SIGSEGVs 应设置si_errno为非零值。请继续阅读以了解详细信息。

当内核设置用户空间进程时,它会为该进程定义一个虚拟内存页表。当内核调度程序运行进程时,它会根据进程的页表重新配置 CPU 的内存管理单元 (MMU)。

当用户空间进程尝试访问其页表之外的内存时,CPU MMU 会检测到此违规并生成异常。请注意,这发生在硬件等级。内核还没有涉及到。

内核被设置为处理 MMU 异常。它捕获由于正在运行的进程尝试访问其页表之外的内存而引起的异常。然后内核调用do_page_fault()它将 SIGSEGV 信号发送到进程。这就是为什么信号来自内核而不是来自进程本身或另一个进程。

当然,这是一个高度简化的解释。我见过的最好的简单解释是 William Gatliff 的精彩文章的“页面错误”部分Linux 内核的内存管理单元 API

请注意,在没有 MMU 的 CPU 上,例如 Blackfin MPU,Linux 用户空间进程通常可以访问任何记忆。即,没有用于内存违规的 SIGSEGV 信号(仅用于堆栈溢出等陷阱),并且调试内存访问问题可能很棘手。

我同意jordanm关于设置ulimit和检查核心文件的评论gdbulimit -c unlimited如果您从 shell 运行进程,则可以从命令行执行操作,或者在程序中使用 libcsetrlimit系统调用包装器 ( )。man setrlimit您可以通过 in file 设置核心文件的名称及其位置/proc/sys/kernel/core_pattern。请参阅美联社劳伦斯 (AP Lawrence) 对此的精彩阐述:控制核心文件 (Linux)。要gdb在 corefile 上使用,请参阅这个小内容教程在 Steve.org 上。

SEGV_MAPERR (0x1)的分段违规si_code可能是空指针取消引用、访问不存在的内存(例如 0xfffffc0000004000)或malloc其他free问题。man getrlimit在 的情况下,堆损坏或进程超出其运行时限制 ( ) malloc;在 的情况下,双重释放或释放未分配的地址free。查看该si_errno元素以获取更多线索。

由于用户空间进程访问超过TASK_SIZE限制的虚拟内存而发生的分段违规将导致分段si_code违规SI_KERNEL。换句话说,TASK_SIZE限制是任何进程允许访问的最高虚拟地址。除非内核配置为高内存支持,否则通常为 3GB。高于限制的区域TASK_SIZE称为“内核段”。看看linux-2.6//arch/x86/mm/fault.c:__bad_area_nosemaphore(...)它在哪里调用force_sig_info_fault(...)

对于每种架构,还有许多导致 SIEGV 的特定陷阱SI_KERNEL。对于 x86,这些是由linux-2.6//arch/x86/kernel/traps.c.

linux-2.6//mm/oom_kill.c:oom_kill_process(...)OOM 处理程序发送 SIGKILL,而不是 SIGSEGV,如函数第 498 行左右所示:

do_send_sig_info(SIGKILL, SEND_SIG_FORCED, p, true);

对于相关流程和第 503 行:

do_send_sig_info(SIGKILL, SEND_SIG_FORCED, victim, true);

该进程是 OOM 的最直接原因。

您可以通过查看wait从其父进程中杀死的进程的状态来获取更多信息,也可以通过查看dmesg或更好地通过配置内核日志并查看它来获取更多信息。

相关内容