我们先来看一个出错的现场:
代码语言:javascript复制[ 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位。
代码语言:javascript复制/*
* 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
再来看下代码是如何判断非法区域的访问的
代码语言:javascript复制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("[6lx] user address but active_mm is swappern",
143 addr);
144 return;
145 }
146 } else if (addr >= VA_START) {
147 /* TTBR1 */
148 mm = &init_mm;
149 } else {
150 pr_alert("[6lx] address between user and kernel address rangesn",
151 addr);
152 return;
153 }
如果访问的地址小于是TASK_SIZE就是用户空间的地址,需要设置ttbr0。
如果访问的地址大于VA_START,就是内核区域的地址,需要设置ttbr1。
如果访问的地址落在非法区域,就会打印上述的错误。