diff --git a/Documentation/implementation/index.rst b/Documentation/implementation/index.rst new file mode 100644 index 0000000000..42f6054e22 --- /dev/null +++ b/Documentation/implementation/index.rst @@ -0,0 +1,9 @@ +====================== +Implementation Details +====================== + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + processes_vs_tasks.rst diff --git a/Documentation/implementation/processes_vs_tasks.rst b/Documentation/implementation/processes_vs_tasks.rst new file mode 100644 index 0000000000..16626a5834 --- /dev/null +++ b/Documentation/implementation/processes_vs_tasks.rst @@ -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 `_. diff --git a/Documentation/index.rst b/Documentation/index.rst index 3ef823b56b..8d756ffde7 100644 --- a/Documentation/index.rst +++ b/Documentation/index.rst @@ -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