地址空间标识符ASID

目录

目的

以ASID为8位长度叙述

引入版本号好处

当进程调度时


目的

        为了减少在进程切换时清空页表缓存的需要,ARM64处理器的页表缓存使用非全局位区分内核和进程的页表项。

  • nG位为0 表示内核页表项;
  • 使用地址空间标识符(Address Space Identifier,ASID)区分不同进程的页表项。
  • ASID的长度可以是8位、16位;
  • 寄存器TTBR0_EL1(转换表基准寄存器0)或TTBR1_EL1(转换表基准寄存器1)都可以用来存放ASID寄存器TCR_EL1的A1位决定使用哪个寄存器存放当前进程的ASID,通常使用TTBR0_EL1寄存器TTBR0_EL1的位[63:48]存放当前ASID,[47:1]存放当前进程的页全局目录物理地址(如果是8位,[63:56]是保留位;)

以ASID为8位长度叙述

ASID只有256个值,其中0是保留值;进程数量超过255,两个ASID可能相同,内核引入版本号,解决。具体步骤如下:

1)每个进程有64位软件ASID,低8位存放硬件ASID,高56位存放ASID版本号;

2)64位全局变量asid_generation的高56位保存全局ASID版本号;

3)当进程被调度时,比较进程ASID版本号和全局ASID版本号,如果版本号相同,那么直接使用上次分配的硬件ASID,否则重新给进程分配硬件ASID。

  • 如果存在空闲的硬件ASID,那么选择一个分配给进程;
  • 如果没有空闲的硬件ASID,把全局ASID版本号加1,重新从1开始分配硬件ASIDA.因为刚分配的ASID可能和某个进程的硬件ASID相同,只是ASID版本号不同,页表缓存可能包含了这个进程的页表项,所以必须把所有处理器的页表缓存清空。

引入版本号好处

        避免每次进程切换都需要清空页表缓存,只需要在硬件ASID回滚时,把处理器的页表缓存清空。

内存描述符成员id存放内核给进程分配的ASID

typedef struct{
    atomic64_t id;

}mm_context_t;

//asid_bits保存ASID长度
static u32 asid_bits;

static DEFINE_RAW_SPINLOCK(cpu_asid_lock);

//asid_generation高56位保存全局ASID版本号
static atomic64_t asid_generation;

//位图asid_map记录哪些ASID被分配
static unsigned long *asid_map;

//active_asids 保存处理器正在使用的ASID
static DEFINE_PER_CPU(atomic64_t, active_asids);
//存放预留的ASID,用来在全局变量ASID加1时保存处理器正在执行的进程的ASID
static DEFINE_PER_CPU(u64, reserved_asids);
static cpumask_t tlb_flush_pending;

处理器给进程分配ASID时,如果ASID分配完了,那么把全局ASID加1,重新从1开始分配ASID,针对每个处理器,使用该处理器的reserved_asids保存该处理器正在执行的进程的ASID,并且把该处理器的active_asids设置0。active_asids为0 具有特殊意义,说明全局ASID版本号变化,ASID从255回到1

当全局ASID加1时,每个处理器都需要清空页表缓存,位图tlb_flush_pending保存需要清空页表缓存的处理器集合。

当进程调度时

void check_and_switch_context(struct mm_struct *mm, struct task_struct *tsk)
{
	unsigned long flags;
	unsigned int cpu = smp_processor_id();
	u64 asid;

	if (unlikely(mm->context.vmalloc_seq != init_mm.context.vmalloc_seq))
		__check_vmalloc_seq(mm);

	/*
	 * We cannot update the pgd and the ASID atomicly with classic
	 * MMU, so switch exclusively to global mappings to avoid
	 * speculative page table walking with the wrong TTBR.
	 */
	cpu_set_reserved_ttbr0();

	asid = atomic64_read(&mm->context.id);
	if (!((asid ^ atomic64_read(&asid_generation)) >> ASID_BITS)
	    && atomic64_xchg(&per_cpu(active_asids, cpu), asid))
		goto switch_mm_fastpath;

    //禁止硬件中断并申请自旋锁
	raw_spin_lock_irqsave(&cpu_asid_lock, flags);
	/* Check that our ASID belongs to the current generation. */
    //重新比较版本号,如果不同重新分配
	asid = atomic64_read(&mm->context.id);
	if ((asid ^ atomic64_read(&asid_generation)) >> ASID_BITS) {
		asid = new_context(mm, cpu);
		atomic64_set(&mm->context.id, asid);
	}
    //tlb_flush_pending 当前处理器对应的位被设置,那么把当前处理器的页表缓存清空
    //把全局ASID版本号加1时,需要把所有处理器的页表缓存清空,在位图tlb_flush_pending中把所有处理器对应的位设置
	if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending)) {
		local_flush_bp_all();
		local_flush_tlb_all();
	}
    //把单当前处理器的active_asids设置为ASID
	atomic64_set(&per_cpu(active_asids, cpu), asid);
	cpumask_set_cpu(cpu, mm_cpumask(mm));
    //释放自旋锁并开启硬中断
	raw_spin_unlock_irqrestore(&cpu_asid_lock, flags);

switch_mm_fastpath://快速路径
	cpu_switch_mm(mm->pgd, mm);
}

1、如果进程的ASID与全局ASID版本号相同,那么调用函数atomic64_xchg 把当前处理器的active_asid设置成进程的ASID,并且返回active_asids的旧值。

2、如果active_asidsde 旧值不是0,那么执行快速路径

3、如果旧值是0,说明其他处理器在分配ASID时把全局ASID版本号加1了,那么执行慢速路径。

4、调用函数cpu_switch_mm设置寄存器TTBR0_EL1

你可能感兴趣的:(linux内核分析,linux,kernel)