Shared memory represents the fastest form of Inter-Process Communication (IPC). Once a memory region is mapped into the address space of multiple processes, data transfer between them no longer requires kernel intervention. This eliminates the overhead of system calls for data exchange between processes.
The System V IPC mechanism includes three primary communication methods:
- Shared Memory
- Message Queues
- Semaphores
Fundamental Principles
Each processs maintains its own virtual address space, which translates to physical memory through page tables. Due to process isolation, each process has independent code, data, and kernel data structures.
To implement shared memory, the operating system allocates a memory region in physical memory. Although the OS creates this region, a process must initiate its creation. The OS then maps this physical memory to the shared region of each process's address space via page tables, returning the starting virtual address to the user. Processes can then directly read and write to this shared region.
This mechanism, where multiple processes map to the same physical memory segment, is called shared memory. Since the OS may need to support multiple independent shared memory segments for various process groups, it must manage these segments using data structures—essentially managing shared memory through linked list operations.
Summary: Shared Memory = Memory Region (data) + Shared Memory Attributes
API Functions
Creating Shared Memory: shmget
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
Parameters:
- key: A unique identifier for the shared memory segment, set by the user.
- size: The size of the shared memory segment in bytes.
- shmflg: Permission flags similar to file mode flags. Common flags include:
IPC_CREAT: Create if not exists, otherwise return the existing segment.IPC_EXCL: When combined withIPC_CREAT, returns an error if the segment already exists.
Return Value: Returns a non-negative integer (shared memory identifier) on success, -1 on failure.
Generating a Key: ftok
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
- pathname: Path to an existing file used to generate the key.
- proj_id: A user-defined integer (0-255) to differentiate IPC objects.
Return Value: Returns a key_t value on success, -1 on error.
Distinguishing key from shmid
- key: Generated by the user and used by the kernel to uniquely identify shared memory internally.
- shmid: An identifier returned by the kernel for user-level management of shared memory (similar to a file descriptor).
Destroying Shared Memory: shmctl
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
Parameters:
- shmid: Shared memory identifier returned by shmget.
- cmd: Action to perform (e.g.,
IPC_RMIDto remove). - buf: Pointer to a shmid_ds structure for storing/retrieving attributes.
Attaching Shared Memory: shmat
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
Parameters:
- shmid: Shared memory identifier.
- shmaddr: Desired attachment address (usually NULL for system choice).
- shmflg: Flags like
SHM_RNDorSHM_RDONLY.
Return Value: Returns the attached address on success, (void *)-1 on failure.
Detaching Shared Memory: shmdt
int shmdt(const void *shmaddr);
Implementation Example
Shared Memory Wrapper Class
// SharedMemory.hpp
#ifndef SHARED_MEMORY_HPP
#define SHARED_MEMORY_HPP
#include <iostream>
#include <string>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
constexpr int CREATOR_ROLE = 1;
constexpr int USER_ROLE = 2;
constexpr size_t DEFAULT_SIZE = 4096;
class SharedMemory {
private:
key_t generateKey(const std::string& path, int projId) {
key_t k = ftok(path.c_str(), projId);
if (k == -1) {
perror("ftok failed");
}
return k;
}
int createOrGetShm(key_t key, size_t size, int flags) {
int id = shmget(key, size, flags);
if (id == -1) {
perror("shmget failed");
}
return id;
}
void* attachShm() {
void* addr = shmat(_shmId, nullptr, 0);
if (addr == (void*)-1) {
perror("shmat failed");
return nullptr;
}
std::cout << "Process attached to shared memory\n";
return addr;
}
void detachShm() {
if (_shmAddr != nullptr && _shmAddr != (void*)-1) {
shmdt(_shmAddr);
std::cout << "Process detached from shared memory\n";
}
}
public:
SharedMemory(const std::string& path, int projId, int role, size_t size = DEFAULT_SIZE)
: _path(path), _projId(projId), _role(role), _size(size), _shmAddr(nullptr) {
_key = generateKey(path, projId);
if (_role == CREATOR_ROLE) {
_shmId = createOrGetShm(_key, _size, IPC_CREAT | IPC_EXCL | 0666);
} else {
_shmId = createOrGetShm(_key, _size, IPC_CREAT | 0666);
}
if (_shmId != -1) {
_shmAddr = attachShm();
}
}
~SharedMemory() {
detachShm();
if (_role == CREATOR_ROLE && _shmId != -1) {
shmctl(_shmId, IPC_RMID, nullptr);
std::cout << "Shared memory removed\n";
}
}
void* getAddr() const { return _shmAddr; }
int getId() const { return _shmId; }
key_t getKey() const { return _key; }
void clear() {
if (_shmAddr != nullptr) {
memset(_shmAddr, 0, _size);
}
}
void printInfo() {
struct shmid_ds ds;
if (shmctl(_shmId, IPC_STAT, &ds) == 0) {
std::cout << "Key: 0x" << std::hex << ds.shm_perm.__key << std::endl;
std::cout << "Attached processes: " << std::dec << ds.shm_nattch << std::endl;
}
}
private:
std::string _path;
int _projId;
int _role;
size_t _size;
key_t _key;
int _shmId;
void* _shmAddr;
};
#endif
Named Pipe for Synchronization
// NamedPipe.hpp
#pragma once
#include <string>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
class NamedPipe {
public:
NamedPipe(const std::string& path, bool isCreator)
: _pipePath(path), _isCreator(isCreator), _fd(-1) {
if (_isCreator) {
mkfifo(_pipePath.c_str(), 0666);
}
}
~NamedPipe() {
if (_fd != -1) close(_fd);
if (_isCreator) unlink(_pipePath.c_str());
}
bool openForRead() {
_fd = open(_pipePath.c_str(), O_RDONLY);
return _fd != -1;
}
bool openForWrite() {
_fd = open(_pipePath.c_str(), O_WRONLY);
return _fd != -1;
}
ssize_t readData(std::string& out) {
char buffer[256];
ssize_t n = read(_fd, buffer, sizeof(buffer) - 1);
if (n > 0) {
buffer[n] = '\0';
out = buffer;
}
return n;
}
ssize_t writeData(const std::string& data) {
return write(_fd, data.c_str(), data.size());
}
private:
std::string _pipePath;
bool _isCreator;
int _fd;
};
Server Process
// server.cpp
#include "SharedMemory.hpp"
#include "NamedPipe.hpp"
const std::string SHM_PATH = "/tmp/shm_demo";
const int SHM_PROJ_ID = 0x66;
const std::string PIPE_PATH = "/tmp/demo_fifo";
int main() {
SharedMemory shm(SHM_PATH, SHM_PROJ_ID, CREATOR_ROLE);
char* data = static_cast<char*>(shm.getAddr());
NamedPipe pipe(PIPE_PATH, true);
pipe.openForRead();
while (true) {
std::string signal;
pipe.readData(signal);
std::cout << "Received data: " << data << std::endl;
}
return 0;
}
Client Process
// client.cpp
#include "SharedMemory.hpp"
#include "NamedPipe.hpp"
const std::string SHM_PATH = "/tmp/shm_demo";
const int SHM_PROJ_ID = 0x66;
const std::string PIPE_PATH = "/tmp/demo_fifo";
int main() {
SharedMemory shm(SHM_PATH, SHM_PROJ_ID, USER_ROLE);
shm.clear();
char* data = static_cast<char*>(shm.getAddr());
NamedPipe pipe(PIPE_PATH, false);
pipe.openForWrite();
for (char c = 'A'; c <= 'Z'; ++c) {
data[c - 'A'] = c;
std::cout << "Wrote: " << c << std::endl;
pipe.writeData("signal");
sleep(1);
}
return 0;
}
Important Notes
Shared memory does not automatically release when a process terminates. It persists until the system reboots or is manually removed. Use the following commands to manage shared memory:
- View shared memory:
ipcs -m - Remove shared memory:
ipcrm -m [shmid]
Advantages and Limitations
Advantages: Shared memory is the fastest IPC method because it eliminates data copying between kernel and user space.
Limitations: Shared memory provides no synchronization or protection mechanisms. Processes may encounter race conditions. To address this, combine shared memory with other synchronization primitives like named pipes or semaphores.
The example above demonstrates using a named pipe to synchronize access—the server only reads when the client signals through the pipe, preventing data inconsistency issues.