Skip to content

Commit 6d1823c

Browse files
fbqPeter Zijlstra
authored andcommitted
lockdep: Optimize the memory usage of circular queue
Qian Cai reported a BFS_EQUEUEFULL warning [1] after read recursive deadlock detection merged into tip tree recently. Unlike the previous lockep graph searching, which iterate every lock class (every node in the graph) exactly once, the graph searching for read recurisve deadlock detection needs to iterate every lock dependency (every edge in the graph) once, as a result, the maximum memory cost of the circular queue changes from O(V), where V is the number of lock classes (nodes or vertices) in the graph, to O(E), where E is the number of lock dependencies (edges), because every lock class or dependency gets enqueued once in the BFS. Therefore we hit the BFS_EQUEUEFULL case. However, actually we don't need to enqueue all dependencies for the BFS, because every time we enqueue a dependency, we almostly enqueue all other dependencies in the same dependency list ("almostly" is because we currently check before enqueue, so if a dependency doesn't pass the check stage we won't enqueue it, however, we can always do in reverse ordering), based on this, we can only enqueue the first dependency from a dependency list and every time we want to fetch a new dependency to work, we can either: 1) fetch the dependency next to the current dependency in the dependency list or 2) if the dependency in 1) doesn't exist, fetch the dependency from the queue. With this approach, the "max bfs queue depth" for a x86_64_defconfig + lockdep and selftest config kernel can get descreased from: max bfs queue depth: 201 to (after apply this patch) max bfs queue depth: 61 While I'm at it, clean up the code logic a little (e.g. directly return other than set a "ret" value and goto the "exit" label). [1]: https://lore.kernel.org/lkml/17343f6f7f2438fc376125384133c5ba70c2a681.camel@redhat.com/ Reported-by: Qian Cai <cai@redhat.com> Reported-by: syzbot+62ebe501c1ce9a91f68c@syzkaller.appspotmail.com Signed-off-by: Boqun Feng <boqun.feng@gmail.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Link: https://lkml.kernel.org/r/20200917080210.108095-1-boqun.feng@gmail.com
1 parent 267580d commit 6d1823c

1 file changed

Lines changed: 60 additions & 39 deletions

File tree

kernel/locking/lockdep.c

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,6 +1606,15 @@ static inline void bfs_init_rootb(struct lock_list *lock,
16061606
lock->only_xr = (hlock->read != 0);
16071607
}
16081608

