如果段错误不可恢复,为什么将其称为错误(而不是中止)?
Why are segfaults called faults (and not aborts) if they are not recoverable?
我对术语的理解如下
1) An interrupt
is "a notification" that is initiated by the hardware to call the OS to run its handlers
2) A trap
is "a notification" that is initiated by the software to call the OS to run its handlers
3) A fault
is an exception that is raised by the processor if an error has occurred but it is recoverable
4) An abort
is an exception that is raised by the processor if an error has occurred but it is non-recoverable
那为什么我们称它为 segmentation fault
而不是 segmentation abort
?
A segmentation fault
is when your program attempts to access memory it
has either not been assigned by the operating system, or is otherwise
not allowed to access.
我的经验(主要是在测试 C
代码时)是,任何时候程序抛出一个 segmentation fault
它都会回到绘图板 - 是否存在程序员可以实际 "catch"异常并用它做一些有用的事情?
有两种类型的异常:故障和陷阱。当发生故障时,指令可以重新启动。当发生陷阱时,指令无法重新启动。
例如,当发生页面错误时,操作系统异常处理程序加载丢失的页面并重新启动导致错误的指令。
如果处理器定义了 "segmentation fault" 则导致异常的指令是可重新启动的——但操作系统的处理程序可能不会重新启动指令。
在 CPU 级别,现代 OSes 不使用 x86 段限制来保护内存。 (实际上,即使他们想在长模式 (x86-64) 下也做不到;段基数固定为 0,限制为 -1)。
OSes 使用虚拟内存页面 tables,因此 out-of-bounds 内存访问上的真正 CPU 异常是页面错误。
x86 手册将其称为 #PF(fault-code)
异常 ,例如参见 the list of exceptions add
can raise。有趣的事实:超出段限制的 x86 异常访问是 #GP(0)
.
由 OS 的 page-fault 处理程序决定如何处理它。许多 #PF
异常是正常操作的一部分:
- copy-on-write 映射已写入:复制页面并在页面 table 中将其标记为可写,然后 return 到 user-space 以重试出错的指令。 (这是一种“软”又名“次要”页面错误。)
- 其他软页面错误,例如内核是懒惰的,实际上并没有更新页面 table 来反映进程所做的映射。 (例如
mmap(2)
没有 MAP_POPULATE
)。
- 硬页面错误:找到一些物理内存并从磁盘读取文件(文件映射或从交换 file/partition 匿名页面)。
在解决以上任何问题后,更新 CPU 自行读取的页面 table,并在必要时使该 TLB 条目无效。 (例如,有效但 read-only 更改为有效 + read-write)。
只有当内核发现进程在逻辑上确实没有任何映射到该地址(或者它是对 read-only 映射的写入)时,内核才会提供 SIGSEGV
到进程。 这纯粹是软件的事情,在整理了硬件异常的原因后
SIGSEGV
(from strerror(3)
) 的英文文本在所有 Unix/Linux 系统上都是“Segmentation Fault”,所以这就是打印的内容 (通过 shell) 当子进程死于该信号时。
这个术语很好理解,所以尽管它主要只是出于历史原因而存在,而且硬件不使用分段。
请注意,您还会收到 SIGSEGV,用于尝试在 user-space 中执行特权指令(例如 wbinvd
或 wrmsr
(write model-specific register))。在 CPU 级别,当您不在 ring 0(内核模式)时,x86 例外是 #GP(0)
特权指令。
也用于未对齐的 SSE 指令(如 movaps
),尽管其他平台上的一些 Unix 发送 SIGBUS
用于未对齐的访问错误(例如 SPARC 上的 Solaris)。
Why do we call it a segmentation fault and not a segmentation abort then?
它是可恢复的。它不会使整个机器/内核崩溃,它只是意味着 user-space 进程试图做一些内核不允许的事情。
即使是出现段错误的进程也可以恢复。这就是为什么它是一个可捕捉的信号,不像 SIGKILL
。通常你不能只是恢复执行,但你可以有用地记录错误的位置(例如打印精确的异常错误消息甚至堆栈回溯)。
SIGSEGV 的信号处理程序可以 longjmp
或其他。或者,如果 SIGSEGV 是预期的,则在从信号处理程序 returning 之前修改用于加载的代码或指针。 (例如 for a Meltdown exploit,虽然有更有效的技术在预测错误或其他抑制异常的情况下进行链式加载,而不是让 CPU 引发异常并捕获内核提供的 SIGSEGV)
大多数编程语言(汇编语言除外)low-level 不足以在围绕可能出现段错误的访问进行优化时提供明确定义的行为,从而让您编写可恢复的处理程序。这就是为什么如果你安装了一个 SIGSEGV 处理程序,通常你除了在 SIGSEGV 处理程序中打印一条错误消息(可能还有一个堆栈回溯)之外什么都不做。
一些用于沙盒语言(如 Javascript)的 JIT 编译器使用硬件内存访问检查来消除 NULL 指针检查。正常情况下没有故障,所以故障情况有多慢都没有关系。
A Java JVM 可以将 JVM 线程接收的 SIGSEGV
转换为 NullPointerException
用于 Java 代码 运行ning,JVM 没有任何问题。
Effective Null Pointer Check Elimination Utilizing Hardware Trap 三位 IBM 科学家为 Java 撰写的关于此的研究论文。
SableVM: 6.2.4 Hardware Support on Various Architectures 关于 NULL 指针检查
另一个技巧是将数组的末尾放在页面的末尾(后面是 large-enough 未映射区域),因此每次访问时 bounds-checking 都是免费的硬件。如果你能静态地证明索引总是正的,并且它不能大于 32 位,那么你就大功告成了。
- 隐式 Java 64 位数组边界检查
架构。他们讨论当数组大小不是页面大小的倍数时该怎么做,以及其他注意事项
陷阱与中止
我认为没有标准的术语可以区分。这取决于您所说的恢复类型。显然 OS 可以在任何 user-space 可以让硬件运行之后保持 运行ning,否则无特权的 user-space 可能会使机器崩溃。
相关:开启
When an interrupt occurs, what happens to instructions in the pipeline?,Andy Glew(CPU 英特尔 P6 微架构的架构师)说“陷阱”基本上是由 运行ning 代码(而不是外部信号)引起的任何中断,并同步发生。 (例如,当错误指令到达管道的退出阶段而没有先检测到更早的 branch-mispredict 或其他异常时)。
“中止”不是标准 CPU-architecture 术语。就像我说的,你希望 OS 无论如何都能继续,只有硬件故障或内核错误通常会阻止它。
AFAIK,“中止”也不是很标准的 operating-systems 术语。 Unix 有信号,其中一些信号是无法捕获的(如 SIGKILL 和 SIGSTOP),但大多数都可以捕获。
SIGABRT
can be caught by a signal handler。如果处理程序 returns,进程将退出,所以如果你不想要它,你可以 longjmp
退出它。但是 AFAIK 没有错误条件引发 SIGABRT;它只能通过软件手动发送,例如通过调用 abort()
库函数。 (它通常会导致堆栈回溯。)
x86 异常术语
如果您查看 x86 手册或 this exception table on the osdev wiki, there are specific meanings in this context ():
trap:指令成功完成后引发,return地址指向陷阱inst之后。 #DB
调试和 #OF
溢出 (into
) 异常是陷阱。 (Some sources of #DB are faults instead)。但int 0x80
或其他软件中断指令也是陷阱,syscall
也是(但它把return地址放在rcx
中,而不是压入;syscall
是不是一个例外,因此在这个意义上并不是真正的陷阱)
故障:在尝试执行然后回滚后引发; return 地址指向错误指令。 (大多数异常类型是故障)
abort 是当 return 地址指向不相关的位置时(即对于 #DF
double-fault 和 #MC
machine-check)。三重故障无法处理;这是当 CPU 遇到异常试图 运行 double-fault 处理程序时发生的情况,并且确实停止了整个 CPU.
请注意,即使像 Andy Glew 这样的英特尔 CPU 架构师有时也会更笼统地使用术语“陷阱”,我认为在讨论 computer-architecture 理论时,它指的是任何同步异常。除非您实际上是在谈论在 x86 上处理特定异常,否则不要指望人们会坚持使用上述术语。虽然它是有用且明智的术语,但您可以在其他上下文中使用它。但是如果你想做出区分,你应该澄清每个术语的意思,这样每个人都在同一页上。
我对术语的理解如下
1) An interrupt
is "a notification" that is initiated by the hardware to call the OS to run its handlers2) A trap
is "a notification" that is initiated by the software to call the OS to run its handlers3) A fault
is an exception that is raised by the processor if an error has occurred but it is recoverable4) An abort
is an exception that is raised by the processor if an error has occurred but it is non-recoverable
那为什么我们称它为 segmentation fault
而不是 segmentation abort
?
A segmentation fault
is when your program attempts to access memory it has either not been assigned by the operating system, or is otherwise not allowed to access.
我的经验(主要是在测试 C
代码时)是,任何时候程序抛出一个 segmentation fault
它都会回到绘图板 - 是否存在程序员可以实际 "catch"异常并用它做一些有用的事情?
有两种类型的异常:故障和陷阱。当发生故障时,指令可以重新启动。当发生陷阱时,指令无法重新启动。
例如,当发生页面错误时,操作系统异常处理程序加载丢失的页面并重新启动导致错误的指令。
如果处理器定义了 "segmentation fault" 则导致异常的指令是可重新启动的——但操作系统的处理程序可能不会重新启动指令。
在 CPU 级别,现代 OSes 不使用 x86 段限制来保护内存。 (实际上,即使他们想在长模式 (x86-64) 下也做不到;段基数固定为 0,限制为 -1)。
OSes 使用虚拟内存页面 tables,因此 out-of-bounds 内存访问上的真正 CPU 异常是页面错误。
x86 手册将其称为 #PF(fault-code)
异常 ,例如参见 the list of exceptions add
can raise。有趣的事实:超出段限制的 x86 异常访问是 #GP(0)
.
由 OS 的 page-fault 处理程序决定如何处理它。许多 #PF
异常是正常操作的一部分:
- copy-on-write 映射已写入:复制页面并在页面 table 中将其标记为可写,然后 return 到 user-space 以重试出错的指令。 (这是一种“软”又名“次要”页面错误。)
- 其他软页面错误,例如内核是懒惰的,实际上并没有更新页面 table 来反映进程所做的映射。 (例如
mmap(2)
没有MAP_POPULATE
)。 - 硬页面错误:找到一些物理内存并从磁盘读取文件(文件映射或从交换 file/partition 匿名页面)。
在解决以上任何问题后,更新 CPU 自行读取的页面 table,并在必要时使该 TLB 条目无效。 (例如,有效但 read-only 更改为有效 + read-write)。
只有当内核发现进程在逻辑上确实没有任何映射到该地址(或者它是对 read-only 映射的写入)时,内核才会提供 SIGSEGV
到进程。 这纯粹是软件的事情,在整理了硬件异常的原因后
SIGSEGV
(from strerror(3)
) 的英文文本在所有 Unix/Linux 系统上都是“Segmentation Fault”,所以这就是打印的内容 (通过 shell) 当子进程死于该信号时。
这个术语很好理解,所以尽管它主要只是出于历史原因而存在,而且硬件不使用分段。
请注意,您还会收到 SIGSEGV,用于尝试在 user-space 中执行特权指令(例如 wbinvd
或 wrmsr
(write model-specific register))。在 CPU 级别,当您不在 ring 0(内核模式)时,x86 例外是 #GP(0)
特权指令。
也用于未对齐的 SSE 指令(如 movaps
),尽管其他平台上的一些 Unix 发送 SIGBUS
用于未对齐的访问错误(例如 SPARC 上的 Solaris)。
Why do we call it a segmentation fault and not a segmentation abort then?
它是可恢复的。它不会使整个机器/内核崩溃,它只是意味着 user-space 进程试图做一些内核不允许的事情。
即使是出现段错误的进程也可以恢复。这就是为什么它是一个可捕捉的信号,不像 SIGKILL
。通常你不能只是恢复执行,但你可以有用地记录错误的位置(例如打印精确的异常错误消息甚至堆栈回溯)。
SIGSEGV 的信号处理程序可以 longjmp
或其他。或者,如果 SIGSEGV 是预期的,则在从信号处理程序 returning 之前修改用于加载的代码或指针。 (例如 for a Meltdown exploit,虽然有更有效的技术在预测错误或其他抑制异常的情况下进行链式加载,而不是让 CPU 引发异常并捕获内核提供的 SIGSEGV)
大多数编程语言(汇编语言除外)low-level 不足以在围绕可能出现段错误的访问进行优化时提供明确定义的行为,从而让您编写可恢复的处理程序。这就是为什么如果你安装了一个 SIGSEGV 处理程序,通常你除了在 SIGSEGV 处理程序中打印一条错误消息(可能还有一个堆栈回溯)之外什么都不做。
一些用于沙盒语言(如 Javascript)的 JIT 编译器使用硬件内存访问检查来消除 NULL 指针检查。正常情况下没有故障,所以故障情况有多慢都没有关系。
A Java JVM 可以将 JVM 线程接收的 SIGSEGV
转换为 NullPointerException
用于 Java 代码 运行ning,JVM 没有任何问题。
Effective Null Pointer Check Elimination Utilizing Hardware Trap 三位 IBM 科学家为 Java 撰写的关于此的研究论文。
SableVM: 6.2.4 Hardware Support on Various Architectures 关于 NULL 指针检查
另一个技巧是将数组的末尾放在页面的末尾(后面是 large-enough 未映射区域),因此每次访问时 bounds-checking 都是免费的硬件。如果你能静态地证明索引总是正的,并且它不能大于 32 位,那么你就大功告成了。
- 隐式 Java 64 位数组边界检查 架构。他们讨论当数组大小不是页面大小的倍数时该怎么做,以及其他注意事项
陷阱与中止
我认为没有标准的术语可以区分。这取决于您所说的恢复类型。显然 OS 可以在任何 user-space 可以让硬件运行之后保持 运行ning,否则无特权的 user-space 可能会使机器崩溃。
相关:开启 When an interrupt occurs, what happens to instructions in the pipeline?,Andy Glew(CPU 英特尔 P6 微架构的架构师)说“陷阱”基本上是由 运行ning 代码(而不是外部信号)引起的任何中断,并同步发生。 (例如,当错误指令到达管道的退出阶段而没有先检测到更早的 branch-mispredict 或其他异常时)。
“中止”不是标准 CPU-architecture 术语。就像我说的,你希望 OS 无论如何都能继续,只有硬件故障或内核错误通常会阻止它。
AFAIK,“中止”也不是很标准的 operating-systems 术语。 Unix 有信号,其中一些信号是无法捕获的(如 SIGKILL 和 SIGSTOP),但大多数都可以捕获。
SIGABRT
can be caught by a signal handler。如果处理程序 returns,进程将退出,所以如果你不想要它,你可以 longjmp
退出它。但是 AFAIK 没有错误条件引发 SIGABRT;它只能通过软件手动发送,例如通过调用 abort()
库函数。 (它通常会导致堆栈回溯。)
x86 异常术语
如果您查看 x86 手册或 this exception table on the osdev wiki, there are specific meanings in this context (
trap:指令成功完成后引发,return地址指向陷阱inst之后。
#DB
调试和#OF
溢出 (into
) 异常是陷阱。 (Some sources of #DB are faults instead)。但int 0x80
或其他软件中断指令也是陷阱,syscall
也是(但它把return地址放在rcx
中,而不是压入;syscall
是不是一个例外,因此在这个意义上并不是真正的陷阱)故障:在尝试执行然后回滚后引发; return 地址指向错误指令。 (大多数异常类型是故障)
abort 是当 return 地址指向不相关的位置时(即对于
#DF
double-fault 和#MC
machine-check)。三重故障无法处理;这是当 CPU 遇到异常试图 运行 double-fault 处理程序时发生的情况,并且确实停止了整个 CPU.
请注意,即使像 Andy Glew 这样的英特尔 CPU 架构师有时也会更笼统地使用术语“陷阱”,我认为在讨论 computer-architecture 理论时,它指的是任何同步异常。除非您实际上是在谈论在 x86 上处理特定异常,否则不要指望人们会坚持使用上述术语。虽然它是有用且明智的术语,但您可以在其他上下文中使用它。但是如果你想做出区分,你应该澄清每个术语的意思,这样每个人都在同一页上。