[PATCH v2 08/13] task_stack.h: Add stack_not_used() support for dynamic stack

David Stevens
2026-04-24T19:17:04Z

From: Pasha Tatashin <pasha.tatashin@soleen.com>

CONFIG_DEBUG_STACK_USAGE is enabled by default on most architectures.

Its purpose is to determine and print the maximum stack depth on
thread exit.

The way it works, is it starts from the bottom of the stack and
searches the first non-zero word in the stack. With dynamic stack it
does not work very well, as it means it faults every pages in every
stack.

Instead, add a specific version of stack_not_used() for dynamic stacks
where instead of starting from the bottom of the stack, we start from
the last page mapped in the stack.

In addition to not doing unnecessary page faulting, this search is
optimized by skipping search through zero pages.

Also, because dynamic stack does not end with MAGIC_NUMBER, there is
no need to skip the bottom most word in the stack.

Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
[Rebased, Kasan oneliner needed preserving, rewrote a bit due to bugs]
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
[Handle init_task's use of init_stack, fix typos]
Signed-off-by: David Stevens <stevensd@google.com>
---
arch/Kconfig | 1 -
kernel/exit.c | 22 ++++++++++++++++++++++
2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/arch/Kconfig b/arch/Kconfig
index 95ded79f0825..beffe7e01296 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -1542,7 +1542,6 @@ config DYNAMIC_STACK
depends on VMAP_STACK
depends on INIT_STACK_ALL_ZERO || INIT_STACK_ALL_PATTERN
depends on !KASAN
- depends on !DEBUG_STACK_USAGE
depends on !STACK_GROWSUP
depends on !PREEMPT_RT
help
diff --git a/kernel/exit.c b/kernel/exit.c
index ede3117fa7d4..6caf4030e8f4 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -71,6 +71,7 @@
#include <linux/unwind_deferred.h>
#include <linux/uaccess.h>
#include <linux/pidfs.h>
+#include <linux/vmalloc.h>

#include <uapi/linux/wait.h>

@@ -791,6 +792,26 @@ unsigned long stack_not_used(struct task_struct *p)
return (unsigned long)end_of_stack(p) - (unsigned long)n;
}
#else /* !CONFIG_STACK_GROWSUP */
+#ifdef CONFIG_DYNAMIC_STACK
+unsigned long stack_not_used(struct task_struct *p)
+{
+ struct vm_struct *vm_area = task_stack_vm_area(p);
+ unsigned long stack = (unsigned long)task_stack_page(p);
+ unsigned long alloc_size, *n;
+
+ /* This is NULL only for init_task, where init_stack is fully allocated. */
+ if (likely(vm_area))
+ alloc_size = vm_area->nr_pages << PAGE_SHIFT;
+ else
+ alloc_size = THREAD_SIZE;
+ n = (unsigned long *)(stack + THREAD_SIZE - alloc_size);
+
+ while (!*n)
+ n++;
+
+ return (unsigned long)n - stack;
+}
+#else
unsigned long stack_not_used(struct task_struct *p)
{
unsigned long *n = end_of_stack(p);
@@ -801,6 +822,7 @@ unsigned long stack_not_used(struct task_struct *p)

return (unsigned long)n - (unsigned long)end_of_stack(p);
}
+#endif /* CONFIG_DYNAMIC_STACK */
#endif /* CONFIG_STACK_GROWSUP */

/* Count the maximum pages reached in kernel stacks */