Truncate and map shared memory

Finally, we need to set the size of the shared memory segment:

...

  sts = ftruncate (shmem_fd, size);
  // error check code omitted
...

and map it into our address space via mmap():

...

  shmem_ptr = mmap (0, size, PROT_READ | PROT_WRITE,
                    MAP_SHARED, shmem_fd, 0);
  // error checking code omitted
...

The flags to mmap() are:

PROT_READ
Let us read from the shared memory region.
PROT_WRITE
Let us write into the shared memory region.
MAP_SHARED
We are mapping an existing object (the one given by shmem_fd), or we anticipate sharing the object.

Now that our shared memory region exists and is the correct size, we assign some utility pointers into the areas within the shared memory:

...

  // set up our utility pointers
  sig = (adios_signature_t *) shmem_ptr;
  daq = (adios_daq_status_t *) (shmem_ptr + sizeof (*sig));
  cis = (adios_cis_t *) (shmem_ptr + sizeof (*sig)
        + sizeof (*daq));
...
Note: Note that inside the shared memory region we never store any pointer values. After all, the shared memory region could be mapped into a different address space within each process that needs to access it. In the code snippet above, we create pointers into the areas of interest after we know where the shared memory region is mapped in our address space.

Then we fill the shared memory structure:

...

  // clear the signature (just for safety, a
  // new shmem region is zeroed anyway)
  memset (sig -> signature, 0, sizeof (sig -> signature));
  sig -> datablock = size_c + FILLER_ALIGN_4kbytes (size_c);
  sig -> datablock /= 4096;  // convert to blocks
  sig -> num_cis = nadios;
  sig -> num_elems = optS;
  database = shmem_ptr + sig -> datablock * 4096;

  daq -> element_size = size_element;

  // head points to the last entry in the buffer, so that
  // when we add our first entry head will point to 0
  daq -> tail = daq -> head = optS - 1;

  for (i = 0; i < nadios; i++) {
    strncpy (cis [i].name, adios [i].name, MAXNAME);
    cis [i].nai = adios [i].nai;
    cis [i].nao = adios [i].nao;
    cis [i].ndi = adios [i].ndi;
    cis [i].ndo = adios [i].ndo;
    cis [i].nbpc = adios [i].nbpc;
    cis [i].maxresai = adios [i].maxresai;
  }
}

Filling the shared memory structure means that we set:

datablock
Indicates which 4 KB block the data block starts on.
num_cis
The number of card information structures (CISs) that are present.
num_elems
The number of data elements (sets) that are in the shared memory ring buffer.
element_size
The size of each element within the data set.
head and tail
Indexes into the data elements to indicate head and tail of the ring buffer.
nai, nao, ndi, ndo
The number of AI, AO, DI, and DO points (note that analog points, AI and AO, are given as the number of channels, and digital points, DI and DO, are given as the number of bits).
nbpc
The number of bytes per channel. This comes in handy when retrieving data from the ring buffer; you'll see it again in the tag utility description.
maxresai
Number of bits of analog input resolution (12 for all of the card types mentioned in this chapter).

Notice that we did not fill in the signature field (we only zeroed it). That's because we validate the signature only when we have at least one valid sample in the shared memory ring buffer.

That was a lot of work. Compared to this, managing the shared memory ring buffer data is much simpler!