进程创建pgd页表
分配每个进程的pgd。
mm_alloc:fs/exec.c: bprm->mm = mm = mm_alloc(); //exec加载新二进制程序
mm_init :kernel/fork.c:
mm_alloc_pgd
pgd_alloc
pgd_alloc函数: arch/arm64/mm/pgd.c
pgd_t *pgd_alloc(struct mm_struct *mm)
{
if (PGD_SIZE == PAGE_SIZE)
return (pgd_t *)__get_free_page(PGALLOC_GFP);
else
return kmem_cache_alloc(pgd_cache, PGALLOC_GFP);
}
#define PGD_SIZE (PTRS_PER_PGD * sizeof(pgd_t))
上述PGD_SIZE在PA_BITS为48bit的机器上:PTRS_PER_PGD 64 * sizeof(pgd_t) 8 = 512。因此会用kmem_cache_create接口slub来分配小块内存。
返回pgd表虚拟地址。
pgd_cache 的kmeme_cache对象初始化在arch/arm64/mm/pgd.c#L31
void __init pgd_cache_init(void)
{
if (PGD_SIZE == PAGE_SIZE)
return;
#ifdef CONFIG_ARM64_PA_BITS_52
/*
* With 52-bit physical addresses, the architecture requires the
* top-level table to be aligned to at least 64 bytes.
*/
BUILD_BUG_ON(PGD_SIZE < 64);
#endif
/*
* Naturally aligned pgds required by the architecture.
*/
pgd_cache = kmem_cache_create("pgd_cache", PGD_SIZE, PGD_SIZE,
SLAB_PANIC, NULL);
}
因此在该arm64平台下分配的pgd表512字节。其中有64个pgd_t 项。
pgd切换
static __always_inline struct rq *
context_switch(struct rq *rq, struct task_struct *prev,
struct task_struct *next, struct rq_flags *rf)
{
if (!next->mm) { // to kernel
enter_lazy_tlb(prev->active_mm, next);
next->active_mm = prev->active_mm;
if (prev->mm) // from user
mmgrab(prev->active_mm);
else
prev->active_mm = NULL;
} else { // to user
membarrier_switch_mm(rq, prev->active_mm, next->mm);
/*
* sys_membarrier() requires an smp_mb() between setting
* rq->curr / membarrier_switch_mm() and returning to userspace.
*
* The below provides this either through switch_mm(), or in
* case 'prev->active_mm == next->mm' through
* finish_task_switch()'s mmdrop().
*/
switch_mm_irqs_off(prev->active_mm, next->mm, next);
if (!prev->mm) { // from kernel
/* will mmdrop() in finish_task_switch(). */
rq->prev_mm = prev->active_mm;
prev->active_mm = NULL;
}
}
/* Here we just switch the register state and the stack. */
switch_to(prev, next, prev);
barrier();
return finish_task_switch(prev);
}
__schedule
context_switch
0: to kernel
....
1: to user //切换用户进程需要切换内存
switch_mm_irqs_off(=switch_mm) arg:next
__switch_mm
check_and_switch_context
cpu_switch_mm
static inline void cpu_switch_mm(pgd_t *pgd, struct mm_struct *mm)
{
BUG_ON(pgd == swapper_pg_dir);
cpu_set_reserved_ttbr0();
cpu_do_switch_mm(virt_to_phys(pgd),mm);
}
switch_to(prev, next, prev);
finish_task_switch(prev);
ENTRY(cpu_do_switch_mm)
mrs x2, ttbr1_el1 //保存最高位为1(内核地址空间)的ttbr1_el1寄存器
mmid x1, x1 // get mm->context.id
phys_to_ttbr x3, x0
#ifdef CONFIG_ARM64_SW_TTBR0_PAN
bfi x3, x1, #48, #16 // set the ASID field in TTBR0
#endif
bfi x2, x1, #48, #16 // set the ASID
msr ttbr1_el1, x2 // in TTBR1 (since TCR.A1 is set)
isb
msr ttbr0_el1, x3 // now update TTBR0 更新用户空间ttbr0寄存器
isb
b post_ttbr_update_workaround // Back to C code...
ENDPROC(cpu_do_switch_mm)