| DevJournal - Finished the switch to dynamic allocation and managed memory | | Print | |
| Written by Akiba | |
| Saturday, 25 October 2008 | |
|
Well, I'm just finishing up the conversion to using dynamically allocated memory using the Contiki managed memory library. It took a bit longer than
The Contiki memory management library overcomes this problem by having a feature called memory compacting. Every time a memory block is freed, all the used blocks that come after it will be moved down. This leaves all of the free memory in one ajacent heap so that you won't run into the fragmentation issues I mentioned earlier.
The memory management comes with a price though. It accomplishes this by using an added level of indirection. This means that when you allocate memory, you don't get a pointer to the actual memory. You get a pointer to a pointer to the memory. When you allocate managed memory, it will create a memory block structure which contains the size of the block and the pointer to the memory. It will then return a pointer to the managed memory pointer which points to the underlying memory.
Here's an example of how the table handling changed. Before: rte_entry = list_head(rte_tbl); // grab the route entry next_hop = rte_entry->next_hop; // get the next hop in the route entry After: mem_ptr = list_head(rte_tbl); // grab the memory pointer to the route entry rte_entry = mem_ptr->mmem_ptr.mem_block_ptr; // de-reference the memory pointer to get to the data next_hop = rte_entry->next_hop; // now we can use the entry There is one dangerous point about using the managed memory and I included a diagram so that it can be more easily seen. To actually access table elements, you have to de-reference the managed memory pointer and access the direct memory. However if in between de-referencing the pointer and actually using the table data, a block of memory gets freed somewhere, the de-reference pointer is useless and dangerous because it is now a dangling pointer. Since Contiki is not pre-emptive, you won't have to worry about a stray thread causing this situation, however it can occur if you have a function in between de-referencing and using the pointer. If the function frees memory somewhere, then it will be dangerous. So its best to use the pointer as soon as possible after de-referencing it. Here's an example of an error case: mem_ptr = list_head(rte_tbl); // grab the first mem pointer from the route table rte_entry = mem_ptr->mmem_ptr.mem_block_ptr; // de-reference the mem pointer some_random_func(); // a memory block gets freed somewhere in here next_hop = rte_entry->next_hop; // this will be incorrect. the entry now points to junk. Updated 2008-10-26: Just learned from Adam Dunkels, the author of the managed memory library (as well as Contiki and uIP), that the memory should only be dereferenced as its used. And the MMEM_PTR macro should be used to access the memory blocks only. That way, the error case won't be encountered. So looks like its time to re-modify the code to handle this. It should lead to a more robust design without needing to "play with fire" or at least run the risk of dangling pointers.
End update. The example error case will now only show what can happen if you abuse the memory management system ;)
I've tried to keep the managed memory pointer handling away from the main parts of the code and isolated to the table files. That way, from a user point of view, you just call functions like rte_table_get_next_hop() and it will return the next hop data without worrying about de-referencing, dangling pointers, etc... That way, stack modification should be much easier for everyone. In most places, you only need to worry about handling the managed memory pointers if you are modifying the behavior of a table. Updated 2008-10-26: I should also mention that the managed memory library will only be used for table and list elements. Those are alloc'd and free'd on a relatively leisurely basis. For frame buffers, that I'm assuming will be alloc'd and free'd frequently, I will be implementing two memory pools of 64-byte and 140-byte sizes. This is because there is a performance impact when the managed memory is free'd and then compacted. I'm hoping this will provide a good balance between performance and ease of use. All in all, the mods will take the stack from tweaking approx 20 table sizes and the frame buffer pool to tweaking the shared heap memory and the amount of frame buffers in each pool.
Hits: 1363 Trackback(0)
Comments (4)
![]()
A pointer to a pointer is a handle
written by David Kopf, February 25, 2009
At least that is what Apple called it in the original Macintosh toolbox (1984-). On the Mac a handle could be locked before dereferencing, then the pointer used in a section of code that called other toolbox functions that might shift memory around. The handle was then supposed to be unlocked to minimize "heap fragmentation". Not locking the handle was a frequent cause of the amusing Mac bomb dialog box.
report abuse
vote down
vote up
Votes: +0
Write comment
|
| < Prev | Next > |
|---|
But, one very important point that needs to be raised: the MMEM_PTR() macro is the only way to access the pointer to the actual memory block. The pointer to the actual memory should never be explicitly accessed through the struct. Also, the pointer should never be stored somewhere else. By always using the MMEM_PTR() macro to access the memory, the problem with the moving addresses should not occur - or at least this was the idea behind the mmem module
The problematic case should be rewritten like this:
mem_ptr = list_head(rte_tbl); // grab the first mem pointer from the route table
/* rte_entry = mem_ptr->mmem_ptr.mem_block_ptr; never de-reference and store the mem pointer like this */
some_random_func(); // a memory block gets freed somewhere in here
next_hop = ((struct tbl *)MMEM_PTR(mem_ptr))->next_hop; /* this is the way to access the memory pointed to by mem_ptr - this works even if a memory block was freed in some_random_func(). */
Sorry if this was not clear from the module's documentation - the documentation for this module is known to be bad