Reentrancy by Jack G
Total Page:16
File Type:pdf, Size:1020Kb
BEGINNER’S CORNER Reentrancy by Jack G. Ganssle Virtually every embedded sys- 2. It does not call non-reentrant then may also try and change foobar. tem uses interrupts; many support mul- functions. Clearly there’s a conflict; foobar will titasking or multithreaded operations. 3. It does not use the hardware in a wind up with an incorrect value, the These sorts of applications can expect non-atomic way. autopilot will crash, and hundreds of the program’s control flow to change screaming people will wonder “why contexts at just about any time. When Quite a mouthful! Let’s look at each of didn’t they teach those developers that interrupt comes, the current oper- these in more detail. about reentrancy?” ation gets put on hold and another Suppose, instead, Function A looks function or task starts running. What Atomic variables like: happens if functions and tasks share variables? Disaster surely looms if one Both the first and last rules use the foobar += 1; routine corrupts another’s data. word atomic, which comes from the By carefully controlling how data Greek word meaning indivisible. In Now the operation is atomic, right? is shared, we create reentrant func- the computer world, atomic means an An interrupt cannot suspend process- tions, those that allow multiple con- operation that cannot be interrupted. ing with foobar in a partially changed ✁ current invocations that do not inter- Consider the assembly language state, so the routine is reentrant. fere with each other. The word pure is instruction: Except… do you really know what sometimes used interchangeably with your C compiler generates? On an x86 CUT HERE reentrant. mov ax,bx processor that statement might com- ✁ Like so many embedded concepts, pile to: reentrancy came from the mainframe Since nothing short of a reset can era, in the days when memory was a stop or interrupt this instruction, it’s mov ax,[foobar] valuable commodity. In those days atomic. It will start and complete with- inc ax compilers and other programs were out any interference from other tasks mov [foobar],ax often written to be reentrant, so a sin- or interrupts gle copy of the tool lived in memory, The first part of Rule 1 requires the which is clearly not atomic, and so not yet was shared by perhaps a hundred atomic use of shared variables. reentrant. The atomic version is: users. Each person had his or her own Suppose two functions each share the data area, yet everyone running the global variable foobar. Function A inc [foobar] compiler quite literally executed the contains: identical code. As the operating sys- The moral is to be wary of the com- tem changed contexts from user to temp = foobar; piler. Assume it generates atomic code user it swapped data areas so one per- temp += 1; and you may find “60 Minutes” knock- son’s work didn’t effect any other. foobar = temp; ing at your door. Share the code, but not the data. The second part of the first reen- In the embedded world a routine This code is not reentrant, because trancy rule reads “…unless each is must satisfy the following conditions foobar is used non-atomically. That is, allocated to a specific instance of the to be reentrant: it takes three statements to change its function.” This is an exception to the value, not one. The foobar handling is atomic rule that skirts the issue of 1. It uses all shared variables in an not indivisible; an interrupt can come shared variables. atomic way, unless each is allocated between these statements and switch An “instance” is a path through the to a specific instance of the function. context to the other function, which code. There’s no reason a single func- Embedded Systems Programming APRIL 2001 183 BEGINNER’S CORNER tion can’t be called from many other functions are reentrant? Obviously, context switches. Disable interrupts, places. In a multitasking environment, string operations and a lot of other do the non-reentrant work, and then it’s quite possible that several copies of complicated things make library calls turn interrupts back on. the function may indeed be executing to do the real work. An awful lot of Shutting interrupts down does concurrently. (Suppose the routine is compilers also generate runtime calls increase system latency, reducing its a driver that retrieves data from a to do, for instance, long math, or even ability to respond to external events in queue; many different parts of the integer multiplications and divisions. a timely manner. A kinder, gentler code may want queued data more or If a function must be reentrant, approach is to use a mutex (also less simultaneously). Each execution talk to the compiler vendor to ensure known as binary semaphore) to indi- path is an “instance” of the code. that the entire runtime package is cate when a resource is busy. Mutexes Consider: pure. If you buy software packages are simple on-off state indicators (like a protocol stack) that may be whose processing is inherently atomic. int foo; called from several places, take similar These are often used as “in-use” flags void some_function(void) { precautions to ensure the purchased to have tasks idle when a shared foo++; routines are also reentrant. resource is not available. } Rule 3 is a uniquely embedded Nearly every commercial real-time caveat. Hardware looks a lot like a vari- operating system includes mutexes. If foo is a global variable whose scope able; if it takes more than a single I/O this is your way of achieving reentrant exists outside that of the function. operation to handle a device, reen- code, by all means use an RTOS. Even if no other routine uses foo, trancy problems can develop. some_function can trash the variable if Consider Zilog’s SCC serial con- Recursion more than one instance of it runs at troller. Accessing any of the device’s ✁ any time. internal registers requires two steps: No discussion of reentrancy is com- CUT HERE C and C++ can save us from this first write the register’s address to a plete without mentioning recursion, if peril. Use automatic variables. That is, port, then read or write the register only because there’s so much confu- declare foo inside of the function. from the same port, the same I/O sion between the two. ✁ Then, each instance of the routine will address. If an interrupt fires between A function is recursive if it calls use a new version of foo created from setting the port and accessing the reg- itself. That’s a classic way to remove the stack, as follows: ister, another function might take over iteration from many sorts of algo- and access the device. When control rithms. Given enough stack space, this void some_function(void) { returns to the first function, the regis- is a perfectly valid—though tough to int foo; ter address you set will be incorrect. debug—way to write code. Since a foo++; recursive function calls itself, clearly it } Keeping code reentrant must be reentrant to avoid trashing its variables. So all recursive functions Another option is to dynamically What are our best options for elimi- must be reentrant, but not all reen- assign memory (using malloc), again nating non-reentrant code? The first trant functions are recursive. esp so each incarnation uses a unique data rule of thumb is to avoid shared vari- area. The fundamental reentrancy ables. Globals are the source of end- Jack G. Ganssle is a lecturer and consul- problem is thus avoided, as it’s impos- less debugging woes and failed code. tant on embedded development issues, and sible for multiple instances to modify a Use automatic variables or dynamical- a regular contributor to Embedded common version of the variable. ly allocated memory. Systems Programming. Contact him at Yet globals are also the fastest way [email protected]. Two more rules to pass data around. It’s not always possible to entirely eliminate them Resources The other rules are very simple. Rule 2 from real time systems. So, when using Greenberg, Kenneth F. “Sharing Your tells us a calling function inherits the a shared resource (variable or hard- Code,” Embedded Systems reentrancy problems of the callee. ware) we must take a different sort of Programming, August 1990, p 40. That makes sense. If other code inside action. Barr, Michael. “Choosing a Compiler: The the function trashes shared variables, The most common approach is to Little Things,” Embedded Systems the system is going to crash. Using a disable interrupts during non-reen- Programming, May 1999, p. 71. compiled language, though, there’s an trant code. With interrupts off, the sys- Simon, David. An Embedded Software insidious problem. Are you sure—real- tem suddenly becomes a single- Primer. Reading, MA: Addison-Wesley, ly sure—that all of the runtime library process environment. There will be no 1999. 184 APRIL 2001 Embedded Systems Programming.