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 ...
随机推荐
- javascript中的toString()、toLocaleString()方法
javascript中的toString()方法,主要用于Array.Boolean.Date.Error.Function.Number等对象.下面是这些方法的一些解析和简单应用,做个纪律,以作备忘 ...
- 2019-1-25-WPF-ListBox-的选择
title author date CreateTime categories WPF ListBox 的选择 lindexi 2019-01-25 21:43:17 +0800 2018-2-13 ...
- Java-java.lang.NoClassDefFoundError:brave.Span.Kind
今天在升级某个框架时,遇到如标题描述的问题.这个问题应该说还是比较明显的,首先去搜了一下NoClassDefFoundError的问题,参考这篇博客:https://www.cnblogs.com/x ...
- 关于Jenkins的网站及其他学习的网站
配置efk https://www.cnblogs.com/fzxiaomange/p/efk-getstart.html https://blog.csdn.net/wangmuming/artic ...
- "=="、equals、hashCode之间的区别
1. "=="分为两种情况: (1) 基本数据类型,比较的是其对应的值是否相等: (2) 引用类型,比较的是他们在内存中存放的地址(或者说,是否指向同意对象). 2. equals ...
- java ArrayList练习题
package java06; /* *随机产生6的1——33的数字,并存储到列表中,再进行遍历 * */ import java.util.ArrayList; import java.util.R ...
- spring cloud学习一--Eureka服务注册与发现
spring cloud Eureka是基于Netflix Eureka服务发现注册产品的二次封装,它提供了服务注册功能(Service Registry)和服务发现功能(Service Discov ...
- ubuntu 安装php xdebug
windows 安装xdebug https://www.jetbrains.com/help/phpstorm/configuring-xdebug.html 一.下载 下载与PHP版兼容的Xdeb ...
- Yii Ar model 查询
Ar model 查询 参照表: CREATE TABLE tbl_user ( id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, username VA ...
- k8s和docker区别
简要介绍: docker是一个开源的应用容器引擎,开发者可以打包他们的应用以及依赖到一个容器中,发布到流行的liunx系统上,或者实现虚拟化. k8s是一个开源的容器集群管理系统,可以实现容器集群的自 ...