http://blog.csdn.net/vanbreaker/article/details/7867720
 缺页异常被触发通常有两种情况——
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;  
-   
-     
-     address = read_cr2(); 
-       
-     ...  
-          ...  
-   
-     if (unlikely(fault_in_kernel_space(address))) { 
-         if (!(error_code & (PF_RSVD | PF_USER | PF_PROT))) {
-             if (vmalloc_fault(address) >= 0)
-                 return;  
-   
-             if (kmemcheck_fault(regs, address, error_code))  
-                 return;  
-         }  
-   
-         
-         
-         if (spurious_fault(error_code, address))  
-             return;  
-   
-         
-         if (notify_page_fault(regs))  
-             return;  
-         
-         bad_area_nosemaphore(regs, error_code, address);  
-   
-         return;  
-     }  
-     ...  
-     ...  
- }  
 
该函数传递进来的两个参数--
regs包含了各个寄存器的值
error_code是触发异常的错误类型,它的含义如下
- 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;  
-   
-     
-     if (!(address >= VMALLOC_START && address < VMALLOC_END))  
-         return -1;  
-   
-     
-     pgd_paddr = read_cr3();
-     pmd_k = vmalloc_sync_one(__va(pgd_paddr), address);
-     if (!pmd_k)  
-         return -1;  
-   
-     
-     pte_k = pte_offset_kernel(pmd_k, address);
-     if (!pte_present(*pte_k))
-         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_k = init_mm.pgd + index;
-   
-     if (!pgd_present(*pgd_k))
-         return NULL;  
-   
-     
-        
-     
-     pud = pud_offset(pgd, address);  
-     pud_k = pud_offset(pgd_k, address);  
-     if (!pud_present(*pud_k))  
-         return NULL;  
-   
-     
-     pmd = pmd_offset(pud, address);  
-     pmd_k = pmd_offset(pud_k, address);  
-     if (!pmd_present(*pmd_k))  
-         return NULL;  
-   
-     if (!pmd_present(*pmd))
-         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()
- <span style="font-size:12px;">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;  
-   
-     
-     
-     if (fixup_exception(regs))  
-         return;  
-   
-     
-     if (is_prefetch(regs, error_code, address))  
-         return;  
-   
-     if (is_errata93(regs, address))  
-         return;  
-   
-     
-     
-     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;  
-   
-     
-     printk(KERN_EMERG "CR2: %016lx\n", address);  
-   
-     oops_end(flags, regs, sig);  
- }  
- </span>  
 linux缺页异常处理--内核空间
原文:http://www.cnblogs.com/feng9exe/p/7282771.html