summaryrefslogtreecommitdiff
path: root/base/SharedMemory.h
blob: a8337c92011a1ee6b885a57c4af0fcf5c2e1d1ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
// Copyright 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once

#ifdef _WIN32
#ifdef _MSC_VER
#include "base\msvc.h"
#include <windows.h>
#else
#include <windows.h>
#endif  // _MSC_VER
#endif  // _WIN32

#include <string>

namespace android {

namespace base {

// SharedMemory - A class to share memory between 2 process. The region can
// be shared in 2 modes:
//
// - As shared memory (/dev/shm, memory page file in windows)
// - Using memory mapped files backed by a file on the file system.
//
// To use memory mapped files prefix the handle with "file://" followed
// by an absolute path. For example: file:///c:/WINDOWS/shared.mem or
// file:///tmp/shared.mem
//
// Usage examples:
// Proc1: The owner
//    StringView message = "Hello world!";
//    SharedMemory writer("foo", 4096);
//    writer.create(0600);
//    memcpy(*writer, message.c_str(), message.size());
//
// Proc2: The observer
//    SharedMemory reader("foo", 4096);
//    reader.open(SharedMemory::AccessMode::READ_ONLY);
//    StringView read((const char*) *reader));
//
// Using file backed ram:
//
// Proc1: The owner
//    StringView message = "Hello world!";
//    SharedMemory writer("file:///abs/path/to/a/file", 4096);
//    writer.create(0600);
//    memcpy(*writer, message.c_str(), message.size());
//
// Proc2: The observer
//    SharedMemory reader("file::///abs/path/to/a/file", 4096);
//    reader.open(SharedMemory::AccessMode::READ_ONLY);
//    StringView read((const char*) *reader));
//
//   It is not possible to find out the size of an in memory shared region on
//   Win32 (boo!), there are undocumented workaround (See:
//   https://stackoverflow.com/questions/34860452/extracting-shared-memorys-size/47951175#47951175)
//   that we are not using.
//
//   For this reason the size has to be explicitly set in the
//   constructor. As a workaround you could write the region size in the first
//   few bytes of the region, or use a different channel to exchange region
//   size.
//
//   Shared memory behaves differently on Win32 vs Posix. You as a user must be
//   very well aware of these differences, or run into unexpected results on
//   different platforms:
//
// Posix (linux/macos):
//  - There is a notion of an OWNER of a SharedMemory region. The one to call
//    create will own the region. If this object goes out of scope the region
//    will be unlinked, meaning that mappings (calls to open) will fail. As
//    soon as all other references to the shared memory go away the handle will
//    disappear from /dev/shm as well.
//  - Multiple calls to create for the same region can cause undefined behavior
//    due to closing and potential resizing of the shared memory.
//  - Shared memory can outlive processes that are using it. So don't crash
//    while a shared object is still alive.
//  - Access control is observed by mode permissions
// Mac:
//  - The name cannot exceed 30 chars.
// Win32:
//   - Names are prefixed with SHM_ to prevent collision with other objects
//     in windows;
//   - There is no notion of an owner. The OS will release the region as
//     soon as all references to a region disappear.
//   - The first call to create will determine the size of the region.
//     According to msdn regions cannot grow. Multiple calls to create
//     have no effect, and behave like open. (Note, you can grow size according
//     to https://blogs.msdn.microsoft.com/oldnewthing/20150130-00/?p=44793)
//   - Win32 does not officially support determining the size of a shared
//     region.
//   - The access control lists (ACL) in the default security descriptor for
//     a file mapping object come from the primary or impersonation token of
//     the creator.
//
// If the shared memory region is backed by a file you must keep the following
// things in mind:
//
// Win32:
//  - If an application specifies a size for the file mapping object that
//    is larger than the size of the actual named file on disk and if the page
//    protection allows write access (that is, the flProtect parameter specifies
//    PAGE_READWRITE or PAGE_EXECUTE_READWRITE), then the file on disk is
//    increased to match the specified size of the file mapping object. If the
//    file is extended, the contents of the file between the old end of the file
//    and the new end of the file are not guaranteed to be zero; the behavior is
//    defined by the file system. If the file on disk cannot be increased,
//    CreateFileMapping fails and GetLastError returns ERROR_DISK_FULL.
//    In practice this means that the shared memory region will not contain
//    useful data upon creation.
//  - A sharing object will be created for each view on the file.
//  - The memory mapped file will be deleted by the owner upond destruction.
//    this is however not guaranteed!
// Posix:
//  - The notion of ownership is handled at the filesystem level. Usually this
//    means that a memory mapped file will be deleted once all references have
//    been removed.
//  - The memory mapped file will be deleted by the owner upond destruction.
//    this is however not guaranteed!
class SharedMemory {
public:
#ifdef _WIN32
    using memory_type = void*;
    using handle_type = HANDLE;
    constexpr static handle_type invalidHandle() {
        // This is the invalid return value for
        // CreateFileMappingW; INVALID_HANDLE_VALUE
        // could mean the paging file on Windows.
        return NULL;
    }
    constexpr static memory_type unmappedMemory() { return nullptr; }
#else
    using memory_type = void*;
    using handle_type = int;
    constexpr static handle_type invalidHandle() { return -1; }
    static memory_type unmappedMemory() {
        return reinterpret_cast<memory_type>(-1);
    }
#endif
    enum class AccessMode { READ_ONLY, READ_WRITE };
    enum class ShareType { SHARED_MEMORY, FILE_BACKED };

