文章目录

  • 1 汇编阶段
    • 1.1 ARM处理器的几种模式
    • 1.2 协处理器
      • 1.2.1 指令格式
  • 2 C语言阶段
    • 2.1 fsr_info
    • 2.2 do_page_fault()
  • 3 缺页中断流程总结

 malloc()和mmap()等内存分配函数,在分配时只是建立了进程虚拟地址空间,并没有分配虚拟内存对应的物理内存。当进程访问这些没有建立映射关系的虚拟内存时,系统触发缺页中断,缺页中断机制根据所访问页面的状态来分配物理页面并建立映射关系。

 触发缺页中断的情况有两种 , 第一,程序访问了非法地址;第二,访问的地址是合法的,但是该地址还未分配物理页框。

缺页中断要经历汇编和C语言两个阶段:

1 汇编阶段

 当程序访问的虚拟页面没有进行过物理页面的映射时,会通过发生缺页中断来分配和映射物理页面。发生缺页中断时,处理器会跳转到异常向量表 Data abort 向量中开始执行缺页中断的汇编阶段,这个阶段与处理器架构紧密联系,例如对于ARMv7-A架构,汇编处理流程为:__vectors_start -> vector_dabt -> __dabt_usr/__dabt_svc -> dabt_helper -> v7_early_abort 。

 从缺页中断的汇编处理流程可以看到对于不同的处理器模式,汇编流程有所不同,比如进入data abort之前处于usr模式,那么跳转到__dabt_usr;如果处于svc模式,那么跳转到__dabt_svc;否则跳转到__dabt_invalid。

 实际上,进入异常向量前Linux只能处于usr或者svc两种模式之一。这是因为irq等其他异常在跳转表中都要经过vector_stub宏,而不管之前是哪种状态,这个宏都会将CPU状态改为svc模式。usr模式即Linux中的用户态模式,svc即内核模式。

以下是ARM 处理器模式的总结。

1.1 ARM处理器的几种模式

处理器模式 描述
usr(user) 属于正常的用户模式,ARM处理器正常的程序执行状态,不能切换到其他模式
fiq(fiq) 快速中断模式,对高速数据传输或通道处理
irq(irq) 对一般情况下的中断进行处理
svc(Supervisor) 操作系统保护模式,系统复位或软件中断响应时进入此模式
abt(abort) 数据访问终止模式,当数据或指令预取终止时进入该模式,可用于处理存储器故障、实现虚拟存储器和存储器保护
sys(system) 系统模式,运行具有特权的操作系统任务,可以直接切换到其他模式
und(undefined) 未定义指令终止模式,处理未定义的指令陷阱,当未定义的指令执行时进入该模式,可用于支持硬件协处理器的软件仿真

 缺页异常中断,汇编阶段最后调用do_DataAbort函数进入到c语言世界,其位置在 arch/arm/mm/abort_ev7.S :

ENTRY(v7_early_abort)mrc p15, 0, r1, c5, c0, 0       @ get FSRmrc   p15, 0, r0, c6, c0, 0       @ get FAR···b  do_DataAbort
ENDPROC(v7_early_abort)

ARM的MMU中有如下两个与存储访问失效相关的寄存器:

  • c5:失效状态寄存器(Data Fault Status Register , FSR)
  • c6:失效地址寄存器(Data Fault Address Register,FAR)

当发生存储访问失效时,失效状态寄存器FSR会反映所发生的存储失效的相关信息,包括存储访问所属域和存储访问类型等,表明是什么原因导致data abort。同时失效地址寄存器会记录访问失效的虚拟地址。汇编函数v7_early_abort通过读取协处理器的寄存器c5和c6来获得导致data abort的相关信息,然后调用C语言的do_DataAbort()函数。

1.2 协处理器

 基于ARM的系统中,存储系统的操作通常由协处理器CP15来完成,CP15有16个32位的寄存器,其编号为0~15。访问CP15寄存器的指令主要是MCR和MRC这两个指令。

  • MCR: ARM处理器寄存器到协处理器寄存器的数据传送指令(写入协处理器寄存器)。
  • MRC: 协处理器寄存器到ARM处理器寄存器的数据传送指令(读出协处理器寄存器)。

