Root Cause Analysis
1. Functions, Structs, and Files Involved
drivers/block/nbd.c:
nbd_genl_reconnect_sock_cb(): NBD netlink socket reconnection callback.
nbd_queue_rq() / nbd_handle_cmd(): Block device request handling path for NBD devices.
struct nbd_sock: Holds socket state and synchronization primitives, specifically nsock->tx_lock (struct mutex).
net/core/sock.c:
sk_set_memalloc(): Sets SOCK_MEMALLOC flag on a socket and increments memalloc_socks_key via static_branch_inc().
kernel/jump_label.c & kernel/cpu.c:
static_key_slow_inc() / cpus_read_lock(): Acquires cpu_hotplug_lock (percpu-rwsem).
mm/slab_common.c:
kmem_cache_destroy(): Destroys a slab cache by acquiring cpus_read_lock() (cpu_hotplug_lock) and subsequently mutex_lock(&slab_mutex).
__kmem_cache_create_args(): Holds slab_mutex while executing GFP_KERNEL memory allocations (kstrdup_const(), create_cache()), which enter might_alloc() / fs_reclaim.
2. Root Cause Mechanism and Invariant Violation
The Linux memory management and CPU hotplug subsystems enforce a well-defined locking hierarchy:
-
kmem_cache_destroy() in mm/slab_common.c:564-565:
cpus_read_lock();
mutex_lock(&slab_mutex);
This establishes: cpu_hotplug_lock -> slab_mutex.
-
Slab cache creation (__kmem_cache_create_args() in mm/slab_common.c:350-387):
Acquires mutex_lock(&slab_mutex) and performs GFP_KERNEL allocations, which are subject to direct memory reclaim (fs_reclaim).
This establishes: slab_mutex -> fs_reclaim.
Together, core kernel code establishes the locking chain:
$$\text{cpu_hotplug_lock} \longrightarrow \text{slab_mutex} \longrightarrow \text{fs_reclaim}$$
-
In the block I/O layer, direct memory reclaim (fs_reclaim) can perform page writeback or swap I/O to a block device, dispatching requests through blk_mq to the block device's request queue (nbd_queue_rq() in drivers/block/nbd.c:1199-1207). In nbd_handle_cmd(), mutex_lock(&nsock->tx_lock) is acquired to transmit the command.
This establishes:
$$\text{fs_reclaim} \longrightarrow \dots \longrightarrow \text{&cmd->lock} \longrightarrow \text{&nsock->tx_lock}$$
-
The Defect:
In drivers/block/nbd.c:1372-1377:
mutex_lock(&nsock->tx_lock);
if (!nsock->dead) {
mutex_unlock(&nsock->tx_lock);
continue;
}
sk_set_memalloc(sock->sk);
...
When reconnecting a dead socket, nbd_genl_reconnect_sock_cb() invokes sk_set_memalloc(sock->sk) inside the critical section protected by mutex_lock(&nsock->tx_lock).
sk_set_memalloc() calls static_branch_inc(&memalloc_socks_key), which calls static_key_slow_inc(), which acquires cpus_read_lock() (cpu_hotplug_lock).
This introduces a reverse dependency:
$$\text{&nsock->tx_lock} \longrightarrow \text{cpu_hotplug_lock}$$
3. Resulting Cycle and Failure Trigger
Combining the dependencies creates a closed circular locking dependency:
$$\text{fs_reclaim} \longrightarrow \text{&nsock->tx_lock} \longrightarrow \text{cpu_hotplug_lock} \longrightarrow \text{slab_mutex} \longrightarrow \text{fs_reclaim}$$
When a task executes any kmem_cache_create() operation (e.g. creating mesh network interface slab caches via ieee80211s_init()), lockdep validates the acquisition of fs_reclaim while holding slab_mutex, observes the existing path fs_reclaim -> ... -> &nsock->tx_lock -> cpu_hotplug_lock -> slab_mutex, and reports a circular locking dependency warning.
Because sock in nbd_genl_reconnect_sock_cb() is a new socket retrieved via nbd_get_socket(), setting its SOCK_MEMALLOC flag via sk_set_memalloc(sock->sk) does not require holding nsock->tx_lock (similarly to how nbd_start_device() invokes sk_set_memalloc() outside tx_lock).