认识ASID


注:以下讨论的前提是针对于ARM V7Cache属性为PIPT的情况。

ASIDARM提供的Context ID register寄存器的低8位。它的全称是Address Space Identifier 也叫 Application Space Identifier. 这两个名称在ARM Reference中都提到了。

为什么要引入ASID呢?
To reduce the software overhead of TLB maintenance, the VMSA distinguishes between Global pages and Process specific pages. The Address Space Identifier (ASID) identifies pages associated with a specific process and provides a mechanism for changing process specific tables without having to perform maintenance on the TLB structures.[1]

Kernel中,对ASID的注释写的言简意赅:
The ASID is used to tag entries in the CPU caches and TLBs.[2]

所以:由于每一个进程对应一个ASID,并且TLB的每一个Entry使用ASID来进行tag,那么就不必在每一次在做进程切换的时候,都需要刷新TLB

不过,这只是一个粗浅的理解。对于ASID的理解,需要和Pagetable中的nG位放在一起讨论。下图是ARM V7mapping entry formatfirst-level descriptor formats[3]second-level descriptor formats[4]

红色标注的地方是nG位。nG位的作用:
nG == 0  The translation is global.
nG == 1  The translation is process specific, meaning it relates to the current ASID, as defined by the CONTEXTIDR.

Each non-global region has an associated Address Space Identifier (ASID). These identifiers enable different translation table mappings to co-exist in a caching structure such as a TLB. This means that a new mapping of a non-global memory region can be created without removing previous mappings.[5]

mapping entryformat中,可以看出,ASID是否tag TLB中的entry,是需要看mapping entry中的nG是否置位。如果不置位,则是全局的,没有ASIDtag;否则,TLBentryASID tag,这样,CPU发出的Virtual AddressTLB中去寻址entry的时候,还需要比对ASID是否和TLB中的tag一致。

Linux Kernel中,通常的虚拟地址的配置是3:1的方案,即User space03GBKernel space3GB~4GB。每一个User space0~3GBSpace是独立的,而3GB~4GB的地址空间则是共享的。当用户进程在内核态的时候,其地址空间是3GB4GB,所有的进程地址空间都是相同的(严格意义上,VMALLOC_START~VMALLOC_END的部分可能是不同的,但实际寻址上,会采用page fault的机制使其相同)。因此,对于3GB~4GB之间的空间是Global(IOW,TLB中,不应该由ASIDtag)。在http://hfli0.blogspot.com/2012/06/mmu-addressing.html中,对mapping的初始化进行过总结。在对3GB~4GB的内存进行映射初始化的时候,其mapping entry的属性为:
PMD_TYPE_SECT|PMD_SECT_AP_WRITE| PMD_SECT_WBWA | ecc_mask|0<<5,其中:

PMD_TYPE_SECT:2<<0
PMD_SECT_AP_WRITE:1<<10
PMD_SECT_WBWA:1<<3 | 1<<2 |  1<<12

019对应的二进制:
0 0 0 1 0 001 01 0 0000 0 1 1 10

即:
nG S AP[2] Tex[2:0]   AP[1:0] Domain  C B
0    1      0        01              01         0           1 1

由于对于3GB~4GBTLB entry是没有ASIDtag的,因此,对于内核线程是没有ASID的。这体现内核Kernel code的两个地方,在fork的时候,如果是用户进程,初始化context id0(代码片段1);在context_switch的时候,需要对context id赋上真正意义上的asidvalue(代码片段2)。
Asid是在init_new_context中分配:

dup_mm->init_new_context:

69 #define init_new_context(tsk,mm)        (__init_new_context(tsk,mm),0)

40 /*
 41  * We fork()ed a process, and we need a new context for the child
 42  * to run in.  We reserve version 0 for initial tasks so we will
 43  * always allocate an ASID. The ASID 0 is reserved for the TTBR
 44  * register changing sequence.
 45  */
 46 void __init_new_context(struct task_struct *tsk, struct mm_struct *mm)
 47 {
 48         mm->context.id = 0;
 49         raw_spin_lock_init(&mm->context.id_lock);
 50 }
 51