1.2.1 指令格式

MRC{cond} p15, <Opcode_1>, <Rd>, <CRn>, <CRm>, <Opcode_2>MCR{cond} p15, <Opcode_1>, <Rd>, <CRn>, <CRm>, <Opcode_2>

cond:为指令执行的条件码。当cond忽略时指令为无条件执行。
Opcode_1:协处理器的特定操作码. 对于CP15寄存器来说,Opcode_1=0 。
Rd:作为源寄存器的ARM寄存器,其值将被传送到协处理器寄存器中,或者将协处理器寄存器的值传送到 该寄存器里面 ,通常为R0 。
CRn:作为目标寄存器的协处理器寄存器,其编号是C0~C15。
CRm:协处理器中附加的目标寄存器或源操作数寄存器。如果不需要设置附加信息,将CRm设置为C0,否则结果未知 。
Opcode_2:可选的协处理器特定操作码。(用来区分同一个编号的不同物理寄存器,当不需要提供附加信息时,指定为0)。

关于详细的协处理器知识请移步至:ARM协处理器介绍

2 C语言阶段

 缺页中断的C语言阶段是从调用do_DataAbort函数开始。下面看看该函数都做了什么工作?

asmlinkage void do_DataAbort(
unsigned long addr, //导致异常的内存地址
unsigned int fsr, //协处理器CP15寄存器中的值
struct pt_regs *regs //进入异常时CPU寄存器的值
)
{const struct fsr_info *inf = fsr_info + fsr_fs(fsr);struct siginfo info;if (!inf->fn(addr, fsr & ~FSR_LNX_PF, regs))return;pr_alert("Unhandled fault: %s (0x%03x) at 0x%08lx\n",inf->name, fsr, addr);show_pte(current->mm, addr);clear_siginfo(&info);info.si_signo = inf->sig;info.si_errno = 0;info.si_code  = inf->code;info.si_addr  = (void __user *)addr;arm_notify_die("", regs, &info, fsr, 0);
}

进入do_DataAbort函数后,首先根据 fsr 的值从全局数组 fsr_info 中获得处理此异常的 fsr_info 结构,然后调用 fsr_info 结构中的 fn 函数来处理该种异常。如果 fn 函数为空,那么就要调用 arm_notify_die 函数。

void arm_notify_die(const char *str, struct pt_regs *regs,struct siginfo *info, unsigned long err, unsigned long trap)
{if (user_mode(regs)) {current->thread.error_code = err;current->thread.trap_no = trap;force_sig_info(info->si_signo, info, current);} else {die(str, regs, err);}
}

该函数首先调用 user_mode 函数来判断是否是 usr 模式,如果是 usr 模式,那么就发送一个信号给导致异常的任务(注意这里的任务可能是一个线程)。具体哪个信号被发送是由 struct fsr_info 结构体中定义的值决定,一般来说,是一个能使进程停止的信号,比如 SIGSEGV 等等(SIGSEGV之类的信号即使被发给一个线程,也会停止整个进程)。如果是 svc 模式,即内核模式,那么就调用die函数,这是 kernel 处理 Oops 的标准函数。

2.1 fsr_info

 fsr_info是一个 struct fsr_info 类型的全局数组,定义在 arch/arm/mm/fsr_2level.c中:

