page fault不能发生在内核态么?为什么?
这里有3个问题:
1、为什么会产生page fault?
2、发生缺页的上下文是否可以位于内核态
3、发生缺页的地址是否可以位于内核态地址空间
问题1:为什么会产生page fault?
page fault是硬件提供的特性,本质为一种“异常”(应该了解“异常”和“中断”的概念吧~),实际由硬件直接触发,触发的条件为:CPU访问某线性地址时,如果没有找到其对应的页表项,则由硬件直接触发page fault。
因此,page fault产生的根源在于线性地址没有对应的页表项,即线性地址还没有分配实际的物理内存,没有进行映射。
------------
问题2:发生缺页的上下文位于内核态,是否可能?是否正常?
答:有可能在内核态,但这种情况下,发生缺页的地址只能位于用户态地址空间,而且也只能为exceptions table中预先定义好的异常,如果exceptions table中没有预先定义的处理,或者缺页的地址位于内核态地址空间,则表示错误,进入oops流程。通常属于异常,会导致oops。
相关代码如下:
-
static void __kprobes
-
__do_page_fault(struct pt_regs *regs, unsigned long error_code)
-
{
-
...
-
if ((error_code & PF_USER) == 0 &&
-
!search_exception_tables(regs->ip)) {
-
bad_area_nosemaphore(regs, error_code, address);
-
return;
-
...
-
-
}
bad_area_nosemaphore()处理流程:
__bad_area_nosemaphore
no_context
oops_begin
__die
------------
问题3:发生缺页的地址位于内核态地址空间是否可能?是否正常?
答:有可能,发生缺页的内核态地址仅可能位于vmalloc区。对内核来说(以32位为例),线性映射(直接通过TASK_SIZE偏移映射)的内存(对32位系统来说,就是前896M,即Zone_Normal)相应的页表在内核初始化时就已经建立,所以这部分内存对应的虚拟地址不可能产生page fault。
那么Vmalloc区为什么会产生page fault呢?内核使用Vmalloc分配内存时,不是已经分配了相应的物理内存,并创建了相应的页表项进行了映射吗?
答:的确,在使用Vmalloc分配物理内存时,确实进行了映射,创建了页表项。但是,其修改的仅为内核的主内核页表,并没有更新相关进程的页表。在Vmalloc分配内存后,第一次访问相关的线性地址时,由于相关进程的页表中并没有Vmalloc分配内存相应的页表项,所以会触发page fault,而这个page fault的处理过程其实就是:从内核主页表中同步相应的页表项到进程的页表中,完成这次同步后,进程中相关的页表项建立,后续再次访问相关线性地址时,就不会再产生缺页异常了。Vmalloc区的page fault处理相关代码如下:
-
/*
-
* 缺页地址位于内核空间。并不代表异常发生于内核空间,有可能是用户
-
* 态访问了内核空间的地址。
-
*/
-
if (unlikely(fault_in_kernel_space(address))) {
-
if (!(error_code & (PF_RSVD | PF_USER | PF_PROT))) {
-
//检查发生缺页的地址是否在vmalloc区,是则进行相应的处理
-
if (vmalloc_fault(address) >= 0)
-
return;
-
/*
-
* 对于发生缺页异常的指针位于vmalloc区情况的处理,主要是将
-
* 主内核页表向当前进程的内核页表同步。
-
*/
-
static noinline __kprobes int vmalloc_fault(unsigned long address)
-
{
-
unsigned long pgd_paddr;
-
pmd_t *pmd_k;
-
pte_t *pte_k;
-
-
/* Make sure we are in vmalloc area: */
-
/* 区域检查 */
-
if (!(address >= VMALLOC_START && address < VMALLOC_END))
-
return -1;
-
-
WARN_ON_ONCE(in_nmi());
-
-
/*
-
* 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(最顶级页目录)地址,直接从CR3寄存器中读取。
-
*不要通过current获取,因为缺页异常可能在上下文切换的过程中发生,
-
*此时如果通过current获取,则可能会出问题*/
-
pgd_paddr = read_cr3();
-
//从主内核页表中,同步vmalloc区发生缺页异常地址对应的页表
-
pmd_k = vmalloc_sync_one(__va(pgd_paddr), address);
-
if (!pmd_k)
-
return -1;
-
//如果同步后,相应的PTE还不存在,则说明该地址有问题了
-
pte_k = pte_offset_kernel(pmd_k, address);
-
if (!pte_present(*pte_k))
-
return -1;
-
-
return 0;
-
}
阅读(1126) | 评论(0) | 转发(0) |