As with any memory management API, it is important to know how to handle the creation and releasing of dbuffers, in other words how to manage ownership of the dbuffers. The conventions used in Simics, which are assumed by many interfaces that employ dbuffers, are described in this section.
The conventions are based on two simple rules:
The first rule means that whoever creates the dbuffer is responsible for releasing it. This often means that the dbuffer is created, filled with data, passed to other functions, and released in the same function. For some long-lived dbuffers, a reference may be stored to the dbuffer, and code in the same module will create it and release it.
Furthermore, this means that if a function receives a dbuffer reference as one of its parameter, and wishes to store it for later use, it must make a copy of it and store the copy. Fortunately, this is a cheap operation.
The second rule means that a function that takes a dbuffer reference as one of its parameter must also make a copy of it if it wants to make any changes to it.
Of course, there are exceptions to this rule. Sometimes the owner of a dbuffer will call a function with the explicit intent that the dbuffer should be updated. But these exceptions should always be documented in the interface description.
Let's take a look at a small example of how these rules work in practice. Consider a small example where a network device reads a packet from memory, adds a header to the packet and sends it to a network model that delivers it to a number of receivers. The receivers will strip away the header and store it in a queue. The dequeur is later run from an event handler and writes the incoming packet to local memory.
// This is part of the sender model void sender(void) { dbuffer_t *buf = new_dbuffer(); long len; char *dataref = get_packet_from_mem(&len); memcpy(dbuffer_append(buf, len), dataref, len); write_header(dbuffer_prepend(buf, HEADERSIZE)); send_to_network(buf); dbuffer_free(buf); } // This is part of the network model void send_to_network(dbuffer_t *buf) { // This function doesn't modify or store the dbuffer. It // sends the same dbuffer to all receivers that match the // first six bytes of the header. for (i = 0; i < nreceivers; i++) { if (memcmp(dbuffer_read(buf, 0, 6), receivers[i]->addr) == 0) receive(receivers[i], buf); } } // This is part of the receiver model void receive(receiver_t *receiver, *inbuf) { if (receiver->enabled) { dbuffer_t *buf = dbuffer_clone(inbuf); dbuffer_remove(buf, 0, HEADERSIZE); enqueue(receiver->queue, buf); } } void dequeue_packet(receiver_t *receiver) { dbuffer_t *buf = dequeue(receiver->queue); write_packet_to_mem(dbuffer_read_all(buf), dbuffer_len(buf)); dbuffer_free(buf); }
Notice how the sender code sends the dbuffer to the network link, and the network links sends the same dbuffer to all receivers. The network code will not try to send separate copies to each receiver, but instead let the receiver clone it if it needs to, since only the receiver code knows whether a copy needs to be made.