缺頁異常被觸發通常有兩種情況——

1.程序設計的不當導致訪問了非法的地址

2.訪問的地址是合法的,但是該地址還未分配物理頁框

下面解釋一下第二種情況,這是虛擬內存管理的一個特性。盡管每個進程獨立擁有3GB的可訪問地址空間,但是這些資源都是內核開出的空頭支票,也就是說進程手握着和自己相關的一個個虛擬內存區域(vma),但是這些虛擬內存區域並不會在創建的時候就和物理頁框掛鉤,由於程序的局部性原理,程序在一定時間內所訪問的內存往往是有限的,因此內核只會在進程確確實實需要訪問物理內存時才會將相應的虛擬內存區域與物理內存進行關聯(为相應的地址分配頁表項,並將頁表項映射到物理內存),也就是說這種缺頁異常是正常的,而第一種缺頁異常是不正常的,內核要采取各種可行的手段將這種異常帶來的破壞減到最小。

缺頁異常的處理函數为do_page_fault(),該函數是和體系結構相關的一個函數,缺頁異常的來源可分为兩種,一種是內核空間(訪問了線性地址空間的第4個GB),一種是用戶空間(訪問了線性地址空間的0~3GB),以X86架構为例,先來看內核空間異常的處理。

dotraplinkage void __kprobes
do_page_fault(struct pt_regs *regs, unsigned long error_code)
{
struct vm_area_struct *vma;
struct task_struct *tsk;
unsigned long address;
struct mm_struct *mm;
int write;
int fault; tsk = current; //獲取當前進程
mm = tsk->mm; //獲取當前進程的地址空間 /* Get the faulting address: */
address = read_cr2(); //讀取CR2寄存器獲取觸發異常的訪問地址 ...
... if (unlikely(fault_in_kernel_space(address))) { //判斷address是否處於內核線性地址空間
if (!(error_code & (PF_RSVD | PF_USER | PF_PROT))) {//判斷是否處於內核態
if (vmalloc_fault(address) >= 0)//處理vmalloc異常
return; if (kmemcheck_fault(regs, address, error_code))
return;
} /* Can handle a stale RO->RW TLB: */
/*異常發生在內核地址空間但不屬於上面的情況或上面的方式無法修正,
則檢查相應的頁表項是否存在,權限是否足夠*/
if (spurious_fault(error_code, address))
return; /* kprobes don't want to hook the spurious faults: */
if (notify_page_fault(regs))
return;
/*
* Don't take the mm semaphore here. If we fixup a prefetch
* fault we could otherwise deadlock:
*/
bad_area_nosemaphore(regs, error_code, address); return;
}
...
...
}

該函數傳遞進來的兩個参數--

regs包含了各個寄存器的值

error_code是觸發異常的錯誤類型,它的含義如下

/*
* Page fault error code bits:
*
* bit 0 == 0: no page found 1: protection fault
* bit 1 == 0: read access 1: write access
* bit 2 == 0: kernel-mode access 1: user-mode access
* bit 3 == 1: use of reserved bit detected
* bit 4 == 1: fault was an instruction fetch
*/
enum x86_pf_error_code { PF_PROT = 1 << 0,
PF_WRITE = 1 << 1,
PF_USER = 1 << 2,
PF_RSVD = 1 << 3,
PF_INSTR = 1 << 4,
};

首先要檢查該異常的觸發地址是不是位於內核地址空間 也就是address>=TASK_SIZE_MAX,一般为3GB。然後要檢查觸發異常時是否處於內核態,滿足這兩個條件就嘗試通過vmalloc_fault()來解决這個異常。由於使用vmalloc申請內存時,內核只會更新主內核頁表,所以當前使用的進程頁表就有可能因为未與主內核頁表同步導致這次異常的觸發,因此該函數試圖將address對應的頁表項與主內核頁表進行同步

static noinline int vmalloc_fault(unsigned long address)
{
unsigned long pgd_paddr;
pmd_t *pmd_k;
pte_t *pte_k; /* 確定觸發異常的地址是否處於VMALLOC區域*/
if (!(address >= VMALLOC_START && address < VMALLOC_END))
return -1; /*
* Synchronize this task's top level page-table
* with the 'reference' page table.
*
* Do _not_ use "current" here. We might be inside
* an interrupt in the middle of a task switch..
*/
pgd_paddr = read_cr3();//獲取當前的PGD地址
pmd_k = vmalloc_sync_one(__va(pgd_paddr), address);//將當前使用的頁表和內核頁表同步
if (!pmd_k)
return -1; /*到這裏已經獲取了內核頁表對應於address的pmd,並且將該值設置给了當前使用頁表的pmd,
最後一步就是判斷pmd對應的pte項是否存在*/
pte_k = pte_offset_kernel(pmd_k, address);//獲取pmd對應address的pte項
if (!pte_present(*pte_k))//判斷pte項是否存在,不存在則失敗
return -1; return 0;
}

