我們先來看一個出錯的現場:
[ 55.195101] Unable to handle kernel paging request at virtual address ffffdfc7be9c2100
[ 55.195107] Mem abort info:
[ 55.195109] ESR = 0x96000004
[ 55.195112] Exception class = DABT (current EL), IL = 32 bits
[ 55.195114] SET = 0, FnV = 0
[ 55.195117] EA = 0, S1PTW = 0
[ 55.195118] Data abort info:
[ 55.195120] ISV = 0, ISS = 0x00000004
[ 55.195122] CM = 0, WnR = 0
[ 55.195125] [ffffdfc7be9c2100] address between user and kernel address ranges
[ 55.195128] Internal error: Oops: 96000004 [#1] PREEMPT SMP
.............
可以看到出錯提示是:Unable to hanle kernel paging requeset at virtual address ffffdfc7be9c2100。爲什麼0xffffdfc7be9c2100這個地址是非法的? 接着再看“address between user and kernel address ranges”
爲什麼這個地址ffffdfc7be9c2100會出現異常呢? 這就得說下ARM64的虛擬地址空間佈局了。再說ARM64之前,需要說下32位的虛擬地址空間。
在32位機器上,整個4G地址空間被分爲2份,用戶空間佔用0-3G, 內核空間佔有3G-4G的1G空間。那到64位機器上,虛擬地址空間應該如何分佈?
在ARM64上目前還不完全支持64位的虛擬地址,那是如何分配的呢?ARM支持多種位數的虛擬地址寬度,如下圖是支持48位的虛擬地址寬度
用戶空間的範圍是0x0000 0000 0000 0000 到 0x0000 FFFF FFFF FFFF,高16位是全0。內核空間的地址範圍是0xFFFF 0000 0000 0000 到 0xFFFF FFFF FFFF FFFF,高16位全位1。
那中間[0x0000 FFFF FFFF FFFF FFFF 0xFFFF 0000 0000 0000 ]就屬於非法區域了。如果運行過程中,CPU訪問到這塊區域,就會觸發mem_abort異常。
那我們使用的模擬器平臺上使用的多少位地址寬度呢? 我們的模擬器平臺使用的地址位寬度爲39位。
/*
* TASK_SIZE - the maximum size of a user space task.
* TASK_UNMAPPED_BASE - the lower boundary of the mmap VM area.
*/
#ifdef CONFIG_COMPAT
#ifdef CONFIG_ARM64_64K_PAGES
/*
* With CONFIG_ARM64_64K_PAGES enabled, the last page is occupied
* by the compat vectors page.
*/
#define TASK_SIZE_32 UL(0x100000000)
#else
#define TASK_SIZE_32 (UL(0x100000000) - PAGE_SIZE)
#endif /* CONFIG_ARM64_64K_PAGES */
#define TASK_SIZE (test_thread_flag(TIF_32BIT) ? \
TASK_SIZE_32 : TASK_SIZE_64)
#define TASK_SIZE_OF(tsk) (test_tsk_thread_flag(tsk, TIF_32BIT) ? \
TASK_SIZE_32 : TASK_SIZE_64)
#else
#define TASK_SIZE TASK_SIZE_64
#endif /* CONFIG_COMPAT */
#define VA_BITS (CONFIG_ARM64_VA_BITS)
#define TASK_SIZE_64 (UL(1) << VA_BITS)
因爲用戶虛擬地址空間各個進程都是相互隔離的,每個進程感覺自己都能看到整個虛擬地址空間,大小用TASK_SIZE表示
- 32位用戶空間程序:TASK_SIZE=TASK_SIZE_32=0x100000000=4G (64位內核運行32位應用程序時)
- 64位用戶空間程序:TASK_SIZE=TASK_SIZE_64=1<<39
如果地址寬度是39位的話,虛擬地址空間分佈如下:
這下清楚了ARM64位虛擬地址空間佈局後,我們再回過頭去看看我們出錯的地址。CPU要訪問的地址是ffffdfc7be9c2100,此地址剛好落在在非法區域,所以導致出錯,此問題可能存在bit位翻轉f→d
再來看下代碼是如何判斷非法區域的訪問的
129/*
130 * Dump out the page tables associated with 'addr' in the currently active mm.
131 */
132void show_pte(unsigned long addr)
133{
134 struct mm_struct *mm;
135 pgd_t *pgdp;
136 pgd_t pgd;
137
138 if (addr < TASK_SIZE) {
139 /* TTBR0 */
140 mm = current->active_mm;
141 if (mm == &init_mm) {
142 pr_alert("[%016lx] user address but active_mm is swapper\n",
143 addr);
144 return;
145 }
146 } else if (addr >= VA_START) {
147 /* TTBR1 */
148 mm = &init_mm;
149 } else {
150 pr_alert("[%016lx] address between user and kernel address ranges\n",
151 addr);
152 return;
153 }
如果訪問的地址小於是TASK_SIZE就是用戶空間的地址,需要設置ttbr0。
如果訪問的地址大於VA_START,就是內核區域的地址,需要設置ttbr1。
如果訪問的地址落在非法區域,就會打印上述的錯誤。