为什么 "Segmentation Fault " 仍然是 C 中的一个东西
Why exactly is "Segmentation Fault " still a thing in C
操作系统中的分段是一个与时间本身一样古老的概念。至少按照我的教授的说法,大多数现代操作系统都放弃了分段的概念,现在主要依靠分页来实现内存保护,从而防止每个进程访问除自己内存之外的任何其他内存。那么我们如何仍然在 C 中得到“分段错误”。我们是否仍然以某种方式将分段作为现代操作系统中的抽象概念?
具体来说,这不是 C 中的东西,而是类 Unix OS 中的东西。任何非内存安全的语言(使得尝试访问未映射的页面成为可能)都可以编译为出现段错误的可执行文件。包括手写汇编或 Fortran。但是,是的,C 和 C++ 是两种使用最广泛的非内存安全语言。
是的,这个名字很古老; Unix 是 old 并且没有 需要 将 SIGSEGV
重命名为内核在用户-space 通过访问未映射的内存而导致 CPU 错误。这样做会破坏很多使用该常量的代码,并且只是将 strerror
/ perror
的英文文本字符串更改为“无效页面错误”也不会特别有用,尽管有可能,因为我认为这些消息已融入 libc。但是不同的 libc 版本在转换前后会有不同的消息,不值得这么麻烦。
在使用分页的系统中,对硬件页表中“不存在”的页面的任何访问,或者仅在您尝试写入时可读的页面或其他任何内容,都会导致 CPU例外。内核的页面错误异常处理程序检查页面 是否应该 可访问,如果是,则将其从磁盘分页,进行写时复制或其他操作。 (分别是主要或次要页面错误)。如果不是,页面错误是“无效的”,内核会向进程发送一个 SIGSEGV 信号。
同样过时的是 SIGFPE
(浮点数异常),通常用于算术异常,在大多数机器上默认情况下唯一可能出错的是整数除法。 (默认的 FP 环境屏蔽了所有 FP 异常,因此它们只是设置粘性标志而不是在机器代码中引发异常。) POSIX standard requires 如果由于算术异常而要传递信号,则必须是SIGFPE.
同样,到现在 SIGSEGV
已经被 POSIX
和其他 Unix 标准 标准化了,所以在早期的 Unix 时代 可以有可能改变它已经过去了。
(某些系统还可以为其他类型的地址错误提供 SIGBUS
,例如,SPARC 上的 Solaris 为未对齐访问提供 SIGBUS
。)
另请注意,一些其他类型的权限错误会过载到 SIGSEGV 上。例如,尝试在 Linux 下执行像 x86 lgdt
这样的特权指令会导致 SIGSEGV。 (在这种情况下,user-space 会从字面上尝试接管仍用于定义 CPU 运行的模式的分段机制,例如 16 与 32 与 64 位代码段在长模式下。)对于未对齐的 SSE SIMD 指令也是如此。所以它不是严格用于无效页面错误。
命名历史?
可执行文件也有像文本和数据这样的“段”,其中 .text 和 .data 部分分别链接。 “堆”过去大多是连续的,在 .data / .bss 之后增长(通过 brk
系统调用,在 mmap(MAP_ANONYMOUS)
之前或从 /dev/zero 映射页面是一回事),所以这是可能的即使在 OSes 开始使用分页而不是 CPU 内存保护分段之后,“分段错误”一词对设计者来说也不是胡说八道,因为可执行文件的“段”仍然被映射到进程内存映像中的连续页面范围。
我不知道关于 Unix 信号命名的历史细节与它在具有或不具有内存保护功能的 PDP-8 和 PDP-11 硬件上的发展,尽管显然 some models of PDP-11 had some form of memory protection, and even virtual memory
TL:DR
There are two hard problems in computer science: Cache invalidation, and naming things 和差一错误。
操作系统中的分段是一个与时间本身一样古老的概念。至少按照我的教授的说法,大多数现代操作系统都放弃了分段的概念,现在主要依靠分页来实现内存保护,从而防止每个进程访问除自己内存之外的任何其他内存。那么我们如何仍然在 C 中得到“分段错误”。我们是否仍然以某种方式将分段作为现代操作系统中的抽象概念?
具体来说,这不是 C 中的东西,而是类 Unix OS 中的东西。任何非内存安全的语言(使得尝试访问未映射的页面成为可能)都可以编译为出现段错误的可执行文件。包括手写汇编或 Fortran。但是,是的,C 和 C++ 是两种使用最广泛的非内存安全语言。
是的,这个名字很古老; Unix 是 old 并且没有 需要 将 SIGSEGV
重命名为内核在用户-space 通过访问未映射的内存而导致 CPU 错误。这样做会破坏很多使用该常量的代码,并且只是将 strerror
/ perror
的英文文本字符串更改为“无效页面错误”也不会特别有用,尽管有可能,因为我认为这些消息已融入 libc。但是不同的 libc 版本在转换前后会有不同的消息,不值得这么麻烦。
在使用分页的系统中,对硬件页表中“不存在”的页面的任何访问,或者仅在您尝试写入时可读的页面或其他任何内容,都会导致 CPU例外。内核的页面错误异常处理程序检查页面 是否应该 可访问,如果是,则将其从磁盘分页,进行写时复制或其他操作。 (分别是主要或次要页面错误)。如果不是,页面错误是“无效的”,内核会向进程发送一个 SIGSEGV 信号。
同样过时的是 SIGFPE
(浮点数异常),通常用于算术异常,在大多数机器上默认情况下唯一可能出错的是整数除法。 (默认的 FP 环境屏蔽了所有 FP 异常,因此它们只是设置粘性标志而不是在机器代码中引发异常。) POSIX standard requires 如果由于算术异常而要传递信号,则必须是SIGFPE.
同样,到现在 SIGSEGV
已经被 POSIX
和其他 Unix 标准 标准化了,所以在早期的 Unix 时代 可以有可能改变它已经过去了。
(某些系统还可以为其他类型的地址错误提供 SIGBUS
,例如,SPARC 上的 Solaris 为未对齐访问提供 SIGBUS
。)
另请注意,一些其他类型的权限错误会过载到 SIGSEGV 上。例如,尝试在 Linux 下执行像 x86 lgdt
这样的特权指令会导致 SIGSEGV。 (在这种情况下,user-space 会从字面上尝试接管仍用于定义 CPU 运行的模式的分段机制,例如 16 与 32 与 64 位代码段在长模式下。)对于未对齐的 SSE SIMD 指令也是如此。所以它不是严格用于无效页面错误。
命名历史?
可执行文件也有像文本和数据这样的“段”,其中 .text 和 .data 部分分别链接。 “堆”过去大多是连续的,在 .data / .bss 之后增长(通过 brk
系统调用,在 mmap(MAP_ANONYMOUS)
之前或从 /dev/zero 映射页面是一回事),所以这是可能的即使在 OSes 开始使用分页而不是 CPU 内存保护分段之后,“分段错误”一词对设计者来说也不是胡说八道,因为可执行文件的“段”仍然被映射到进程内存映像中的连续页面范围。
我不知道关于 Unix 信号命名的历史细节与它在具有或不具有内存保护功能的 PDP-8 和 PDP-11 硬件上的发展,尽管显然 some models of PDP-11 had some form of memory protection, and even virtual memory
TL:DR
There are two hard problems in computer science: Cache invalidation, and naming things 和差一错误。