该问题发生于centos7内核3.10.0-693.1.1.el7.x86_64,源码部分分析也来自该版本内核。
一、机器重启后拿到kernel coredump第一件事自然是先看下为什么重启:
1. 查看栈信息可以确认系统是由于进程等待rw_semaphore锁超时后被khungtaskd触发的重启:
crash> bt
PID: 89 TASK: ffff8801745ceeb0 CPU: 3 COMMAND: "khungtaskd"
#0 [ffff8807ff4ffcb0] machine_kexec at ffffffff8105c52b
#1 [ffff8807ff4ffd98] __crash_kexec at ffffffff81104adf
#2 [ffff8807ff4ffea8] watchdog at ffffffff8112ef00
#3 [ffff8807ff4ffec8] kthread at ffffffff810b099f
#4 [ffff8807ff4fff50] sysret_check at ffffffff816b4fd8
#5 [ffff8807ff4fff80] kthread at ffffffff810b08d0
crash>
crash> bt ffff880011b44f10
PID: 2187 TASK: ffff880011b44f10 CPU: 12 COMMAND: "java"
#0 [ffff880011223d58] __schedule at ffffffff816a9015
#1 [ffff880011223dc0] yield_to at ffffffff816a9599
#2 [ffff880011223dd0] rwsem_down_write_failed at ffffffff816aabcd
#3 [ffff880011223e58] memmove at ffffffff81331a28
#4 [ffff880011223ec0] __do_page_fault at ffffffff816b029c
#5 [ffff880011223f20] trace_do_page_fault at ffffffff816b03a5
#6 [ffff880011223f50] paranoid_swapgs at ffffffff816ac5c8
RIP: 00007f441d052585 RSP: 00007f44329dcbf0 RFLAGS: 00010282
RAX: 006100760061006a RBX: 00000000eb360f80 RCX: 000000000000001e
RDX: fffffffffffffffd RSI: 00000000eb361030 RDI: 00000000eb360fc0
RBP: 00007f44329dcbf0 R8: 000000000000001e R9: 000000000000001e
R10: 00007f441d0525c0 R11: 000000000000001e R12: 0000000000000000
R13: 00000000eb361000 R14: 00000000eb3609b0 R15: 00007f442c008800
ORIG_RAX: ffffffffffffffff CS: 0033 SS: 002b
crash>
2. 找出是reader还是writer拿锁:
2.1 找出mmap_sem.owner:
结合down_read的实现可以知道当前rw_semaphore.owner可以知道是有进程拿了读锁:
二. 因为是reader拿的锁,所以无法通过owner直接找出拿锁的进程,下面开始找出拿锁进程的过程:
分析代码可以知道struct rw_semaphore.wait_list通过struct rwsem_waiter.list将所有等待该读写信号量的进程串联起来:
rw_semaphore.wait_list.next的值为rwsem_waiter.list地址:
因此通过crash的list命令可以列出所有等待该rw信号量的rwswm_waiter信息,0xffff880009513e60为 rwsem_waiter.list地址:
crash> list -l rwsem_waiter.list -s rwsem_waiter.list,task,type 0xffff880009513e60
ffff880009513e60
list = {
next = 0xffff88000f70fe60,
prev = 0xffff88002021bdb0
}
task = 0xffff88001758af70
type = RWSEM_WAITING_FOR_WRITE
ffff88000f70fe60
list = {
next = 0xffff880011223df0,
prev = 0xffff880009513e60
}
task = 0xffff880174bdaf70
type = RWSEM_WAITING_FOR_WRITE
ffff880011223df0
list = {
next = 0xffff880015ba3df0,
prev = 0xffff88000f70fe60
}
task = 0xffff880011b44f10
type = RWSEM_WAITING_FOR_READ
.....
.....
大部分情况下,拿锁的进程不释放锁的原因都是因为在等待其他事件(其他锁或者IO等,因此可以尝试找下所有UN状态的进程
跟rw_semaphore等待队列的所有进程进行对比,筛选出不在rw_semaphore等待队列并且UN状态的进程:
列出所有UN状态的进程:
ps | grep UN | awk '{print "0x"$4}' | sort > ps-task.txt
列出所有跟0xffff880009513e60 对应的进程在同一个rw_semaphore等待队列的所有进程:
list rwsem_waiter.list -s rwsem_waiter.task -h 0xffff880009513e60 | grep task | awk '{print $3}' | sort > list-task.txt
对比找出ps-task.txt独有的进程,代表这两个UN进程跟其他UN状态的进程不在rw_semaphore等待队列里:
# comm -23 ps-task.txt list-task.txt
0xffff8807bb6d5ee0
0xffff8807fc27bf40
代码语言:javascript复制备注: 有时因为跟需要排查的rwsem没关系并且处于UN状态的进程比较多的时候对比出来的进程会较多,用search关键字搜索
出进程栈中有对应rwsem地址的进程后再对比也是一个不错的方法。比如:
search -t $address | grep TASK | awk '{print "0x"$4}' | sort >search_$address .txt
comm -23 search_0xffff880009513e60.txt list-task.txt //$address 为rw_semaphore地址
查看0xffff8807bb6d5ee0进程栈信息结合源码可以排除该进程拿的锁。
再看下0xffff8807fc27bf40进程栈信息可以知道其由于写信号量失败而被置为UN状态:
查看进程0xffff8807fc27bf40对应的mmap_sem信息可以知道其mmap_sem.next的值指向0xffff88001758af70进程对应的&rwsem_waiter.list,也就是0xffff8807fc27bf40跟前面list出来的进程都在等待同一个rw信号量,但是该进程对应的&rwsem_waiter.list却不在链表里。
crash> struct task_struct.mm 0xffff8807fc27bf40
mm = 0xffff8807fa443200
crash>
crash> struct mm_struct.mmap_sem 0xffff8807fa443200
mmap_sem = {
{
count = {
counter = 0xffffffff00000001
},
....
....
wait_list = {
next = 0xffff880009513e60
},
owner = 0x1
}
crash>
分析释放的rw_semaphore实现,可以知道在函数rwsem_wake中是先把进程从等待队列删除然后再唤醒进程:
__rwsem_mark_wake:
因此推断问题可能是进程被从rw_semaphore等待队列删除后没有被正常唤醒导致的。
找到commit e158488be27b157802753a59b336142dc0eb0380修复了一个会导致此类现象的问题。
http://lkml.iu.edu/hypermail/linux/kernel/1903.0/01537.html
Redhat也分别在rhel7.7和rhel7.6修复了该问题:
https://access.redhat.com/solutions/3393611