Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

mm: enable lazy_mmu sections to nest

Despite recent efforts to prevent lazy_mmu sections from nesting, it
remains difficult to ensure that it never occurs - and in fact it does
occur on arm64 in certain situations (CONFIG_DEBUG_PAGEALLOC). Commit
1ef3095b1405 ("arm64/mm: Permit lazy_mmu_mode to be nested") made nesting
tolerable on arm64, but without truly supporting it: the inner call to
leave() disables the batching optimisation before the outer section ends.

This patch actually enables lazy_mmu sections to nest by tracking the
nesting level in task_struct, in a similar fashion to e.g.
pagefault_{enable,disable}(). This is fully handled by the generic
lazy_mmu helpers that were recently introduced.

lazy_mmu sections were not initially intended to nest, so we need to
clarify the semantics w.r.t. the arch_*_lazy_mmu_mode() callbacks. This
patch takes the following approach:

* The outermost calls to lazy_mmu_mode_{enable,disable}() trigger
calls to arch_{enter,leave}_lazy_mmu_mode() - this is unchanged.

* Nested calls to lazy_mmu_mode_{enable,disable}() are not forwarded
to the arch via arch_{enter,leave} - lazy MMU remains enabled so
the assumption is that these callbacks are not relevant. However,
existing code may rely on a call to disable() to flush any batched
state, regardless of nesting. arch_flush_lazy_mmu_mode() is
therefore called in that situation.

A separate interface was recently introduced to temporarily pause the lazy
MMU mode: lazy_mmu_mode_{pause,resume}(). pause() fully exits the mode
*regardless of the nesting level*, and resume() restores the mode at the
same nesting level.

pause()/resume() are themselves allowed to nest, so we actually store two
nesting levels in task_struct: enable_count and pause_count. A new helper
is_lazy_mmu_mode_active() is introduced to determine whether we are
currently in lazy MMU mode; this will be used in subsequent patches to
replace the various ways arch's currently track whether the mode is
enabled.

In summary (enable/pause represent the values *after* the call):

lazy_mmu_mode_enable() -> arch_enter() enable=1 pause=0
lazy_mmu_mode_enable() -> ø enable=2 pause=0
lazy_mmu_mode_pause() -> arch_leave() enable=2 pause=1
lazy_mmu_mode_resume() -> arch_enter() enable=2 pause=0
lazy_mmu_mode_disable() -> arch_flush() enable=1 pause=0
lazy_mmu_mode_disable() -> arch_leave() enable=0 pause=0

Note: is_lazy_mmu_mode_active() is added to <linux/sched.h> to allow
arch headers included by <linux/pgtable.h> to use it.

Link: https://lkml.kernel.org/r/20251215150323.2218608-10-kevin.brodsky@arm.com
Signed-off-by: Kevin Brodsky <kevin.brodsky@arm.com>
Acked-by: David Hildenbrand (Red Hat) <david@kernel.org>
Reviewed-by: Yeoreum Yun <yeoreum.yun@arm.com>
Cc: Alexander Gordeev <agordeev@linux.ibm.com>
Cc: Andreas Larsson <andreas@gaisler.com>
Cc: Anshuman Khandual <anshuman.khandual@arm.com>
Cc: Borislav Betkov <bp@alien8.de>
Cc: Boris Ostrovsky <boris.ostrovsky@oracle.com>
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Christophe Leroy <christophe.leroy@csgroup.eu>
Cc: David Hildenbrand <david@redhat.com>
Cc: David S. Miller <davem@davemloft.net>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: "H. Peter Anvin" <hpa@zytor.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Jann Horn <jannh@google.com>
Cc: Juegren Gross <jgross@suse.com>
Cc: Liam Howlett <liam.howlett@oracle.com>
Cc: Lorenzo Stoakes <lorenzo.stoakes@oracle.com>
Cc: Madhavan Srinivasan <maddy@linux.ibm.com>
Cc: Michael Ellerman <mpe@ellerman.id.au>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: Nicholas Piggin <npiggin@gmail.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Ritesh Harjani (IBM) <ritesh.list@gmail.com>
Cc: Ryan Roberts <ryan.roberts@arm.com>
Cc: Suren Baghdasaryan <surenb@google.com>
Cc: Thomas Gleinxer <tglx@linutronix.de>
Cc: Venkat Rao Bagalkote <venkat88@linux.ibm.com>
Cc: Vlastimil Babka <vbabka@suse.cz>
Cc: Will Deacon <will@kernel.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Kevin Brodsky and committed by
Andrew Morton
5ab24674 9273dfae

