nikel-rcu死锁问题分析

一. 问题描述

1.1 问题JIRA

  XXX

1.2 现象

  手机卡死

1.3 结论

 XXX
 我们能看到在lowswap_fn方法中先调用了rcu_read_lock方法,然后调用了get_swap_unshared方法,并且在get_swap_unshared方法里边又调用了wait_rcu_gp,由于这时候还没有调用rcu_read_unlock,所以在wait_rcu_gp这里必定会卡死.卡死之后就无法调用rcu_read_unlock,这时候会导致所有调用了wait_rcu_gp方法的线程都卡死.

二. 初步分析

2.1 查看system_server trace

"InputReader" prio=10 tid=40 Native

group="main" sCount=1 dsCount=0 obj=0x13a530a0 self=0x7f99ad9c00
sysTid=1316 nice=-8 cgrp=default sched=0/0 handle=0x7f95f06440
state=D schedstat=( 377512554 46994296 385 ) utm=12 stm=26 core=1 HZ=100
stack=0x7f95e0a000-0x7f95e0c000 stackSize=1013KB
held mutexes=
kernel: __switch_to+0x74/0x8c
kernel: wait_rcu_gp+0x44/0x54
kernel: synchronize_rcu+0x28/0x40

kernel: wakeup_source_remove+0x70/0xa4
kernel: pm_wake_unlock+0x35c/0x40c
kernel: wake_unlock_store+0x10/0x2c
kernel: kobj_attr_store+0x10/0x24
kernel: sysfs_kf_write+0x3c/0x48
kernel: kernfs_fop_write+0x10c/0x178
kernel: vfs_write+0x98/0x1b8
kernel: SyS_write+0x40/0xa0
kernel: el0_svc_naked+0x20/0x28
native: (backtrace::Unwind failed for thread 1316)


从这里我们能看到system_server的
InputReader线程卡在了wait_rcu_gp的调用上.

2.2 了解rcu

      rcu的核心理念是读者访问的同时,写者可以更新访问对象的副本,但写者需要等待所有读者完成访问之后,才能删除老对象。这个过程实现的关键和难点就在于如何判断所有的读者已经完成访问。通常把写者开始更新,到所有读者完成访问这段时间叫做宽限期(Grace Period)。内核中实现宽限期等待的函数是synchronize_rcu。想要深入了解的同学请参考:http://www.wowotech.net/kernel_synchronization/223.html

 一般synchronize_rcu是要等待所有的读线程退出,例如:

 rcu_read_lock();

 XXX

 rcu_read_unlock();

只有所有的线程unlock后synchronize_rcu()这行代码才会往下执行,否则会一直等待.

了解了ruc的大概原理之后我们能知道为什么这里会卡在synchronize_rcu上了,就是因为有其他线程没有调用rcu_read_unlock这个方法导致的,那么我们需要调查哪里调用了rcu_read_lock方法,却没有调用rcu_read_unlock方法.

2.3 排查当时合入的代码

 由于这个问题并没有有效的信息直接能看出哪里没有调用rcu_read_unlock方法,所以从log上并没有能直接找到有效信息.去排查了下可疑的提交,并且在念总的帮助下发现了这个提交:

 XXX

 我将这个change revert后发现该问题不在复现,所以直接锁定这这个修复.

三. 进一步分析

3.1确认问题

  在确认问题是由于这个提交引入的后,我从大量的log中发现了如下的调用栈:
[ 2205.544479] (1)[178:hang_detect]kworker/u20:4 D
[ 2205.544514] (1)[178:hang_detect] ffffffc000087228 <3>[ 2205.544555] (1) [178:hang_detect] 0 254 2 0x00000000
[ 2205.544605] (1)[178:hang_detect]6Workqueue: events_unbound lowswap_fn
[ 2205.544651] (1)[178:hang_detect]Call trace:
[ 2205.544698] (1)[178:hang_detect][] __switch_to+0x74/0x8c
[ 2205.544745] (1)[178:hang_detect][] __schedule+0x388/0x794
[ 2205.544792] (1)[178:hang_detect][] schedule+0x24/0x74
[ 2205.544842] (1)[178:hang_detect][] schedule_timeout+0x1ac/0x204
[ 2205.544890] (1)[178:hang_detect][] wait_for_common+0x9c/0x14c
[ 2205.544938] (1)[178:hang_detect][] wait_for_completion+0x10/0x1c
[ 2205.544986] (1)[178:hang_detect][wait_rcu_gp+0x44/0x54
[ 2205.545036] (1)[178:hang_detect][] synchronize_rcu+0x28/0x40
[ 2205.545083] (1)[178:hang_detect][] kbase_special_vm_close+0x40/0x9c
[ 2205.545135] (1)[178:hang_detect][] remove_vma+0x24/0x54
[ 2205.545178] (1)[178:hang_detect][] exit_mmap+0xd4/0x114
[ 2205.545225] (1)[178:hang_detect][] mmput+0x4c/0xd8
[ 2205.545273] (1)[178:hang_detect][get_swap_unshared+0xa4/0xf8
[ 2205.545320] (1)[178:hang_detect][lowswap_fn+0x148/0x2a4
[ 2205.545368] (1)[178:hang_detect][] process_one_work+0x154/0x418
[ 2205.545415] (1)[178:hang_detect][] worker_thread+0x2ac/0x4ec
[ 2205.545457] (1)[178:hang_detect][] kthread+0xd4/0xec

对应的代码:
XXX
我们能看到在lowswap_fn方法中先调用了rcu_read_lock方法,然后调用了get_swap_unshared方法,并且在get_swap_unshared方法里边又调用了wait_rcu_gp,由于这时候还没有调用rcu_read_unlock,所以在wait_rcu_gp这里必定会卡死.卡死之后就无法调用rcu_read_unlock,这时候会导致所有调用了wait_rcu_gp方法的线程都卡死.

3.2写测试case

写了个测试case:

XXX

直接cat对应的设备节点:

我们能看到只要5950线程直接变成了D状态,卡在了wait_rcu_gp上

3.3解决问题

XXX

四. 总结

 这个问题最核心的原因就是某个线程调用rcu_read_lock后在没有调用rcu_read_unlock的情况下直接调用了synchronize_rcu,导致了死锁.

你可能感兴趣的:(安卓系统,kernel)