同步處理:

static inline pmd_t *vmalloc_sync_one(pgd_t *pgd, unsigned long address)
{
unsigned index = pgd_index(address);
pgd_t *pgd_k;
pud_t *pud, *pud_k;
pmd_t *pmd, *pmd_k; pgd += index; //記錄當前頁表pgd對應address的偏移
pgd_k = init_mm.pgd + index;//記錄內核頁表對應address的偏移 if (!pgd_present(*pgd_k))//內核PGD頁表對應的項不存在,則無法進行下一步,返回NULL
return NULL; /*
* set_pgd(pgd, *pgd_k); here would be useless on PAE
* and redundant with the set_pmd() on non-PAE. As would
* set_pud.
*/ /*獲取當前頁表對應address的PUD地址和內核頁表對應address的地址,並判斷pud_k對應的項是否存在*/
pud = pud_offset(pgd, address);
pud_k = pud_offset(pgd_k, address);
if (!pud_present(*pud_k))
return NULL; /*對pmd進行和上面類似的操作*/
pmd = pmd_offset(pud, address);
pmd_k = pmd_offset(pud_k, address);
if (!pmd_present(*pmd_k))
return NULL; if (!pmd_present(*pmd))//當前使用頁表對應的pmd項不存在,則修正pmd項使其和內核頁表的pmd_k項相同
set_pmd(pmd, *pmd_k);
else
BUG_ON(pmd_page(*pmd) != pmd_page(*pmd_k)); return pmd_k;
}

如果do_page_fault()函數執行到了bad_area_nosemaphore(),那麼就表明這次異常是由於對非法的地址訪問造成的。在內核中產生這样的結果的情況一般有兩種:

1.內核通過用戶空間傳遞的系統調用参數,訪問了無效的地址

2.內核的程序設計缺陷

第一種情況內核尚且能通過異常修正機制來進行修复,而第二種情況就會導致OOPS錯誤了,內核將強制用SIGKILL結束當前進程。

內核態的bad_area_nosemaphore()的實際處理函數为bad_area_nosemaphore()-->__bad_area_nosemaphore()-->no_context()

static noinline void
no_context(struct pt_regs *regs, unsigned long error_code,
unsigned long address)
{
struct task_struct *tsk = current;
unsigned long *stackend;
unsigned long flags;
int sig; /* Are we prepared to handle this kernel fault? */
/*fixup_exception()用於搜索異常表,並試圖找到一個對應該異常的例程來進行修正,
這個例程在fixup_exception()返回後執行*/
if (fixup_exception(regs))
return; /*
* 32-bit:
*
* Valid to do another page fault here, because if this fault
* had been triggered by is_prefetch fixup_exception would have
* handled it.
*
* 64-bit:
*
* Hall of shame of CPU/BIOS bugs.
*/
if (is_prefetch(regs, error_code, address))
return; if (is_errata93(regs, address))
return; /*
* Oops. The kernel tried to access some bad page. We'll have to
* terminate things with extreme prejudice:
*/
/* 走到這裏就說明異常確實是由於內核的程序設計缺陷導致的了,內核將
產生一個oops,下面的工作就是打印CPU寄存器和內核態堆棧的信息到控制台並
終結當前的進程*/
flags = oops_begin(); show_fault_oops(regs, error_code, address); stackend = end_of_stack(tsk);
if (*stackend != STACK_END_MAGIC)
printk(KERN_ALERT "Thread overran stack, or stack corrupted\n"); tsk->thread.cr2 = address;
tsk->thread.trap_no = 14;
tsk->thread.error_code = error_code; sig = SIGKILL;
if (__die("Oops", regs, error_code))
sig = 0; /* Executive summary in case the body of the oops scrolled away */
printk(KERN_EMERG "CR2: %016lx\n", address); oops_end(flags, regs, sig);
}

