NewLib. STMicroelectronics STM32 F1
Reklama
Reklama
Chapter 15
NewLib
Throughout this book we’ve lived without the standard C libraries
(libc) even though an implementation is distributed with the Sourcery tools that we use for building projects. The primary reason we have avoided libc thus far is to maintain clarity about what code is actually executing – as we’ll see, introducing libc can quickly lead to obfuscation. A second reason is that libc is a memory hog – with only 8k SRAM available on the discovery board processor, there isn’t much room to spare. However, the first consideration is now moot – you’ve had a pretty complete introduction to writing code for the
STM32, and the second consideration is much less important if you move on to larger members of the STM32 family. There are many advantages to using libc including access to the standard library functions.
The implementation of libc distributed with the Sourcery tools and widely used for embedded systems is “newlib.” newlib includes the stdlib functions (e.g. abs, atoi, atoll, malloc, and unicode support), stdio (printf, sprintf, scanf, ...), string support, and many others. newlib also includes an implementation of the standard math libraries libm. Unfortunately, the stdio functions are particular memory hogs as they allocate rather large memory buffers to support I/O. Furthermore, by default (for example, the Sourcery distribution) newlib is not configured to minimize memory use. Thus, we will also show how to compile newlib from sources to optimize memory use.
The architecture of newlib requires an external implementation of key system functions including – open, close, read, write, and sbrk – the last of these is the building block upon which malloc is built. In order to use libc, it is necessary to provide at least stubs for these and other functions. We will show how to design these stubs to support standard I/O using the STM32
USART
Revision: 1396a85 (2013-01-07)
209
CHAPTER 15. NEWLIB
This chapter is organized as follows. We will begin with a simple example – “hello world” (surprise !) to illustrate the issues we must resolve in order to use newlib, and the memory issues that it raises. Recall that in
Chapter 3 we pointed out that “hello world” is actually a complex program,
although most of the complexity is “under the hood.” Finally, we will show how to compile libc in a manner that minimizes the memory requirements
(especially SRAM). If you’re going to use libc on an STM32 variant with substantial SRAM (at least 20k) this step is unnecessary – you may use the libraries distributed with the Sourcery tools.
15.1 Hello World
“Hello World” is the most famous programming example in the C uni-
verse. As we pointed out in Chapter 3, it’s actually pretty complex. Consider
the program:
# include <stdio .h> main () { printf (" hello world \n");
} printf is a library function which takes a format string and an optional list of parameters. It must stringify these parameters, merge them with the format string, and write the resulting string to stdout. What is stdout ?
It’s a buffered stream – the stdio libraries manage buffered streams. Data are buffered as they are written, and then the buffered data are written to a file or device. In this case, our intuition is that this stream should somehow be connected to a UART to print to a screen somewhere. While our use of printf is trivial, more complex invocations allocate memory in order to have space for performing any conversions.
If we attempt to compile “hello world” (create a project and try this !), we immediately learn that there are a number of undefined functions (List-
In a desktop environment, these all correspond to operating system calls
– libc is just a code library, ultimately it needs access to an operating system
API. In order to use newlib, we must provide the missing functionality in the form of procedures. Some of these we will replace with simple stubs, others
(read and write) with code that accesses a uart.
As we mentioned, libc allocates memory – a lot of it – from the “heap.” libc provides functions to manage the heap (malloc and free), but it de-
210
Revision: 1396a85 (2013-01-07)
15.1. HELLO WORLD undefined reference to `_sbrk ' undefined reference to `_write ' undefined reference to `_close ' undefined reference to `_fstat ' undefined reference to `_isatty ' undefined reference to `_lseek ' undefined reference to `_read '
Listing 15.1: Undefined Functions in newlib pends upon a system function _sbrk to allocate the memory that it manages.
Consider Figure 15.1 repeated from Chapter 2 which illustrates the use of
RAM by an executing program. The heap grows upward from the compiler allocated data towards the stack which grows downwards. Within the heap, memory is allocated by malloc and deallocated by free; however, whenever malloc has insufficient free space it asks for more memory via _sbrk, which has the effect of moving the heap end up (or down for a negative request).
In a desktop system, this is implemented by allocated more memory pages, indeed malloc generally asks for memory blocks that are integral numbers of pages (e.g. 4096 bytes), which is a problem in a memory constrained system such as the discovery board.
RAM End (high)
Main Stack
SP
Heap End
Heap Start
Data
RAM Start (low)
Figure 15.1: Program Memory Model
In order to implement _sbrk we need to know where the heap starts
(i.e. the end of compiler/linker allocated memory) and what its physical limit is (i.e the maximum value for heap end). In the linker script, we define two values _end – the end of the data segment – and _stackend which is the limit
Revision: 1396a85 (2013-01-07)
211
CHAPTER 15. NEWLIB of the space reserved for the stack.
Before providing an implementation of _sbrk, it is enlightening to read the Linux man page for sbrk (which calls the system function _sbrk).
void *sbrk(intptr_t increment);
...
brk() and sbrk() change the location of the program break, which defines the end of the process’s data segment (i.e., the program break is the first location after the end of the uninitialized data segment). Increasing the program break has the effect of allocating memory to the process; decreasing the break deallocates memory.
A few other key points are in order. _sbrk(0) returns the current
“program break.” If the call cannot be satisfied, _sbrk returns -1 and sets the current errno to an appropriate error code. In newlib, errno is part of a structure used to make the library reentrant – this “reent” structure is another large source of memory usage which must be replicated in a multi-threaded
environment. An implementation of _sbrk is shown in Listing 15.2
The remaining stubs are provided in Listing 15.3. The read and write
operations utilize the putc and getc interface described in Chapter 5; in both
cases we restrict our transfers to a single character. Notice that most of the remaining stubs simply return an error code; an exception is _isatty which returns 1 since we’re using the uart as a terminal. _fstat which provides meta data for open files always sets the file mode to S_IFCHR which denotes a character oriented device.
There is one final detail required to use newlib. The startup code must call _libc_init_array(void) before main. This ensures that any required data structures are correctly initialized. In our startup code, we provide a
“weak” default implementation for the case where we compile without libc.
void __attribute__ (( weak)) __libc_init_array (void){} void Reset_Handler (void) {
...
__libc_init_array ();
} main ();
...
212
Revision: 1396a85 (2013-01-07)
15.1. HELLO WORLD
# include <errno .h>
// defined in linker script
extern caddr_t _end , _stackend ; caddr_t _sbrk (int nbytes ){ static caddr_t heap_ptr = NULL; caddr_t base; if ( heap_ptr == NULL) { heap_ptr = ( caddr_t )&_end;
}
} if (( caddr_t ) & _stackend > heap_ptr + nbytes ) { base = heap_ptr ; heap_ptr += nbytes ; return (base);
} else { errno = ENOMEM ; return (( caddr_t ) -1);
}
Listing 15.2: _sbrk Implementation
Exercise 15.1
Hello World
Complete and test the hello world example. You should put all the stub code in a single file – syscalls.c and compile this with your code.
1
Calculating stack sizes can be challenging – especially in the presence of library code.
It’s best to relatively conservative !
Revision: 1396a85 (2013-01-07)
213
CHAPTER 15. NEWLIB int _close (int file) { errno = ENOTSUP ; return -1;
} int _fstat (int file , struct stat *st) { st -> st_mode = S_IFCHR ; // character device return 0;
} int _isatty (int file) { return 1;
} int _link(char *old , char *new) { errno = EMLINK ; return -1;
} int _lseek (int file , int ptr , int dir) { errno = ENOTSUP ; return -1;
} int _open( const char *name , int flags , int mode) { errno = ENOTSUP ; return -1;
} int _read(int file , char *ptr , int len) { if (len){
*ptr = (char) uart_getc ( USART1 ); return 1;
} return 0;
} int _unlink (char *name) { errno = ENOENT ; return -1;
} int _write (int file , char *ptr , int len) { if (len) { uart_putc (*ptr , USART1 ); return 1;
} return 0;
}
214
Listing 15.3: Basic Syscall stubs
Revision: 1396a85 (2013-01-07)
15.2. BUILDING NEWLIB
15.2 Building newlib
The distribution of newlib with the Sourcery tool was not compiled for minimum memory usage. You can build your own version by downloading the newlib sources and using the following build process: mkdir newlib -build cd newlib - build export CFLAGS_FOR_TARGET =''-g -O2 -DSMALL_MEMORY ''
/ path_to_newlib_source / configure --target =arm -none -eabi
,
→--prefix=/target -directory --disable -newlib -supplied -syscalls
,
→--disable -libgloss --disable -nls
Revision: 1396a85 (2013-01-07)
215
Stáhnout
Reklama