Skip to content

Fix shell threadless support #93184

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

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

bjarki-andreasen
Copy link
Contributor

@bjarki-andreasen bjarki-andreasen commented Jul 16, 2025

The shell at some point supported CONFIG_MULTITHREADING=n, but this has not been maintained. This PR fixes that build option, and adds a sample to show how to use it, and ensure it is not broken again :)

This PR is based top of the k_poll to k_event refactor, not really needed to fix the shell but I believe it will go in so this could save a bit of rebasing later :)

Tested on nrf54l15dk/nrf54l15/cpuapp (nordic), b_u585i_iot02a (stm) and frdm_ke15z (nxp)

@bjarki-andreasen bjarki-andreasen force-pushed the shell-threadless branch 2 times, most recently from a5749f7 to 5d63c28 Compare July 16, 2025 16:22
@nashif
Copy link
Member

nashif commented Jul 16, 2025

shell is not listed as something we wanted working/supported with no threads.

https://docs.zephyrproject.org/latest/kernel/services/threads/nothread.html

@bjarki-andreasen
Copy link
Contributor Author

bjarki-andreasen commented Jul 16, 2025

shell is not listed as something we wanted working/supported with no threads.

https://docs.zephyrproject.org/latest/kernel/services/threads/nothread.html

Time to change that then :) Logging is not listed here either, but that works single threaded as well

@nashif
Copy link
Member

nashif commented Jul 16, 2025

Time to change that then :) Logging is not listed here either, but that works single threaded as well

well, no. That page https://docs.zephyrproject.org/latest/kernel/services/threads/nothread.html exists for a reason. This is to limit this non-sense of making everything in zephyr single threaded and littering the code with senseless ifdefs. The project has decided and agreed long time ago to contain this and not allow single thread support in more than what is listed there. Maybe it is time to enable threading in the bootloader, because the reasoning was always footprint, but then, why are you enabling shell in a bootloader? (or logging or whatever).

@bjarki-andreasen
Copy link
Contributor Author

bjarki-andreasen commented Jul 16, 2025

Time to change that then :) Logging is not listed here either, but that works single threaded as well

well, no. That page https://docs.zephyrproject.org/latest/kernel/services/threads/nothread.html exists for a reason. This is to limit this non-sense of making everything in zephyr single threaded and littering the code with senseless ifdefs. The project has decided and agreed long time ago to contain this and not allow single thread support in more than what is listed there. Maybe it is time to enable threading in the bootloader, because the reasoning was always footprint, but then, why are you enabling shell in a bootloader? (or logging or whatever).

Shell in a bootloader can be useful, and takes up waay less resources if it works with single threading. Furthermore, the change for now just allows either no thread (threadless) or multithreaded (one thread for each shell). One thread for each shell is really expensive, adding flexibility to allow for a shell backend to be run from a work queue for example can dramatically optimize systems with multiple shells (if the user chooses to, it should be an option).

The end goal is to separate the shell instance from the thread instance, similar to what we recently did for work queues, where any thread can "take over" / "execute" any shell instance, for example, reusing the main thread to run a shell would be efficient no?

The shell subsystem currently uses k_poll for signalling. However,
k_poll is only used for simple event signals, results are not used.

Replacing the k_poll with k_event greatly simplifies the code, and
saves 4 struct k_poll_signal and 4 struct k_poll_event (one of which
was entirely unused) while costing a single struct k_event, for
every shell instance. It also allows us to not select POLL,
as we are using the simpler EVENTS instead.

A quick test build of the shell test suite on an nrf54l15 produces
the following build info:

using EVENTS:

           FLASH:       71592 B      1428 KB      4.90%
             RAM:        9872 B       188 KB      5.13%
        IDT_LIST:          0 GB        32 KB      0.00%

using POLL

           FLASH:       75524 B      1428 KB      5.16%
             RAM:       11224 B       188 KB      5.83%
        IDT_LIST:          0 GB        32 KB      0.00%

Signed-off-by: Bjarki Arge Andreasen <bjarki.andreasen@nordicsemi.no>
The mutex is being used as a simple binary semaphore. It is not
recursed so we don't need to track thread ownership nor lock count.

Exchange the mutex for a binary semaphore to save resources and
speed up shell.

Signed-off-by: Bjarki Arge Andreasen <bjarki.andreasen@nordicsemi.no>
andyross
andyross previously approved these changes Jul 17, 2025
Copy link
Contributor

@andyross andyross left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The existential argument about whether/how to support MULTITHREADING=n notwithstanding, this looks pretty reasonable to me.

One tidiness complaint is that this is now filled with "#ifdef CONFIG_MULTITHREADING around common idioms like the lock and event handling that you might clean up with local function like shell_un/lock(), shell_event_signal() etc... where they can evaluate to noops when not needed.

@bjarki-andreasen
Copy link
Contributor Author

bjarki-andreasen commented Jul 17, 2025

The existential argument about whether/how to support MULTITHREADING=n notwithstanding, this looks pretty reasonable to me.

One tidiness complaint is that this is now filled with "#ifdef CONFIG_MULTITHREADING around common idioms like the lock and event handling that you might clean up with local function like shell_un/lock(), shell_event_signal() etc... where they can evaluate to noops when not needed.

great minds something something: 954f8b6

I'm doing quite a bit of cleanup so the ifdefs should go to near extinction :) This PR for now is just a draft POC.

On top of this, I am looking into making the struct k_sem itself adapt to single threading (thread + isr) where it gets converted to a while loop and an atomic :), this way drivers using k_sem for signalling from ISRs will still "work", as in behave similarly from the outside, but that is a future thing

The shell at some point supported CONFIG_MULTITHREADING=n but this
has not been maintained. Adjust the shell to support threadless
operation similar to how logging works with no logging thread.

With these adjustments, building with CONFIG_SHELL_MINIMAL and
CONFIG_MULTITHREADING=n, shells can now be used like logging by
calling shell_process() with non-polling backends, like async
or irq driven UART based backends.

Signed-off-by: Bjarki Arge Andreasen <bjarki.andreasen@nordicsemi.no>
Introduce threadless shell sample which shows how to build and use
shells in threadless (single threaded) applications.

Signed-off-by: Bjarki Arge Andreasen <bjarki.andreasen@nordicsemi.no>
Extend nothread docs to list the shell subsystem using the
serial shell backend as expected to work with
CONFIG_MULTITHREADING=n

Signed-off-by: Bjarki Arge Andreasen <bjarki.andreasen@nordicsemi.no>
@bjarki-andreasen
Copy link
Contributor Author

bjarki-andreasen commented Jul 18, 2025

rebased to include the k_mutex to k_sem commit, which also adds a nice place to collect some of the ifdefs: z_shell_lock(), z_shell_unlock(), z_shell_trylock()

Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants