[English | 日本語]
🎯 Purpose
The purpose of SHM (Shared-memory based Handy-communication Manager) is to provide the most secure and fastest possible communication between different processes. It is also designed with careful consideration to make it easy for students to use. Please refer to README.md for installation instructions.
📖 Abstract
Framework Context
The Instrumentation and Robotics Laboratory at Utsunomiya University uses shared memory for data exchange between programs, in addition to local memory generally used by programs.
Shared memory differs from local memory in several key aspects:
- Memory Management: Developers must not release allocated memory (inadvertent release prevents data passing to other programs)
- Programming Complexity: Higher barrier for novice programmers due to pointer usage
- Development Overhead: Designers must create custom processes for each memory type when creating new libraries
This framework hides data exchange using shared memory and provides easy-to-understand inter-process communication for novice programmers.
🚀 System Functions
Memory Management Process Hiding
Easy inter-process communication is achieved by hiding shared memory area allocation and buffer access within classes. By default, only standard layout type classes are supported. Other classes can be supported by defining specialized Publishers/Subscribers for each case. See samples for details.
Pointer-Free Coding
The system fundamentally only requires passing variables allocated in local memory to Publishers or receiving topics from Subscribers, eliminating the need to code with shared memory pointers as in traditional approaches.
👥 User Characteristics
🎓 Developer
Developers create new programs using internal and external libraries, including this library. Primarily intended for first-time programming students such as fourth-year undergraduates.
🏗️ Designer
Designers create new libraries using this framework and transfer current know-how to junior members. Primarily intended for second-year master students.
📚 Definitions and Terms
Local Memory
Local memory is a virtual storage area accessible within a process. It's the storage area used during normal programming. If not properly released after use, it may cause future problems (programs working correctly for a while may suddenly stop).
Shared Memory
Shared memory is a storage area that can be used commonly among processes. It's allocated by special means and can be implemented in various ways. This implementation uses POSIX file-mapped memory, where data stored in shared memory is treated as a file. In Linux, the allocated memory area can be confirmed directly under /dev/shm
.
Standard Layout Type
A class or structure that:
- Contains no specific C++ language features (like virtual functions) not found in C
- Has all members with the same access control
- Enables
memcpy
operations
- Has clearly defined layout for use in C programs
Standard layout types have the following characteristics:
- No virtual functions or virtual base classes
- All non-static data members have the same access control
- All non-static members of class type are standard layout
- All base classes have standard layout
- No base class of the same type as the first non-static data member
- Meets one of the following conditions:
- The most derived class has no non-static data members and only one base class with non-static data members
- No base class contains non-static data members
🏗️ Architecture Design
Overall System Architecture
graph TB
subgraph "Process A"
PA[Application A]
PubA[Publisher A]
end
subgraph "Process B"
PB[Application B]
SubB[Subscriber B]
end
subgraph "Process C"
PC[Application C]
SubC[Subscriber C]
end
subgraph "Shared Memory Area"
SM[Shared Memory Segment]
RB[Ring Buffer]
Meta[Metadata]
end
PA --> PubA
PubA --> SM
SM --> RB
RB --> SubB
RB --> SubC
SubB --> PB
SubC --> PC
SM --> Meta
Layer Architecture
graph TB
subgraph "Application Layer"
APP[User Application]
end
subgraph "SHM API Layer"
PUB["Publisher"]
SUB["Subscriber"]
end
subgraph "Shared Memory Management Layer"
SHM[SharedMemory]
POSIX[SharedMemoryPosix]
RB[RingBuffer]
end
subgraph "OS Layer"
KERNEL[Linux Kernel]
SHMFS["/dev/shm Filesystem"]
end
APP --> PUB
APP --> SUB
PUB --> SHM
SUB --> SHM
SHM --> POSIX
POSIX --> RB
POSIX --> KERNEL
KERNEL --> SHMFS
🔧 Detailed Design
Class Hierarchy Structure
classDiagram
class SharedMemory {
<<abstract>>
#int shm_fd
#int shm_oflag
#PERM shm_perm
#size_t shm_size
#unsigned char* shm_ptr
+SharedMemory(int oflag, PERM perm)
+getSize() size_t
+getPtr() unsigned char*
+connect(size_t size)* bool
+disconnect()* int
+isDisconnected()* bool
}
class SharedMemoryPosix {
-string shm_name
+SharedMemoryPosix(string name, int oflag, PERM perm)
+connect(size_t size) bool
+disconnect() int
+isDisconnected() bool
}
class RingBuffer {
-unsigned char* memory_ptr
-pthread_mutex_t* mutex
-pthread_cond_t* condition
-size_t* element_size
-size_t* buf_num
-atomic_uint64_t* timestamp_list
-unsigned char* data_list
-uint64_t timestamp_us
-uint64_t data_expiry_time_us
+RingBuffer(unsigned char* first_ptr, size_t size, int buffer_num)
+getSize(size_t element_size, int buffer_num)$ size_t
+getTimestamp_us() uint64_t
+setTimestamp_us(uint64_t input_time_us, int buffer_num)
+getNewestBufferNum() int
+getOldestBufferNum() int
+allocateBuffer(int buffer_num) bool
+getElementSize() size_t
+getDataList() unsigned char*
+signal()
+waitFor(uint64_t timeout_usec) bool
+isUpdated() bool
+setDataExpiryTime_us(uint64_t time_us)
}
class PublisherT {
-string shm_name
-int shm_buf_num
-PERM shm_perm
-unique_ptr_SharedMemory shared_memory
-unique_ptr_RingBuffer ring_buffer
-size_t data_size
+Publisher(string name, int buffer_num, PERM perm)
+publish(const T& data)
}
class SubscriberT {
-string shm_name
-unique_ptr_SharedMemory shared_memory
-unique_ptr_RingBuffer ring_buffer
-int current_reading_buffer
-uint64_t data_expiry_time_us
+Subscriber(string name)
+subscribe(bool* state) T
+waitFor(uint64_t timeout_usec) bool
+setDataExpiryTime_us(uint64_t time_us)
}
SharedMemory <|-- SharedMemoryPosix
PublisherT *-- SharedMemory
PublisherT *-- RingBuffer
SubscriberT *-- SharedMemory
SubscriberT *-- RingBuffer
Shared Memory Layout
graph TB
subgraph "Shared Memory Segment"
subgraph "Metadata Area"
MUTEX[pthread_mutex_t]
COND[pthread_cond_t]
ESIZE[element_size]
BUFNUM[buffer_num]
end
subgraph "Timestamp Area"
TS0["timestamp[0]"]
TS1["timestamp[1]"]
TS2["timestamp[2]"]
TSN["timestamp[n-1]"]
end
subgraph "Data Area"
DATA0["data_buffer[0]"]
DATA1["data_buffer[1]"]
DATA2["data_buffer[2]"]
DATAN["data_buffer[n-1]"]
end
end
MUTEX --> COND
COND --> ESIZE
ESIZE --> BUFNUM
BUFNUM --> TS0
TS0 --> TS1
TS1 --> TS2
TS2 --> TSN
TSN --> DATA0
DATA0 --> DATA1
DATA1 --> DATA2
DATA2 --> DATAN
Data Flow
Publish Process Flow
sequenceDiagram
participant App as Application
participant Pub as Publisher
participant RB as RingBuffer
participant SM as SharedMemory
App->>+Pub: publish(data)
Pub->>+RB: getOldestBufferNum()
RB-->>-Pub: buffer_index
loop Maximum 10 retries
Pub->>+RB: allocateBuffer(buffer_index)
alt Buffer allocation success
RB-->>-Pub: true
else Buffer allocation failure
RB-->>Pub: false
Note over Pub: Wait 1ms
Pub->>RB: getOldestBufferNum()
RB-->>Pub: new buffer_index
end
end
Pub->>SM: Copy data to buffer
Pub->>RB: setTimestamp_us(current_time, buffer_index)
Pub->>RB: signal()
Note over RB: Notify waiting Subscribers
Pub-->>-App: Process complete
Subscribe Process Flow
sequenceDiagram
participant App as Application
participant Sub as Subscriber
participant RB as RingBuffer
participant SM as SharedMemory
App->>+Sub: subscribe(&is_success)
alt Shared memory disconnected
Sub->>+SM: connect()
SM-->>-Sub: Connection result
alt Connection failed
Sub-->>App: (default_value, false)
end
Sub->>RB: Create new RingBuffer instance
end
Sub->>+RB: getNewestBufferNum()
RB-->>-Sub: buffer_index
alt No valid buffer found
Sub-->>App: (previous_value, false)
else Valid buffer found
Sub->>SM: Copy data from buffer
Sub-->>-App: (data, true)
end
waitFor Process Flow
sequenceDiagram
participant App as Application
participant Sub as Subscriber
participant RB as RingBuffer
App->>+Sub: waitFor(timeout_usec)
alt Shared memory disconnected
Sub->>Sub: Reconnection process
alt Reconnection failed
Sub-->>App: false
end
end
Sub->>+RB: waitFor(timeout_usec)
Note over RB: Wait with pthread_cond_timedwait
alt Signal received before timeout
RB-->>-Sub: true
Sub-->>-App: true
else Timeout
RB-->>Sub: false
Sub-->>App: false
end
📡 Communication Protocol
Ring Buffer Algorithm
Buffer Selection Algorithm
flowchart TD
Start([Start]) --> GetOldest[Identify buffer with oldest timestamp]
GetOldest --> TryAlloc{Try buffer allocation}
TryAlloc -->|Success| WriteData[Write data]
TryAlloc -->|Failure| CheckRetry{Retry count < 10?}
CheckRetry -->|Yes| Wait[Wait 1ms]
Wait --> GetOldest
CheckRetry -->|No| Error[Error: Buffer allocation failed]
WriteData --> UpdateTime[Update timestamp]
UpdateTime --> Signal[Send signal via condition variable]
Signal --> End([End])
Error --> End
Data Reading Algorithm
flowchart TD
Start([Start]) --> CheckConn{Shared memory connected?}
CheckConn -->|No| Reconnect[Try reconnection]
Reconnect --> ConnSuccess{Connection success?}
ConnSuccess -->|No| ReturnFail[Return failure]
ConnSuccess -->|Yes| GetNewest
CheckConn -->|Yes| GetNewest[Identify buffer with newest timestamp]
GetNewest --> ValidBuffer{Valid buffer?}
ValidBuffer -->|No| ReturnOld[Return previous value and failure flag]
ValidBuffer -->|Yes| CheckExpiry{Data within expiry time?}
CheckExpiry -->|No| ReturnOld
CheckExpiry -->|Yes| ReadData[Read data]
ReadData --> ReturnSuccess[Return data and success flag]
ReturnFail --> End([End])
ReturnOld --> End
ReturnSuccess --> End
Synchronization Mechanism
Mutex and Condition Variable
stateDiagram-v2
[*] --> Unlocked : Initial state
state Publisher {
Unlocked --> Locked : pthread_mutex_lock
Locked --> Writing : Buffer allocation success
Writing --> Unlocked : pthread_mutex_unlock + pthread_cond_signal
Locked --> Unlocked : Buffer allocation failure + pthread_mutex_unlock
}
state Subscriber {
Unlocked --> Waiting : waitFor() called
Waiting --> Unlocked : Timeout
Waiting --> Processing : Signal received
Processing --> Unlocked : Data reading complete
}
⚡ Performance Characteristics
Memory Usage
Shared memory segment size is calculated by the following formula:
total_size = metadata_size + timestamp_array_size + data_array_size
metadata_size = sizeof(pthread_mutex_t) + sizeof(pthread_cond_t) +
sizeof(size_t) + sizeof(size_t)
timestamp_array_size = sizeof(uint64_t) * buffer_num
data_array_size = element_size * buffer_num
Latency Characteristics
graph LR
subgraph "Latency Components"
A[Application Processing] --> B[Publisher Processing]
B --> C[Mutex Acquisition]
C --> D[Memory Copy]
D --> E[Timestamp Update]
E --> F[Signal Transmission]
F --> G[Subscriber Processing]
G --> H[Application Processing]
end
subgraph "Typical Times"
T1[App: ~1μs]
T2[Pub: ~2μs]
T3[Mutex: ~0.5μs]
T4[Copy: ~0.1μs]
T5[Time: ~0.1μs]
T6[Signal: ~0.5μs]
T7[Sub: ~2μs]
T8[App: ~1μs]
end
🔒 Security Considerations
Access Permissions
graph TB
subgraph "POSIX Permission Model"
Owner[Owner]
Group[Group]
Others[Others]
end
subgraph "Permission Types"
Read[Read: S_IRUSR/S_IRGRP/S_IROTH]
Write[Write: S_IWUSR/S_IWGRP/S_IWOTH]
end
subgraph "Default Settings"
Default["DEFAULT_PERM = 0666
(All users read/write)"]
end
Owner --> Read
Owner --> Write
Group --> Read
Group --> Write
Others --> Read
Others --> Write
Default -.-> Owner
Default -.-> Group
Default -.-> Others
Data Integrity
sequenceDiagram
participant P1 as Publisher 1
participant P2 as Publisher 2
participant Mutex as Mutex
participant Buffer as SharedBuffer
participant S as Subscriber
Note over P1,S: Concurrent writes from multiple Publishers
P1->>+Mutex: lock()
P2->>Mutex: lock() (blocked)
Mutex-->>-P1: Acquisition success
P1->>Buffer: Write data
P1->>Buffer: Update timestamp
P1->>+Mutex: unlock() + signal()
Mutex-->>-P2: Acquisition success
P2->>Buffer: Write data
P2->>Buffer: Update timestamp
P2->>Mutex: unlock() + signal()
S->>Buffer: Read latest data
Note over S: Gets P2's data
❌ Error Handling
Error Classification and Response
flowchart TD
Error([Error Occurred]) --> CheckType{Error Type}
CheckType -->|Initialization Error| InitError[Initialization Error]
CheckType -->|Communication Error| CommError[Communication Error]
CheckType -->|Memory Error| MemError[Memory Error]
CheckType -->|Timeout| TimeoutError[Timeout Error]
InitError --> InitActions[・Name verification
・Permission check
・POD type verification]
CommError --> CommActions[・Shared memory reconnection
・Publisher side check
・Process liveness check]
MemError --> MemActions[・Memory shortage check
・Segment deletion
・System restart]
TimeoutError --> TimeoutActions[・Timeout value adjustment
・Publisher frequency check
・System load check]
InitActions --> LogError[Output error log]
CommActions --> LogError
MemActions --> LogError
TimeoutActions --> LogError
LogError --> Recovery{Recoverable?}
Recovery -->|Yes| Retry[Retry process]
Recovery -->|No| Abort[Abort process]
Retry --> Success{Success?}
Success -->|Yes| End([Normal termination])
Success -->|No| Recovery
Abort --> End
🐍 Python Binding Design
Boost.Python Wrapper Structure
classDiagram
class PublisherBool {
+PublisherBool(string name, bool arg, int buffer_num)
+_publish(bool data)
}
class PublisherInt {
+PublisherInt(string name, int arg, int buffer_num)
+_publish(int data)
}
class PublisherFloat {
+PublisherFloat(string name, float arg, int buffer_num)
+_publish(float data)
}
class SubscriberBool {
+SubscriberBool(string name, bool arg)
+_subscribe() (bool,bool)
}
class SubscriberInt {
+SubscriberInt(string name, int arg)
+_subscribe() (int,bool)
}
class SubscriberFloat {
+SubscriberFloat(string name, float arg)
+_subscribe() (float,bool)
}
class Publisher {
<<C++ Template>>
}
class Subscriber {
<<C++ Template>>
}
Publisher <|-- PublisherBool
Publisher <|-- PublisherInt
Publisher <|-- PublisherFloat
Subscriber <|-- SubscriberBool
Subscriber <|-- SubscriberInt
Subscriber <|-- SubscriberFloat
Python/C++ Data Conversion
sequenceDiagram
participant Py as Python App
participant Boost as Boost.Python
participant Wrapper as C++ Wrapper
participant Core as SHM Core
Note over Py,Core: Publish Process
Py->>+Boost: pub.publish(data)
Boost->>+Wrapper: _publish(converted_data)
Note over Boost: Python type → C++ type conversion
Wrapper->>+Core: publish(data)
Core-->>-Wrapper: void
Wrapper-->>-Boost: void
Boost-->>-Py: None
Note over Py,Core: Subscribe Process
Py->>+Boost: data, success = sub.subscribe()
Boost->>+Wrapper: _subscribe()
Wrapper->>+Core: subscribe(&is_success)
Core-->>-Wrapper: result_data
Wrapper-->>-Boost: make_tuple(result_data, is_success)
Note over Boost: C++ type → Python type conversion
Boost-->>-Py: (data, success)
🔧 Extensibility Considerations
Adding New Data Types
flowchart TD
Start([Add new type T]) --> CheckPOD{POD type?}
CheckPOD -->|Yes| UseTemplate[Use existing templates]
CheckPOD -->|No| Specialize[Template specialization]
UseTemplate --> InstantiateC["Instantiate PublisherT,
SubscriberT in C++"]
Specialize --> CustomImpl["Custom implementation
・Serialization
・Deserialization"]
CustomImpl --> InstantiateC
InstantiateC --> PythonNeeded{Python support needed?}
PythonNeeded -->|Yes| CreateWrapper["Create Boost.Python wrapper
・PublisherT
・SubscriberT"]
PythonNeeded -->|No| TestC[Implement C++ tests]
CreateWrapper --> UpdateModule[Add to BOOST_PYTHON_MODULE]
UpdateModule --> TestPy[Implement Python tests]
TestPy --> TestC
TestC --> Document[Update documentation]
Document --> End([Complete])
📚 References
man shm_overview
The following URL provides an overview of POSIX shared memory: https://linuxjm.osdn.jp/html/LDP_man-pages/man7/shm_overview.7.html
Related Technical Specifications
- POSIX.1-2001 Shared memory objects
- POSIX.1-2001 pthread mutex and condition variables
- C++11 Standard layout types
- Boost.Python 1.75+ Python bindings
Additional Resources
- Linux Kernel Documentation -
/dev/shm
filesystem implementation
- POSIX Real-time Extensions - Inter-process synchronization
- C++ Core Guidelines - Memory safety and RAII patterns
- Boost Documentation - Python/C++ integration best practices
📋 Technical Note: This specification provides comprehensive documentation for the SHM library architecture, enabling developers and designers to understand, extend, and maintain the system effectively. For implementation examples, see the tutorial documentation.