How to perform initialization of static storage variables in embedded systems?
Whenever declaring a variable in C outside a function at file scope or when specifying it as
static, it gets assigned a life time known as static storage duration. Meaning it will be accessible throughout the whole execution of the program.
Such variables are typically allocated in one of two RAM segments most often named
.datais used for all static storage duration variables that were explicitly initialized by the programmer to a value other than zero.
.bssis used for all static storage duration variables that are initialized to zero.
Also as per C language rules, any static storage duration variable which is not initialized to a value explicitly, gets implicitly initialized to zero and therefore ends up in
.bss. In case of pointers, they are initialized as null pointers.
On a PC/hosted system, everything including the executable code is simply loaded into RAM at once by the OS. But in case of microcontrollers we have no such thing and often prefer to execute code out of flash/non-volatile memory.
When starting up a new software project in any microcontroller IDE, I am often given an option "minimal/fast start-up" versus "ANSI/ISO/standard start-up". This seems to refer to if these initialization rules should be used or not.
How are these variables initialized in case I pick "minimal" start-up? What are the advantages and disadvantages of this feature?
Things happening/things we want to happen before main() is called:
The code executed before
main() is called is often referred to as "the C runtime (CRT)" or just "start-up code". This code is typically provided by the microcontroller-specific libraries and among other things, it initializes
.bss - since the C standard requires that static storage duration variables are initialized at some point before
main() is called. This is carried out by copying a chunk of initializer values from flash into
.data, sometimes referred to as "copy-down", or in case of
.bss, the CRT simply sets the whole region to zero.
This initialization potentially takes a lot of time from the point where the microcontroller is started until
main() is called. Be aware that some dysfunctionally written CRTs do not set-up funamental things like watchdog and clock settings before running the initialization code, but relies on the application programmer doing that from
main(). This is the wrong way to do things - it is both dangerous and also potentially needlessly slow. Suppose that a CPU/software error happens during start-up - then we want the watchdog to be enabled. Or suppose the MCU is running on a slow internal oscillator out of reset, but we would like to use an external one or set up some PLL/FLL, in order to speed up the system clock. If so, we would naturally like to do that way before running the whole
Either way, there might be lots of other reasons as well why we don't want a lot of slow start-up code executing out of reset:
- The application might need to perform something quickly when it starts, or
- In case we encountered critical errors during previous execution that merited a MCU reboot, we might want to get back up running as soon as possible, or
- In case we are writing safety-related software, RAM could be considered too unreliable to hold default values for long time periods, unless continuously refreshed from non-volatile memory. There could be any amount of time passing from the point where the variable was initialized until it was first used.
The minimal start-up option provides a way to skip all initialization and thereby create a significantly shorter start-up time. There are disadvantages of using the minimal start-up, however. First of all, our static storage duration variables end up not initialized. At all. Not to a specific value, not to zero. We have sacrificed C language standard compliance in favour of execution speed.
So if we have a file scope variable declared as
int x=5;, we can't actually know what value it has - it is not 5 but indeterminate. If we aren't aware of this, minimal start-up is obviously very dangerous.
Whenever a minimal/fast start-up option is used, variables with static storage duration must be initialized in run-time!
"Run-time" as in not by the CRT, but by the application program. This means that in the above example we should just have declared
int x; and then later inside some
init() function assign
x=5;. Every single variable in the program must be initialized in run-time before use.
Now as it happens, every module in our program is likely to have some initialization function anyway. For example any driver for a hardware peripheral will likely need to initialize a bunch of hardware registers before it does anything else. And then it might as well initialize all variables used by that driver at the same time. This way we move the initialization execution time away from start-up and into the specific code module where the variable is used.
This is the correct way of initializing such variables anyway, because of the previously mentioned "RAM is considered unreliable" reason. Sure, modern microcontroller RAM is way more reliable than what it used to be some decades ago. But like with any electronic component, it is also subject to EMI. And in case of RAM specifically, it is also sensitive to cosmic rays. Plain software bugs is a far more likely reason for RAM corruption however, so not relying on default initialization values is also a method of defensive programming.
(Similarly, executing code out of RAM is considered bad practice on high reliability systems, at least not without error detection mechanisms like ECC and/or CRC checksums.)
It is considered good practice in any embedded system to never rely on default or explicit initialization of static storage duration variables.
Doing so enables the option for using a minimal start-up, including portability to compilers that may use such a start-up. But it also makes the variables somewhat less vulnerable to problems like EMI, cosmic rays or plain bugs. It is perfectly possible to write fully standard compliant C code still - the minimal start-up makes the CRT non-compliant, not our own source code.
Also, relying on implicit initialization of any variable is considered bad practice, for example
static int x;, simply because it is confusing and sloppy. Did the programmer intend to zero-initialize this variable or not? Writing
static int x = 0; is self-documenting. Now of course the programmer could also have been an embedded systems veteran who adhere to the best practices default initialization mentioned above and therefore left out the initializer for that purpose.
0 comment threads