Linux X86上Segmentation fault原因分析

3 系统对段错误的处理

上一节说明了段错误发生的原因,这一节说明系统是怎样处理段错误的。系统对段错误的处理大致可以分为三个部分:CPU对段错误的捕获,内核对段错误的处理,和用户程序对段错误的处理。

3.1 CPU对段错误的捕获

非法访问内存导致的段错误实际上是页面异常的一种,对这种段错误的捕获是包含在对页面异常的捕获中的。不太古老的X86 CPU都提供了段式和页式内存管理,Linux结合X86的段式保护机制和页式保护机制实现了对页面异常的捕获。X86的段式保护机制给CPU提供了4种运行级别(0~3),Linux内核只使用了其中的两种运行级别,内核模式(kernel mode)对应0级,用户模式(user mode)对应3级。CPU的当前运行级别Intel称之为CPL(Current Privilege Level),CPL保存在段寄存器CS中。X86的页式保护机制提供了页表机制,CPU借助页表来实现虚拟地址到物理地址的转换和检测页面异常。考虑两级页表,页目录项(Page-Directory Entry)和页表项(Page-Table Entry),如果页目录项或页表项尚未建立(值为0),则对相应虚拟地址进行地址转换时会触发页面异常。页目录项和页表项都包含了R/W位和U/S位,如果某个虚拟地址对应的页目录项或页表项的的R/W为0,表示该虚拟地址对应的物理页面为只读,对其进行写访问会触发页面异常。如果某个虚拟地址对应的页目录项或页表项的U/S位为0,则当CPL为3时访问该虚拟地址会触发页面异常,即只允许内核访问该页面。大体上CPU根据CPL和当前进行转换的虚拟地址对应的页目录项/页表项的值来确定是否发生页面异常,如果发生页面异常,CPU会自动保存现场,并把错误代码(错误代码中包含页面异常发生时CPU的一些状态信息)压入内核栈中,之后会跳转到内核对页面异常的处理程序并开始执行。

3.2 内核对段错误的处理

非法访问内存导致的段错误发生后,CPU会自动跳转到页面异常处理程序处执行。需要注意的是,引发页面异常的内存访问并不都是非法的,合法的内存访问有时也会触发页面异常。内核需要区分哪些内存访问是合法的,哪些是非法的。常见的导致页面异常的合法内存访问有:

本文不考虑这些合法情况,不再细说。内核根据如下3个因素来判断此次页面异常是否非法即是否发生了段错误:

内核会依据下列条件来判断是否发生了段错误:

这三种情况依次对应上述的三种示例代码,当内核发现情况满足以上任一条件时,服务器空间,就知道引发异常的是非法内存访问,香港服务器,于是向当前进程发送SIGSEGV信号。

3.3 用户程序对段错误的处理

从内核对段错误的处理可以看到,内核会对发生段错误的进程的发一个SIGSEGV信号,而程序一般不会捕获这个信号,从而会得到默认处理,服务器空间,结果就是进程被杀死。于是我们通常会在屏幕上看到可恨的”Segmentation fault”。然而出人意料的是SIGSEGV信号属于可以捕获的信号(不能捕获的信号只有SIGKILL和SIGSTOP),再考虑到CPU对异常的处理特性(段错误是异常的一种)——异常处理代码执行完毕之后CPU会重新执行导致异常的指令,我们便可以写出如下有趣的代码,下面的代码没有任何跳转语句,但是却会进入死循环:

(int sig){(void)sig;return;}, char* argv[]){;action.sa_handler = foo;sigemptyset(&action.sa_mask);action.sa_flags = 0;if(sigaction(SIGSEGV, &action, NULL) == -1){return -1;}int* p = NULL;*p = 0;return 0;}

拿望远镜看别人,拿放大镜看自己。

Linux X86上Segmentation fault原因分析

相关文章:

你感兴趣的文章:

标签云: