The original
The development of network programs requires buffers to hold data that is about to be sent, as well as data that has been received but not yet processed or decoded. Libevent’s buffer implementation also has a lot in common with other implementations. Understanding its implementation can achieve the purpose of drawing inferences from one another. It also has some reference for implementing its own buffer according to business needs in the future.
2 Evbuffer data structure Overview
Libevent’s buffer structure is called evbuffer. The following figure shows the general structure of evbuffer. Here we have taken out some of the segments, leaving only the outline of the structure.
Rather than being a contiguous memory space like STD :: Vector, evBuffers are organized by linked lists of memory blocks that hold data. The linked list stores the actual data for each node, and other information about the structure records the length of the data, the capacity of the node, and so on. The off variable is interesting because it represents the actual size of the data stored in the block.
3 Reference Counting
An important design goal of evBuffer is to minimize data copying, which often leads to multiple objects referencing the same EVbuffer_chain, making it difficult to release the logic that releases an EVbuffer_chain. The solution to evBuffer is to use reference counting. Whenever there is an internal structure that has a pointer to it, its reference count increases by one, objects pointing to it are released, or the reference count decreases by one when it is no longer referenced, and memory is freed when the reference count reaches zero.
One condition for freeing memory is that the flags flag does not satisfy EVBUFFER_MEM_PINNED_ANY. I’ve never seen such an outrageous request anywhere else, and I think it’s possible to just use reference counts and not rely on other flags, but evBuffer doesn’t do that. Although but, not a problem, no bugs found, very efficient.
4 Apply for memory at a time
The implementation of buffers requires frequent memory requisition and release. To reduce the number of memory requisitions/frees, most implementations requisition a single block of memory at a time, putting both the cache headers and the array that holds the data into this buffer. For example, to implement a string structure with length, you can define it as follows.
struct LenStr {
int len;
int cap;
char buf[0];
};
Copy the code
To apply for a string structure that can hold length N, then
LenStr *str = malloc(sizeof(LenStr) + n);
str->cap = n;
str->len = 0;
Copy the code
Buf [0] here is a language trick that must be placed at the end of the structure. After memory is allocated, buf[n] is the NTH data. Beginners to C might also be surprised to see buf[0] as slang. C11 is not written this way by default.
Evbuffer does not use the slang buf[0], but the basic idea is similar. It allocates a whole block of memory, with the front of the memory storing information such as length, capacity, reference counts, and Pointers, and the back of the memory storing the actual data. After each memory request, the actual starting memory address to save the data is calculated and the data pointer is pointed to it.
5 evbuffer_chain structure
Evbuffer_chain is a node of the cache linked list. The purpose of caching as a linked list structure is obviously to minimize data copying while saving memory space. When using evBuffer in practice, it is impossible to know how long the data will be from the start, and data will be generated and consumed irregularly and quantitatively. In use, the convenient idea is to allocate as much memory as possible to hold as much data as you generate. So you have the structure of linked list splicing memory.
Note that not all structures are suitable for this; STD ::vector does not do this. STD :: Vector is defined as a contiguous memory space and is not allowed to be implemented using a linked list splicing structure. The way it works is to apply for an initial memory size, add data to fill it, apply for a new memory space larger than the required memory, and copy the contents of the original memory. This new large enough memory space standard is not defined, GNU’s STL is doubled every time, Microsoft’s STL is doubled every time. Some linked list concatenated buffers use similar memory increments to save as much memory as possible. Evbuffer takes a similar approach. When applying for memory, the block size is usually an exponent of 2, so that each request exceeds the size of the data to be added at the next time, there is probably no need to apply for a new EVbuffer_chain.
The structure of evbuffer_chain is as follows:
struct evbuffer_chain {
struct evbuffer_chain *next;
size_t buffer_len;
ev_misalign_t misalign;
size_t off;
unsigned flags;
int refcnt;
unsigned char *buffer;
};
Copy the code
- Next points to the next one in the list
evbuffer_chain
。 - Buffer_len is the data capacity of this node.
- Misalign indicates the starting position of the data stored in the buffer. When a certain amount of data is cleared from a linked list node, the value of misalign increases, and when the value of misalign equals the capacity, the node can be deleted.
- Off indicates the length of the data currently saved.
- Flag is a bit flag indicating whether the memory block is from a file fast, used for sendfile, whether the buffer is requested at the same time as the structure, whether the referenced external memory space, whether it cannot be freed, and whether multiple instances reference the memory space of the block.
- Refcnt is reference counting.
- The buffer points to the starting position that holds the data. (buffer+misalign) points to the first data actually saved.
6 evbuffer structure
This structure has a lot of member variables that interfere with pure cache structure analysis. It starts out as a linked list header, which is sufficient for the cache structure. The other members are additional functions that have nothing to do with the cache structure.