线程被IO卡住的原因之一

在内存trace中经常遇到线程uninterruptable sleep,而打出来的trace可以看到是如下:

wait_on_page_bit_killable+0xb0/0xcc
__lock_page_or_retry+0xb8/0xf4
filemap_fault+0x4cc/0x630
ext4_filemap_fault+0x34/0x48
__do_fault+0x88/0x110
handle_mm_fault+0x854/0xb68
do_page_fault+0x2a4/0x3b4
do_DataAbort+0x84/0x158

认识的可能只有do_page_fault()开始到do_fault()结束,而被卡在了wait_on_page_bit_killable()函数,通过分析得知这主要和PG_locked flag有关。

我们知道发生do_page_fault主要是为了给虚拟地址分配物理内存,但是这个发现卡在了wait_on_page_bit_killable()函数,导致此问题的原因在于PG_locked被长期置为1导致,

在__do_fault()函数中:

static int __do_fault(struct vm_area_struct *vma, unsigned long address,
			pgoff_t pgoff, unsigned int flags,
			struct page *cow_page, struct page **page)
{
	struct vm_fault vmf;
	int ret;

	vmf.virtual_address = (void __user *)(address & PAGE_MASK);
	vmf.pgoff = pgoff;
	vmf.flags = flags;
	vmf.page = NULL;
	vmf.cow_page = cow_page;

	ret = vma->vm_ops->fault(vma, &vmf);------------------(1)
	if (unlikely(ret & (VM_FAULT_ERROR | VM_FAULT_NOPAGE | VM_FAULT_RETRY)))
		return ret;
	if (!vmf.page)
		goto out;

	if (unlikely(PageHWPoison(vmf.page))) {
		if (ret & VM_FAULT_LOCKED)
			unlock_page(vmf.page);
		page_cache_release(vmf.page);
		return VM_FAULT_HWPOISON;
	}

	if (unlikely(!(ret & VM_FAULT_LOCKED)))
		lock_page(vmf.page);------------------------------(2)
	else
		VM_BUG_ON_PAGE(!PageLocked(vmf.page), vmf.page);

 out:
	*page = vmf.page;
	return ret;
}

(1)此处为执行vm_ops的fault函数,也就是ext4_filemap_fault()

(2)此处如果没有返回VM_FAULT_LOCKED,则调用lock_page()设置PG_locked标志位,而此处的lock_page在拿不到PG_locked flag时会导致系统睡眠,将进程设置为UNINTERRUPTABLE sleep

ext4_filemap_fault()->filemap_fault()->lock_page_or_retry():

static inline int lock_page_or_retry(struct page *page, struct mm_struct *mm,
                     unsigned int flags)
{
    might_sleep();
    return trylock_page(page) || __lock_page_or_retry(page, mm, flags);
}

以上是该函数的源码, 最后一行中, 先执行 trylock_page(page) , 当其返回为 true 的时候, 则不再执行 __lock_page_or_retry . 那我们先来看一下 trylock_page(page):

static inline int trylock_page(struct page *page)
{
	return (likely(!test_and_set_bit_lock(PG_locked, &page->flags)));
}

它是先判断 page 的 PG_locked 标志位, 然后再设置该标志位, 即, 如果该 PG_locked 标志位没被设置, 那么 trylock_page 返回true , 同时把该标志位置1.所以, 如果该 page 的 PG_locked 标志位当前没被置位, 即该 page 的 IO 操作被执行完了, 那么直接返回, 不会执行 __lock_page_or_retry ,也不会执行 wait_on_page_locked_killable 的阻塞.

相反, 如果该 page 的 PG_locked 标志位当前已经被置位了, 那么则会执行到 wait_on_page_locked_killable, 一直阻塞, 直到 PG_locked 被清除.显然, wait_on_page_locked_killable 阻塞的时间长, 说明该 page 的 PG_locked 标志位长时间处于1状态, 没被清除.该 PG_locked 标志位是在回写开始时和 IO 读完成时才会被清除.

一个 page 才 4KB 大小, IO 操作不至于很长时间的, 那又是为什么会出现卡在这么长时间呢?

PG_locked 是 page 的一个标志位, 而这个标志位又跟 IO 有关, 那么就肯定了这个 page 一定是 page cache 中的, 也就是说, 当磁盘的内容被读到内存之前是会设置 PG_locked 的标志位的, 然后等待磁盘的内容读出到 该 page 后才清除该标志位.

整个流程如下:

  1. 先从内存中分配一个 page.

  2. 把该 page 放入 page_cache 的 lru 里(即存放 page cache 的链表)

  3. 设置 page 的 PG_locked 标志位, 这里应该要注意的是: 此时并没有填充该 page, 即是一个空内容的 page, 用户拿到后是不可以使用的,所以这时需要设置该标志位做保护.

  4. 调用 block 层的 readpage 回调函数, 最终会调用 ext4_mpage_readpages.

  5. 最后会调用 submit_bio 申请一个 io 操作, 随后直接返回做别的事去了, 注意: 这里只是提交的 io 读操作的异步申请, 并没执行真正的读操作, 所以该 page 仍然处于空内容状态,即 PG_locked 仍然被置位.

  6. block 层收到以上申请之后开始真正的把磁盘中的内容读到 page 中去.

  7. 当 io 操作完成后, 会回调 mpage_end_io 的函数.

  8. 在该函数中调用 unlock_page 这时才真正地把 PG_locked 标志位清除.

在以上流程中, 我们可以注意到, linux 系统的 page cache 链表中有时会出现一些还没准备好的 page(即还没把磁盘中的内容完全地读出来) , 而正好此时用户在访问这个 page 时就会出现 wait_on_page_locked_killable 阻塞了. 只有系统当 io 操作很繁忙时, 每笔的 io 操作都需要等待排队时, 极其容易出现且阻塞的时间往往会比较长.

  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当使用.NET多线程时,卡住定位通常是由于线程死锁、竞争条件或资源争用引起的。下面我会逐一解释并讨论这些问题。 1. 线程死锁:线程死锁是指两个或多个线程相互等待对方释放持有的资源而无法继续执行的情况。这通常发生在多个线程试图同时访问相同的资源,并且彼此互相持有对方需要的资源。要解决这个问题,可以使用互斥锁(Mutex)或Monitor类来同步线程,并确保资源按顺序被访问。 2. 竞争条件:竞争条件是指多个线程尝试同时更改共享资源而导致的错误行为。这可以通过使用锁定机制(例如Monitor类或lock语句)来解决,以确保每次只有一个线程可以访问共享资源。 3. 资源争用:资源争用是指多个线程尝试同时访问有限的资源而导致的性能下降。这可以通过使用线程池(ThreadPool)来处理,并将工作任务分配给可用的线程,以避免频繁地创建和销毁线程。 另外,要定位.NET多线程卡住的问题,可以使用调试工具来跟踪线程的执行路径、查看线程间的相互作用以及检查资源的使用情况。Visual Studio提供了可视化调试工具,如线程窗口和内存窗口,可以帮助我们定位和解决这些问题。 综上所述,解决.NET多线程卡住定位问题需要在编码阶段避免线程死锁、竞争条件和资源争用,并使用调试工具来定位问题并进行恰当的修复。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值