死锁是如何发生的?

背景介绍

之前一直有一个疑问:为什么在 drm_gpu_sheduler 的 run_job 路径里带 GFP_KERNEL 标志的内存申请可以造成死锁?, 直到了解内核内存 PinShrink 的机制后,好像是明白了死锁的过程。

内存 Pin

Linux 是基于页 (page) 来管理内存的,物理内存页和虚拟内存页一般有相同的大小,比较常见的是 4KB 页大小。

Paging mechanism

内存 Reclaim

内存页回收有两种方式:

  • direct reclaim (同步)

direct reclaim 是指在内存分配时,如果系统内存不足,直接触发内存回收,这时内存申请调用是会被阻塞的

  • background reclaim (异步)

background reclaim 是指 kswapd 线程被唤醒异步地扫描 Zone 回收内存。

内存 Shrink

为了能够动态的回收内存,内核提供了一套 “shrinker” 接口,通过该机制,内存管理子系统 (mm) 可以通过回调的方式回收部分内存页,以减小系统内存压力。

根据文章 Linux内核页回收 中的描述,无论是 direct reclaim 还是 kswapd, 最终都会调到 shrink_node(), 而 shrink_node() 又是如何调到由其它文件系统或驱动注册的 shrinker 的钩子函数的呢?

flowchart TD
    A["shrink_node()"]
    B["lru_gen_shrink_node()"]
    C["shrink_many()"]
    D["shrink_one()"]
    E["shrink_slab()"]
    F["do_shrink_slab()"]
    G["`shrinker->count_objects()
    shrinker->scan_objects()`"]

    A --> B --> C --> D
    B -- mem_cgroup_disabled() --> D
    D --> E --> F --> G

GFP_KERNEL

带有 GFP_KERNEL 标志的内存申请既可以触发 direct reclaim, 也可以触发 background reclaim。如果 direct reclaim 被触发,回调注册的 shrinker 回调函数,而这个回调函数可以实现任何逻辑(取决于实现这个 shrinker 的文件系统或驱动),如果这个逻辑恰好是在等待某个 GPU job 的 dma_fence 被 signaled, 而你又正好是在 kick off 这个 GPU job 时触发的 direct reclaim, 这样是不是就死锁了?

参考