再回收内存时出现 kernel callsite when blocked:: "page_lock_anon_vma_read+0xdc/0x138" 处于 TASK_UNINTERRUPTIBLE状态,初步分析和读写锁有关,通过ramdump 查找读写锁得持有者
在分配内存时长时间处于不可打断得状态,通过crash 工具解析ramdump 查找block 得原因
crash> ps -u |grep UN
10457 1 7 ffffffea80bb2f40 UN 6.2 8856372 646112 [email protected]
10515 673 5 ffffffeb71240000 UN 1.4 2069416 146256 azeroth-api-thr
provider 是当前正在使用得任务, 通过bt 命令查看调用栈
crash> bt 10457
PID: 10457 TASK: ffffffea80bb2f40 CPU: 7 COMMAND: "[email protected]"
#0 [ffffff801e24ace0] __switch_to at ffffffa9a2e87a98
#1 [ffffff801e24ad70] __schedule at ffffffa9a424d924
#2 [ffffff801e24ad90] schedule at ffffffa9a424dd6c
#3 [ffffff801e24ae10] rwsem_down_read_failed at ffffffa9a42510dc
#4 [ffffff801e24ae30] down_read at ffffffa9a42506f0
#5 [ffffff801e24ae60] page_lock_anon_vma_read at ffffffa9a3078d48
#6 [ffffff801e24aeb0] rmap_walk_anon at ffffffa9a307a354
#7 [ffffff801e24af40] page_referenced at ffffffa9a30790c8
#8 [ffffff801e24b090] shrink_page_list at ffffffa9a30417d0
#9 [ffffff801e24b130] shrink_inactive_list at ffffffa9a3046bf0
#10 [ffffff801e24b220] shrink_node_memcg at ffffffa9a30436d8
#11 [ffffff801e24b2b0] shrink_node at ffffffa9a3045cf0
#12 [ffffff801e24b330] do_try_to_free_pages at ffffffa9a3042df0
#13 [ffffff801e24b420] try_to_free_pages at ffffffa9a3042a0c
#14 [ffffff801e24b590] __alloc_pages_nodemask at ffffffa9a302dff4
#15 [ffffff801e24b5d0] ion_page_pool_alloc at ffffffa9a3a31278
#16 [ffffff801e24b630] alloc_largest_available at ffffffa9a3a32c54
#17 [ffffff801e24b6e0] ion_system_heap_allocate at ffffffa9a3a32484
#18 [ffffff801e24b7b0] ion_alloc_dmabuf at ffffffa9a3a2d218
#19 [ffffff801e24b7f0] ion_alloc at ffffffa9a3a2d5ec
#20 [ffffff801e24b890] cam_mem_mgr_alloc_and_map at ffffffa9a3dd43e0
#21 [ffffff801e24bbc0] cam_private_ioctl at ffffffa9a3dd2368
#22 [ffffff801e24bc60] __video_do_ioctl at ffffffa9a3888468
#23 [ffffff801e24bd70] video_usercopy at ffffffa9a3887e98
#24 [ffffff801e24bd80] video_ioctl2 at ffffffa9a38881a8
#25 [ffffff801e24bd90] v4l2_ioctl at ffffffa9a3887594
#26 [ffffff801e24be20] do_vfs_ioctl at ffffffa9a30c32b0
#27 [ffffff801e24be60] __arm64_sys_ioctl at ffffffa9a30c3704
#28 [ffffff801e24bea0] el0_svc_common at ffffffa9a2e96eb4
#29 [ffffff801e24beb0] el0_svc_handler at ffffffa9a2e96df8
#30 [ffffff801e24bff0] el0_svc at ffffffa9a2e83844
PC: 0000007c0f728c88 LR: 0000007c0f6e31a8 SP: 0000007ab8063c10
X29: 0000007ab8063d00 X28: 0000007c0a4efd38 X27: 0000007ab8067018
X26: 0000007c0a2458b0 X25: 0000000001e07000 X24: 0000000000000818
X23: 0000007ab8067018 X22: 0000000000000112 X21: 0000007ab8064068
X20: 0000007a6d4b7b28 X19: 0000007ab8067018 X18: 0000007ab7a2c000
X17: 0000007c0f6e3120 X16: 0000007c0a1a7c70 X15: 0000000000000001
X14: 0000000000000000 X13: 0000000000000410 X12: 0000007ab8063d10
X11: 0000007ab8063cc8 X10: 0000007ab8063c90 X9: 0000007ab8063cc8
X8: 000000000000001d X7: 0000000000000010 X6: 0000007c0a2458b0
X5: 0000007c0a1953c8 X4: 0000000000000068 X3: 0000000000000000
X2: 0000007ab8063d38 X1: 00000000c01856c0 X0: 000000000000000a
ORIG_X0: 000000000000000a SYSCALLNO: 1d PSTATE: a0001000
可以很明显得看到在down_read 时 获取读写锁失败导致block 在这里。 provider 任务通过ion分配内存,由于内存不足,在分配得时候直接开始回收内存,在回收内存时需要将回收得页面通过反向映射机制找到映射过这个物理页面的任务,并解除映射关系。但是由于匿名页在父任务创建子任务时惠继承,导致很多子任务使用的是同一个anon_vma ,读写锁也是一个,不同 app 的任务会相互制约,后面分析是哪个任务拿到了这个读写锁不释放。匿名页的反向映射具体参考https://www.cnblogs.com/tolimit/p/5398552.html
要找到当前读写锁的持有者就要在内核的配置文件中加入CONFIG_RWSEM_SPIN_ON_OWNER,这样owner 成员会指向当前正在持有写锁的任务。
要找到owner 成员首先就得找到anon_vma 这个结构体当前在内存中地址,需要分析page_lock_anon_vma_read 函数中是否
有关键成员压入到栈中,通过栈里得数据找到关键结构体地址,最后找到owner。
struct anon_vma *page_lock_anon_vma_read(struct page *page)
{
struct anon_vma *anon_vma = NULL;
struct anon_vma *root_anon_vma;
unsigned long anon_mapping;
rcu_read_lock();
// 如果此页被分配作为一个匿名页,那么它的mapping会指向一个anon_vma
anon_mapping = (unsigned long)READ_ONCE(page->mapping);
//判断是不是匿名页, 因为是文件页时也是mapping 指向文件页
if ((anon_mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
goto out;
if (!page_mapped(page))
goto out;
........
if (!page_mapped(page)) {
rcu_read_unlock();
put_anon_vma(anon_vma);
return NULL;
}
/* we pinned the anon_vma, its safe to sleep */
rcu_read_unlock();
anon_vma_lock_read(anon_vma);//获得读者锁
if (atomic_dec_and_test(&anon_vma->refcount)) {
/*
* Oops, we held the last refcount, release the lock
* and bail -- can't simply use put_anon_vma() because
* we'll deadlock on the anon_vma_lock_write() recursion.
*/
anon_vma_unlock_read(anon_vma);
__put_anon_vma(anon_vma);
anon_vma = NULL;
}
return anon_vma;
out:
rcu_read_unlock();
return anon_vma;
}
mapping保存anon_vma变量地址时,会执行如下代码:
page->mapping = (void *)&anon_vma + 1;
anon_vma分配时要2字节对齐,也就是所有分配的anon_vma其最低位都为0,经过上面的操作,mapping最低位就为1了,然后通过mapping获取anon_vma地址时,进行如下操作:
struct anon_vma * anon_vma = (struct anon_vma *)(page->mapping - 1);
上面函数中得代码
anon_vma = (struct anon_vma *) (anon_mapping - PAGE_MAPPING_ANON);
PAGE_MAPPING_ANON 就是1,#define PAGE_MAPPING_ANON 0x1
所以如果想确定anon_vma 得地址,知道传入进来得page 地址也是可以得。
所以如果想确定anon_vma 得地址,知道传入进来得page 地址也是可以得。查找调用栈中是否有page 相关的信息压栈
首先反汇编下page_lock_anon_vma_read
crash> dis page_lock_anon_vma_read
0xffffffa9a3078c70 : str x21, [sp,#-48]!
0xffffffa9a3078c74 : stp x20, x19, [sp,#16]
0xffffffa9a3078c78 : stp x29, x30, [sp,#32]
0xffffffa9a3078c7c : add x29, sp, #0x20
0xffffffa9a3078c80 : mov x20, x0
0xffffffa9a3078c84 : bl 0xffffffa9a2f5e940
0xffffffa9a3078c88 : ldr x19, [x20,#24]
0xffffffa9a3078c8c : and x8, x19, #0x3
0xffffffa9a3078c90 : cmp x8, #0x1
0xffffffa9a3078c94 : b.ne 0xffffffa9a3078ccc
0xffffffa9a3078c98 : mov x0, x20
0xffffffa9a3078c9c : bl 0xffffffa9a304ef88
0xffffffa9a3078ca0 : tbz w0, #0, 0xffffffa9a3078ccc
0xffffffa9a3078ca4 : ldr x8, [x19,#-1]!
0xffffffa9a3078ca8 : add x21, x8, #0x8
0xffffffa9a3078cac : mov x0, x21
0xffffffa9a3078cb0 : bl 0xffffffa9a2f3f438
0xffffffa9a3078cb4 : cbz w0, 0xffffffa9a3078ce8
0xffffffa9a3078cb8 : mov x0, x20
0xffffffa9a3078cbc : bl 0xffffffa9a304ef88
0xffffffa9a3078cc0 : tbnz w0, #0, 0xffffffa9a3078cd0
0xffffffa9a3078cc4 : mov x0, x21
0xffffffa9a3078cc8 : bl 0xffffffa9a2f3f4d8
0xffffffa9a3078ccc : mov x19, xzr
0xffffffa9a3078cd0 : bl 0xffffffa9a2f5e958
0xffffffa9a3078cd4 : mov x0, x19
0xffffffa9a3078cd8 : ldp x29, x30, [sp,#32]
0xffffffa9a3078cdc : ldp x20, x19, [sp,#16]
0xffffffa9a3078ce0 : ldr x21, [sp],#48
0xffffffa9a3078ce4 : ret
0xffffffa9a3078ce8 : ldr w8, [x19,#56]
0xffffffa9a3078cec : cbz w8, 0xffffffa9a3078ccc
0xffffffa9a3078cf0 : add w10, w8, #0x1
0xffffffa9a3078cf4 : add x11, x19, #0x38
0xffffffa9a3078cf8 : sxtw x9, w8
0xffffffa9a3078cfc : sxtw x10, w10
0xffffffa9a3078d00 : prfm pstl1strm, [x11]
0xffffffa9a3078d04 : ldxr w13, [x11]
0xffffffa9a3078d08 : eor w12, w13, w9
0xffffffa9a3078d0c : cbnz w12, 0xffffffa9a3078d1c
0xffffffa9a3078d10 : stlxr w12, w10, [x11]
0xffffffa9a3078d14 : cbnz w12, 0xffffffa9a3078d04
0xffffffa9a3078d18 : dmb ish
0xffffffa9a3078d1c : cmp w8, w13
0xffffffa9a3078d20 : mov w8, w13
0xffffffa9a3078d24 : b.ne 0xffffffa9a3078cec
0xffffffa9a3078d28 : mov x0, x20
0xffffffa9a3078d2c : bl 0xffffffa9a304ef88
0xffffffa9a3078d30 : mov w20, w0
0xffffffa9a3078d34 : bl 0xffffffa9a2f5e958
0xffffffa9a3078d38 : tbz w20, #0, 0xffffffa9a3078d78
0xffffffa9a3078d3c : mov x20, x19
0xffffffa9a3078d40 : ldr x8, [x20],#56
0xffffffa9a3078d44 : add x0, x8, #0x8
0xffffffa9a3078d48 : bl 0xffffffa9a42506a0
0xffffffa9a3078d4c : prfm pstl1strm, [x20]
0xffffffa9a3078d50 : ldxr w8, [x20]
0xffffffa9a3078d54 : sub w8, w8, #0x1
0xffffffa9a3078d58 : stlxr w9, w8, [x20]
0xffffffa9a3078d5c : cbnz w9, 0xffffffa9a3078d50
0xffffffa9a3078d60 : dmb ish
0xffffffa9a3078d64 : cbnz w8, 0xffffffa9a3078cd4
0xffffffa9a3078d68 : ldr x8, [x19]
0xffffffa9a3078d6c : add x0, x8, #0x8
0xffffffa9a3078d70 : bl 0xffffffa9a2f3f4d8
0xffffffa9a3078d74 : b 0xffffffa9a3078d98
0xffffffa9a3078d78 : add x8, x19, #0x38
0xffffffa9a3078d7c : prfm pstl1strm, [x8]
0xffffffa9a3078d80 : ldxr w9, [x8]
0xffffffa9a3078d84 : sub w9, w9, #0x1
0xffffffa9a3078d88 : stlxr w10, w9, [x8]
0xffffffa9a3078d8c : cbnz w10, 0xffffffa9a3078d80
0xffffffa9a3078d90 : dmb ish
0xffffffa9a3078d94 : cbnz w9, 0xffffffa9a3078da0
0xffffffa9a3078d98 : mov x0, x19
0xffffffa9a3078d9c : bl 0xffffffa9a3078da8
0xffffffa9a3078da0 : mov x19, xzr
0xffffffa9a3078da4 : b 0xffffffa9a3078cd4
page_lock_anon_vma_read 只有一个参数page, 根据arm 体系结构,前6 个寄存器都是通过 寄存器传值,所以x0寄存器存放着page 参数的值
在page_lock_anon_vma_read 反汇编中没有发现x0寄存器有压栈操作。这样就没办法从page_lock_anon_vma_read 的栈帧中查找到page 的地址信息。
再往上查看rmap_walk_anon 函数,如果rmap_walk_anon在调用page_lock_anon_vma_read 之前将page 值压入到栈中,那也可以获得到page 信息。或者某个寄存器含有page 的值,在page_lock_anon_vma_read
执行时候将这个寄存器压栈了,我们看到page_lock_anon_vma_read函数除了x29,x30这两个栈帧寄存器,返回地址寄存器被压栈了,还将x20, x19 进行了压栈。如果x20,x19 正好保存了page 的地址我们也就得到了page
的压栈信息。
查看rmap_walk_anon函数内容,看是否能找到page 相关的信息。rmap_walk_anon没有直接调用page_lock_anon_vma_read,而是通过page_anon_vma->page_lock_anon_vma_read
static void rmap_walk_anon(struct page *page, struct rmap_walk_control *rwc,
bool locked)
{
struct anon_vma *anon_vma;
pgoff_t pgoff_start, pgoff_end;
struct anon_vma_chain *avc;
if (rwc->target_vma) {
unsigned long address = vma_address(page, rwc->target_vma);
rwc->rmap_one(page, rwc->target_vma, address, rwc->arg);
return;
}
if (locked) {
anon_vma = page_anon_vma(page); //这个里把获得到的page,传递给了page_anon_vma
/* anon_vma disappear under us? */
VM_BUG_ON_PAGE(!anon_vma, page);
} else {
anon_vma = rmap_walk_anon_lock(page, rwc);
}
省略一部分代码................
}
page_anon_vma 的page 参数是通过x0 寄存器传入,我们反汇编看看x0相关的寄存器是否在调用page_anon_vma之前有压栈
反汇编page_anon_vma
crash> dis rmap_walk_anon
0xffffffa9a307a2c8 : stp x26, x25, [sp,#-80]!
0xffffffa9a307a2cc : stp x24, x23, [sp,#16]
0xffffffa9a307a2d0 : stp x22, x21, [sp,#32]
0xffffffa9a307a2d4 : stp x20, x19, [sp,#48]
0xffffffa9a307a2d8 : stp x29, x30, [sp,#64]
0xffffffa9a307a2dc : add x29, sp, #0x40
0xffffffa9a307a2e0 : mov x19, x1
0xffffffa9a307a2e4 : ldr x1, [x1,#8]
0xffffffa9a307a2e8 : mov x20, x0
0xffffffa9a307a2ec : cbz x1, 0xffffffa9a307a334
0xffffffa9a307a2f0 : ldr x8, [x20,#32]
0xffffffa9a307a2f4 : mov x0, x20
0xffffffa9a307a2f8 : ldr x9, [x1,#152]
0xffffffa9a307a2fc : ldr x10, [x1]
0xffffffa9a307a300 : ldr x3, [x19]
0xffffffa9a307a304 : sub x8, x8, x9
0xffffffa9a307a308 : ldr x9, [x19,#16]
0xffffffa9a307a30c : add x8, x10, x8, lsl #12
0xffffffa9a307a310 : cmp x8, x10
0xffffffa9a307a314 : csel x2, x8, x10, hi
0xffffffa9a307a318 : blr x9
0xffffffa9a307a31c : ldp x29, x30, [sp,#64]
0xffffffa9a307a320 : ldp x20, x19, [sp,#48]
0xffffffa9a307a324 : ldp x22, x21, [sp,#32]
0xffffffa9a307a328 : ldp x24, x23, [sp,#16]
0xffffffa9a307a32c : ldp x26, x25, [sp],#80
0xffffffa9a307a330 : ret
0xffffffa9a307a334 : mov w21, w2
0xffffffa9a307a338 : tbz w2, #0, 0xffffffa9a307a348
0xffffffa9a307a33c : mov x0, x20
0xffffffa9a307a340 : bl 0xffffffa9a304f018 ///anon_vma = page_anon_vma(page);
0xffffffa9a307a344 : b 0xffffffa9a307a358
0xffffffa9a307a348 : ldr x8, [x19,#32]
0xffffffa9a307a34c : cbz x8, 0xffffffa9a307a364
0xffffffa9a307a350 : mov x0, x20
0xffffffa9a307a354 : blr x8
0xffffffa9a307a358 : mov x22, x0
0xffffffa9a307a35c : cbnz x0, 0xffffffa9a307a380
0xffffffa9a307a360 : b 0xffffffa9a307a31c
0xffffffa9a307a364 : mov x0, x20
0xffffffa9a307a368 : bl 0xffffffa9a304f018
0xffffffa9a307a36c : cbz x0, 0xffffffa9a307a31c
0xffffffa9a307a370 : ldr x8, [x0]
0xffffffa9a307a374 : mov x22, x0
0xffffffa9a307a378 : add x0, x8, #0x8
0xffffffa9a307a37c : bl 0xffffffa9a42506a0
0xffffffa9a307a380 : ldr x23, [x20,#32]
0xffffffa9a307a384 : add x0, x22, #0x48
0xffffffa9a307a388 : mov x1, x23
0xffffffa9a307a38c : mov x2, x23
0xffffffa9a307a390 : bl 0xffffffa9a3064e18
0xffffffa9a307a394 : cbz x0, 0xffffffa9a307a41c
0xffffffa9a307a398 : mov x24, x0
0xffffffa9a307a39c : ldr x25, [x24]
0xffffffa9a307a3a0 : ldr x8, [x20,#32]
0xffffffa9a307a3a4 : ldr x9, [x25,#152]
0xffffffa9a307a3a8 : ldr x10, [x25]
0xffffffa9a307a3ac : sub x8, x8, x9
0xffffffa9a307a3b0 : add x9, x10, x8, lsl #12
0xffffffa9a307a3b4 : ldr x8, [x19,#40]
0xffffffa9a307a3b8 : cmp x9, x10
0xffffffa9a307a3bc : csel x26, x9, x10, hi
0xffffffa9a307a3c0 : cbz x8, 0xffffffa9a307a3d4
0xffffffa9a307a3c4 : ldr x1, [x19]
0xffffffa9a307a3c8 : mov x0, x25
0xffffffa9a307a3cc : blr x8
0xffffffa9a307a3d0 : tbnz w0, #0, 0xffffffa9a307a404
0xffffffa9a307a3d4 : ldr x8, [x19,#16]
0xffffffa9a307a3d8 : mov x0, x20
0xffffffa9a307a3dc : ldr x3, [x19]
0xffffffa9a307a3e0 : mov x1, x25
0xffffffa9a307a3e4 : mov x2, x26
0xffffffa9a307a3e8 : blr x8
0xffffffa9a307a3ec : tbz w0, #0, 0xffffffa9a307a41c
0xffffffa9a307a3f0 : ldr x8, [x19,#24]
0xffffffa9a307a3f4 : cbz x8, 0xffffffa9a307a404
0xffffffa9a307a3f8 : mov x0, x20
0xffffffa9a307a3fc : blr x8
0xffffffa9a307a400 : cbnz w0, 0xffffffa9a307a41c
0xffffffa9a307a404 : mov x0, x24
0xffffffa9a307a408 : mov x1, x23
0xffffffa9a307a40c : mov x2, x23
0xffffffa9a307a410 : bl 0xffffffa9a3064ea8
0xffffffa9a307a414 : mov x24, x0
0xffffffa9a307a418 : cbnz x0, 0xffffffa9a307a39c
0xffffffa9a307a41c : tbnz w21, #0, 0xffffffa9a307a31c
0xffffffa9a307a420 : ldr x8, [x22]
0xffffffa9a307a424 : add x0, x8, #0x8
0xffffffa9a307a428 : bl 0xffffffa9a2f3f4d8
0xffffffa9a307a42c : b 0xffffffa9a307a31c
我们要查找那个函数跳转指令是跳转到page_anon_vma。 有个简单方法是执行sym 命令
crash> sym 0xffffffa9a304f018
ffffffa9a304f018 (T) page_anon_vma /home/mi/miui/J1/kernel/msm-4.19/include/linux/compiler.h: 193
确定了跳转page_anon_vma 的代码是那一句,在往前查找x0寄存器。 从代码中看到x0 的值是来自x20,也就是x20 就是存放着x20的page 的值
联系前面的page_lock_anon_vma_read 函数,进去后就立马将x20压栈,我们只要从page_lock_anon_vma_read栈中读取出来x20 压入的数据就得到page
0xffffffa9a3078c74 : stp x20, x19, [sp,#16]
上面这句压栈指令,是将x20 压入到sp +16 的位置 这里的#16 是10进制的,也就是sp + 0x10 就是x20寄存器的值,现在要计算出page_lock_anon_vma_read 的sp 值。
要计算sp 还是要从调用栈入手
#4 [ffffff801e24ae30] down_read at ffffffa9a42506f0
down_read 前面的ffffff801e24ae30 是 page_lock_anon_vma_read 的fp。 将fp +0x10 就是page_lock_anon_vma_read 的sp
备注: 为什么 sp 是fp +x10,从的反汇编能够告诉我们为什么是0x10
crash> dis down_read
0xffffffa9a42506a0
0xffffffa9a42506a4
刚进函数sp = sp - 32; fp(x29) = sp + 16; fp(x29) = sp - 16; 转换成16 进制就是 fp = sp - 0x10;
经过以上信息最后计算page_lock_anon_vma_read 的sp = ffffff801e24ae30 + 0x10 = ffffff801e24ae40;
page_lock_anon_vma_read 的栈中x20的值 = sp + 16 = sp +0x10 = ffffff801e24ae40 + 0x10 = ffffff801e24ae50;
即 page = ffffff801e24ae50;
读此地址的值,就是page 页面的位置
crash> rd ffffff801e24ae50 4
ffffff801e24ae50: ffffffbfaf1bbb80 ffffff801e24aed0 ..........$.....
ffffff801e24ae60: ffffff801e24aeb0 ffffffa9a307a358 ..$.....X.......
ffffff801e24ae50 地址的值是ffffffbfaf1bbb80,查看page 得相关信息
crash> struct page ffffffbfaf1bbb80
struct page {
flags = 525389,
{
{
lru = {
next = 0xdead000000000100,
prev = 0xdead000000000200
},
mapping = 0xffffffec1bcc38a1, //如果时匿名页最后一位会是1,这里是1
//也再次验证查找得是对得
index = 62,//再vma 中对应得页 index
private = 265679
},
{
{
slab_list = {
next = 0xdead000000000100,
prev = 0xdead000000000200
},
{
next = 0xdead000000000100,
pages = 512,
pobjects = -559087616
}
},
slab_cache = 0xffffffec1bcc38a1,
freelist = 0x3e,
{
s_mem = 0x40dcf,
counters = 265679,
{
inuse = 3535,
objects = 4,
frozen = 0
}
}
},
{
compound_head = 16045481047390945536,
compound_dtor = 0 '\000',
compound_order = 2 '\002',
compound_mapcount = {
counter = -559087616
}
},
{
_compound_pad_1 = 16045481047390945536,
_compound_pad_2 = 16045481047390945792,
deferred_list = {
next = 0xffffffec1bcc38a1,
prev = 0x3e
}
},
{
_pt_pad_1 = 16045481047390945536,
pmd_huge_pte = 0xdead000000000200,
_pt_pad_2 = 18446743988276574369,
{
pt_mm = 0x3e,
pt_frag_refcount = {
counter = 62
}
},
ptl = {
{
rlock = {
raw_lock = {
{
val = {
counter = 265679
},
{
locked = 207 '\317',
pending = 13 '\r'
},
{
locked_pending = 3535,
tail = 4
}
}
}
}
}
}
},
{
pgmap = 0xdead000000000100,
hmm_data = 16045481047390945792,
_zd_pad_1 = 18446743988276574369
},
callback_head = {
next = 0xdead000000000100,
func = 0xdead000000000200
}
},
{
_mapcount = {
counter = 14
},
page_type = 14,
active = 14,
units = 14
},
_refcount = {
counter = 17
},
mem_cgroup = 0xffffffeabfcb1000
}
mapping = 0xffffffec1bcc38a1 的最后一位是1,证明找到的page 地址应该是对的。这里需要主要如果计算的page 地址出错最后读取struct page 也是能读出信息,但是信息可能是错误的。
要通过读出的信息去判断上面反推的对不对。
index = 62 存放着是 page 对应的虚拟地址在vma 中的偏移。 例如vma 的虚拟地址其实地址是a, 假设page 对应的虚拟地址是b。 那么b = a + 4096 * 62; 4096 是假设一个页面大小是4k
anon_vma = mapping - 1 = 0xffffffec1bcc38a1 -1 =0xffffffec1bcc38a0;
读取anon_vma 的信息
crash> struct anon_vma 0xffffffec1bcc38a0
struct anon_vma {
root = 0xffffffec1bcc38a0,
rwsem = {
count = {
counter = -8589934591
},
wait_list = {
next = 0xffffff80095b3928,
prev = 0xffffff801e24adc8
},
wait_lock = {
raw_lock = {
{
val = {
counter = 0
},
{
locked = 0 '\000',
pending = 0 '\000'
},
{
locked_pending = 0,
tail = 0
}
}
}
},
osq = {
tail = {
counter = 0
}
},
owner = 0xffffffeb44564ec0,
m_count = 0
},
refcount = {
counter = 57
},
degree = 51,
parent = 0xffffffec1bcc38a0,
rb_root = {
rb_root = {
rb_node = 0xffffffea87bbed60
},
rb_leftmost = 0xffffffec1d7a71a0
}
}
anon_vma 相关的信息已经获得。
回到之前函数page_lock_anon_vma_read 查看 anon_vma_lock_read 获得 读写锁
static inline void anon_vma_lock_read(struct anon_vma *anon_vma)
{
down_read(&anon_vma->root->rwsem);
}
可以看到是先指向root 的读写锁。 anon_vma 有个root 指针,指向当前anon_vma 的父anon_vma 。如果一个进程是被父任务创建,继承父的anon_vma 会指向父任务的anon_vma。
这里没有获取的anon_vma 的root 是指向自己,说明这个匿名页没有父anon_vma。 这个读写锁也就是自己的读写锁。
owner = 0xffffffeb44564ec0 指向写着的task
crash> struct task_struct.comm,pid,tpid 0xffffffeb44564ec0
comm = "ThreadPool_thre"
pid = 28624
struct: invalid data structure reference: task_struct.tpid
crash> struct task_struct.comm,pid,tgid 0xffffffeb44564ec0
comm = "ThreadPool_thre"
pid = 28624
tgid = 28350
crash>
可以看到此任务是有品app创建的。 他的优先级是139 优先级非常低。 查看28624 的调用栈查看他在干嘛,为啥长时间持有写锁
crash> bt 28624
PID: 28624 TASK: ffffffeb44564ec0 CPU: 0 COMMAND: "ThreadPool_thre"
#0 [ffffff8027e1b780] __switch_to at ffffffa9a2e87a98
#1 [ffffff8027e1b810] __schedule at ffffffa9a424d924
#2 [ffffff8027e1b830] preempt_schedule_irq at ffffffa9a424df10
#3 [ffffff8027e1b970] el1_preempt at ffffffa9a2e82c6c
#4 [ffffff8027e1b9b0] anon_vma_interval_tree_remove at ffffffa9a3064ba0
#5 [ffffff8027e1ba10] unlink_anon_vmas at ffffffa9a3078918
#6 [ffffff8027e1ba60] free_pgtables at ffffffa9a3068e78
#7 [ffffff8027e1bb20] exit_mmap at ffffffa9a3075610
#8 [ffffff8027e1bb40] mmput at ffffffa9a2eb48e0
#9 [ffffff8027e1bbb0] do_exit at ffffffa9a2ebcb34
#10 [ffffff8027e1bbe0] do_group_exit at ffffffa9a2ebd288
#11 [ffffff8027e1bd00] get_signal at ffffffa9a2eca5b4
#12 [ffffff8027e1beb0] do_notify_resume at ffffffa9a2e8ccb0
#13 [ffffff8027e1bff0] work_pending at ffffffa9a2e8372c
PC: e947c898 LR: e944bac5 SP: a701cb48 PSTATE: 208b0010
X12: a701cb58 X11: ffffffff X10: 00000000 X9: adc99390
X8: adc993dc X7: 0000015a X6: ea2ae00c X5: 00000008
X4: 00000000 X3: ffffffff X2: 00000010 X1: a701cba8
X0: fffffffffffffffc
crash>
可以看到进程是再被杀后退出,然后解除映射,这个时候持有了锁。unlink_anon_vmas 会编译任务的全部vma 然后解除全部的映射。
unlink_anon_vmas 会获得写锁,在这我们看到当前任务 有el1_preempt 函数,这意味着任务被抢占了。获得写锁后被切出去,一直没有回cpu运行。
这也和前面看到的有限机是139 非常低项符合。为了验证此问题,在查看cpu的运行等待队列,看看28624 这个任务是否一直在等待被调度上去。
crash> runq
CPU 0 RUNQUEUE: ffffffec3794cfc0
CURRENT: PID: 22277 TASK: ffffffebec926e40 COMMAND: "HeapTaskDaemon"
RT PRIO_ARRAY: ffffffec3794d1c0
[no tasks queued]
CFS RB_ROOT: ffffffec3794d078
[120] PID: 10484 TASK: ffffffea80bb3f00 COMMAND: "omi.bsp.gps.nps"
[139] PID: 8319 TASK: ffffffead05f1f80 COMMAND: "rameworkStarted"
[139] PID: 4218 TASK: ffffffea88561f80 COMMAND: "utdid:0"
[120] PID: 10455 TASK: ffffffeb35083f00 COMMAND: "Res_ResDownload"
[139] PID: 31022 TASK: ffffffeb78c5bf00 COMMAND: "ThreadPool_thre"
[130] PID: 10026 TASK: ffffffeacf8bde80 COMMAND: "AsyncTask #2"
[139] PID: 28624 TASK: ffffffeb44564ec0 COMMAND: "ThreadPool_thre" //在cpu等待队列中
28624 任务在cpu0 上等待被调度上去运行。
这里涉及到父子进程之前匿名页的 继承关系。 他们两个很可能都是由同一个父任务创建的。要判断出当前回收的page 是有品父任务的还是有品自己的。
crash> ps -p 28624
PID: 0 TASK: ffffffa9a561bdc0 CPU: 0 COMMAND: "swapper/0"
PID: 1 TASK: ffffffea46881f80 CPU: 4 COMMAND: "init"
PID: 673 TASK: ffffffec1ebbcec0 CPU: 4 COMMAND: "main"
PID: 28624 TASK: ffffffeb44564ec0 CPU: 0 COMMAND: "ThreadPool_thre"
可以看到 ThreadPool_thre 是由 main 创建的。main 就是Zygote 任务。 android 的应用都是他创建的。
anon_vma 中有个红黑树,父任务创建的子任务都是拷贝父任务的vma 时都是创建一个avc 结构体,挂载到这个红黑树中,可以通过这个红黑树找到 anon_vma 对应的vma 的task 创建了多少子任务。
anon_vma 中也有
refcount = {
counter = 57
},
记录红黑树中有多少成员。前面获得anon_vma 的值时有红黑树的root 节点地址
rb_root = {
rb_root = {
rb_node = 0xffffffea87bbed60
},
rb_leftmost = 0xffffffec1d7a71a0
}
crash> tree -t rbtree -o rb_root_cached.rb_root -N 0xffffffea87bbed60
ffffffea87bbed60
ffffffeb726d25e0
ffffffead4ac1720
ffffffeb7238c160
ffffffebbdc4f5e0
ffffffec1d7a71a0
ffffffec0a8beda0
ffffffea99f3f520
ffffffeb20871160
ffffffeacbc5d760
ffffffeb7c20c160
ffffffea65bc2d60
ffffffea87f82e20
ffffffea89cb6020
ffffffeb7ff08ce0
ffffffeb7a05ade0
ffffffeb728497e0
ffffffeb7afb8060
ffffffeb960e8e20
ffffffea80314620
ffffffebb535a320
ffffffead61162a0
ffffffebc6b96b60
ffffffeb8061ae60
ffffffeb75d93520
ffffffeb724a2b20
ffffffeb71cf5760
ffffffeaa04c7720
ffffffeae17eb660
ffffffeb759bae20
ffffffeb712c83a0
ffffffec1bfb20a0
ffffffeadb28c260
ffffffebb5f387a0
ffffffeb33713da0
ffffffead36e93e0
ffffffeaebadfa20
ffffffeb6dedb620
ffffffeac67373e0
ffffffeb43d7f6e0
ffffffea76aafa60
ffffffeac94f9020
ffffffebd7ae7460
ffffffeae524f0a0
ffffffea6defab20
ffffffead5948be0
ffffffea8fbe7fe0
ffffffea5a54cce0
ffffffebb3f00e60
ffffffeb71e1e1a0
ffffffeb75ed1ae0
ffffffeb75512460
ffffffea7152af60
ffffffebf081d1a0
ffffffead68e82a0
ffffffebaa1487a0
crash>
查出来的个数和counter=57 相对应。查出来的的地址是anon_vma_chain对应的地址,结构体如下:
crash> whatis -o anon_vma_chain
struct anon_vma_chain {
[0] struct vm_area_struct *vma;
[8] struct anon_vma *anon_vma;
[16] struct list_head same_vma;
[32] struct rb_node rb;
[56] unsigned long rb_subtree_last;
}
SIZE: 64
vma 指向了对应的vma 结构。 vma 又有指针指向了mm_struct , mm_struct有指针指向了task ,通过反推最后能找到是那个任务的anon_vma_chain。
vma 地址 = ffffffebaa1487A0 -0x20 = ffffffebaa148780
crash> struct anon_vma_chain ffffffebaa148780
struct anon_vma_chain {
vma = 0xffffffeb68cbc3c0,
anon_vma = 0xffffffec1bcc38a0,
same_vma = {
next = 0xffffffeb68cbc438,
prev = 0xffffffebaa1483d0
},
rb = {
__rb_parent_color = 18446743982819934880,
rb_right = 0x0,
rb_left = 0x0
},
rb_subtree_last = 77
}
crash> struct -x vm_area_struct 0xffffffeb68cbc3c0
struct vm_area_struct {
vm_start = 0x6f7c4000,
vm_end = 0x6f812000,
vm_next = 0xffffffeb68cbc900,
vm_prev = 0xffffffeb68cbc480,
vm_rb = {
__rb_parent_color = 0xffffffeb68cbc921,
rb_right = 0x0,
rb_left = 0x0
},
rb_subtree_gap = 0x0,
vm_mm = 0xffffffea803ee200,
vm_page_prot = {
pgprot = 0x60000000000fd3
},
vm_flags = 0x100073,
{
shared = {
rb = {
__rb_parent_color = 0xffffffec27db1a18,
rb_right = 0x0,
rb_left = 0x0
},
rb_subtree_last = 0x4d
},
anon_name = 0xffffffec27db1a18 "\231\230\377m\353\377\377\377\030\304\313h\353\377\377\377"
},
anon_vma_chain = {
next = 0xffffffebaa1483d0,
prev = 0xffffffebaa148790
},
anon_vma = 0xffffffeb67ac99c0,
vm_ops = 0xffffffa9a442d840,
vm_pgoff = 0x0,
vm_file = 0xffffffec1d4ce800,
vm_private_data = 0x0,
swap_readahead_info = {
counter = 0x6f7fb004
},
vm_userfaultfd_ctx = {},
vm_sequence = {
sequence = 0x0
},
vm_ref_count = {
counter = 0x1
}
}
crash> struct -x mm_struct 0xffffffea803ee200
struct mm_struct {
{
mmap = 0xffffffeb68cbc540,
mm_rb = {
rb_node = 0xffffffebadcc0ce0
},
vmacache_seqnum = 0x189,
mm_rb_lock = {
raw_lock = {
{
cnts = {
counter = 0x0
},
{
wlocked = 0x0,
__lstate = "\000\000"
}
},
wait_lock = {
{
val = {
counter = 0x0
},
{
locked = 0x0,
pending = 0x0
},
{
locked_pending = 0x0,
tail = 0x0
}
}
}
}
},
get_unmapped_area = 0xffffffa9a30742f8,
mmap_base = 0xeb2b1000,
mmap_legacy_base = 0x0,
task_size = 0xfffff000,
highest_vm_end = 0xffff1000,
pgd = 0xffffffeaa0ebd000,
mm_users = {
counter = 0x37
},
mm_count = {
counter = 0x2
},
pgtables_bytes = {
counter = 0x139000
},
map_count = 0x94a,
page_table_lock = {
{
rlock = {
raw_lock = {
{
val = {
counter = 0x0
},
{
locked = 0x0,
pending = 0x0
},
{
locked_pending = 0x0,
tail = 0x0
}
}
}
}
}
},
mmap_sem = {
count = {
counter = 0x0
},
wait_list = {
next = 0xffffffea803ee270,
prev = 0xffffffea803ee270
},
wait_lock = {
raw_lock = {
{
val = {
counter = 0x0
},
{
locked = 0x0,
pending = 0x0
},
{
locked_pending = 0x0,
tail = 0x0
}
}
}
},
osq = {
tail = {
counter = 0x0
}
},
owner = 0x1,
m_count = 0x0
},
mmlist = {
next = 0xffffffea803e8418,
prev = 0xffffffec27e07418
},
hiwater_rss = 0x657b,
hiwater_vm = 0x7e27b,
total_vm = 0x7e4ea,
locked_vm = 0x0,
pinned_vm = 0x0,
data_vm = 0x48aed,
exec_vm = 0x99ec,
stack_vm = 0x800,
def_flags = 0x0,
arg_lock = {
{
rlock = {
raw_lock = {
{
val = {
counter = 0x0
},
{
locked = 0x0,
pending = 0x0
},
{
locked_pending = 0x0,
tail = 0x0
}
}
}
}
}
},
start_code = 0x4ffb000,
end_code = 0x4fffd70,
start_data = 0x5000000,
end_data = 0x50002f4,
start_brk = 0x57f6000,
brk = 0x57f6000,
start_stack = 0xffc550f0,
arg_start = 0xffc556a5,
arg_end = 0xffc55712,
env_start = 0xffc55712,
env_end = 0xffc55fde,
saved_auxv = {0x37b0d600000010, 0x100000000006, 0x6400000011, 0x4ffb03400000003, 0x2000000004, 0xa00000005, 0xeb1e700000000007, 0x8, 0x4ffd00100000009, 0xb, 0xc, 0xd, 0xe, 0x100000017, 0xffc551ec00000019, 0x1f0000001a, 0xffc55fde0000001f, 0xffc551fc0000000f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
rss_stat = {
count = {{
counter = 0x4fcc
}, {
counter = 0x1547
}, {
counter = 0x11b4
}, {
counter = 0x49
}, {
counter = 0x0
}}
},
binfmt = 0xffffffa9a5659e08,
context = {
id = {
counter = 0x12324
},
vdso = 0xffff0000,
flags = 0x1
},
flags = 0x8d,
core_state = 0x0,
membarrier_state = {
counter = 0x32
},
ioctx_lock = {
{
rlock = {
raw_lock = {
{
val = {
counter = 0x0
},
{
locked = 0x0,
pending = 0x0
},
{
locked_pending = 0x0,
tail = 0x0
}
}
}
}
}
},
ioctx_table = 0x0,
owner = 0xffffffeb73878000,
user_ns = 0xffffffa9a562a2e8,
exe_file = 0xffffffec1e6ede00,
tlb_flush_pending = {
counter = 0x0
},
uprobes_state = {
xol_area = 0x0
},
async_put_work = {
data = {
counter = 0x0
},
entry = {
next = 0x0,
prev = 0x0
},
func = 0x0
}
},
cpu_bitmap = 0xffffffea803ee570
}
crash>
crash>
crash> struct task_struct.comm,pid,tgid,prio 0xffffffeb73878000
comm = ".smile.gifmaker"
pid = 10353
tgid = 10353
prio = 120
".smile.gifmaker" 应该是快手的
crash> ps -p 10353
PID: 0 TASK: ffffffa9a561bdc0 CPU: 0 COMMAND: "swapper/0"
PID: 1 TASK: ffffffea46881f80 CPU: 4 COMMAND: "init"
PID: 673 TASK: ffffffec1ebbcec0 CPU: 4 COMMAND: "main"
PID: 10353 TASK: ffffffeb73878000 CPU: 5 COMMAND: ".smile.gifmaker"
也是zygote 创建的。
多个这样查找发现这些任务都是zygote 创建的, 并且这个page 对应的anon_vma的 root 指针指向自己,也就是没有父的anon_vma,说明他就是zygote 创建的匿名页。
因为子会复制父任务的vma ,所以各个子任务中的到的vma 都是相同的
struct vm_area_struct {
vm_start = 0x6f7c4000,
vm_end = 0x6f812000,
vm_next = 0xffffffeb68cbc900,
vm_prev = 0xffffffeb68cbc480,
vm_rb = {
__rb_parent_color = 0xffffffeb68cbc921,
rb_right = 0x0,
rb_left = 0x0
},
crash> vm -R 0x6f7c4000
PID: 673 TASK: ffffffec1ebbcec0 CPU: 4 COMMAND: "main"
MM PGD RSS TOTAL_VM
ffffffec27e07380 ffffffec1dd86000 36968k 1798172k
VMA START END FLAGS FILE
ffffffec27db19c0 6f7c4000 6f812000 100073 /system/framework/arm/boot-bouncycastle.art
VIRTUAL PHYSICAL
6f7c4000 252c52000
FLAGS
是100073 表明是可读写。 由于在crash 写是没有办法看proc 下得smaps,在实际手机中查看下main 进程得smaps
70eca000-70f24000 rw-p 00000000 fc:00 2245 /system/framework/arm64/boot-bouncycastle.art
Size: 360 kB
KernelPageSize: 4 kB
MMUPageSize: 4 kB
Rss: 360 kB
Pss: 4 kB
Shared_Clean: 0 kB
Shared_Dirty: 360 kB
Private_Clean: 0 kB
Private_Dirty: 0 kB
Referenced: 360 kB
Anonymous: 360 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
VmFlags: rd wr mr mw me lo ac
可以看到存在匿名页,在回收这些页面时候,就会收到有品这类得app 获得读写锁导致卡顿