啟動信息描述
內核啟動過程中有如下打印信息:
CPU: PIPT / VIPT nonaliasing data cache, VIPT nonaliasing instruction cache
這行打印信息代表了處理器L1 CACHE所支持的尋址方式。
在kernel啟動過程中,雖然這里第一次出現CACHE相關的打印信息,但是,此處并不是kernel第一次操作CACHE。
例如:對于zImage而言,解壓縮過程中會開啟CACHE并配置CACHE 寫屬性。
#ifdef CONFIG_CPU_DCACHE_WRITETHROUGH
#define CB_BITS 0x08
#else
#define CB_BITS 0x0c
#endif
CPU初始化的匯編文件head.S中,會根據kernel配置來使能CACHE。
#ifdef CONFIG_CPU_DCACHE_DISABLE
bic r0, r0, #CR_C
#endif
#ifdef CONFIG_CPU_BPREDICT_DISABLE
bic r0, r0, #CR_Z
#endif
#ifdef CONFIG_CPU_ICACHE_DISABLE
bic r0, r0, #CR_I
#endif
代碼分析
以上打印信息源自于 kernel 代碼的setup.c中。
static void __init cacheid_init(void)
{
unsigned int arch = cpu_architecture();
if (arch >= CPU_ARCH_ARMv6) {
unsigned int cachetype = read_cpuid_cachetype();
if ((arch == CPU_ARCH_ARMv7M) && !(cachetype & 0xf000f)) {
cacheid = 0;
} else if ((cachetype & (7 < < 29)) == 4 < < 29) {
/* ARMv7 register format */
arch = CPU_ARCH_ARMv7;
cacheid = CACHEID_VIPT_NONALIASING;
switch (cachetype & (3 < < 14)) {
case (1 < < 14):
cacheid |= CACHEID_ASID_TAGGED;
break;
case (3 < < 14):
cacheid |= CACHEID_PIPT;
break;
}
} else {
arch = CPU_ARCH_ARMv6;
if (cachetype & (1 < < 23))
cacheid = CACHEID_VIPT_ALIASING;
else
cacheid = CACHEID_VIPT_NONALIASING;
}
if (cpu_has_aliasing_icache(arch))
cacheid |= CACHEID_VIPT_I_ALIASING;
} else {
cacheid = CACHEID_VIVT;
}
pr_info("CPU: %s data cache, %s instruction cache\\n",
cache_is_vivt() ? "VIVT" :
cache_is_vipt_aliasing() ? "VIPT aliasing" :
cache_is_vipt_nonaliasing() ? "PIPT / VIPT nonaliasing" : "unknown",
cache_is_vivt() ? "VIVT" :
icache_is_vivt_asid_tagged() ? "VIVT ASID tagged" :
icache_is_vipt_aliasing() ? "VIPT aliasing" :
icache_is_pipt() ? "PIPT" :
cache_is_vipt_nonaliasing() ? "VIPT nonaliasing" : "unknown");
}
首先,通過cpu_architecture()獲取處理器內核版本號
int __pure cpu_architecture(void)
{
BUG_ON(__cpu_architecture == CPU_ARCH_UNKNOWN);
return __cpu_architecture;
}
處理器內核版本號通過數據結構proc_arch來維護,數據結構成員的值代表的ARM-vXXX
static const char *proc_arch[] = {
"undefined/unknown",
"3",
"4",
"4T",
"5",
"5T",
"5TE",
"5TEJ",
"6TEJ",
"7",
"7M",
"?(12)",
"?(13)",
"?(14)",
"?(15)",
"?(16)",
"?(17)",
};
處理器內核版本編號如下,例如__cpu_architecture為4則代表的是ARM-v5內核。
#define CPU_ARCH_UNKNOWN 0
#define CPU_ARCH_ARMv3 1
#define CPU_ARCH_ARMv4 2
#define CPU_ARCH_ARMv4T 3
#define CPU_ARCH_ARMv5 4
#define CPU_ARCH_ARMv5T 5
#define CPU_ARCH_ARMv5TE 6
#define CPU_ARCH_ARMv5TEJ 7
#define CPU_ARCH_ARMv6 8
#define CPU_ARCH_ARMv7 9
#define CPU_ARCH_ARMv7M 10
需要注意的是,這里獲取處理器內核版本號,不是通過讀取MIDR寄存器來實現的。
針對具體的ARM內核版本,獲取CACHE所支持的尋址方式,并據此初始化cacheid。后面以cacheid為依據對CACHE的所支持的尋址方式進行分類。
在ARM處理器內部包含了CACHE類型信息的只讀寄存器,這在芯片設計初期便已經定義好,后期無法修改。在這個寄存器中包含了CACHE的尋址策略。對于ARM v系列的處理器內核而言,獲取CACHE類型的指令代碼如下:
#define read_cpuid(reg) \\
({ \\
unsigned int __val; \\
asm("mrc p15, 0, %0, c0, c0, " __stringify(reg) \\
: "=r" (__val) \\
: \\
: "cc"); \\
__val; \\
})
基于讀取CACHE類型寄存器得到信息來初始化cacheid,利用cachetype.h中定義的宏,打印出cache類型。
基于cacheid定義了如下的宏定義:
#define cache_is_vivt() cacheid_is(CACHEID_VIVT)
#define cache_is_vipt() cacheid_is(CACHEID_VIPT)
#define cache_is_vipt_nonaliasing() cacheid_is(CACHEID_VIPT_NONALIASING)
#define cache_is_vipt_aliasing() cacheid_is(CACHEID_VIPT_ALIASING)
#define icache_is_vivt_asid_tagged() cacheid_is(CACHEID_ASID_TAGGED)
#define icache_is_vipt_aliasing() cacheid_is(CACHEID_VIPT_I_ALIASING)
#define icache_is_pipt() cacheid_is(CACHEID_PIPT)
...
static inline unsigned int __attribute__((pure)) cacheid_is(unsigned int mask)
{
return (__CACHEID_ALWAYS & mask) |
(~__CACHEID_NEVER & __CACHEID_ARCH_MIN & mask & cacheid);
}
而本文開頭中打印出來的信息,就是基于上面這些宏得出的結果。
從kenrel啟動的日志信息中可以看出,當前CPU內核的CACHE類型如下:
DCACHE
PIPT / VIPT nonaliasing
ICACHE
VIPT nonaliasing
ARM處理器不同內核版本的CACHE類型如下:
通常來說,CACHE得尋址類型包括VIPT、PIPT、VIVT,而本文重點分析得是VIPT以及PIPT的工作原理。
VIPT和PIPT
具備MMU(TLB)、CACHE的ARM處理器,其地址翻譯的流程如下:
那么上圖中紅框內的CACHE部分,根據索引或標簽對應的是物理地址還是虛擬地址,將CACHE的尋址方式分為VIPT CACHE和PIPT CACHE。
VI的含義是使用虛擬地址來構建緩存索引
(index)。反之,PI的含義是使用物理地址來構建緩存索引。
緩存索引用于從緩存中提取標記,將它與從物理地址計算的緩存標記比對。如果匹配,則緩存中命中
該查找,從緩存中提取相關的數據。否則,將從下一層緩存(或從內存)中提取。
PT的含義是使用物理地址來構建緩存標記
(tag),之所以使用PT作為CACHE查找索引是為了解決VIVT中存在的索引沖突,即兩個進程可以為不同的物理地址使用相同的虛擬地址。
VIPT
VIPT CACHE使用物理地址作為Tag,邏輯地址作為Index。通過Index查詢CACHE獲取到物理地址中的tag部分。同時呢,利用邏輯地址去查TLB,在TLB中獲取到物理地址。然后將CACHE中查詢到的物理地址Tag部分,同TLB中獲取到的物理地址Tag部分作比較。若二者相同,則CACHE hit,否則CACHE miss。
aliasing
對于VIPT CACHE而言,下面代碼中的virtual_addr_A和virtual_addr_B雖然不同,但是它們指向了同一個物理地址PA。
mmap(virtual_addr_A,4096,prot,flags,file_descriptor,offset)
mmap(virtual_addr_B,4096,prot,flags,file_descriptor,offset)
VIPT使用虛擬地址作為CACHE Index,因此物理地址A的數據在CACHE中有兩份,分別由virtual_addr_A和virtual_addr_B進行管理。這樣的情形稱之為CACHE 別名。
可以通過下面的4種方法來避免別名問題帶來的影響:
1.當進行內存數據更新時進行cache invalid操作
- 多副本數據同步更新
以上兩種方法需要進程通過虛實地址轉換等操作,獲取到是否有副本數據存在這樣的信息。而VIPT的設計初衷是避免虛實地址轉換,因此,這兩種方法并不是非常可取。
- 縮小CACHE size
假設對于一個32bit位寬的虛擬地址而言,他的page offset為12bit。若CACHE size小于4K則不會產生別名問題,假設CACHE size大于4K,則會產生別名問題。但是CACHE過小,cache miss又會大幅增加。
- page color或者cache cloring
若以上3種方法都沒有解決CACHE別名問題,那么可以使用緩存著色的方法。
PIPT
PIPT中的tag和index均為物理地址。而CPU發出的邏輯地址也稱之為虛擬地址,因此,首先需要通過TLB或查詢內存中的頁表,將邏輯地址轉換為對應的物理地址。再進行CACHE緩存查找。索引和標簽都使用物理地址。雖然這很簡單,避免了重名問題,但速度也很慢,因為必須先查找物理地址(這可能涉及TLB丟失和訪問主內存),才能在緩存中查找該地址。
-
處理器
+關注
關注
68文章
19259瀏覽量
229657 -
ARM
+關注
關注
134文章
9087瀏覽量
367398 -
寄存器
+關注
關注
31文章
5336瀏覽量
120235 -
LINUX內核
+關注
關注
1文章
316瀏覽量
21644 -
cache技術
+關注
關注
0文章
41瀏覽量
1062
發布評論請先 登錄
相關推薦
評論