linux缺頁異常處理--內核空間[v3.10]的更多相关文章

  1. 第一章 Linux內核簡介

    1. Linux是類Unix系統,但他不是Unix. 儘管Linux借鑑了Unix的許多設計並且實現了Unix的API(由Posix標準和其他Single Unix Specification定義的) ...

  2. 【转】Linux內核驅動之GPIO子系統(一)GPIO的使用 _蝸牛

    原文网址:http://tc.chinawin.net/it/os/article-2512b.html 一 概述 Linux內核中gpio是最簡單,最常用的資源(和interrupt ,dma,ti ...

  3. 整理幾種常見PCB表面處理的優缺點

    這只是一篇整理文,而且我個人僅從事過後段的電路板組裝,而未從事過電路板製程,所以有些見解純粹只是個人看法,如果有些不一樣的聲音或錯誤也歡迎留言討論. 隨著時代的演進,科技的進步,環保的要求,電子業也隨 ...

  4. 在 Windows 上遇到非常多 TIME_WAIT 連線時應如何處理

        我們公司所代管的網站裡,有幾個流量是非常大的,在尖峰的時刻同時上線人數可能高達數千到數萬人,而在這個時候如果使用 netstat 或 TCPView 查看所有 TCP 連線時就會看到非常多處於 ...

  5. C++ 檔案、資料夾、路徑處理函式庫:boost::filesystem

    原帖:https://tokyo.zxproxy.com/browse.php?u=uG7kXsFlW1ZmaxKEvCzu8HrCJ0bXIAddA1s5dtIUZ%2FYzM1u9JI7jjKLT ...

  6. java 異常抛出 throw 與 return

    package 異常;    public class TestException {      public TestException() {      }        boolean test ...

  7. Linux下安裝Oracle database內核參數設置

    參考:1529864.1 ************************************************** RAM                                  ...

  8. T-SQL 簡易小數處理

    今天因應同事提的一則需求,寫了一段 CASE WHEN 的整數與小數處理 過程中居然踩了個雷,特此記錄下來 首先,需求如下: 當內容為整數或零時則去掉尾端的小數否則就顯示原本的小數內容 若內容為 NU ...

  9. 關於 WebClient wc = new WebClient() 下載第三方數據不能進安安信任異常

    報錯異常:The underlying connection was closed: Could not establish trust relationship for SSL/TLS secure ...

随机推荐

  1. ASE Alpha Sprint - backend scrum 6

    本次scrum于2019.11.11在sky garden进行,持续30分钟. 参与人: Zhikai Chen, Jia Ning, Hao Wang 请假: Xin Kang, Lihao Ran ...

  2. 五 shell 变量与字符串操作

    特点:1 shell变量没有数据类型的区分 2 Shell 把任何存储在变量中的值,皆视为以字符组成的“字符串”.    3  设定的变量值只在当前shell环境中有作用    4   不能以数字开头 ...

  3. 数据库系统实现 第一章 DBMS实现概述

    DBMS提供的能力 1)持久存储 DBMS在灵活性方面比文件系统要好,同时支持对非常大量数据的存储 2)编程接口 3)事务管理 DBMS支持对数据的并发存取,即多个不同的进程(称作事物)同时存取操作, ...

  4. BZOJ 2141 排队 (CDQ分治)

    [BZOJ2141]排队 这道题和动态逆序对比较像(BZOJ-3295 没做过的同学建议先做这题),只是删除操作变成了交换.解法:交换操作可以变成删除加插入操作,那么这题就变成了 (时间,位置,值)的 ...

  5. Gitbook环境搭建及制作——2019年10月24日

    1.gitbook介绍 GitBook 是一个基于 Node.js 的命令行工具,支持 Markdown 和 AsciiDoc 两种语法格式,可以输出 HTML.PDF.eBook 等格式的电子书.可 ...

  6. MySQL查看表索引

    mysql> show index from tblname; mysql> show keys from tblname; · Table 表的名称. · Non_unique 如果索引 ...

  7. matlab中求解线性方程组的rref函数

    摘自:http://www.maybe520.net/blog/987/ matlab中怎么求解线性方程组呢? matlab中求解线性方程组可应用克拉默法则(Cramer's Rule)即通过det( ...

  8. Graphics 绘图

    Graphics类提供基本绘图方法,Graphics2D类提供更强大的绘图能力. Graphics类提供基本的几何图形绘制方法,主要有:画线段.画矩形.画圆.画带颜色的图形.画椭圆.画圆弧.画多边形等 ...

  9. vue中使用canvas绘制签名

    不多说,上代码: <template>         <div class="sign-canvas">             <canvas   ...

  10. Redis缓存在django中的配置

    django  settings中的配置 # 缓存 CACHES = { "default": { "BACKEND": "django_redis. ...