linux缺頁異常處理--內核空間[v3.10]
缺頁異常被觸發通常有兩種情況——
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]的更多相关文章
- 第一章 Linux內核簡介
1. Linux是類Unix系統,但他不是Unix. 儘管Linux借鑑了Unix的許多設計並且實現了Unix的API(由Posix標準和其他Single Unix Specification定義的) ...
- 【转】Linux內核驅動之GPIO子系統(一)GPIO的使用 _蝸牛
原文网址:http://tc.chinawin.net/it/os/article-2512b.html 一 概述 Linux內核中gpio是最簡單,最常用的資源(和interrupt ,dma,ti ...
- 整理幾種常見PCB表面處理的優缺點
這只是一篇整理文,而且我個人僅從事過後段的電路板組裝,而未從事過電路板製程,所以有些見解純粹只是個人看法,如果有些不一樣的聲音或錯誤也歡迎留言討論. 隨著時代的演進,科技的進步,環保的要求,電子業也隨 ...
- 在 Windows 上遇到非常多 TIME_WAIT 連線時應如何處理
我們公司所代管的網站裡,有幾個流量是非常大的,在尖峰的時刻同時上線人數可能高達數千到數萬人,而在這個時候如果使用 netstat 或 TCPView 查看所有 TCP 連線時就會看到非常多處於 ...
- C++ 檔案、資料夾、路徑處理函式庫:boost::filesystem
原帖:https://tokyo.zxproxy.com/browse.php?u=uG7kXsFlW1ZmaxKEvCzu8HrCJ0bXIAddA1s5dtIUZ%2FYzM1u9JI7jjKLT ...
- java 異常抛出 throw 與 return
package 異常; public class TestException { public TestException() { } boolean test ...
- Linux下安裝Oracle database內核參數設置
參考:1529864.1 ************************************************** RAM ...
- T-SQL 簡易小數處理
今天因應同事提的一則需求,寫了一段 CASE WHEN 的整數與小數處理 過程中居然踩了個雷,特此記錄下來 首先,需求如下: 當內容為整數或零時則去掉尾端的小數否則就顯示原本的小數內容 若內容為 NU ...
- 關於 WebClient wc = new WebClient() 下載第三方數據不能進安安信任異常
報錯異常:The underlying connection was closed: Could not establish trust relationship for SSL/TLS secure ...
随机推荐
- BUUCTF--easyer
测试文件下载:https://buuoj.cn/files/b66a080016da04abfc002a336c0132e5/easyre.zip?token=eyJ0ZWFtX2lkIjpudWxs ...
- BJSV-P-002高精度测速一体机
测速.抓拍.录像于一体,产品处于行业顶尖水平. 1 测速一体机参数 2 接口和资源 3 相机接口 1. 前面板接口 测速一体机镜头接口采用C-Mount ...
- Machine Learning:机器学习算法
原文链接:https://riboseyim.github.io/2018/02/10/Machine-Learning-Algorithms/ 摘要 机器学习算法分类:监督学习.半监督学习.无监督学 ...
- Docker的使用(未完待续)
一.帮助命令 docker version docker info docker --help 二.镜像命令 列出机器上所有的镜像 docker images 查找某个镜像 docker search ...
- ivew 绑定时间控件
<FormItem label="开始时间" style="width: 100%" prop="startDate"> < ...
- bzoj 1003物流运输 区间dp+spfa
基本思路: 一开始确实没什么思路,因为觉得怎么着都会超时,然后看一下数据范围,呵,怎么都不会超时. 思路: 1.看到能改变线路,想到可以用以下区间dp,区间dp的话,先枚举长度,枚举开始位置,然后枚举 ...
- 前端每日实战:53# 视频演示如何用纯 CSS 创作一个文本淡入淡出的 loader 动画
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/ERwpeG 可交互视频 此视频是可 ...
- Struts2基础-4 -struts拦截器
Struts2拦截器工作原理 拦截器围绕着 Action和 Result的执行而执行. Struts2拦截器的工作方式如图10.2所示.从上图中可以看出, Struts2拦截器的实现原理和 Servl ...
- oracle集合的应用
union:求并集,公共部分只包含一个 ABC 和AB都没有显示出来 2:union all 相同的数据会包含两个 3:交集 intersect 既包含A又包含B 4:求差集 A集合中的数据B集合中 ...
- SGU 194 Reactor Cooling (无源上下界网络流)
The terrorist group leaded by a well known international terrorist Ben Bladen is buliding a nuclear ...