mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2025-12-14 09:03:50 +01:00
doc/guides: migrate dev best practices
Co-authored-by: crasbe <crasbe@gmail.com>
This commit is contained in:
parent
749896fb8e
commit
10488f73b9
@ -1,118 +1,4 @@
|
||||
# Hints for quicker & better RIOT development {#dev-best-practices}
|
||||
# Hints for quicker & better RIOT development (Deprecated) {#dev-best-practices}
|
||||
|
||||
[TOC]
|
||||
|
||||
* Use the [methodology](#methodology) described below.
|
||||
* Use [`ccache`](https://guide.riot-os.org/build-system/advanced_build_system_tricks/#speed-up-builds-with-ccache) to speedup compilation
|
||||
|
||||
## Coding "Dos" and "Don'ts": {#coding-dos-and-donts}
|
||||
|
||||
### Dos
|
||||
* Use static memory. See also [Static vs. Dynamic Memory](#static-vs-dynamic).
|
||||
* Select the priorities carefully.
|
||||
* Minimize stack usage with `DEVELHELP` and `CREATE_STACKTEST`.
|
||||
* Use threads to increase flexibility, modularity, and robustness by leveraging IPC.
|
||||
* Use unsigned or signed integer (`unsigned`, `int`, `size_t` or `ssize_t`) for loop variables wherever possible, but keep in mind that on some platforms an `int` has a width of only 16-bit. In general, you should avoid types like `uint8_t` for loop iterators as they will probably make it more expensive on some platforms.
|
||||
* Join and factor out parts of the code with existing code in RIOT, where it makes sense.
|
||||
* Check all `size/length` parameters when passing memory, e.g. using `sizeof(x)` or `strlen(x)` as appropriate. Make sure you don't use the wrong one with a pointer.
|
||||
* Make sure all code paths can be reached. Make sure there are no always `true/false` conditions.
|
||||
* Make sure all critical sections (`lock/unlock`, `acquire/release`, ...) are always closed on every code path.
|
||||
* Make sure return values are consistent with our API documentation.
|
||||
* Use `assert()` statements to check parameters rather than returning an error code at run-time, to keep the code size down.
|
||||
* Use the `DEBUG(...)` macro rather than `log_x(...)`
|
||||
* Declare all internal module variables and functions `static`
|
||||
* Make sure variables are reduced in scope as much as possible
|
||||
* Use an appropriate signedness in your variables
|
||||
* Make sure the variables are big enough to prevent overflow. Be aware that the code may run on platforms with different sizes of variables. For example, `int/unsigned` is only 16-bit on msp430 and avr8. If in doubt, use portable types.
|
||||
* Reduce the number of function calls as far as possible without duplicating code.
|
||||
* Use good judgement when using `static inline` functions and macros. If they are used in multiple places, is the increase in performance worth the penalty in code size?
|
||||
* Use memory judiciously in general. For example:
|
||||
```c
|
||||
typedef enum {
|
||||
A,
|
||||
B,
|
||||
...
|
||||
} foo_t;
|
||||
|
||||
int bar(foo_t v)
|
||||
{
|
||||
int abc;
|
||||
...
|
||||
|
||||
switch(v) {
|
||||
case A:
|
||||
abc = 23;
|
||||
break;
|
||||
case B:
|
||||
abc = 42;
|
||||
break;
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
/* VS */
|
||||
|
||||
typedef enum {
|
||||
A = 23,
|
||||
B = 42,
|
||||
...
|
||||
} foo_t;
|
||||
|
||||
int bar(foo_t v) {
|
||||
int abc = v;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Don'ts
|
||||
* Don't use too many threads. Try not to use more than one thread per module. Don't create threads for one-time tasks.
|
||||
* Don't use the POSIX wrapper if implementing something from scratch.
|
||||
* Don't allocate big chunks of memory (for instance the IPC message queue) on the stack, but use rather static memory for that.
|
||||
* Don't over-provision memory.
|
||||
* Don't pass stack memory between different contexts unless you can prove conclusively that it won't be a problem.
|
||||
* Don't use enums for flags, because flags have a width in memory that is in most cases smaller than `sizeof(enum)` (most bitfields are 16 bits max, on most of our newer platforms, `sizeof(enum)` is however 32 bits). This results in every assignment needed to be cast to either `uint8_t` or `uint16_t`. With macros you don't need to cast since they are typeless. Making the enum packed makes its width unpredictable in terms of alignment issues, when used in struct.
|
||||
* Don't duplicate code from elsewhere in the RIOT code base, unless there is a very good reason to do so.
|
||||
* Don't duplicate code within your own code, unless there is a very good reason to do so. Use internal functions to this end.
|
||||
* Don't mix up logical and bitwise operations (`!` vs `~`, or `&&` vs `&`)
|
||||
|
||||
## Methodology: emulator first, target IoT hardware last! {#methodology}
|
||||
|
||||
The below methodology is recommended, using well-known de facto standard tools from the FLOSS community that are compatible with RIOT. Using the below workflow improves time-to-running-code compared to typical IoT software workflows (which can be as retro as "LED-driven" debugging).
|
||||
|
||||
0. For newbies, preliminaries are typically faster with the provisioned virtual environment setup, e.g. with **Vagrant**.
|
||||
1. To check your code, first use available **static analysis** as much as possible initially, which means (i) enable all compiler warnings and fix all problems found, then (ii) use a supported linter such as **cppcheck** to find bad coding patterns (i.e. code smells) and identify misuse of standard APIs.
|
||||
2. Next, use available **dynamic analysis** tools to find further defects while running the code on **RIOT native**, which means (i) running unit tests and integration tests on RIOT native emulator, and (ii) using **Valgrind** memcheck, as well as the **GCC stack smashing detection**, to detect and avoid undefined behavior due to invalid memory access.
|
||||
3. In case of networked applications or protocols, test **several instances of native** communicating via a virtual network mimicking the targeted scenario, which means (i) either using the default virtual full-mesh or other topologies configured via DESvirt, and (ii) using **Wireshark** to capture and analyze virtual network traffic, e.g. to ensure protocol packets are syntactically correct, and to observe network communication patterns.
|
||||
4. In case of incorrect behavior at this stage, analyze the system state for semantic errors on native using the standard debugger **gdb**, which allows virtually unlimited conditional breakpoints, record and replay, catchpoints, tracepoints and watchpoints.
|
||||
5. In case of suspected performance bottleneck, use performance profilers **gprof**, or else cachegrind, to identify precisely the bottlenecks.
|
||||
6. At this stage the implementation has proven bug-free on the native emulator. One can thus finally move on to hardware-in-the-loop, which means (i) flashing the binary on the targeted IoT hardware, typically using standard flasher **OpenOCD** or **edbg**, and (ii) using the **RIOT shell** running on the target IoT device(s) for easier debugging on the target hardware.
|
||||
7. In case the hardware is not available on-site, one can consider remotely flashing and testing the binary on supported open-access testbeds, e.g. [IoT-LAB](https://www.iot-lab.info) hardware is fully supported by RIOT.
|
||||
8. In case of failure, after analyzing the failure and attempting to fix the defect, go back to step 1 to make sure the fix did not itself introduce a new defect.
|
||||
|
||||
## Static vs. Dynamic Memory {#static-vs-dynamic}
|
||||
|
||||
In your C program you have to decide where the memory you want to use comes from.
|
||||
There are two ways to get memory in your C code:
|
||||
|
||||
1. Define static memory.
|
||||
2. Use dynamic memory (call `malloc()`/`free()` to get memory from the heap).
|
||||
|
||||
Both ways have some drawbacks which are listed here.
|
||||
If you want to analyze the static memory consumption of your code you can use [otm](https://github.com/LudwigOrtmann/otm) or `make cosy`.
|
||||
|
||||
### Static memory
|
||||
* Access the memory in one operation O(1) ⇒ real time condition
|
||||
* Programmer needs to know the amount of memory on compile time
|
||||
* Leads to over and undersized buffers
|
||||
* Forces the programmer to think about the amount of need memory at compile time
|
||||
|
||||
### Dynamic memory
|
||||
* `malloc()` and `free()` are implemented in your `libc` (RIOT on ARM: `newlib`/`picolib`)
|
||||
* Runtime behavior is not predictable
|
||||
* Code can request the amount of memory it needs on runtime
|
||||
* On most platforms: the size of the heap is `sizeof(<RAM>)-sizeof(<static memory>)`
|
||||
* If you reduce your usage of static memory your heap gets bigger
|
||||
* On some platforms calling `free()` will not or not always make heap memory available again (see @ref oneway_malloc on MSP430)
|
||||
* Programmer needs to handle failed memory allocation calls at runtime
|
||||
* Static code analysis is unable to find errors regarding memory management
|
||||
@deprecated Guides have moved to the [Guide Site](https://guide.riot-os.org/misc/dev_best_practices/).
|
||||
This page will be removed after release 2026.04.
|
||||
|
||||
151
doc/guides/misc/dev_best_practices.md
Normal file
151
doc/guides/misc/dev_best_practices.md
Normal file
@ -0,0 +1,151 @@
|
||||
---
|
||||
title: Hints for quicker & better RIOT development
|
||||
description: Best practices and recommended tools for RIOT development
|
||||
---
|
||||
|
||||
* Use the [methodology](#methodology-emulator-first-target-iot-hardware-last) described below.
|
||||
* Use [`ccache`](/build-system/advanced_build_system_tricks/#speed-up-builds-with-ccache) to speedup compilation
|
||||
|
||||
## Coding "Dos" and "Don'ts"
|
||||
|
||||
### Dos
|
||||
* Use static memory. See also [Static vs. Dynamic Memory](#static-vs-dynamic-memory).
|
||||
* Select the priorities carefully.
|
||||
* Minimize stack usage with `DEVELHELP` and `CREATE_STACKTEST`.
|
||||
* Use threads to increase flexibility, modularity and robustness by leveraging IPC.
|
||||
* Use unsigned or signed integer (`unsigned`, `int`, `size_t` or `ssize_t`) for loop variables wherever possible,
|
||||
but keep in mind that on some platforms an `int` has a width of only 16-bit. In general, you should avoid types
|
||||
like `uint8_t` for loop iterators as they will probably make it more expensive on some platforms.
|
||||
* Join and factor out parts of the code with existing code in RIOT, where it makes sense.
|
||||
* Check all `size/length` parameters when passing memory, e.g. using `sizeof(x)` or `strlen(x)` as appropriate.
|
||||
Make sure you don't use the wrong one with a pointer.
|
||||
* Make sure all code paths can be reached. Make sure there are no always `true/false` conditions.
|
||||
* Make sure all critical sections (`lock/unlock`, `acquire/release`, ...) are always closed on every code path.
|
||||
* Make sure return values are consistent with our API documentation.
|
||||
* Use `assert()` statements to check parameters rather than returning an error code at run-time,
|
||||
to keep the code size down.
|
||||
* Use the `DEBUG(...)` macro rather than `log_x(...)`
|
||||
* Declare all internal module variables and functions `static`
|
||||
* Make sure variables are reduced in scope as much as possible
|
||||
* Use an appropriate signedness in your variables
|
||||
* Make sure the variables are big enough to prevent overflow. Be aware that the code may run on platforms with
|
||||
different sizes of variables. For example, `int/unsigned` is only 16-bit on msp430 and avr8. If in doubt,
|
||||
use portable types.
|
||||
* Reduce the number of function calls as far as possible without duplicating code.
|
||||
* Use good judgement when using `static inline` functions and macros. If they are used in multiple places,
|
||||
is the increase in performance worth the penalty in code size?
|
||||
* Use memory judiciously in general. For example:
|
||||
```c
|
||||
typedef enum {
|
||||
A,
|
||||
B,
|
||||
...
|
||||
} foo_t;
|
||||
|
||||
int bar(foo_t v)
|
||||
{
|
||||
int abc;
|
||||
...
|
||||
|
||||
switch(v) {
|
||||
case A:
|
||||
abc = 23;
|
||||
break;
|
||||
case B:
|
||||
abc = 42;
|
||||
break;
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
/* VS */
|
||||
|
||||
typedef enum {
|
||||
A = 23,
|
||||
B = 42,
|
||||
...
|
||||
} foo_t;
|
||||
|
||||
int bar(foo_t v) {
|
||||
int abc = v;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Don'ts
|
||||
* Don't use too many threads. Try not to use more than one thread per module. Don't create threads for one-time tasks.
|
||||
* Don't use the POSIX wrapper if implementing something from scratch.
|
||||
* Don't allocate big chunks of memory (for instance the IPC message queue) on the stack,
|
||||
but use rather static memory for that.
|
||||
* Don't over-provision memory.
|
||||
* Don't pass stack memory between different contexts unless you can prove conclusively that it won't be a problem.
|
||||
* Don't use enums for flags, because flags have a width in memory that is in most cases smaller than `sizeof(enum)`
|
||||
(most bitfields are 16 bits max, on most of our newer platforms, `sizeof(enum)` is however 32 bits).
|
||||
This results in every assignment needed to be cast to either `uint8_t` or `uint16_t`. With macros you don't need to
|
||||
cast since they are typeless. Making the enum packed makes its width unpredictable in terms of alignment issues,
|
||||
when used in struct.
|
||||
* Don't duplicate code from elsewhere in the RIOT code base, unless there is a very good reason to do so.
|
||||
* Don't duplicate code within your own code, unless there is a very good reason to do so.
|
||||
Use internal functions to this end.
|
||||
* Don't mix up logical and bitwise operations (`!` vs `~`, or `&&` vs `&`)
|
||||
|
||||
## Methodology: emulator first, target IoT hardware last!
|
||||
|
||||
The below methodology is recommended, using well-known de facto standard tools from the FLOSS community that are
|
||||
compatible with RIOT. Using the below workflow improves time-to-running-code compared to typical IoT software
|
||||
workflows (which can be as retro as "LED-driven" debugging).
|
||||
|
||||
0. For newbies, preliminaries are typically faster with the provisioned virtual environment setup, e.g. with **Vagrant**.
|
||||
1. To check your code, first use available **static analysis** as much as possible initially, which means
|
||||
(i) enable all compiler warnings and fix all problems found, then (ii) use a supported linter such as **cppcheck**
|
||||
to find bad coding patterns (i.e. code smells) and identify misuse of standard APIs.
|
||||
2. Next, use available **dynamic analysis** tools to find further defects while running the code on **RIOT native**,
|
||||
which means (i) running unit tests and integration tests on RIOT native emulator, and (ii) using **Valgrind** memcheck,
|
||||
as well as the **GCC stack smashing detection**, to detect and avoid undefined behavior due to invalid memory access.
|
||||
3. In case of networked applications or protocols, test **several instances of native** communicating via a virtual
|
||||
network mimicking the targeted scenario, which means (i) either using the default virtual full-mesh or other
|
||||
topologies configured via DESvirt, and (ii) using **Wireshark** to capture and analyze virtual network traffic,
|
||||
e.g. to ensure protocol packets are syntactically correct, and to observe network communication patterns.
|
||||
4. In case of incorrect behavior at this stage, analyze the system state for semantic errors on native using the
|
||||
standard debugger **gdb**, which allows virtually unlimited conditional breakpoints, record and replay,
|
||||
catchpoints, tracepoints and watchpoints.
|
||||
5. In case of suspected performance bottleneck, use performance profilers **gprof**, or else cachegrind,
|
||||
to identify precisely the bottlenecks.
|
||||
6. At this stage the implementation has proven bug-free on the native emulator. One can thus finally move on to
|
||||
hardware-in-the-loop, which means (i) flashing the binary on the targeted IoT hardware, typically using
|
||||
standard flasher **OpenOCD** or **edbg**, and (ii) using the **RIOT shell** running on the target IoT device(s)
|
||||
for easier debugging on the target hardware.
|
||||
7. In case the hardware is not available on-site, one can consider remotely flashing and testing the binary on
|
||||
supported open-access testbeds, e.g. [IoT-LAB](https://www.iot-lab.info) hardware is fully supported by RIOT.
|
||||
8. In case of failure, after analyzing the failure and attempting to fix the defect, go back to step 1 to make sure
|
||||
the fix did not itself introduce a new defect.
|
||||
|
||||
## Static vs. Dynamic Memory
|
||||
|
||||
In your C program you have to decide where the memory you want to use comes from.
|
||||
There are two ways to get memory in your C code:
|
||||
|
||||
1. Define static memory.
|
||||
2. Use dynamic memory (call `malloc()`/`free()` to get memory from the heap).
|
||||
|
||||
Both ways have some drawbacks which are listed here.
|
||||
If you want to analyze the static memory consumption of your code you can use
|
||||
[otm](https://github.com/LudwigOrtmann/otm) or `make cosy`.
|
||||
|
||||
### Static memory
|
||||
* Access the memory in one operation O(1) ⇒ real time condition
|
||||
* Programmer needs to know the amount of memory on compile time
|
||||
* Leads to over and undersized buffers
|
||||
* Forces the programmer to think about the amount of need memory at compile time
|
||||
|
||||
### Dynamic memory
|
||||
* `malloc()` and `free()` are implemented in your `libc` (RIOT on ARM: `newlib`/`picolib`)
|
||||
* Runtime behavior is not predictable
|
||||
* Code can request the amount of memory it needs on runtime
|
||||
* On most platforms: the size of the heap is `sizeof(<RAM>)-sizeof(<static memory>)`
|
||||
* If you reduce your usage of static memory your heap gets bigger
|
||||
* On some platforms calling `free()` will not or not always make heap memory available again
|
||||
(see [Oneway Malloc on MSP430](https://doc.riot-os.org/group__oneway__malloc.html))
|
||||
* Programmer needs to handle failed memory allocation calls at runtime
|
||||
* Static code analysis is unable to find errors regarding memory management
|
||||
Loading…
x
Reference in New Issue
Block a user