Hi all,
dynamic memory allocation from an ISR is often a necessary evil in communication systems. Therefore most of us create our own memory pool version of malloc/free that runs in predictable time. I'd really much like to see your heap_n.c files allowing a _from_isr() style call. This may be irrelevant for heap1 and heap3, but the others could very well use it.
The reason for using heap in ISR is to forward system generated data structures, such as send/receive descriptors, to a task. This is where dynamic queue size comes in. Currently one is forced to pre-determine the maximum depth for a queue, allowing FreeRTOS to allocate the maximum storage area at queue creation.
There is of course a limit on the total amount of queued items that can be allocated, however in many systems you will never experience the maximum queue depth in all queues at once. An example is time multiplexed communication systems, where a scheduler alternates between transmission and reception, hence alternating between filling up transmission or receive queues, but not both at once. Pre-allocating maximum queue depth for all hypothetical buffering is very costly from a memory use perspective.
What I have done is to let my ISRs send a binary semaphore style notification to the appropriate task.
I use heap_4.c and have done my best guess how much the entire system will need worst case to get around the inability to allocate from ISR.
The ISR will dynamically allocate memory for the item to be queued using a memory block from the pool mentioned above and it will then put the item in my own dynamic queue solution before notifying the task that there is something in its queue.
While my solution is functional, it's something that I think should be a part of a full-featured RTOS.
Best regards!
Knut
One BIG issue with using the heap in an ISR, is that will necessitate thatt malloc will need to be inside a critical section that needs to disable the interrupts (or at least any that are allowed to use malloc). One option to avoid the need of malloc in the ISR is to use xTimerPendFunctionCallFromISR() to delay the data phase of the ISR to a top level task operation (which can use malloc).
I suspecrt also you are putting too big of objects in your queue. If I am sending a lot of variable sized descriptors, it may actually make a lot more sense to just queue a pointer to the descriptor, and manage the memory for them outside of the queue. You should have much less concern for over allocating a queue of pointers.
Hi Richard,
thanks for your response. I'm using xTimerPendFunctionCallFromISR() for some initialization code triggered by GPIO. This is very handy when the decision is simply "if button N is pushed, run function X".
For other functions it can be trickier. For example during reception, I get a descriptor pointer from the lower layer. I then need to add some information like timestamp, to which task to forward the descriptor, and a few more parameters. xTimerPendFunctionCallFromISR() only allows a void * and an uint32_t, so I can't easily leave all parameters I need on the doorstep of the timer task and let it pick it up later. The only way I could pack up all the parameters I need is to create a buffer to put them in and hand that over to xTimerPendFunctionCallFromISR() as a void * , but now we're in a catch-22!
You are of course correct that a pointer requires less memory of the queue that a complete struct, but I can't really get to that pointer in all cases as shown above.
I have similar problems with timers, although these can be solved. I had to create my own timer queue to a HW timer with µs resolution. I can't use RTOS timers, since there is no way to get the ticks down to the intervals that can handle the responsiveness I need (true for any RTOS, n.b.). When the timer expires I need to notify the owning task about the event. In this case a solution was possible, since the queued timers are set by a task, the task can allocate all needed memory for the timer expiration ISR.
Another area popular nowadays is reading of sensors. You may get an interrupt from a sensor, after which you need to read a set of measurements in a timely fashion and forward the result to a task. The sensor won't do the job for you, it will only say "read me!" and expect the ISR to respond fast enough before it's ready to measure again.
So yes, however memory is allocated, the "malloc/free" functions needs to disable interrupts while accessing the memory management structure. Not only that, I suspect the ISR using them needs at least temporarily to set its interrupt priority high enough to block out any contention, but I haven't
really thought that part through.
thanks for your consideration
Knut
I think you don't understand the use of xTimerPendFunctionCallFromISR() here. The ISR gets called and DOESN'T read the data from the I/O buffer to determine the message, but just clears the interrupt and defers to the call back. When setup properly, the callback will be the highest priority task, so it WILL be the next thing to run. Assuming malloc doesn't block, it should be able to get the data nearly as fast as if the ISR did it. If malloc() would block, than any ISR safe version of malloc would have needed to disable the interrupt, so the ISR would have been delayed anyway. In essense, the call back function IS a 'Interrupt Service Routine' (as it is in charge of doing most the processing from the interrupt), it just is running in the context of a high priority task. It can then pass on the data to a lower prioirty task to process.
You're right that I didn't understand it so thanks for your insights. I measured the overhead. Of course it depends on your hardware, but on my cortex M4 I saw 69 +/- 4 usec, which is acceptable for my needs. I still unfortunately need to malloc in ISR. The reason is that I'm using a 3rd party stack that makes a callback from the radio reception ISR where it expects the return value to be a pointer to allocated memory. I've solved that with a memory pool. I wouldn't suggest that it's the best practice but it's what I've got to work with.
First, let me point out that it is fairly trivial to implement this request, heap3 is the easiest (since the allocation routine is already a distinct call (you would need to factor this out in the other versions). The current routines call xTaskSuspendAll() at the beginning and xTaskResumeAll(() at the end, change that to taskENTER_CRITICAL() / taskEXIT_CRITICAL() for the current routine and taskENTTER_CRITICAL_FROM_ISR() and taskEXIT_CRITICAL_FROM_ISR() for the FROM_ISR version.
As to your measurements, the big issue is that the value is likely to be strongly a function of the status of the heap, and unless you are very disciplined and controlled in your use of the heap, will then to increase as the program runs longer and you build up fragmentation in the heap.
One big questiion is can the 3rd party driver be put in the defered call back function triggered via xTimerPendFunctionCallFromISR(). II wouuld find it a bit surprizing if they really expected malloc calls in the ISR, since that is rarely an option. More likely, they are expecting the application to setup a pool of a couple of buffers (fixed size) to provide, or a circular buffer to hand out fifo buffers to be processed and fairlyy rapidly released (in order).
Memory allocation is part of the port layer so users are free to add their own implementations.