![]() |
Home | Libraries | People | FAQ | More |
Boost.Interprocess offers managed shared memory
between processes using managed_shared_memory
or managed_mapped_file. Two
processes just map the same the memory mappable resource and read from and
write to that object.
Many times, we don't want to use that shared memory approach and we prefer
to send serialized data through network, local socket or message queues. Serialization
can be done through Boost.Serialization or
similar library. However, if two processes share the same ABI (application
binary interface), we could use the same object and container construction
capabilities of managed_shared_memory
or managed_heap_memory to build
all the information in a single buffer that will be sent, for example, though
message queues. The receiver would just copy the data to a local buffer, and
it could read or modify it directly without deserializing the data . This approach
can be much more efficient that a complex serialization mechanism.
Applications for Boost.Interprocess services using non-shared memory buffers:
To help with this management, Boost.Interprocess
provides two useful classes, basic_managed_heap_memory
and basic_managed_external_buffer:
Sometimes, the user wants to create simple objects, STL compatible containers, STL compatible strings and more, all in a single buffer. This buffer could be a big static buffer, a memory-mapped auxiliary device or any other user buffer.
This would allow an easy serialization and we-ll just need to copy the buffer to duplicate all the objects created in the original buffer, including complex objects like maps, lists.... Boost.Interprocess offers managed memory segment classes to handle user provided buffers that allow the same functionality as shared memory classes:
//Named object creation managed memory segment //All objects are constructed in a user provided buffer template < class CharType, class MemoryAlgorithm, template<class IndexConfig> class IndexType > class basic_managed_external_buffer; //Named object creation managed memory segment //All objects are constructed in a user provided buffer // Names are c-strings, // Default memory management algorithm // (rbtree_best_fit with no mutexes and relative pointers) // Name-object mappings are stored in the default index type (flat_map) typedef basic_managed_external_buffer < char, rbtree_best_fit<null_mutex_family, offset_ptr<void> >, flat_map_index > managed_external_buffer; //Named object creation managed memory segment //All objects are constructed in a user provided buffer // Names are wide-strings, // Default memory management algorithm // (rbtree_best_fit with no mutexes and relative pointers) // Name-object mappings are stored in the default index type (flat_map) typedef basic_managed_external_buffer< wchar_t, rbtree_best_fit<null_mutex_family, offset_ptr<void> >, flat_map_index > wmanaged_external_buffer;
To use a managed external buffer, you must include the following header:
#include <boost/interprocess/managed_external_buffer.hpp>
Let's see an example of the use of managed_external_buffer:
#include <boost/interprocess/managed_external_buffer.hpp> #include <boost/interprocess/allocators/allocator.hpp> #include <boost/container/list.hpp> #include <cstring> #include <boost/aligned_storage.hpp> int main() { using namespace boost::interprocess; //Create the static memory who will store all objects const int memsize = 65536; static boost::aligned_storage<memsize>::type static_buffer; //This managed memory will construct objects associated with //a wide string in the static buffer wmanaged_external_buffer objects_in_static_memory (create_only, &static_buffer, memsize); //We optimize resources to create 100 named objects in the static buffer objects_in_static_memory.reserve_named_objects(100); //Alias an integer node allocator type //This allocator will allocate memory inside the static buffer typedef allocator<int, wmanaged_external_buffer::segment_manager> allocator_t; //Alias a STL compatible list to be constructed in the static buffer typedef boost::container::list<int, allocator_t> MyBufferList; //The list must be initialized with the allocator //All objects created with objects_in_static_memory will //be stored in the static_buffer! MyBufferList *list = objects_in_static_memory.construct<MyBufferList>(L"MyList") (objects_in_static_memory.get_segment_manager()); //Since the allocation algorithm from wmanaged_external_buffer uses relative //pointers and all the pointers constructed int the static memory point //to objects in the same segment, we can create another static buffer //from the first one and duplicate all the data. static boost::aligned_storage<memsize>::type static_buffer2; std::memcpy(&static_buffer2, &static_buffer, memsize); //Now open the duplicated managed memory passing the memory as argument wmanaged_external_buffer objects_in_static_memory2 (open_only, &static_buffer2, memsize); //Check that "MyList" has been duplicated in the second buffer if(!objects_in_static_memory2.find<MyBufferList>(L"MyList").first) return 1; //Destroy the lists from the static buffers objects_in_static_memory.destroy<MyBufferList>(L"MyList"); objects_in_static_memory2.destroy<MyBufferList>(L"MyList"); return 0; }
Boost.Interprocess STL compatible allocators can also be used to place STL compatible containers in the user segment.
basic_managed_external_buffer
can be also useful to build small databases for embedded systems limiting
the size of the used memory to a predefined memory chunk, instead of letting
the database fragment the heap memory.
Note: The external memory supplied by the
user shall be aligned to the maximum value between alignof(max_align_t) and the alignment of the strictest over-aligned
type to be built inside that memory.
The use of heap memory (new/delete) to obtain a buffer where the user wants to store all his data is very common, so Boost.Interprocess provides some specialized classes that work exclusively with heap memory.
These are the classes:
//Named object creation managed memory segment //All objects are constructed in a single buffer allocated via new[] template < class CharType, class MemoryAlgorithm, template<class IndexConfig> class IndexType > class basic_managed_heap_memory; //Named object creation managed memory segment //All objects are constructed in a single buffer allocated via new[] // Names are c-strings, // Default memory management algorithm // (rbtree_best_fit with no mutexes and relative pointers) // Name-object mappings are stored in the default index type (flat_map) typedef basic_managed_heap_memory < char, rbtree_best_fit<null_mutex_family>, flat_map_index > managed_heap_memory; //Named object creation managed memory segment //All objects are constructed in a single buffer allocated via new[] // Names are wide-strings, // Default memory management algorithm // (rbtree_best_fit with no mutexes and relative pointers) // Name-object mappings are stored in the default index type (flat_map) typedef basic_managed_heap_memory< wchar_t, rbtree_best_fit<null_mutex_family>, flat_map_index > wmanaged_heap_memory;
To use a managed heap memory, you must include the following header:
#include <boost/interprocess/managed_heap_memory.hpp>
The use is exactly the same as basic_managed_external_buffer,
except that memory is created by the managed memory segment itself using
dynamic (new/delete) memory.
basic_managed_heap_memory also offers a
grow(std::size_t
extra_bytes)
function that tries to resize internal heap memory so that we have room for
more objects. But be careful, if memory
is reallocated, the old buffer will be copied into the new one so all the
objects will be binary-copied to the new buffer. To be able to use this function,
all pointers constructed in the heap buffer that point to objects in the
heap buffer must be relative pointers (for example offset_ptr).
Otherwise, the result is undefined. Here is an example:
#include <boost/container/list.hpp> #include <boost/interprocess/managed_heap_memory.hpp> #include <boost/interprocess/allocators/allocator.hpp> #include <cstddef> #include <cassert> using namespace boost::interprocess; typedef boost::container::list<int, allocator<int, managed_heap_memory::segment_manager> > MyList; int main () { //We will create a buffer of 1000 bytes to store a list managed_heap_memory heap_memory(1000); MyList * mylist = heap_memory.construct<MyList>("MyList") (heap_memory.get_segment_manager()); //Obtain handle, that identifies the list in the buffer managed_heap_memory::handle_t list_handle = heap_memory.get_handle_from_address(mylist); //Fill list until there is no more memory in the buffer try { while(1) { mylist->insert(mylist->begin(), 0); } } catch(const bad_alloc &){ //memory is full } //Let's obtain the size of the list MyList::size_type old_size = mylist->size(); //To make the list bigger, let's increase the heap buffer //in 1000 bytes more. heap_memory.grow(1000); //If memory has been reallocated, the old pointer is invalid, so //use previously obtained handle to find the new pointer. mylist = static_cast<MyList *> (heap_memory.get_address_from_handle(list_handle)); //Fill list until there is no more memory in the buffer try { while(1) { mylist->insert(mylist->begin(), 0); } } catch(const bad_alloc &){ //memory is full } //Let's obtain the new size of the list MyList::size_type new_size = mylist->size(); assert(new_size > old_size); //Destroy list heap_memory.destroy_ptr(mylist); return 0; }
Note: Heap memory is not guaranteed to be compatible with over-aligned types so it is not supported to use memory allocation algorithms or construct objects that need over-aligned memory:
// 64 byte aligned memory might not be supported typedef basic_managed_heap_memory< char, rbtree_best_fit<mutex_family, offset_ptr<void, long, unsigned long, 64>, 64>, iset_index > managed_heap_with_overalignment_t;
All managed memory segments have similar capabilities (memory allocation inside the memory segment, named object construction...), but there are some remarkable differences between managed_shared_memory, managed_mapped_file and managed_heap_memory, managed_external_file.
To see the utility of managed heap memory and managed external buffer classes, the following example shows how a message queue can be used to serialize a whole database constructed in a memory buffer using Boost.Interprocess, send the database through a message queue and duplicated in another buffer:
//This test creates a in memory data-base using Interprocess machinery and //serializes it through a message queue. Then rebuilds the data-base in //another buffer and checks it against the original data-base bool test_serialize_db() { //Typedef data to create a Interprocess map typedef std::pair<const std::size_t, std::size_t> MyPair; typedef std::less<std::size_t> MyLess; typedef node_allocator<MyPair, managed_external_buffer::segment_manager> node_allocator_t; typedef boost::container::map<std::size_t, std::size_t, std::less<std::size_t>, node_allocator_t> MyMap; //Some constants const std::size_t BufferSize = 65536; const std::size_t MaxMsgSize = 100; //Allocate a memory buffer to hold the destiny database using vector<char> std::vector<char> buffer_destiny(BufferSize, 0); message_queue::remove("MyName"); { //Create the message-queues message_queue mq1(create_only, "MyName", 1, MaxMsgSize); //Open previously created message-queue simulating other process message_queue mq2(open_only, "MyName"); //A managed heap memory to create the origin database managed_heap_memory db_origin(buffer_destiny.size()); //Construct the map in the first buffer MyMap *map1 = db_origin.construct<MyMap>("MyMap") (MyLess(), db_origin.get_segment_manager()); if(!map1) return false; //Fill map1 until is full try { std::size_t i = 0; while(1){ (*map1)[i] = i; ++i; } } catch(boost::interprocess::bad_alloc &){} //Data control data sending through the message queue std::size_t sent = 0; message_queue::size_type recvd = 0; message_queue::size_type total_recvd = 0; unsigned int priority; //Send whole first buffer through the mq1, read it //through mq2 to the second buffer while(1){ //Send a fragment of buffer1 through mq1 std::size_t bytes_to_send = MaxMsgSize < (db_origin.get_size() - sent) ? MaxMsgSize : (db_origin.get_size() - sent); mq1.send( &static_cast<char*>(db_origin.get_address())[sent] , bytes_to_send , 0); sent += bytes_to_send; //Receive the fragment through mq2 to buffer_destiny mq2.receive( &buffer_destiny[total_recvd] , BufferSize - recvd , recvd , priority); total_recvd += recvd; //Check if we have received all the buffer if(total_recvd == BufferSize){ break; } } //The buffer will contain a copy of the original database //so let's interpret the buffer with managed_external_buffer managed_external_buffer db_destiny(open_only, &buffer_destiny[0], BufferSize); //Let's find the map std::pair<MyMap *, managed_external_buffer::size_type> ret = db_destiny.find<MyMap>("MyMap"); MyMap *map2 = ret.first; //Check if we have found it if(!map2){ return false; } //Check if it is a single variable (not an array) if(ret.second != 1){ return false; } //Now let's compare size if(map1->size() != map2->size()){ return false; } //Now let's compare all db values MyMap::size_type num_elements = map1->size(); for(std::size_t i = 0; i < num_elements; ++i){ if((*map1)[i] != (*map2)[i]){ return false; } } //Destroy maps from db-s db_origin.destroy_ptr(map1); db_destiny.destroy_ptr(map2); } message_queue::remove("MyName"); return true; }