【严重程度】 一般
【特性】 文件系统/xfs
【重现类型】 无规律重现
【定位分析】 考虑以下两种情况:
1.在多线程并发写相同页面并且执行fsync时,存在这样一种情况:
执行fsync时,会将page都标记为PAGECACHE_TAG_WRITEBACK,然后将等待page更新的线程都放入该page的链表中。
之后,bio回写时,会清除page中的PAGECACHE_TAG_WRITEBACK,同时从该page链表中唤醒等待该page的线程,这个过程是持锁的,但是由于设置了bookmark,一次只能释放最多64个线程,然后会短暂的放锁再持锁。
在这个放锁和持锁的间隔期内,前面被唤醒的线程如果被调度到,会继续写该页面,并执行fsync,这又会使得该page被设置为PAGECACHE_TAG_WRITEBACK,并且线程被放入到page的链表中。
之后,bio回写线程再次持锁,重复上述过程,他一次最多释放64个线程,只要在放锁持锁的窗口期内,重新加入的线程数一直大于64,这个循环就会一直持续,造成soft lockup。
2.在多线程并发写不同页面并且执行fsync时,存在这样一种情况:
执行fsync时,会将修改的page标记为PAGECACHE_TAG_WRITEBACK,然后将等待page更新的线程都放入该page的链表中。由于每个线程写的不同的页面,执行fsync是针对整个文件而言的。因此最终会使得每个page的链表上都关联了许多线程。
为了简化问题,在这里假设fsync执行时,各个页面都已经被置脏了。则每个page链表上都关联了所有的线程。
之后,bio回写时,会清除page中的PAGECACHE_TAG_WRITEBACK,同时从该page链表中唤醒等待该page的线程,这个过程是持锁的,但是由于设置了bookmark,一次只能释放最多64个线程,然后会短暂的放锁再持锁。
假设共有128个线程,写128个不同的page,则每个page链表上都链接了128个线程。fsync需要按序等待所有涉及到的page都更新完毕,才返回。当更新到最后一个page的链表时,先更新前64个线程,更新完毕后短暂释放锁。
在这个放锁期间,前面被唤醒的线程如果被调度到,会继续写不同页面,并执行fsync,这又会使得不同的page被设置为PAGECACHE_TAG_WRITEBACK,并且线程被放入到对应page的链表中。
如果这前64个线程中,有一个写的是第128个page,那么就会将这第128号page的flag再次设置为PAGECACHE_TAG_WRITEBACK。
之后,bio再次持锁并循环时,会唤醒后64个线程然后再放锁。
后64个线程在fsync流程中等待page更新,然后被唤醒后发现第128个页面的PAGECACHE_TAG_WRITEBACK这个flag依然存在,那么会再次被放入到这个page所在的链表中。
之后,bio回写线程继续持锁,再次遍历这第128号page的链表,还是这64个线程,唤醒并再放锁。然后64个线程的fsync流程中发现flag还在,会被继续加到链表中,这个循环就会一直持续,造成soft lockup。
那么这第128号page的PAGECACHE_TAG_WRITEBACK flag何时被清除?得等到前1->127号page都被更新完毕,另外的一些bio回写线程执行到第128号page,才能将此page的PAGECACHE_TAG_WRITEBACK这个flag清除掉。
但是,在这之后又会唤醒前64个线程,重新置脏page,重复上述流程,造成soft lockup。