    // Creates a SharedMemory region either backed by a shared memory handle
    // or by a file. If the string uriOrHandle starts with `file://` it will be
    // file backed otherwise it will be a named shared memory region.
    // |uriHandle| A file:// uri or handle
    // |size| Size of the desired shared memory region. Cannot change after
    // creation.
    SharedMemory(const std::string& uriOrHandle, size_t size);
    ~SharedMemory() { close(); }

    SharedMemory(SharedMemory&& other) {
        mName = std::move(other.mName);
        mSize = other.mSize;
        mAddr = other.mAddr;
        mFd = other.mFd;
        mCreate = other.mCreate;
        mShareType = other.mShareType;
        other.clear();
    }

    SharedMemory& operator=(SharedMemory&& other) {
        mName = std::move(other.mName);
        mSize = other.mSize;
        mAddr = other.mAddr;
        mShareType = other.mShareType;
        mFd = other.mFd;
        mCreate = other.mCreate;

        other.clear();
        return *this;
    }

    // Let's not have any weirdness due to double unlinks due to destructors.
    SharedMemory(const SharedMemory&) = delete;
    SharedMemory& operator=(const SharedMemory&) = delete;

    // Creates a shared region, you will be considered the owner, and will have
    // write access. Returns 0 on success, or an negative error code otheriwse.
    // The error code (errno) is platform dependent.
    int create(mode_t mode);
    // Creates a shared object in the same manner as create(), except for
    // performing actual mapping.
    int createNoMapping(mode_t mode);

    // Opens the shared memory object, returns 0 on success
    // or the negative error code.
    // The shared memory object will be mapped.
    int open(AccessMode access);

    bool isOpen() const;
    void close(bool forceDestroy = false);

    size_t size() const { return mSize; }
    std::string name() const { return mName; }
    memory_type get() const { return mAddr; }
    memory_type operator*() const { return get(); }
    ShareType type() const { return mShareType; }
    handle_type getFd() { return mFd; }
    bool isMapped() const { return mAddr != unmappedMemory(); }

private:
#ifdef _WIN32
    int openInternal(AccessMode access, bool doMapping = true);
#else
    int openInternal(int oflag, int mode, bool doMapping = true);
#endif

    void clear() {
        mSize = 0;
        mName = "";
        mCreate = false;
        mFd = invalidHandle();
        mAddr = unmappedMemory();
    }

    memory_type mAddr = unmappedMemory();
    handle_type mFd = invalidHandle();
    handle_type mFile = invalidHandle();
    bool mCreate = false;

    std::string mName;
    size_t mSize;
    ShareType mShareType;
};
}  // namespace base
}  // namespace android