Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 64

Shared Memory Management with Pointers

Recommended resources

Hello! Welcome back to our hands-on exploration of pointers in automotive and embedded systems.

In our last lesson, we established the theoretical foundation for using shared memory as a high-performance Inter-Process Communication (IPC) mechanism. We discussed the central role of pointers returned by the OS, the critical challenge of using relative offsets instead of absolute addresses, and the absolute necessity of synchronization.

Today, we transition from theory to practice. Our goal is to implement a program to create/attach, write to, and read from a shared memory segment using pointers. We will build a pair of C programs—a "writer" and a "reader"—that communicate data through a shared memory block. This is a common pattern in complex systems like the ADAS platforms you work on, where different processes (e.g., for perception and fusion) need to exchange data efficiently.

We'll explore two widely-used APIs for this on Unix-like systems: the classic System V API and the more modern POSIX API.

1. The System V Approach: Keys and Segments

One of the original and still very common methods for IPC on Unix-like systems is System V IPC. It uses a numeric key to identify a shared memory segment. Processes that know the key can gain access to the same segment.

System V Shared Memory Diagram
This diagram shows the core concept: multiple processes gain access to a common memory segment managed by the kernel but mapped into each process's user-space address space.

The general workflow for System V shared memory involves a few key functions:

  1. ftok(): Generates a unique key from a file path and an ID, ensuring different applications don't accidentally use the same key.
  2. shmget(): Creates a new shared memory segment or gets the ID of an existing one using the key.
  3. shmat(): Attaches the shared memory segment to the current process's address space. This is the crucial step that returns the void* pointer we can use to access the memory.
  4. shmdt(): Detaches the segment from the process's address space when it's no longer needed.
  5. shmctl(): Performs control operations, most importantly destroying the segment with the IPC_RMID command once it's no longer needed by any process.

The following video provides an excellent, practical walkthrough of implementing a complete writer/reader/destroyer application using these functions.

How to Set up Shared Memory in Your Linux and MacOS Programs. (shmget, shmat, shmdt, shmctl, ftok)

The video 'How to Set up Shared Memory...' by Jacob Sorber provides a clear, step-by-step guide to implementing shared memory using the System V IPC calls. It demonstrates a very practical approach by building a small library of helper functions.

Please watch from 03:20 to 11:38. Pay close attention to: The sequence of API calls in the helper functions: ftok -> shmget -> shmat. How the writer program uses the pointer returned by attach_memory_block to write data with strcpy. How the reader program gets the same pointer and reads the data. The purpose of the destroy program and the shmctl call.

As you saw in the video, once shmat returns a valid pointer, you can treat it just like any other block of memory you might have allocated with malloc. You can cast it to a specific type and use standard pointer arithmetic and dereferencing to read and write data.

Let's look at how you might structure data within the shared segment. Instead of just a raw string, you can define a struct to hold more complex, organized data.

Shared Memory

The webpage 'Shared Memory' from cs.kent.edu provides a good textual summary and C code examples for the System V API. The section on placing data is particularly useful.

Please read the section titled 'Placing Data In Shared Memory'. Focus on how the shm_addr pointer is cast to different pointer types (int*, struct country*) to structure the data within the single shared memory block. This demonstrates how to manage the memory layout yourself using pointer arithmetic.

The example in the reading shows an important pattern: the first part of the shared memory is used for metadata (like a counter), and the rest is used for the data array. This is all managed manually using pointers.


2. The POSIX Approach: Named Objects and Memory Mapping

The POSIX standard introduced a more modern, and often preferred, API for shared memory. Instead of abstract keys, it uses human-readable names that look like file paths (e.g., /my_shared_memory). The workflow leverages the mmap function, which you've seen before.

The core POSIX functions are:

  1. shm_open(): Creates and opens a new shared memory object or opens an existing one, identified by name. It returns a file descriptor, similar to open().
  2. ftruncate(): Sets the size of the shared memory object, using the file descriptor from shm_open().
  3. mmap(): Maps the shared memory object into the process's address space. Like with shmat, this returns the all-important void* pointer.
  4. munmap(): Unmaps the memory region.
  5. shm_unlink(): Removes the shared memory object from the system once it's no longer needed.