dup_mmcopy_mm调用:
827 static int copy_mm(unsigned long clone_flags, struct task_struct *tsk)
 828 {
 837
 838         tsk->mm = NULL;
 839         tsk->active_mm = NULL;
 840
 841         /*
 842          * Are we cloning a kernel thread?
 843          *
 844          * We need to steal a active VM for that..
 845          */
 846         oldmm = current->mm;
 847         if (!oldmm)
 848                 return 0;
 849
 850         if (clone_flags & CLONE_VM) {
 851                 atomic_inc(&oldmm->mm_users);
 852                 mm = oldmm;
 853                 goto good_mm;
 854         }
 855
 856         retval = -ENOMEM;
 857         mm = dup_mm(tsk);
 858         if (!mm)
 859                 goto fail_nomem;
}
如果新fork的进程是fork一个kernel thread,那么该进程是没有context id的。即,不会运行到857行。

另外,在__new_context中,有对asid进行赋值(152行和170行)。

context_switch ->switch_mm->check_context ->__new_context

131 void __new_context(struct mm_struct *mm)
132 {
133         unsigned int asid;
134
135         raw_spin_lock(&cpu_asid_lock);
136 #ifdef CONFIG_SMP
137         /*
138          * Check the ASID again, in case the change was broadcast from
139          * another CPU before we acquired the lock.
140          */
141         if (unlikely(((mm->context.id ^ cpu_last_asid) >> ASID_BITS) == 0)) {
142                 cpumask_set_cpu(smp_processor_id(), mm_cpumask(mm));
143                 raw_spin_unlock(&cpu_asid_lock);
144                 return;
145         }
146 #endif
147         /*
148          * At this point, it is guaranteed that the current mm (with
149          * an old ASID) isn't active on any other CPU since the ASIDs
150          * are changed simultaneously via IPI.
151          */
152         asid = ++cpu_last_asid;
153         if (asid == 0)
154                 asid = cpu_last_asid = ASID_FIRST_VERSION;
155
156         /*
157          * If we've used up all our ASIDs, we need
158          * to start a new version and flush the TLB.
159          */
160         if (unlikely((asid & ~ASID_MASK) == 0)) {
161                 asid = cpu_last_asid + smp_processor_id() + 1;
162                 flush_context();
163 #ifdef CONFIG_SMP
164                 smp_wmb();
165                 smp_call_function(reset_context, NULL, 1);
166 #endif
167                 cpu_last_asid += NR_CPUS;
168         }
169
170         set_mm_context(mm, asid);
171         raw_spin_unlock(&cpu_asid_lock);
172 }
但是,对于context_switch,如果将要调度到CPU上运行的是内核线程,则不会运行switch_mm的。

以上的两个代码片段是Kernel code中两个对asid赋值的地方。从以上分析来看,对于内核线程,确实是没有asid的。另外,从ASID的名字Application Space Identifier来看,ASID是与用户进程有关系,和内核线程是没有关系的。

[1]. Page B3-3, ARM ® Architecture Reference Manual-ARM ® v7-A and ARM ® v7-R edition(ARM DDI 0406B)
[2]. Line 36, arch/arm/include/asm/mmu_context.h, Linux Kernel v3.3
[3]. Page B3-8, ARM ® Architecture Reference Manual-ARM ® v7-A and ARM ® v7-R edition(ARM DDI 0406B)
[4]. Page B3-10, ARM ® Architecture Reference Manual-ARM ® v7-A and ARM ® v7-R edition(ARM DDI 0406B)
[5].Page B3-54, ARM ® Architecture Reference Manual-ARM ® v7-A and ARM ® v7-R edition(ARM DDI 0406B)


评论

此博客中的热门博文

Linux/ARM Page Table Entry 属性设置分析

提交了30次才AC ---【附】POJ 2488解题报告

笔记