-
Notifications
You must be signed in to change notification settings - Fork 7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
kernel: support dynamic thread stack allocation #44379
kernel: support dynamic thread stack allocation #44379
Conversation
b5b819c
to
f58ea3a
Compare
@dcpleung - would it make sense to separate the |
53f2ccd
to
2993fbf
Compare
2993fbf
to
03fbcb4
Compare
Architectures have |
12621e6
to
8ec3552
Compare
I didn't bother creating a sys interface, it seemed to be overkill for now. |
774f93b
to
b87c6de
Compare
d32fbd4
to
b97445a
Compare
some yaml fixups... hopefully I didn't break anything this time 🤣 |
Flavio & I will likely merge our PRs tomorrow, so no rush at the moment. |
Add support for dynamic thread stack allocation Signed-off-by: Christopher Friedt <[email protected]>
b97445a
to
2a012bb
Compare
Test that automatic thread stack allocation works for both user and kernel threads. Signed-off-by: Christopher Friedt <[email protected]>
2a012bb
to
71ca732
Compare
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should always allocate stacks from the heap; reduces code complexity, avoids issues with the pre-allocated stacks being the wrong size and doesn't consume any more memory.
@keith-packard - I think that makes sense for a large majority of use cases, in particular userspace code, and we actually can get that behaviour via Kconfig (by specifying the pool size to be zero and enabling heap allocation). The pool option is really there to support high reliability / safety critical if they choose to use it. |
Even fragmentation is an issue in an RTOS. Stacks are big, memories are small, uptimes are (sometimes very) long. A dynamic spawned thread that works fine in testing in legacy code might suddenly die after two years in the field. A core Zephyr paradigm is that the system heap should be 100% optional. Everything that can be should be allocatable statically. I'm actually dealing with a bunch of issues like this currently while fuzzing SOF, which is very heap-centric. The allocation errors can be handled correctly (and are), but the resulting runtime failures are still failures, and prevent reaching further states. The impact there is actually paradoxical: the random input tends to leak a lot of allocations and run out of space quickly, so in fact fuzz coverage drops like a rock after the first few thousand commands. We're seeing failures come back from the (extremely parallel) oss-fuzz rig that I never saw over days and days of running locally. |
yep ... One thing though that I am thinking now is that having two separated API is probably better. The reason is that whe you are allocating from the pool the stack size is defined at build time by CONFIG_DYNAMIC_THREAD_STACK_SIZE (so the given parameter is not really needed and is confusing) while allocating from the heap we don't have this constraint. Also, K_USER does not make sense IMHO, since if you have userspace enabled they stacks declared in the pool are already user capable. |
Oh, so just exposing the One more request -- can we have the stack automatically freed when the thread exits? |
If that's the general consensus, it would probably be better to not even expose pool allocation this way - just have a separate K_THREAD_POOL() that only ever allocates at build time. It would also make the pthread use case more complicated, which I was hoping to avoid, since that was the entire reason for doing this in a way to silently support both safety and non-safety in the same way. @romkell, @andyross - do you have any preferences?
That was tried a long time ago in a couple of different ways and it tended to be problematic and a bit racey. Additionally, there was a vito from the safety side, because invariably then the malloc-ing code would be intermixed with the non-mallocing code. |
My major point concerning safety is that we retain the API where we instantiate every thing statically on the appl. side (and pass to init function etc.). |
Technically, that is a maximum size for pooled stacks (maybe the Kconfig could be named better). The size parameter can still provide non-redundant information when size < max (especially in the case where an MMU or Stack Canaries are present). In any case though, given that both the pure-heap, pure-pool, and mixed cases are realizable with a single API, I think it would eliminate a non-trivial amount of code duplication and maybe some future bugs with virtually zero overhead (since kconfig-controlled code paths are dead-stripped by the linker in the current implementation). To me that's a big selling feature that makes supporting the standard threading model in C, C++, pthreads (not to mention other languages) dramatically easier. Just at a glance, it does not seem possible to support statically allocated C11 or C++11 thread stacks at all, externally to the implementation. https://en.cppreference.com/w/c/thread https://en.cppreference.com/w/cpp/thread/thread/thread I think it may be possible in Rust to specify a statically allocated thread stack. So there is a significant probability we would be forced to duplicate all of that heap / pool logic for several language runtimes.
|
Mentioned offline as well, but it would likely make sense to support flags for pool or heap as well |
I think that was well ... just the |
I agree, that is error prone too, someone may try to re-use the allocated stack in a different thread ... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should drop a +1 to move this along. Really I think this is very reasonable. It's a big area with lots of edges (especially once we start talking about userspace!), and no API is going to meet everyone's needs. This seems straightforward and useful enough for common cases, and apps with complicated needs can use it as a reference if necessary.
config DYNAMIC_THREAD_STACK_SIZE | ||
int "Size of each pre-allocated thread stack" | ||
default 1024 if !64BIT | ||
default 2048 if 64BIT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default MMU_PAGE_SIZE if MMU
as stack needs to use the full page for access control (especially for userspace).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
userspace is coming in my pr ... do not worry about it now please.
So if it's desirable,
to indicate a suitable allocation strategy as well (a default of zero could imply either). |
Amen to that. Lets get Flavio's changes in as well so people can mix things up and develop organically. Better to do it early in the release cycle. |
That is what I was thinking ... this way we can remove the build symbols about the allocation preferences and remove all fallbacks ... that is way is clear for the user from where he is getting the memory. Note that is not a blocker for me, we can do it incrementally. |
Opening this up for review. It's a biggie, and has some wide-reaching implications.
TL;DR - we should be able to support automatic pthread stack allocation with this. It's in the Roadmap (#51211).
However, at least with GCC, this does a lot more though. With compliant POSIX threads
<pthread.h>
, we also get<threads.h>
<thread>
(<mutex>
,<condition_variable>
,<semaphore>
), etc.Later, we can optimize-out the POSIX layer of course, which I would like to take great care in doing with Keith and Stephanos.
Taking a step back, excluding coroutines, that represents the de-facto threading model for approximately the last 25 years, and probably the de-facto threading model for most other programming languages for the foreseeable future.
The API supports both safety-critical and non-safety critical because it uses the same interface for both pool-based and heap-based thread stack allocations.
Covered in my talks at EOSS 2023.
Thanks for the help everyone 🙏 ❤️
@andrewboie , @pfalcon, @andyross , @peter-mitsis , @nashif , @dcpleung , @ceolin , @stephanosio , @keith-packard ... I really could keep going.
Fixes #26999
There is some icing on the top added by @ceolin for
k_object
management in #59773BELOW FOR POSTERITY
Just a WIP at the moment. Working fine for cortex-m3.