static struct fsr_info fsr_info[] = {/** The following are the standard ARMv3 and ARMv4 aborts.  ARMv5* defines these to be "precise" aborts.*/{ do_bad,      SIGSEGV, 0,     "vector exception"           },{ do_bad,      SIGBUS,  BUS_ADRALN,    "alignment exception"        },{ do_bad,      SIGKILL, 0,     "terminal exception"         },{ do_bad,      SIGBUS,  BUS_ADRALN,    "alignment exception"        },{ do_bad,      SIGBUS,  0,     "external abort on linefetch"    },{ do_translation_fault,    SIGSEGV, SEGV_MAPERR,   "section translation fault"      },{ do_bad,      SIGBUS,  0,     "external abort on linefetch"    },{ do_page_fault,   SIGSEGV, SEGV_MAPERR,   "page translation fault"     },{ do_bad,      SIGBUS,  0,     "external abort on non-linefetch"  },{ do_bad,        SIGSEGV, SEGV_ACCERR,   "section domain fault"           },{ do_bad,      SIGBUS,  0,     "external abort on non-linefetch"  },{ do_bad,        SIGSEGV, SEGV_ACCERR,   "page domain fault"          },{ do_bad,      SIGBUS,  0,     "external abort on translation"      },{ do_sect_fault,   SIGSEGV, SEGV_ACCERR,   "section permission fault"       },{ do_bad,      SIGBUS,  0,     "external abort on translation"      },{ do_page_fault,   SIGSEGV, SEGV_ACCERR,   "page permission fault"          },/** The following are "imprecise" aborts, which are signalled by bit* 10 of the FSR, and may not be recoverable.  These are only* supported if the CPU abort handler supports bit 10.*/{ do_bad,     SIGBUS,  0,     "unknown 16"             },{ do_bad,      SIGBUS,  0,     "unknown 17"             },{ do_bad,      SIGBUS,  0,     "unknown 18"             },{ do_bad,      SIGBUS,  0,     "unknown 19"             },{ do_bad,      SIGBUS,  0,     "lock abort"             }, /* xscale */{ do_bad,     SIGBUS,  0,     "unknown 21"             },{ do_bad,      SIGBUS,  BUS_OBJERR,    "imprecise external abort"       }, /* xscale */{ do_bad,     SIGBUS,  0,     "unknown 23"             },{ do_bad,      SIGBUS,  0,     "dcache parity error"        }, /* xscale */{ do_bad,     SIGBUS,  0,     "unknown 25"             },{ do_bad,      SIGBUS,  0,     "unknown 26"             },{ do_bad,      SIGBUS,  0,     "unknown 27"             },{ do_bad,      SIGBUS,  0,     "unknown 28"             },{ do_bad,      SIGBUS,  0,     "unknown 29"             },{ do_bad,      SIGBUS,  0,     "unknown 30"             },{ do_bad,      SIGBUS,  0,     "unknown 31"             },
};

fsr_info 数组中的每一项对应一个 struct fsr_info 结构:

struct fsr_info {int (*fn)(unsigned long addr, unsigned int fsr, struct pt_regs *regs); //异常处理函数int  sig;//当 fn 函数执行失败或为空时,内核发送给进程的信号int  code;//const char *name;//表示这条异常的名称,从这个字串中可以看出发生什么异常
};

kernel 对于大多数异常都会调用 do_bad 函数:

static int do_bad(unsigned long addr, unsigned int esr, struct pt_regs *regs)
{return 1; /* "fault" */
}

该函数只是简单地返回1,让内核去调用后面的 arm_notify_die 函数来处理该异常。
kernel有几个异常是需要单独处理:

  • do_translation_fault : 段转换错误,即找不到二级页表
  • do_page_fault:页表错误,即线性地址无效,没有对应的物理地址
  • do_sect_fault:段权限错误,即二级页表权限错误
  • do_page_fault:页权限错误

2.2 do_page_fault()

 do_page_fault()函数是缺页中断的核心函数:

