Saturday, September 10, 2011

mmap(): Now you're coding with portals

I decided to write a circular buffer (CB) because my bittorrent client was responding to a peer's requests faster than what it could write. The poll function of my API threw me completely. My first two efforts were superseded by the third!

Effort #1: Reading straight from the CB

/**
* Get pointer of data to be read.
* @return pointer to data, null if we can't poll this much data
* */

unsigned char *cbuf_poll(
cbuf_t * cb,
const int size
);

Looks great, and would be an easy to use function. Unfortunately, boundary cases are the killer. For example, say as if you want to poll 10 bytes, but the head of the CB is currently at byte 9. This means that the caller of cbuf_poll has to know that we've hit a boundary case. In fact, the API just can't work this way, and it'd be easier on the user if the function was instead:

Effort #2: Writing from the CB to a buffer

/**
* Write data to be read to buffer "out".
* */

void cbuf_poll(
cbuf_t * cb,
const int size,
char* out
);


I'd just use a for loop for writing each byte and check the indices for the boundary conditions. Easy. But, this feels kind of slow because of the extra memory writing you wouldn't need to do if the original function worked.

Effort #3: Reading straight from the CB, using a contiguous memory space via mmap()

/**
* Get pointer of data to be read.
* @return pointer to data, null if we can't poll this much data
* */

unsigned char *cbuf_poll(
cbuf_t * cb,
const int size
);

Exactly the same as #1, but we use some mmap() magic to:
1. Create an underlying buffer of size N with mktempfs()
2. Create a memory space of size N * 2 with mmap()
3. Create a memory space that points to the beginning of the memory space in ref 2, of size N using mmap(). This space maps the underlying buffer.
4. Create a memory space that points to the middle of memory space in ref 2, of size N using mmap(). This space also maps the underlying buffer.

Wikipedia for more details: http://en.wikipedia.org/wiki/Circular_buffer

Maybe I'm thinking aloud, but this is how I visualise it:



Great for two reasons:
1. The user doesn't have to worry about boundary conditions.
2. The implementation is easier (ignoring the mmap() setup code)



Pretty cool huh? Code and SVG diagram are on Github. And if I'm allowed to stretch this to another analogy: