Documentation: migrate "Linux Processes vs NuttX Tasks" from wiki
link: https://cwiki.apache.org/confluence/display/NUTTX/Linux+Processes+vs+NuttX+Tasks
This commit is contained in:
parent
03b4ec60a5
commit
3b23a693ac
9
Documentation/implementation/index.rst
Normal file
9
Documentation/implementation/index.rst
Normal file
@ -0,0 +1,9 @@
|
||||
======================
|
||||
Implementation Details
|
||||
======================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
processes_vs_tasks.rst
|
263
Documentation/implementation/processes_vs_tasks.rst
Normal file
263
Documentation/implementation/processes_vs_tasks.rst
Normal file
@ -0,0 +1,263 @@
|
||||
==============================
|
||||
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 kernal 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>`_.
|
@ -33,6 +33,7 @@ Last Updated: |today|
|
||||
platforms/index.rst
|
||||
components/index.rst
|
||||
applications/index.rst
|
||||
implementation/index.rst
|
||||
reference/index.rst
|
||||
faq/index.rst
|
||||
guides/index.rst
|
||||
|
Loading…
Reference in New Issue
Block a user