线程被IO卡住的原因wait_on_page_bit_killable

本文探讨了内存trace中的线程阻塞现象,焦点在于PG_lockedflag如何影响页锁定和IO操作。当页面的PG_locked被长期置为1时,导致进程陷入uninterruptable sleep,深入剖析了锁页过程与文件系统操作的交互,揭示了为何长时间卡在wait_on_page_bit_killable。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在内存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()函数中:

 
  1. static int __do_fault(struct vm_area_struct *vma, unsigned long address,

  2. pgoff_t pgoff, unsigned int flags,

  3. struct page *cow_page, struct page **page)

  4. {

  5. struct vm_fault vmf;

  6. int ret;

  7.  
  8. vmf.virtual_address = (void __user *)(address & PAGE_MASK);

  9. vmf.pgoff = pgoff;

  10. vmf.flags = flags;

  11. vmf.page = NULL;

  12. vmf.cow_page = cow_page;

  13.  
  14. ret = vma->vm_ops->fault(vma, &vmf);------------------(1)

  15. if (unlikely(ret & (VM_FAULT_ERROR | VM_FAULT_NOPAGE | VM_FAULT_RETRY)))

  16. return ret;

  17. if (!vmf.page)

  18. goto out;

  19.  
  20. if (unlikely(PageHWPoison(vmf.page))) {

  21. if (ret & VM_FAULT_LOCKED)

  22. unlock_page(vmf.page);

  23. page_cache_release(vmf.page);

  24. return VM_FAULT_HWPOISON;

  25. }

  26.  
  27. if (unlikely(!(ret & VM_FAULT_LOCKED)))

  28. lock_page(vmf.page);------------------------------(2)

  29. else

  30. VM_BUG_ON_PAGE(!PageLocked(vmf.page), vmf.page);

  31.  
  32. out:

  33. *page = vmf.page;

  34. return ret;

  35. }

(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():

 
  1. static inline int lock_page_or_retry(struct page *page, struct mm_struct *mm,

  2. unsigned int flags)

  3. {

  4. might_sleep();

  5. return trylock_page(page) || __lock_page_or_retry(page, mm, flags);

  6. }

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

 
  1. static inline int trylock_page(struct page *page)

  2. {

  3. return (likely(!test_and_set_bit_lock(PG_locked, &page->flags)));

  4. }

它是先判断 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 操作都需要等待排队时, 极其容易出现且阻塞的时间往往会比较长.

你不知道的SAM V系列MCU:SAM V MCU基于 ARM Cortex:trade_mark:-M7 的微控制器系列可提供最佳的连接接口组合,包括以太网 AVB、MediaLB、USB 和 CAN-FD,以及可提供高达 1500 CoreMark 的高性能 ARM Cortex-M 内核。SAM V MCU专注于音频放大器、汽车通信控制单元或车头单元的车载信息娱乐链接。 关键特性: 高性能—由于 Cortex-M7 能够以 300 MHz 的频率运行,外加前所未有的 DSP 性能,可实现高达 1500 CoreMark 先进的存储器架构— 高达 384 KB 的多端口存储器,其中高达 256 KB 可分配作为紧密耦合存储器(数据和指令),实现以 300 MHz 频率运行时零等待 以太网 AVB— SAM V71 在以太网 MAC 中嵌入了对音频视频桥接 (AVB) 的特定硬件支持。在 MAC 的硬件中实现了基于信用的流量整形,从而无需 CPU 干预 高速 USB— 主机和器件模式高速 USB 控制器集成了 PHY 以降低 BOM 成本 MOST 连接— MediaLB 3 线接口可无缝集成到 MOST 总线 音频接口— 灵活的 TDM/I2S 接口可以连接音频源、编解码器或 DSP CAN-FD— 最新的 CAN 2.0 和 CAN 灵活数据速率 (FD) 控制器可实现更高带宽 汽车等级— 通过 AEC-Q100 等级 2 (-40C/105C) 认证 2015年最新评估开发板——SAM V71 Xplained Atmel年初刚刚推出了基于SAM V71Xplained评估套件,目前已在官网发布出售。 开发板资源: 处理器:ATSAMV71Q21(ATSAMV71Q21数据手册) 用户:一个电源开关按钮、一个机械复位按钮、两个用户按钮、两个黄色LED指示灯 存储:2MB SDRAM、2MB QSPI Flash、256KByte EEPROM 网口:IEEE 802.3az 10Base-T/100Base-TX Ethernet RMII PHY 媒体接口:立体音频编解码器、相机接口、耳机和麦克风接口 SDIO接口SD连接器、CAN收发器 MediaLB接口 接口:外部调试连接器接口、一个扩展LCD接口、两个外部扩展接口、调试接口、虚拟COM口、USB接口 电源:外部电源输入、USB供电 用到的主要芯片: 基于 ARM Cortex:trade_mark:-M7 的微控制器:ATSAMV71Q21 AVR 32-bit RISC MCU:AT32UC3A4256J CAN收发器:ATA6561-GBQW 超低功耗、便携式音频编解码器:WM8904 IEEE 802.3az 10Base-T/100Base-TX Ethernet RMII PHY:KSZ8061RNBVA 2kbit I2C EEPROM:AT24MAC402-MAHM-T
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值