mirror of
https://github.com/apache/nuttx.git
synced 2026-10-08 06:45:19 +00:00
The documentation grew one page at a time, so the tree follows the
history of who wrote what and not the shape of NuttX. Scheduling is
spread over three places, a driver page can sit above the subsystem
that owns it, and the front page lists everything at the same level.
That is a lot to face when all you want to know is where the scheduler
lives.
This change files every page under the code it describes. It is a move,
not a rewrite: outside the ten pages named below, every page keeps the
text that is already in master, and no page's text is deleted.
What it does:
* Groups the table of contents into nine chapters.
* Moves the OS subsystems under os/: scheduling, memory, drivers,
filesystem, networking, IPC, interrupts, libs, time.
* Renames the platform pages to the names the source tree uses, and
derives their tags from the tree instead of by hand.
* Splits guides/ by subject.
* Adds Documentation/redirects.py, with a rule for every page that left
its old path, so old URLs keep working. The redirect page also carries
a link's #anchor across to the new page.
Ten pages have text that is new or rewritten. Nine of them are the
landing page of a chapter, which has to exist for the new structure:
index the front page
os/index OS Design
os/scheduling/index Scheduling
os/interrupts/index Interrupts
os/ipc/index IPC
os/time/index Time and timers
about/index About
developing/index Developing NuttX
ReleaseNotes/index Release notes
The tenth is os/libs/libbuiltin, the only page here with technical
content: libs/libbuiltin/ had no page at all. Five SVG diagrams come
with these pages, hand-written XML with no editor metadata.
Nothing outside Documentation/ is touched.
How it was checked:
* Sphinx builds with -W: no warnings, and no document left outside a
toctree.
* A script, offered in the PR, proves the narrow claim this rests on.
For every page outside the ten named above it erases what a move
touches -- link target, path, tag line, toctree block, table border --
from the whole old text and the whole new text, and requires the two
to be byte for byte identical. It also requires every sentence of a
deleted page to turn up somewhere, and every page that left its old
path to have a redirect, from a URL that existed, to where its content
went. It exits non-zero and names the page if any of that is not true,
and it tests added pages too, so forgetting to declare one cannot make
it pass.
* An independent audit checked 133 factual claims on these ten pages
against the tree, one shell command per claim: 130 confirmed, 1
refuted and fixed here, 2 not checkable.
* tools/checkpatch.sh is clean over the range.
The diff is large because moving a page changes every link that points
to it. Most of it is pure renames, and board pages that gained one tag
line.
Assisted-by: Claude:claude-opus-5
263 lines
8.4 KiB
ReStructuredText
263 lines
8.4 KiB
ReStructuredText
==============================
|
|
Linux Processes vs NuttX Tasks
|
|
==============================
|
|
|
|
You may be used to running programs that are stored in files on Linux or Windows.
|
|
If you transition to using NuttX tasks on an MCU with limited resources, you will
|
|
encounter some behavioral differences. This Wiki page will summarize a few of
|
|
those differences.
|
|
|
|
NuttX Build Types
|
|
=================
|
|
|
|
NuttX can be built in several different ways:
|
|
|
|
* **Kernel Build** The kernel build, selected with ``CONFIG_BUILD_KERNEL``, uses
|
|
the MCU's Memory Management Unit (MMU) to implement processes very similar to
|
|
Linux processes. There is no interesting discussion here; NuttX behaves very
|
|
much like Linux.
|
|
|
|
* **Flat Build** Most resource-limited MCUs have no MMU and the code is built as
|
|
a blob that runs in an unprotected, flat address space out of on-chip FLASH
|
|
memory. This build mode is selected with ``CONFIG_BUILD_FLAT`` and is, by far, the
|
|
most common way that people build NuttX. This is the interesting case to which
|
|
this Wiki page is directed.
|
|
|
|
* **Protected Build** Another build option is the protected build. This is essentially
|
|
the same as the flat build, but uses the MCU's Memory Protection Unit (MPU) to
|
|
separate unproctect user address ranges from protected system address ranges.
|
|
The comments of this Wiki page also apply in this case.
|
|
|
|
Initialization of Global Variables
|
|
==================================
|
|
|
|
Linux Behavior
|
|
--------------
|
|
|
|
If you are used to writing programs for Linux, then one thing you will notice is that
|
|
global variables are initialized only once when the system powers up. For example.
|
|
Consider this tiny program:
|
|
|
|
.. code-block:: C
|
|
|
|
bool test = true;
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
printf("test: %i\n", test);
|
|
test = false;
|
|
printf("test: %i\n", test);
|
|
return 0;
|
|
}
|
|
|
|
If you build and run this program under Linux, you will always see this output::
|
|
|
|
test: 1
|
|
test: 0
|
|
|
|
In this case, the global variables are re-initialized each time that you load the
|
|
file into memory and run it.
|
|
|
|
NuttX Flat-Build Behavior
|
|
-------------------------
|
|
|
|
But if you build this program into on-chip FLASH and start it as a task (via, say,
|
|
``task_start()``) you will see this the first time that you run the program::
|
|
|
|
test: 1
|
|
test: 0
|
|
|
|
But after that, you will always see::
|
|
|
|
test: 0
|
|
test: 0
|
|
|
|
The test variable was initialized to true (1) only once at power up, but reset to
|
|
false (0) each time that the program runs.
|
|
|
|
If you want the same behavior when the program is built into the common FLASH blob,
|
|
then you will have modify the code so that global variables are explicitly reset
|
|
each time the program runs like:
|
|
|
|
.. code-block:: C
|
|
|
|
bool test;
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
test = true;
|
|
printf("test: %i\n", test);
|
|
test = false;
|
|
printf("test: %i\n", test);
|
|
return 0;
|
|
}
|
|
|
|
NuttX Load-able Programs
|
|
------------------------
|
|
|
|
If you load programs from an file into RAM and execute them, as Linux does, then
|
|
NuttX will again behave like Linux. Because the flat build NuttX works the same way:
|
|
When you execute a NuttX ELF or NxFLAT module in a file, the file is copied into RAM
|
|
and the global variables are initialized before the program runs.
|
|
|
|
But code that is built into FLASH works differently. There is only one set of global
|
|
variables: All of the global variables for the blob that is the monolithic FLASH image.
|
|
They are all initialized once at power-up reset.
|
|
|
|
This is one of the things that makes porting Linux applications into the FLASH blob
|
|
more complex. You have to manually initialize each global variable in the ``main()``
|
|
each time your start the task.
|
|
|
|
Global Variables and Multiple Task Copies
|
|
=========================================
|
|
|
|
It is better to avoid the use of global variables in the flat build context whenever
|
|
possible because that usage adds another limitation: No more that one copy of the
|
|
program can run at any given time. That is because the global variables are shared
|
|
by each instance (unlike, again, running a program from a file where there is a private
|
|
copy of each global variable).
|
|
|
|
One way to support multiple copies of an in-FLASH program is to move all global variables
|
|
into a structure. If the amount of memory need for global variables is small, then each
|
|
``main()`` could simply allocate a copy of that structure on the stack. In the simple
|
|
example above, this might be:
|
|
|
|
.. code-block:: C
|
|
|
|
struct my_globals_s
|
|
{
|
|
bool test;
|
|
};
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
struct my_globals_s my_globals = { true };
|
|
|
|
printf("test: %i\n", my_globals.test);
|
|
my_globals.test = false;
|
|
printf("test: %i\n", my_globals.test);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
A pointer to the structure containing the allocated global variables would then have
|
|
to passed as a parameter to every internal function that needs access to the global
|
|
variables. So you would change a internal function like:
|
|
|
|
.. code-block:: C
|
|
|
|
static void print_value(void)
|
|
{
|
|
printf("test: %i\n", test);
|
|
}
|
|
|
|
to:
|
|
|
|
.. code-block:: C
|
|
|
|
static void print_value(FAR struct my_globals_s *globals)
|
|
{
|
|
printf("test: %i\n", globals->test);
|
|
}
|
|
|
|
Then pass a reference to the allocated global data structure each time that the
|
|
function is called like:
|
|
|
|
.. code-block:: C
|
|
|
|
print_value(&my_globals);
|
|
|
|
If the size of the global variable structure is large, then allocating the instance
|
|
on the stack might not be such a good idea. In that case, it might be better to
|
|
allocate the global variable structure using ``malloc()``. But don't forget to ``free()``
|
|
the allocated variable structure before exiting! (See the following Memory Clean-Up
|
|
discussion).
|
|
|
|
.. code-block:: C
|
|
|
|
struct my_globals_s
|
|
{
|
|
bool test;
|
|
};
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
FAR struct my_globals_s *my_globals;
|
|
|
|
my_globals = (FAR struct my_globals_s *)malloc(sizeof(struct my_globals_s));
|
|
if (my_globals = NULL)
|
|
{
|
|
fprintf(stderr, "ERROR: Failed to allocate state structure\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
my_globals=>test = true;
|
|
printf("test: %i\n", my_globals->test);
|
|
my_globals=>test = false;
|
|
printf("test: %i\n", my_globals->test);
|
|
|
|
free(my_globals);
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
Memory Clean-Up
|
|
===============
|
|
|
|
Linux Process Exit
|
|
------------------
|
|
|
|
Another, unrelated thing that makes porting Linux programs into the FLASH blob
|
|
is the memory clean-up. When a Linux process exits, its entire address environment
|
|
is destroyed including all of allocated memory. This tiny program will not leak
|
|
memory if implemented as a Linux process:
|
|
|
|
.. code-block:: C
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
char *buffer = malloc(1024);
|
|
... do stuff with buffer ...
|
|
return 0;
|
|
}
|
|
|
|
That same program, if ported into the FLASH blob will now have memory leaks because
|
|
there is no automatic clean-up of allocated memory when the task exits. Instead, you
|
|
must explicitly clean up all allocated memory by freeing it:
|
|
|
|
.. code-block:: C
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
char *buffer = malloc(1024);
|
|
... do stuff with buffer ...
|
|
free(buffer);
|
|
return 0;
|
|
}
|
|
|
|
The memory clean-up with the Linux process exits is a consequent of the teardown of
|
|
the process address environment when the process terminates. Each process contains
|
|
its own heap; when the process address environment is torndown, that process heap
|
|
is returned to the OS page allocator. So the memory clean-up basically comes for free.
|
|
|
|
NuttX Task Exit
|
|
---------------
|
|
|
|
But when you run a task in the monolithic, on-chip FLASH blob, you share the same
|
|
heap with all other tasks. There is no magic clean-up that can find and free your
|
|
tasks's allocations within the common heap (see "Ways to Free Memory on Task Exit").
|
|
|
|
NuttX Process Exit
|
|
------------------
|
|
|
|
NOTE that when you run processes on NuttX (with ``CONFIG_BUILD_KERNEL``), NuttX also
|
|
behaves the same way as Linux: The address environment is destroyed with the task
|
|
exits and all of the memory is reclaimed. But all other cases will leak memory.
|
|
|
|
Ways to Free Memory on Task Exit
|
|
--------------------------------
|
|
|
|
There are ways that you could associate allocated memory with a task so that it could
|
|
cleaned up when the task exits. That approach has been rejected, however, because (1)
|
|
it could not be done reliably, and (2) it would add a memory allocation overhead that
|
|
would not be acceptable in context where memory is constrained.
|
|
|
|
Related issue can be found on `Github <https://github.com/apache/nuttx/issues/3345>`_.
|