1 | // Licensed to the .NET Foundation under one or more agreements. |
2 | // The .NET Foundation licenses this file to you under the MIT license. |
3 | // See the LICENSE file in the project root for more information. |
4 | |
5 | /*++ |
6 | |
7 | |
8 | |
9 | Module Name: |
10 | |
11 | shmemory/shmemory.c |
12 | |
13 | Abstract: |
14 | |
15 | Implementation of shared memory infrastructure for IPC |
16 | |
17 | |
18 | |
19 | --*/ |
20 | |
21 | #include "pal/dbgmsg.h" |
22 | #include "pal/shmemory.h" |
23 | #include "pal/critsect.h" |
24 | #include "pal/process.h" |
25 | |
26 | #if HAVE_YIELD_SYSCALL |
27 | #include <sys/syscall.h> |
28 | #endif /* HAVE_YIELD_SYSCALL */ |
29 | |
30 | SET_DEFAULT_DEBUG_CHANNEL(SHMEM); |
31 | |
32 | /* Type definitions ***********************************************************/ |
33 | |
34 | /* |
35 | SHM_HEADER |
36 | Global information about the shared memory system |
37 | |
38 | The spinlock is used to ensure that only one process accesses shared memory at |
39 | the same time. A process can only take the spinlock if its contents is 0, and |
40 | it takes the spinlock by placing its PID in it. (this allows a process to catch |
41 | the special case where it tries to take a spinlock it already owns. |
42 | */ |
43 | |
44 | typedef struct |
45 | { |
46 | Volatile<pid_t> spinlock; |
47 | Volatile<SHMPTR> shm_info[SIID_LAST]; /* basic blocks of shared information.*/ |
48 | } ; |
49 | |
50 | static SHM_HEADER ; |
51 | |
52 | /* Static variables ***********************************************************/ |
53 | |
54 | /* Critical section to ensure that only one thread at a time accesses shared |
55 | memory. Rationale : |
56 | -Using a spinlock means that processes must busy-wait for the lock to be |
57 | available. The critical section ensures taht only one thread will busy-wait, |
58 | while the rest are put to sleep. |
59 | -Since the spinlock only contains a PID, it isn't possible to make a difference |
60 | between threads of the same process. This could be resolved by using 2 |
61 | spinlocks, but this would introduce more busy-wait. |
62 | */ |
63 | static CRITICAL_SECTION shm_critsec; |
64 | |
65 | /* number of locks the process currently holds (SHMLock calls without matching |
66 | SHMRelease). Because we take the critical section while inside a |
67 | SHMLock/SHMRelease pair, this is actually the number of locks held by a single |
68 | thread. */ |
69 | static Volatile<LONG> lock_count; |
70 | |
71 | /* thread ID of thread holding the SHM lock. used for debugging purposes : |
72 | SHMGet/SetInfo will verify that the calling thread holds the lock */ |
73 | static Volatile<HANDLE> locking_thread; |
74 | |
75 | /* Public function implementations ********************************************/ |
76 | |
77 | /*++ |
78 | SHMInitialize |
79 | |
80 | Hook this process into the PAL shared memory system; initialize the shared |
81 | memory if no other process has done it. |
82 | |
83 | --*/ |
84 | BOOL SHMInitialize(void) |
85 | { |
86 | InternalInitializeCriticalSection(&shm_critsec); |
87 | |
88 | TRACE("Now initializing global shared memory system\n" ); |
89 | |
90 | InterlockedExchange((LONG *)&shm_header.spinlock, 0); |
91 | |
92 | /* SHM information array starts with NULLs */ |
93 | memset((void *)shm_header.shm_info, 0, SIID_LAST*sizeof(SHMPTR)); |
94 | |
95 | TRACE("Global shared memory initialization complete.\n" ); |
96 | |
97 | lock_count = 0; |
98 | locking_thread = 0; |
99 | |
100 | return TRUE; |
101 | } |
102 | |
103 | /*++ |
104 | SHMCleanup |
105 | |
106 | Release all shared memory resources held; remove ourselves from the list of |
107 | registered processes, and remove all shared memory files if no process remains |
108 | |
109 | Note that this function does not use thread suspension wrapper for unlink and free |
110 | because all thread objects are deleted before this function is called |
111 | in PALCommonCleanup. |
112 | |
113 | --*/ |
114 | void SHMCleanup(void) |
115 | { |
116 | pid_t my_pid; |
117 | |
118 | TRACE("Starting shared memory cleanup\n" ); |
119 | |
120 | SHMLock(); |
121 | SHMRelease(); |
122 | |
123 | /* We should not be holding the spinlock at this point. If we are, release |
124 | the spinlock. by setting it to 0 */ |
125 | my_pid = gPID; |
126 | |
127 | _ASSERT_MSG(shm_header.spinlock != my_pid, |
128 | "SHMCleanup called while the current process still owns the lock " |
129 | "[owner thread=%u, current thread: %u]\n" , |
130 | locking_thread.Load(), THREADSilentGetCurrentThreadId()); |
131 | |
132 | /* Now for the interprocess stuff. */ |
133 | DeleteCriticalSection(&shm_critsec); |
134 | |
135 | TRACE("SHMCleanup complete!\n" ); |
136 | } |
137 | |
138 | /*++ |
139 | SHMLock |
140 | |
141 | Restrict shared memory access to the current thread of the current process |
142 | |
143 | (no parameters) |
144 | |
145 | Return value : |
146 | New lock count |
147 | |
148 | Notes : |
149 | see comments at the declaration of shm_critsec for rationale of critical |
150 | section usage |
151 | --*/ |
152 | int SHMLock(void) |
153 | { |
154 | /* Hold the critical section until the lock is released */ |
155 | PALCEnterCriticalSection(&shm_critsec); |
156 | |
157 | _ASSERTE((0 == lock_count && 0 == locking_thread) || |
158 | (0 < lock_count && (HANDLE)pthread_self() == locking_thread)); |
159 | |
160 | if(lock_count == 0) |
161 | { |
162 | pid_t my_pid, tmp_pid; |
163 | int spincount = 1; |
164 | |
165 | TRACE("First-level SHM lock : taking spinlock\n" ); |
166 | |
167 | // Store the id of the current thread as the (only) one that is |
168 | // trying to grab the spinlock from the current process |
169 | locking_thread = (HANDLE)pthread_self(); |
170 | |
171 | my_pid = gPID; |
172 | |
173 | while(TRUE) |
174 | { |
175 | // |
176 | // Try to grab the spinlock |
177 | // |
178 | tmp_pid = InterlockedCompareExchange((LONG *) &shm_header.spinlock, my_pid,0); |
179 | |
180 | if (0 == tmp_pid) |
181 | { |
182 | // Spinlock acquired: break out of the loop |
183 | break; |
184 | } |
185 | |
186 | /* Check if lock holder is alive. If it isn't, we can reset the |
187 | spinlock and try to take it again. If it is, we have to wait. |
188 | We use "spincount" to do this check only every 8th time through |
189 | the loop, for performance reasons.*/ |
190 | if( (0 == (spincount&0x7)) && |
191 | (-1 == kill(tmp_pid,0)) && |
192 | (errno == ESRCH)) |
193 | { |
194 | TRACE("SHM spinlock owner (%08x) is dead; releasing its lock\n" , |
195 | tmp_pid); |
196 | |
197 | InterlockedCompareExchange((LONG *) &shm_header.spinlock, 0, tmp_pid); |
198 | } |
199 | else |
200 | { |
201 | /* another process is holding the lock... we want to yield and |
202 | give the holder a chance to release the lock |
203 | The function sched_yield() only yields to a thread in the |
204 | current process; this doesn't help us much, and doesn't help |
205 | at all if there's only 1 thread. There doesn't seem to be |
206 | any clean way to force a yield to another process, but the |
207 | FreeBSD syscall "yield" does the job. We alternate between |
208 | both methods to give other threads of this process a chance |
209 | to run while we wait. |
210 | */ |
211 | #if HAVE_YIELD_SYSCALL |
212 | if(spincount&1) |
213 | { |
214 | #endif /* HAVE_YIELD_SYSCALL */ |
215 | sched_yield(); |
216 | #if HAVE_YIELD_SYSCALL |
217 | } |
218 | else |
219 | { |
220 | /* use the syscall first, since we know we'l need to yield |
221 | to another process eventually - the lock can't be held |
222 | by the current process, thanks to the critical section */ |
223 | syscall(SYS_yield, 0); |
224 | } |
225 | #endif /* HAVE_YIELD_SYSCALL */ |
226 | } |
227 | |
228 | // Increment spincount |
229 | spincount++; |
230 | } |
231 | |
232 | _ASSERT_MSG(my_pid == shm_header.spinlock, |
233 | "\n(my_pid = %u) != (header->spinlock = %u)\n" |
234 | "tmp_pid = %u\n" |
235 | "spincount = %d\n" |
236 | "locking_thread = %u\n" , |
237 | (DWORD)my_pid, (DWORD)shm_header.spinlock, |
238 | (DWORD)tmp_pid, |
239 | (int)spincount, |
240 | (HANDLE)locking_thread); |
241 | } |
242 | |
243 | lock_count++; |
244 | TRACE("SHM lock level is now %d\n" , lock_count.Load()); |
245 | return lock_count; |
246 | } |
247 | |
248 | /*++ |
249 | SHMRelease |
250 | |
251 | Release a lock on shared memory taken with SHMLock. |
252 | |
253 | (no parameters) |
254 | |
255 | Return value : |
256 | New lock count |
257 | |
258 | --*/ |
259 | int SHMRelease(void) |
260 | { |
261 | /* prevent a thread from releasing another thread's lock */ |
262 | PALCEnterCriticalSection(&shm_critsec); |
263 | |
264 | if(lock_count==0) |
265 | { |
266 | ASSERT("SHMRelease called without matching SHMLock!\n" ); |
267 | PALCLeaveCriticalSection(&shm_critsec); |
268 | return 0; |
269 | } |
270 | |
271 | lock_count--; |
272 | |
273 | _ASSERTE(lock_count >= 0); |
274 | |
275 | /* If lock count is 0, this call matches the first Lock call; it's time to |
276 | set the spinlock back to 0. */ |
277 | if(lock_count == 0) |
278 | { |
279 | pid_t my_pid, tmp_pid; |
280 | |
281 | TRACE("Releasing first-level SHM lock : resetting spinlock\n" ); |
282 | |
283 | my_pid = gPID; |
284 | |
285 | /* Make sure we don't touch the spinlock if we don't own it. We're |
286 | supposed to own it if we get here, but just in case... */ |
287 | tmp_pid = InterlockedCompareExchange((LONG *) &shm_header.spinlock, 0, my_pid); |
288 | |
289 | if (tmp_pid != my_pid) |
290 | { |
291 | ASSERT("Process 0x%08x tried to release spinlock owned by process " |
292 | "0x%08x! \n" , my_pid, tmp_pid); |
293 | PALCLeaveCriticalSection(&shm_critsec); |
294 | return 0; |
295 | } |
296 | |
297 | /* indicate no thread (in this process) holds the SHM lock */ |
298 | locking_thread = 0; |
299 | } |
300 | |
301 | TRACE("SHM lock level is now %d\n" , lock_count.Load()); |
302 | |
303 | /* This matches the EnterCriticalSection from SHMRelease */ |
304 | PALCLeaveCriticalSection(&shm_critsec); |
305 | |
306 | /* This matches the EnterCriticalSection from SHMLock */ |
307 | PALCLeaveCriticalSection(&shm_critsec); |
308 | |
309 | return lock_count; |
310 | } |
311 | |
312 | /*++ |
313 | Function : |
314 | SHMGetInfo |
315 | |
316 | Retrieve some information from shared memory |
317 | |
318 | Parameters : |
319 | SHM_INFO_ID element : identifier of element to retrieve |
320 | |
321 | Return value : |
322 | Value of specified element |
323 | |
324 | Notes : |
325 | The SHM lock should be held while manipulating shared memory |
326 | --*/ |
327 | SHMPTR SHMGetInfo(SHM_INFO_ID element) |
328 | { |
329 | SHMPTR retval = 0; |
330 | |
331 | if(element < 0 || element >= SIID_LAST) |
332 | { |
333 | ASSERT("Invalid SHM info element %d\n" , element); |
334 | return 0; |
335 | } |
336 | |
337 | /* verify that this thread holds the SHM lock. No race condition: if the |
338 | current thread is here, it can't be in SHMLock or SHMUnlock */ |
339 | if( (HANDLE)pthread_self() != locking_thread ) |
340 | { |
341 | ASSERT("SHMGetInfo called while thread does not hold the SHM lock!\n" ); |
342 | } |
343 | |
344 | retval = shm_header.shm_info[element]; |
345 | |
346 | TRACE("SHM info element %d is %08x\n" , element, retval ); |
347 | return retval; |
348 | } |
349 | |
350 | |
351 | /*++ |
352 | Function : |
353 | SHMSetInfo |
354 | |
355 | Place some information into shared memory |
356 | |
357 | Parameters : |
358 | SHM_INFO_ID element : identifier of element to save |
359 | SHMPTR value : new value of element |
360 | |
361 | Return value : |
362 | TRUE if successfull, FALSE otherwise. |
363 | |
364 | Notes : |
365 | The SHM lock should be held while manipulating shared memory |
366 | --*/ |
367 | BOOL SHMSetInfo(SHM_INFO_ID element, SHMPTR value) |
368 | { |
369 | if(element < 0 || element >= SIID_LAST) |
370 | { |
371 | ASSERT("Invalid SHM info element %d\n" , element); |
372 | return FALSE; |
373 | } |
374 | |
375 | /* verify that this thread holds the SHM lock. No race condition: if the |
376 | current thread is here, it can't be in SHMLock or SHMUnlock */ |
377 | if( (HANDLE)pthread_self() != locking_thread ) |
378 | { |
379 | ASSERT("SHMGetInfo called while thread does not hold the SHM lock!\n" ); |
380 | } |
381 | |
382 | TRACE("Setting SHM info element %d to %08x; used to be %08x\n" , |
383 | element, value, shm_header.shm_info[element].Load() ); |
384 | |
385 | shm_header.shm_info[element] = value; |
386 | |
387 | return TRUE; |
388 | } |