+157 -19
-12
arch/arm64/include/asm/pgtable.h
··· 82 82 83 83 static inline void arch_enter_lazy_mmu_mode(void) 84 84 { 85 - /* 86 - * lazy_mmu_mode is not supposed to permit nesting. But in practice this 87 - * does happen with CONFIG_DEBUG_PAGEALLOC, where a page allocation 88 - * inside a lazy_mmu_mode section (such as zap_pte_range()) will change 89 - * permissions on the linear map with apply_to_page_range(), which 90 - * re-enters lazy_mmu_mode. So we tolerate nesting in our 91 - * implementation. The first call to arch_leave_lazy_mmu_mode() will 92 - * flush and clear the flag such that the remainder of the work in the 93 - * outer nest behaves as if outside of lazy mmu mode. This is safe and 94 - * keeps tracking simple. 95 - */ 96 - 97 85 set_thread_flag(TIF_LAZY_MMU); 98 86 } 99 87
+5
include/linux/mm_types_task.h
··· 88 88 #endif 89 89 }; 90 90 91 + struct lazy_mmu_state { 92 + u8 enable_count; 93 + u8 pause_count; 94 + }; 95 + 91 96 #endif /* _LINUX_MM_TYPES_TASK_H */
+107 -7
include/linux/pgtable.h
··· 236 236 * The mode is disabled in interrupt context and calls to the lazy_mmu API have 237 237 * no effect. 238 238 * 239 - * Nesting is not permitted. 239 + * The lazy MMU mode is enabled for a given block of code using: 240 + * 241 + * lazy_mmu_mode_enable(); 242 + * <code> 243 + * lazy_mmu_mode_disable(); 244 + * 245 + * Nesting is permitted: <code> may itself use an enable()/disable() pair. 246 + * A nested call to enable() has no functional effect; however disable() causes 247 + * any batched architectural state to be flushed regardless of nesting. After a 248 + * call to disable(), the caller can therefore rely on all previous page table 249 + * modifications to have taken effect, but the lazy MMU mode may still be 250 + * enabled. 251 + * 252 + * In certain cases, it may be desirable to temporarily pause the lazy MMU mode. 253 + * This can be done using: 254 + * 255 + * lazy_mmu_mode_pause(); 256 + * <code> 257 + * lazy_mmu_mode_resume(); 258 + * 259 + * pause() ensures that the mode is exited regardless of the nesting level; 260 + * resume() re-enters the mode at the same nesting level. Any call to the 261 + * lazy_mmu_mode_* API between those two calls has no effect. In particular, 262 + * this means that pause()/resume() pairs may nest. 263 + * 264 + * is_lazy_mmu_mode_active() can be used to check whether the lazy MMU mode is 265 + * currently enabled. 240 266 */ 241 267 #ifdef CONFIG_ARCH_HAS_LAZY_MMU_MODE 268 + /** 269 + * lazy_mmu_mode_enable() - Enable the lazy MMU mode. 270 + * 271 + * Enters a new lazy MMU mode section; if the mode was not already enabled, 272 + * enables it and calls arch_enter_lazy_mmu_mode(). 273 + * 274 + * Must be paired with a call to lazy_mmu_mode_disable(). 275 + * 276 + * Has no effect if called: 277 + * - While paused - see lazy_mmu_mode_pause() 278 + * - In interrupt context 279 + */ 242 280 static inline void lazy_mmu_mode_enable(void) 243 281 { 244 - if (in_interrupt()) 282 + struct lazy_mmu_state *state = &current->lazy_mmu_state; 283 + 284 + if (in_interrupt() || state->pause_count > 0) 245 285 return; 246 286 247 - arch_enter_lazy_mmu_mode(); 287 + VM_WARN_ON_ONCE(state->enable_count == U8_MAX); 288 + 289 + if (state->enable_count++ == 0) 290 + arch_enter_lazy_mmu_mode(); 248 291 } 249 292 293 + /** 294 + * lazy_mmu_mode_disable() - Disable the lazy MMU mode. 295 + * 296 + * Exits the current lazy MMU mode section. If it is the outermost section, 297 + * disables the mode and calls arch_leave_lazy_mmu_mode(). Otherwise (nested 298 + * section), calls arch_flush_lazy_mmu_mode(). 299 + * 300 + * Must match a call to lazy_mmu_mode_enable(). 301 + * 302 + * Has no effect if called: 303 + * - While paused - see lazy_mmu_mode_pause() 304 + * - In interrupt context 305 + */ 250 306 static inline void lazy_mmu_mode_disable(void) 251 307 { 252 - if (in_interrupt()) 308 + struct lazy_mmu_state *state = &current->lazy_mmu_state; 309 + 310 + if (in_interrupt() || state->pause_count > 0) 253 311 return; 254 312 255 - arch_leave_lazy_mmu_mode(); 313 + VM_WARN_ON_ONCE(state->enable_count == 0); 314 + 315 + if (--state->enable_count == 0) 316 + arch_leave_lazy_mmu_mode(); 317 + else /* Exiting a nested section */ 318 + arch_flush_lazy_mmu_mode(); 319 + 256 320 } 257 321 322 + /** 323 + * lazy_mmu_mode_pause() - Pause the lazy MMU mode. 324 + * 325 + * Pauses the lazy MMU mode; if it is currently active, disables it and calls 326 + * arch_leave_lazy_mmu_mode(). 327 + * 328 + * Must be paired with a call to lazy_mmu_mode_resume(). Calls to the 329 + * lazy_mmu_mode_* API have no effect until the matching resume() call. 330 + * 331 + * Has no effect if called: 332 + * - While paused (inside another pause()/resume() pair) 333 + * - In interrupt context 334 + */ 258 335 static inline void lazy_mmu_mode_pause(void) 259 336 { 337 + struct lazy_mmu_state *state = &current->lazy_mmu_state; 338 + 260 339 if (in_interrupt()) 261 340 return; 262 341 263 - arch_leave_lazy_mmu_mode(); 342 + VM_WARN_ON_ONCE(state->pause_count == U8_MAX); 343 + 344 + if (state->pause_count++ == 0 && state->enable_count > 0) 345 + arch_leave_lazy_mmu_mode(); 264 346 } 265 347 348 + /** 349 + * lazy_mmu_mode_resume() - Resume the lazy MMU mode. 350 + * 351 + * Resumes the lazy MMU mode; if it was active at the point where the matching 352 + * call to lazy_mmu_mode_pause() was made, re-enables it and calls 353 + * arch_enter_lazy_mmu_mode(). 354 + * 355 + * Must match a call to lazy_mmu_mode_pause(). 356 + * 357 + * Has no effect if called: 358 + * - While paused (inside another pause()/resume() pair) 359 + * - In interrupt context 360 + */ 266 361 static inline void lazy_mmu_mode_resume(void) 267 362 { 363 + struct lazy_mmu_state *state = &current->lazy_mmu_state; 364 + 268 365 if (in_interrupt()) 269 366 return; 270 367 271 - arch_enter_lazy_mmu_mode(); 368 + VM_WARN_ON_ONCE(state->pause_count == 0); 369 + 370 + if (--state->pause_count == 0 && state->enable_count > 0) 371 + arch_enter_lazy_mmu_mode(); 272 372 } 273 373 #else 274 374 static inline void lazy_mmu_mode_enable(void) {}
+45
include/linux/sched.h
··· 1419 1419 1420 1420 struct page_frag task_frag; 1421 1421 1422 + #ifdef CONFIG_ARCH_HAS_LAZY_MMU_MODE 1423 + struct lazy_mmu_state lazy_mmu_state; 1424 + #endif 1425 + 1422 1426 #ifdef CONFIG_TASK_DELAY_ACCT 1423 1427 struct task_delay_info *delays; 1424 1428 #endif ··· 1705 1701 { 1706 1702 return task_index_to_char(task_state_index(tsk)); 1707 1703 } 1704 + 1705 + #ifdef CONFIG_ARCH_HAS_LAZY_MMU_MODE 1706 + /** 1707 + * __task_lazy_mmu_mode_active() - Test the lazy MMU mode state for a task. 1708 + * @tsk: The task to check. 1709 + * 1710 + * Test whether @tsk has its lazy MMU mode state set to active (i.e. enabled 1711 + * and not paused). 1712 + * 1713 + * This function only considers the state saved in task_struct; to test whether 1714 + * current actually is in lazy MMU mode, is_lazy_mmu_mode_active() should be 1715 + * used instead. 1716 + * 1717 + * This function is intended for architectures that implement the lazy MMU 1718 + * mode; it must not be called from generic code. 1719 + */ 1720 + static inline bool __task_lazy_mmu_mode_active(struct task_struct *tsk) 1721 + { 1722 + struct lazy_mmu_state *state = &tsk->lazy_mmu_state; 1723 + 1724 + return state->enable_count > 0 && state->pause_count == 0; 1725 + } 1726 + 1727 + /** 1728 + * is_lazy_mmu_mode_active() - Test whether we are currently in lazy MMU mode. 1729 + * 1730 + * Test whether the current context is in lazy MMU mode. This is true if both: 1731 + * 1. We are not in interrupt context 1732 + * 2. Lazy MMU mode is active for the current task 1733 + * 1734 + * This function is intended for architectures that implement the lazy MMU 1735 + * mode; it must not be called from generic code. 1736 + */ 1737 + static inline bool is_lazy_mmu_mode_active(void) 1738 + { 1739 + if (in_interrupt()) 1740 + return false; 1741 + 1742 + return __task_lazy_mmu_mode_active(current); 1743 + } 1744 + #endif 1708 1745 1709 1746 extern struct pid *cad_pid; 1710 1747