jcwren wrote on Wednesday, July 25, 2007:
Darrick,
I’m using GCC 4.1.2, newlib 1.15 (modified syscalls.c), FreeRTOS 4.3.1, FatFS 0.04b, and lpcusb 20060903 (modified).
The stacks and such are pretty straight forward, although at first glance they do seem hard to wrap your head around. Basically, there are 6 stacks, one for each mode. The processor uses the stack that corresponds to the mode. When an IRQ occurs, the IRQ stack is used. When a FIQ occurs, the FIQ stack is used, etc. System/user mode share a stack.
FreeRTOS runs the scheduler in supervisor mode (as noted in the a00111.html docs), while tasks run in system mode. In a normal newlib environment (not running a scheduler), the stacks are (in order, from highest RAM address going lower) UNDEF (undefined instruction execution), ABORT (basically non-word aligned access to memory), FIQ (fast interrupt), IRQ (vectored and non-vectored interrupt), SVC (supervisor), then system/user. FreeRTOS requires that that scheduler be started while in supervisor mode, and runs tasks in system mode. However, when a task is created with xTaskCreate, one of the parameters is the size of the stack for that task.
On of the configuration variables in FreeRTOSConfig.h is configTOTAL_HEAP_SIZE, which dictates how much total space will be reserved for tasks, queues, semaphores, etc. When the task is created, it allocates a task control block (TCB) from this total space, and also the amount of stack required. When the task is running, it’s stack pointer is actually pointing to the stack space allocated by the xTaskCreate call. When the scheduler itself is running, it uses a little bit of the supervisor stack (safe to say less than 64 bytes, based on prefilling memory and watching what gets used), and a little bit of the IRQ stack (when Timer 0 fires into the interrupt handler). Once the scheduler is running, your application can use any memory from the end of .bss up to the initial supervisor SP, minus 64 bytes.
This is where _sbrk() comes in (probably telling you something you already know here). The heap, as GCC/newlib knows it, starts at the end of .bss, and grows up towards the top of memory. In a non-FreeRTOS system, sbrk() checks to see if the request to increase the heap size (as a result of a malloc/calloc/etc) will be larger than the current stack pointer (the system/user SP grows towards heap space). If the current end of heap plus the requested space is greater than the current SP value, ENOMEM is returned.
Of course, this fails miserably when trying to use this scheme under FreeRTOS, because the process that’s requesting memory has it’s SP below the start of the heap, so it looks like a collision occurred, and ENOMEM is returned (not all functions check the return of malloc, since we all know that real computers have gigs of memory, and we only know that something went wrong when it locks up hard). Your ABORT condition stands a good chance of a pointer getting overwritten by stack, or your stack overwritten by data, and something goes to fetch some memory, and it’s pointing to nowheresville (like reference non-existent space, say address 0x40008000, where there’s no RAM, no FLASH, no peripheral blocks, etc).
Back to _sbrk_r. The newlib, as compiled by the crossdev utility on my machine, has reentrancy enabled. _sbrk_r is not in newlib/syscalls.c, but is a library function which has a _reent structure pointer. It calls _sbrk, which I believe it expects to be a system call, therefore non-interruptable. On return, it copies the system errno to the local copy of errno in the _reent structure for that thread. I’m not clear how _sbrk_r guarantees that errno isn’t overwritten between the exit of the system call and another interrupt.
Currently, my solution is only permit one of the tasks to really use the newlib calls. Most of the tasks I have do things like monitor devices or parse GPS input, then update a global structure protected by semaphores. The main worker task uses things like sprintf() for formatting, handles the file I/O, etc. So from newlibs point of view, the system is single threaded.
On the ucHeap not being present in your memory map, it’s because ucHeap is inside the statically allocated structure xHeap inside of heap_2.c The map file will show where a large chunk of memory the size of configTOTAL_HEAP_SIZE exists in the heap_2.o file. That’s ucHeap, and where FreeRTOS does all it’s memory management magic.
I tend to ramble in my explanations somewhat, but maybe this helped you out. Take a look at the LPC2148_Demo code at http://jcwren.com/arm. I’ll have an updated version up in the next couple days (ver 120) that has a command you’ll likely find useful in understanding all this (‘mem map’). You can also allocate and free memory from the command line to see where it’s actually returning pointers to, how it affects the heap end, etc.
(Any errors contained within this explanation are purely mine, and based on my understanding of FreeRTOS, newlib, GCC, etc, so far. I’ve a lot of experience with micros (AVR, MSP430, Z80, etc), and x86 systems, but the ARM7 is my first major foray into 32 bit micros. It’s likely I’m doing some really stupid things here and there, and people should feel free to (politely) point them out).
--jc