In the previous lesson, we looked at the theory behind these calls. Now, let's see them in a full-fledged program.

POSIX Shared Memory

Let's revisit the 'POSIX Shared Memory' slides by Michael Kerrisk. This time, focus on the complete code examples that implement a writer and a reader process.

Please review slides 23-24 through 23-27, which contain the source code for pshm_write.c and pshm_read.c. For pshm_write.c, trace the flow: shm_open with O_RDWR to get a file descriptor. ftruncate to set the object's size. mmap to get the char *addr pointer. memcpy to write data into the memory via the addr pointer. For pshm_read.c, notice the similar pattern, but using O_RDONLY and PROT_READ.

The fundamental principle remains the same across both APIs. The goal is to get a pointer from the OS that points to a memory region accessible by other processes.

Writer Process (High-Level Logic):

  1. Create or open the shared memory object/segment (shmget or shm_open). If creating, specify creation flags (IPC_CREAT / O_CREAT).
  2. Set its size if necessary (ftruncate).
  3. Attach or map the memory to get a void* pointer (shmat or mmap).
  4. Cast the pointer to the desired data type (e.g., struct my_data*).
  5. Write data into the memory by dereferencing the pointer (e.g., data_ptr->field = 123; or memcpy(data_ptr, ...)).
  6. Detach or unmap the memory (shmdt or munmap).

Reader Process (High-Level Logic):

  1. Open the existing shared memory object/segment (using the same key/name).
  2. Attach or map the memory to get its own void* pointer.
  3. Cast the pointer to the same data type.
  4. Read data from memory by dereferencing its pointer.
  5. Detach or unmap the memory.
  6. Optionally, the last process to finish can destroy the shared memory object (shmctl or shm_unlink).
Test your understanding!

You are tasked with sharing a struct Radar_TargetList from a RADAR driver process to a sensor fusion process. The fusion process should only be able to read the data, not modify it.

Using the POSIX API, what oflag would you use in shm_open() and what prot flag would you use in mmap() for the sensor fusion (reader) process to ensure this read-only constraint?

Show answer

For the sensor fusion (reader) process:

  • In shm_open(), you would use the O_RDONLY flag to open the shared memory object in read-only mode.
  • In mmap(), you would use the PROT_READ flag. This tells the memory management unit (MMU) to configure the memory pages for this process as read-only. Any attempt by the fusion process to write to this memory region would result in a segmentation fault, enforcing the design constraint.

Conclusion

Today, we bridged the gap between theory and practice, implementing programs that use pointers to communicate across process boundaries. You now have a practical understanding of two major APIs for achieving this. Your experience with communication protocols like SOME/IP is built on these foundational IPC concepts, although with many more layers of abstraction for service discovery and serialization. Understanding the raw shared memory mechanism gives you a much deeper appreciation for what's happening under the hood.

Key Takeaways:

  • System V IPC and POSIX IPC are two common APIs for creating and managing shared memory.
  • The core steps are to create/open an object, attach/map it into the process's address space to get a pointer, and then use that pointer for reading and writing.
  • For the System V API, the key functions are shmget() to get an ID and shmat() to get the pointer.
  • For the POSIX API, the key functions are shm_open() to get a file descriptor and mmap() to get the pointer.
  • Once you have the pointer, you use standard C pointer operations (*, ->, [], memcpy) to interact with the shared data.

Preview of the Next Lesson

A crucial point was made in the resources: our simple writer/reader programs are not safe. They contain a race condition. If the reader tries to access the memory while the writer is in the middle of an update, it could read corrupted or incomplete data. In a system requiring functional safety, this is unacceptable.

In our next lesson, we will address this critical flaw. We will modify our shared memory access code to include synchronization primitives to prevent race conditions, making our IPC mechanism robust and safe for concurrent access.