1 | // Copyright 2019 The SwiftShader Authors. All Rights Reserved. |
2 | // |
3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | // you may not use this file except in compliance with the License. |
5 | // You may obtain a copy of the License at |
6 | // |
7 | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | // |
9 | // Unless required by applicable law or agreed to in writing, software |
10 | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | // See the License for the specific language governing permissions and |
13 | // limitations under the License. |
14 | |
15 | #ifndef VK_SEMAPHORE_EXTERNAL_LINUX_H_ |
16 | #define VK_SEMAPHORE_EXTERNAL_LINUX_H_ |
17 | |
18 | #include "System/Linux/MemFd.hpp" |
19 | #include "System/Memory.hpp" |
20 | |
21 | #include <errno.h> |
22 | #include <pthread.h> |
23 | #include <string.h> |
24 | #include <sys/mman.h> |
25 | |
26 | // An external semaphore implementation for Linux, that uses memfd-backed |
27 | // shared memory regions as the underlying implementation. The region contains |
28 | // a single SharedSemaphore instance, which is a reference-counted semaphore |
29 | // implementation based on a pthread process-shared mutex + condition variable |
30 | // pair. |
31 | // |
32 | // This implementation works on any Linux system with at least kernel 3.17 |
33 | // (which should be sufficient for any not-so-recent Android system) and doesn't |
34 | // require special libraries installed on the system. |
35 | // |
36 | // NOTE: This is not interoperable with other Linux ICDs that use Linux kernel |
37 | // sync file objects (which correspond to |
38 | // VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) instead. |
39 | // |
40 | |
41 | // A process-shared semaphore implementation that can be stored in |
42 | // a process-shared memory region. It also includes a reference count to |
43 | // ensure it is only destroyed when the last reference to it is dropped. |
44 | class SharedSemaphore |
45 | { |
46 | public: |
47 | SharedSemaphore() |
48 | { |
49 | pthread_mutexattr_t mattr; |
50 | pthread_mutexattr_init(&mattr); |
51 | pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); |
52 | pthread_mutex_init(&mutex, &mattr); |
53 | pthread_mutexattr_destroy(&mattr); |
54 | |
55 | pthread_condattr_t cattr; |
56 | pthread_condattr_init(&cattr); |
57 | pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); |
58 | pthread_cond_init(&cond, &cattr); |
59 | pthread_condattr_destroy(&cattr); |
60 | } |
61 | |
62 | ~SharedSemaphore() |
63 | { |
64 | pthread_cond_destroy(&cond); |
65 | pthread_mutex_destroy(&mutex); |
66 | } |
67 | |
68 | // Increment reference count. |
69 | void addRef() |
70 | { |
71 | pthread_mutex_lock(&mutex); |
72 | ref_count++; |
73 | pthread_mutex_unlock(&mutex); |
74 | } |
75 | |
76 | // Decrement reference count and returns true iff it reaches 0. |
77 | bool deref() |
78 | { |
79 | pthread_mutex_lock(&mutex); |
80 | bool result = (--ref_count == 0); |
81 | pthread_mutex_unlock(&mutex); |
82 | return result; |
83 | } |
84 | |
85 | void wait() |
86 | { |
87 | pthread_mutex_lock(&mutex); |
88 | while (!signaled) |
89 | { |
90 | pthread_cond_wait(&cond, &mutex); |
91 | } |
92 | // From Vulkan 1.1.119 spec, section 6.4.2: |
93 | // Unlike fences or events, the act of waiting for a semaphore also |
94 | // unsignals that semaphore. |
95 | signaled = false; |
96 | pthread_mutex_unlock(&mutex); |
97 | } |
98 | |
99 | // Just like wait() but never blocks. Returns true if the semaphore |
100 | // was signaled (and reset by the function), or false otherwise. |
101 | // Used to avoid using a background thread for waiting in the case |
102 | // where the semaphore is already signaled. |
103 | bool tryWait() |
104 | { |
105 | pthread_mutex_lock(&mutex); |
106 | bool result = signaled; |
107 | if (result) |
108 | { |
109 | signaled = false; |
110 | } |
111 | pthread_mutex_unlock(&mutex); |
112 | return result; |
113 | } |
114 | |
115 | void signal() |
116 | { |
117 | pthread_mutex_lock(&mutex); |
118 | signaled = true; |
119 | pthread_cond_broadcast(&cond); |
120 | pthread_mutex_unlock(&mutex); |
121 | } |
122 | |
123 | private: |
124 | pthread_mutex_t mutex; |
125 | pthread_cond_t cond; |
126 | int ref_count = 1; |
127 | bool signaled = false; |
128 | }; |
129 | |
130 | namespace vk |
131 | { |
132 | |
133 | class Semaphore::External { |
134 | public: |
135 | // The type of external semaphore handle types supported by this implementation. |
136 | static const VkExternalSemaphoreHandleTypeFlags kExternalSemaphoreHandleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT; |
137 | |
138 | // Default constructor. Note that one should call either init() or |
139 | // importFd() before any call to wait() or signal(). |
140 | External() = default; |
141 | |
142 | ~External() { close(); } |
143 | |
144 | // Initialize instance by creating a new shared memory region. |
145 | void init() |
146 | { |
147 | // Allocate or import the region's file descriptor. |
148 | const size_t size = sw::memoryPageSize(); |
149 | // To be exportable, the PosixSemaphore must be stored in a shared |
150 | // memory region. |
151 | static int counter = 0; |
152 | char name[40]; |
153 | snprintf(name, sizeof(name), "SwiftShader.Semaphore.%d" , ++counter); |
154 | if (!memfd.allocate(name, size)) |
155 | { |
156 | ABORT("memfd.allocate() returned %s" , strerror(errno)); |
157 | } |
158 | mapRegion(size, true); |
159 | } |
160 | |
161 | // Import an existing semaphore through its file descriptor. |
162 | VkResult importFd(int fd) |
163 | { |
164 | close(); |
165 | memfd.importFd(fd); |
166 | mapRegion(sw::memoryPageSize(), false); |
167 | return VK_SUCCESS; |
168 | } |
169 | |
170 | // Export the current semaphore as a duplicated file descriptor to the same |
171 | // region. This can be consumed by importFd() running in a different |
172 | // process. |
173 | VkResult exportFd(int* pFd) const |
174 | { |
175 | int fd = memfd.exportFd(); |
176 | if (fd < 0) |
177 | { |
178 | return VK_ERROR_INVALID_EXTERNAL_HANDLE; |
179 | } |
180 | *pFd = fd; |
181 | return VK_SUCCESS; |
182 | } |
183 | |
184 | void wait() |
185 | { |
186 | semaphore->wait(); |
187 | } |
188 | |
189 | bool tryWait() |
190 | { |
191 | return semaphore->tryWait(); |
192 | } |
193 | |
194 | void signal() |
195 | { |
196 | semaphore->signal(); |
197 | } |
198 | |
199 | private: |
200 | // Unmap the semaphore if needed and close its file descriptor. |
201 | void close() |
202 | { |
203 | if (semaphore) |
204 | { |
205 | if (semaphore->deref()) |
206 | { |
207 | semaphore->~SharedSemaphore(); |
208 | } |
209 | memfd.unmap(semaphore, sw::memoryPageSize()); |
210 | memfd.close(); |
211 | semaphore = nullptr; |
212 | } |
213 | } |
214 | |
215 | // Remap the shared region and setup the semaphore or increment its reference count. |
216 | void mapRegion(size_t size, bool needInitialization) |
217 | { |
218 | // Map the region into memory and point the semaphore to it. |
219 | void* addr = memfd.mapReadWrite(0, size); |
220 | if (!addr) |
221 | { |
222 | ABORT("mmap() failed: %s" , strerror(errno)); |
223 | } |
224 | semaphore = reinterpret_cast<SharedSemaphore *>(addr); |
225 | if (needInitialization) |
226 | { |
227 | new (semaphore) SharedSemaphore(); |
228 | } |
229 | else |
230 | { |
231 | semaphore->addRef(); |
232 | } |
233 | } |
234 | |
235 | LinuxMemFd memfd; |
236 | SharedSemaphore* semaphore = nullptr; |
237 | }; |
238 | |
239 | } // namespace vk |
240 | |
241 | #endif // VK_SEMAPHORE_EXTERNAL_LINUX_H_ |
242 | |