static int __kprobes
do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
{struct task_struct *tsk;struct mm_struct *mm;int sig, code;vm_fault_t fault;unsigned int flags = FAULT_FLAG_ALLOW_RETRY | FAULT_FLAG_KILLABLE;/*如果打开了kprobe,并且由内核态触发,则交由kprobe处理完成并返回*/if (notify_page_fault(regs, fsr))return 0;/*获得当前进程*/tsk = current;/*获得当前进程的内存描述符*/mm  = tsk->mm;/*进入该异常之前中断处于打开状态,则还要保持中断打开,意味着在下面的异常处理中还可以被中断打断*/if (interrupts_enabled(regs))local_irq_enable();/** faulthandler_disabled():判断是否关闭了page_fault 和 判断当前状态是否处于中断上下文或禁止抢占状态,*                          若是,则调用__do_kernel_fault 触发kernel panic* !mm : 若mm不存在,则判断是内核线程,则则调用__do_kernel_fault 触发kernel panic。说明内核线程不会*        缺页异常。*/if (faulthandler_disabled() || !mm)goto no_context;/*判断是否由用户态触发进来*/if (user_mode(regs))flags |= FAULT_FLAG_USER;/*判断是否由可写权限触发进来*/if (fsr & FSR_WRITE)flags |= FAULT_FLAG_WRITE;/*尝试获取当前进程的mm读写信号量,返回1表示成功获得锁,返回0表示锁已被别人占用*/if (!down_read_trylock(&mm->mmap_sem)) {/*锁在内核空间被占用,并且在exception table没有查询到该地址,则触发kernel panic*/if (!user_mode(regs) && !search_exception_tables(regs->ARM_pc))goto no_context;
retry:/*锁在用户空间被占用,则调用down_read()函数来睡眠等待锁持有者释放锁*/down_read(&mm->mmap_sem);} else {/** The above down_read_trylock() might have succeeded in* which case, we'll have missed the might_sleep() from* down_read()*/might_sleep();
#ifdef CONFIG_DEBUG_VMif (!user_mode(regs) &&!search_exception_tables(regs->ARM_pc))goto no_context;
#endif}/*上面已经将内核态触发的异常过滤了,内核态的异常走no_context,来到这里说明是用户态引发的异常*/fault = __do_page_fault(mm, addr, fsr, flags, tsk);/* If we need to retry but a fatal signal is pending, handle the* signal first. We do not need to release the mmap_sem because* it would already be released in __lock_page_or_retry in* mm/filemap.c. */if ((fault & VM_FAULT_RETRY) && fatal_signal_pending(current)) {if (!user_mode(regs))goto no_context;return 0;}/** Major/minor page fault accounting is only done on the* initial attempt. If we go through a retry, it is extremely* likely that the page will be found in page cache at that point.*/perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, regs, addr);if (!(fault & VM_FAULT_ERROR) && flags & FAULT_FLAG_ALLOW_RETRY) {if (fault & VM_FAULT_MAJOR) {tsk->maj_flt++;perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1,regs, addr);} else {tsk->min_flt++;perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1,regs, addr);}if (fault & VM_FAULT_RETRY) {/* Clear FAULT_FLAG_ALLOW_RETRY to avoid any risk* of starvation. */flags &= ~FAULT_FLAG_ALLOW_RETRY;flags |= FAULT_FLAG_TRIED;goto retry;}}up_read(&mm->mmap_sem);/** Handle the "normal" case first - VM_FAULT_MAJOR*/if (likely(!(fault & (VM_FAULT_ERROR | VM_FAULT_BADMAP | VM_FAULT_BADACCESS))))return 0;/** If we are in kernel mode at this point, we* have no context to handle this fault with.*/if (!user_mode(regs))goto no_context;if (fault & VM_FAULT_OOM) {/** We ran out of memory, call the OOM killer, and return to* userspace (which will retry the fault, or kill us if we* got oom-killed)*/pagefault_out_of_memory();return 0;}if (fault & VM_FAULT_SIGBUS) {/** We had some memory, but were unable to* successfully fix up this page fault.*/sig = SIGBUS;code = BUS_ADRERR;} else {/** Something tried to access memory that* isn't in our memory map..*/sig = SIGSEGV;code = fault == VM_FAULT_BADACCESS ?SEGV_ACCERR : SEGV_MAPERR;}__do_user_fault(tsk, addr, fsr, sig, code, regs);return 0;no_context:__do_kernel_fault(mm, addr, fsr, regs);return 0;
}

跟踪该函数,得到以下这样的调用流程:

3 缺页中断流程总结

[Linux][内核学习笔记]--缺页中断相关推荐

  1. 操作系统进程学习(Linux 内核学习笔记)

    操作系统进程学习(Linux 内核学习笔记) 进程优先级 并非所有进程都具有相同的重要性.除了大多数我们所熟悉的进程优先级之外,进程还有不同的关键度类别,以满足不同需求.首先进程比较粗糙的划分,进程可 ...

  2. 我的Linux内核学习笔记

    在开始今天的内容之前,其实有一些题外话可以和大家分享一下.自从工作以来,我个人一直都有一个观点.那就是怎么样利用简单的代码来说明开发中的问题,或者是解释软件中的原理,这是一个很高的学问.有些道理看上去 ...

  3. Linux内核学习笔记

    1.vanbreaker的专栏 2.LinuxKernel Exploration 3.DroidPhone的专栏 4.Linux内核研究以及学习文档和ARM学习以及研究的开放文档   [力荐] 5. ...

  4. linux内核学习笔记【一】临时内核页表 Provisional kernel Page Tables

    最近开始学习linux内核,看了<深入理解linux内核>,开始写点学习收获.内核版本为2.6.11 临时全局目录(provisional page global directory)是在 ...

  5. Linux内核学习笔记之网卡驱动的详细分析:RTL8139

    学习应该是一个先把问题简单化,在把问题复杂化的过程.一开始就着手处理复杂的问题,难免让 人有心惊胆颤,捉襟见肘的感觉.读Linux网卡驱动也是一样.那长长的源码夹杂着那些我们陌生的变量和符号,望而生畏 ...

  6. 20135316王剑桥Linux内核学习笔记第三周

    20135316王剑桥 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC 1000029000 三个法宝:存储程序计算机.函数调 ...

  7. Linux内核学习笔记 - RCU机制总结

    目录 一.什么是RCU机制 1.历史背景 -- 原始的RCU思想 2.基础架构 -- RCU算法设计 3.实现思路-- 读写回收实现思路 4.实现思路 -- 实例说明 宽限期 订阅--发布机制 数据读 ...

  8. [Linux][内核学习笔记]--CFS调度器

    文章目录 1. 进程的状态转换 2. 内核调度器的发展 3. 调度策略 4. 与调度相关的系统调用 5. 优先级 6. CFS调度器的实现 6.1 相关结构体 6.1.1 sched_entity 结 ...

  9. Linux内核学习笔记(十)中断处理的下半部(Bottom Halve)

    为什么需要下半部 中断处理程序有如下局限性: 中断处理程序是异步中断,被其中断执行的代码(包括别的中断处理程序)可能正在执行非常重要的任务,为了避免被中断进程停止过长时间,中断处理程序的执行应该越快越 ...

最新文章

  1. 函数式编程是啥玩意?map() reduce()(reduce()函数将数字列表转换为x进制数字)闭包、装饰器、偏函数
  2. Direct2D (11) : 画刷之 ID2D1LinearGradientBrush
  3. VTK:图表之VertexSize
  4. yum仓库、源以及编译安装笔记
  5. python统计汉字字数_Python 统计字数的思路详解
  6. 基于MATLAB BP神经网络的数字图像识别
  7. Apizza在chrome上安装apizzaSQ扩展
  8. Windows操作系统发展简史
  9. 计算机或信息化的专业职称,信息系统项目管理师是高级职称吗?
  10. Matlab中FrechetDistance方法实现---比较两条曲线的相似性,并绘制曲线
  11. PMP证书现在还值得考吗?
  12. 鸿蒙系统报名选择一个应用,申请鸿蒙系统有一个应用选择怎么选择呢
  13. AM335x启动流程(bootrom)
  14. uniapp 调用阿里云OCR行驶证识别
  15. Spark综合学习笔记(三)搜狗搜索日志分析
  16. 检测浏览器版本并升级jQuery插件
  17. 安卓微信二次分享不显示描述和图片
  18. 软科:数据科学与大数据技术专业排名
  19. error updating changes:Cannot identify version of git executable git.exe
  20. 基于NASA C-MAPSS数据的介绍

热门文章

  1. 宝来客分享怎样才能让每一位导购主动成为金店的增长发动机
  2. 从dhcpd.lease中提取MAC和IP地址
  3. 动手学数据分析——Task01_数据加载
  4. Android 系统-进入recovery的问题集
  5. Unity3d 导入图片 自动修改Texture Type为Sprite (2D and UI) 及设置 Packing Tag为文件夹名
  6. 一套让我成功拿下21k13薪offer的自动化测试常见面试题
  7. 【C++要笑着学】泛型编程 | 函数模板 | 函数模板实例化 | 类模板
  8. Ledger of Harms
  9. “死锁”四个必要条件的合理解释
  10. 20160815命令行进入其他盘