Transforming the srbchannel to memfds
Big Picture of the srbchannel protocol
As we've seen in the From Pstreams to srbchannels section, the following srbchannel initialization sequence occurs between the client and the server:
- A PA client sends
COMMAND_AUTH
, and includes with it whether it supports shared memory, the pulse protocol version, a cookie, and its credentials (uid, gid, etc.). This is done atcontext.c::setup_context()
- The server receives and parses this information
protocol-native.c::command_auth()
, then sets up the srbchannel - Server "sets up the srbchannel" by creating the eventfds, and the per-client mempool. A memblock is created out of this mempool, then the events fds and the memory block are sent as parameters to the
COMMAND_ENABLE_SRBCHANNEL
command. This work done by the server is atprotocol-native.c::setup_srbchannel()
. - The client then receives the
ENABLE_SRBCHANNEL
command and handles it atcontext.c::pa_command_enable_srbchannel
. The eventfds are extracted from this command tagstruct.context->srb_template.readfd
is set to the first eventfd, andcontext-srb_template.writefd
is set to the second eventfd. - Unlike receiving parameters through tagstructs, receiving memblocks is done in a special manner, and is exclusively handled by the client through
context.c::pstream_memblock_callback()
. In quite a heurestic manner, this method checks if thecontext->srb_template.readfd
is set and the srbchannel memblock is not set. If this is the case, it assumes (correctly) that the received memblock is for the srbchannel and makes the client handles the received memblock atcontext.c::handle_srbchannel_memblock
. - The client then "handles" the srbchannel memblock by using it for its own pstream srbchannel instance, replies with an ACK using also
COMMAND_ENABLE_SRBCHANNEL
, and forces the pstream to use the srbchannel for communication directly afterwards.
How can this picture be modified?
We are lucky that the file-descriptor passing mechanisms are already there. Instead of passing a memblock for the srbchannel, a file descriptor needs to be passed instead.
How is the srbchannel memory allocated and used?
Shared rinbuffer code, from its name, makes the communication exclusively uses a ringbuffer:
struct pa_ringbuffer {
pa_atomic_t *count; /* amount of data in the buffer */
int capacity;
uint8_t *memory;
int readindex, writeindex;
};
Notice the memory
field? From where is it allocated?
We note that each srbchannel has actually two ringbuffers instead of just one
struct pa_srbchannel {
pa_ringbuffer rb_read, rb_write;
...
}
So, where are the read and write ringbuffers memory initialized? The answer is that this happens in two places:
- On the client side, when creating a real srbchannel from the "tremplate" passed from the server (template which contains the memblock).
- On the server side, when creating an srbchannel directly at
protocol-native.c::setup_srbchannel()
method.
Note that in both the server and client cases, both uses the per-client rw_mempool created earlier.
From the server side:
pa_srbchannel* pa_srbchannel_new(pa_mainloop_api *m, pa_mempool *p) {
int capacity;
int readfd;
struct srbheader *srh;
pa_srbchannel* sr = pa_xmalloc0(sizeof(pa_srbchannel));
sr->mainloop = m;
sr->memblock = pa_memblock_new_pool(p, -1);
srh = pa_memblock_acquire(sr->memblock);
pa_zero(*srh);
sr->rb_read.memory = (uint8_t*) srh + PA_ALIGN(sizeof(*srh));
srh->readbuf_offset = sr->rb_read.memory - (uint8_t*) srh;
...
sr->rb_write.memory = PA_ALIGN_PTR(sr->rb_read.memory + capacity);
srh->writebuf_offset = sr->rb_write.memory - (uint8_t*) srh;
}
Note the fields readbuf_offset
and writebuf_offset
. They will be used by the client to find the read rinbuffer start and the write rinbuffer start.
And from the client side, we can see that:
pa_srbchannel* pa_srbchannel_new_from_template(pa_mainloop_api *m, pa_srbchannel_template *t)
{
int temp;
struct srbheader *srh;
pa_srbchannel* sr = pa_xmalloc0(sizeof(pa_srbchannel));
sr->mainloop = m;
sr->memblock = t->memblock;
pa_memblock_ref(sr->memblock);
srh = pa_memblock_acquire(sr->memblock);
...
sr->rb_read.memory = (uint8_t*) srh + srh->readbuf_offset;
sr->rb_write.memory = (uint8_t*) srh + srh->writebuf_offset;
The client side srbchannel is initialized from the "template" sent from the server. This template includes the memblock created by the server at srbchannel.c::pa_srbchannel_new()
. The passed memblock itself also includes the read and write ringbuffer start addresses.
References
- What to do if the passed fd is of the wrong type?, Alexander E. Paltrakov