1609+
static inline struct lock_list *__bfs_next(struct lock_list *lock, int offset)
1610+
{
1611+
if (!lock || !lock->parent)
1612+
return NULL;
1613+
1614+
return list_next_or_null_rcu(get_dep_list(lock->parent, offset),
1615+
&lock->entry, struct lock_list, entry);
1616+
}
1617+
16091618
/*
16101619
* Breadth-First Search to find a strong path in the dependency graph.
16111620
*
@@ -1639,36 +1648,25 @@ static enum bfs_result __bfs(struct lock_list *source_entry,
16391648
struct lock_list **target_entry,
16401649
int offset)
16411650
{
1651+
struct circular_queue *cq = &lock_cq;
1652+
struct lock_list *lock = NULL;
16421653
struct lock_list *entry;
1643-
struct lock_list *lock;
16441654
struct list_head *head;
1645-
struct circular_queue *cq = &lock_cq;
1646-
enum bfs_result ret = BFS_RNOMATCH;
1655+
unsigned int cq_depth;
1656+
bool first;
16471657

16481658
lockdep_assert_locked();
16491659

1650-
if (match(source_entry, data)) {
1651-
*target_entry = source_entry;
1652-
ret = BFS_RMATCH;
1653-
goto exit;
1654-
}
1655-
1656-
head = get_dep_list(source_entry, offset);
1657-
if (list_empty(head))
1658-
goto exit;
1659-
16601660
__cq_init(cq);
16611661
__cq_enqueue(cq, source_entry);
16621662

1663-
while ((lock = __cq_dequeue(cq))) {
1664-
bool prev_only_xr;
1665-
1666-
if (!lock->class) {
1667-
ret = BFS_EINVALIDNODE;
1668-
goto exit;
1669-
}
1663+
while ((lock = __bfs_next(lock, offset)) || (lock = __cq_dequeue(cq))) {
1664+
if (!lock->class)
1665+
return BFS_EINVALIDNODE;
16701666

16711667
/*
1668+
* Step 1: check whether we already finish on this one.
1669+
*
16721670
* If we have visited all the dependencies from this @lock to
16731671
* others (iow, if we have visited all lock_list entries in
16741672
* @lock->class->locks_{after,before}) we skip, otherwise go
@@ -1680,13 +1678,13 @@ static enum bfs_result __bfs(struct lock_list *source_entry,
16801678
else
16811679
mark_lock_accessed(lock);
16821680

1683-
head = get_dep_list(lock, offset);
1684-
1685-
prev_only_xr = lock->only_xr;
1686-
1687-
list_for_each_entry_rcu(entry, head, entry) {
1688-
unsigned int cq_depth;
1689-
u8 dep = entry->dep;
1681+
/*
1682+
* Step 2: check whether prev dependency and this form a strong
1683+
* dependency path.
1684+
*/
1685+
if (lock->parent) { /* Parent exists, check prev dependency */
1686+
u8 dep = lock->dep;
1687+
bool prev_only_xr = lock->parent->only_xr;
16901688

16911689
/*
16921690
* Mask out all -(S*)-> if we only have *R in previous
@@ -1701,26 +1699,49 @@ static enum bfs_result __bfs(struct lock_list *source_entry,
17011699
continue;
17021700

17031701
/* If there are only -(*R)-> left, set that for the next step */
1704-
entry->only_xr = !(dep & (DEP_SN_MASK | DEP_EN_MASK));
1702+
lock->only_xr = !(dep & (DEP_SN_MASK | DEP_EN_MASK));
1703+
}
17051704

1705+
/*
1706+
* Step 3: we haven't visited this and there is a strong
1707+
* dependency path to this, so check with @match.
1708+
*/
1709+
if (match(lock, data)) {
1710+
*target_entry = lock;
1711+
return BFS_RMATCH;
1712+
}
1713+
1714+
/*
1715+
* Step 4: if not match, expand the path by adding the
1716+
* forward or backwards dependencis in the search
1717+
*
1718+
*/
1719+
first = true;
1720+
head = get_dep_list(lock, offset);
1721+
list_for_each_entry_rcu(entry, head, entry) {
17061722
visit_lock_entry(entry, lock);
1707-
if (match(entry, data)) {
1708-
*target_entry = entry;
1709-
ret = BFS_RMATCH;
1710-
goto exit;
1711-
}
17121723

1713-
if (__cq_enqueue(cq, entry)) {
1714-
ret = BFS_EQUEUEFULL;
1715-
goto exit;
1716-
}
1724+
/*
1725+
* Note we only enqueue the first of the list into the
1726+
* queue, because we can always find a sibling
1727+
* dependency from one (see __bfs_next()), as a result
1728+
* the space of queue is saved.
1729+
*/
1730+
if (!first)
1731+
continue;
1732+
1733+
first = false;
1734+
1735+
if (__cq_enqueue(cq, entry))
1736+
return BFS_EQUEUEFULL;
1737+
17171738
cq_depth = __cq_get_elem_count(cq);
17181739
if (max_bfs_queue_depth < cq_depth)
17191740
max_bfs_queue_depth = cq_depth;
17201741
}
17211742
}
1722-
exit:
1723-
return ret;
1743+
1744+
return BFS_RNOMATCH;
17241745
}
17251746

17261747
static inline enum bfs_result

0 commit comments

Comments
 (0)