2017-01-18

来自本人的旧博客: http://blog.163.com/awaken_ing/blog/static/120613197201510308269773

1. 背景

平台为qemu ARM Versatile/PB (ARM926EJ-S) ARMv5TEJ, UP

我们知道进程的内核地址空间的页表 和 swapperpgdir 进行同步, 在进程创建时, do_fork()会拷贝一级页表.

2. 问题

vmalloc() area发生异常, 由dotranslationfault()处理, 处理的是一级页表无效的情况, 处理方法: If the init_task's first level page tables contains the relevant entry, we copy the it to this task.

可以猜想, vmalloc区域的页表是 两级页表(低两位D1D0为0b01), 而不是section (D1D0为0b10)

打印vmalloc区域的对应的页表, 可以看到 D1D0 既有0b01(指向二级页表), 也有0b10(无需二级页表) 的.

从cat /proc/vmallocinfo可知, 可知0b10不是由vmalloc()引发的页表创建, 而是ioremap(). (这里并不是说ioremap创建的都是section)

虚拟地址0xc9000000开始对应的一级页表entry

(gdb) p /x *(int *)0xc0007240@65
$1 = {0x34000452, 0x34100452, 0x34200452, 0x34300452, 0x34400452, 0x34500452, 0x34600452, 0x34700452, 0x34800452, 
  0x34900452, 0x34a00452, 0x34b00452, 0x34c00452, 0x34d00452, 0x34e00452, 0x34f00452, 0x35000452, 0x35100452, 0x35200452, 
  0x35300452, 0x35400452, 0x35500452, 0x35600452, 0x35700452, 0x35800452, 0x35900452, 0x35a00452, 0x35b00452, 0x35c00452, 
  0x35d00452, 0x35e00452, 0x35f00452, 0x36000452, 0x36100452, 0x36200452, 0x36300452, 0x36400452, 0x36500452, 0x36600452, 
  0x36700452, 0x36800452, 0x36900452, 0x36a00452, 0x36b00452, 0x36c00452, 0x36d00452, 0x36e00452, 0x36f00452, 0x37000452, 
  0x37100452, 0x37200452, 0x37300452, 0x37400452, 0x37500452, 0x37600452, 0x37700452, 0x37800452, 0x37900452, 0x37a00452, 
  0x37b00452, 0x37c00452, 0x37d00452, 0x37e00452, 0x37f00452, 0x0}

# cat /proc/vmallocinfo
...
0xc9000000-0xcd001000 67112960 armflash_subdev_probe+0x64/0x120 ioremap

对于 0b10(无需二级页表) 就不好理解. 考虑这样一种情况,

进程A执行ioremap(), 然后进程B更新了the first level page table. 之后进程A执行iounmap(), 进程B如何能够知道呢? (use-after-iounmap cases)

所以, ioremap()对应的iounmap() 如何处理一级页表, 才能达到 页表间的同步?

3. 解

先看下在何处创建:

虚拟地址的0xc9000000的一级页表在0xc0007240

(gdb) watch *(unsigned int *)0xc0007240
Watchpoint 5: *(unsigned int *)0xc0007240
(gdb) c
Continuing.
Watchpoint 5: *(unsigned int *)0xc0007240

Old value = 0
New value = 872416338
remap_area_sections (virt=<value optimized out>, pfn=213248, size=<value optimized out>, type=<value optimized out>)
    at arch/arm/mm/ioremap.c:207
207         pmd[1] = __pmd(__pfn_to_phys(pfn) | type->prot_sect);
(gdb) p /x 872416338
$7 = 0x34000452

由 (gdb) bt 可知, 调用路径

ioremap
|--__arm_ioremap
|    |--__arm_ioremap_caller
|    |   |--__arm_ioremap_pfn_caller
|    |   |   |--remap_area_sections

ioremap()有remapareasections(), 相对应的iounmap()有unmapareasections()

linux-2.6.35.7/arch/arm/mm/ioremap.c

#ifndef CONFIG_SMP
/*
 * Section support is unsafe on SMP - If you iounmap and ioremap a region,
 * the other CPUs will not see this change until their next context switch.
 * Meanwhile, (eg) if an interrupt comes in on one of those other CPUs
 * which requires the new ioremap'd region to be referenced, the CPU will
 * reference the _old_ region.
 *
 * Note that get_vm_area_caller() allocates a guard 4K page, so we need to
 * mask the size back to 1MB aligned or we will overflow in the loop below.
 */
static void unmap_area_sections(unsigned long virt, unsigned long size)
{
        do {
            ...
                init_mm.context.kvm_seq++;
                ...
        } while (addr < end);

 /*
  * Ensure that the active_mm is up to date - we want to
  * catch any use-after-iounmap cases.
  */
 if (current->active_mm->context.kvm_seq != init_mm.context.kvm_seq)
  __check_kvm_seq(current->active_mm);
}
void __check_kvm_seq(struct mm_struct *mm)
{
...
memcpy(pgd_offset(mm, VMALLOC_START), ...);
...
}

这里只处理了一个进程, 其他进程呢? 搜索 kvm_seq, 找到

linux-2.6.35.7/arch/arm/include/asm/mmu_context.h

check_context
{
if (unlikely(mm->context.kvm_seq != init_mm.context.kvm_seq))
  __check_kvm_seq(mm);
}

switch_mm
{
check_context(next);
}

所以, 在上下文切换时, 会知道一级页表发生了变化, 进而同步上.

本文地址: https://awakening-fong.github.io/posts/arm/arm_iounmap_master_table

转载请注明出处: https://awakening-fong.github.io


若无法评论, 请打开JavaScript, 并通过proxy.


blog comments powered by Disqus