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
9Module Name:
10
11 shmemory/shmemory.c
12
13Abstract:
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
30SET_DEFAULT_DEBUG_CHANNEL(SHMEM);
31
32/* Type definitions ***********************************************************/
33
34/*
35SHM_HEADER
36Global information about the shared memory system
37
38The spinlock is used to ensure that only one process accesses shared memory at
39the same time. A process can only take the spinlock if its contents is 0, and
40it takes the spinlock by placing its PID in it. (this allows a process to catch
41the special case where it tries to take a spinlock it already owns.
42*/
43
44typedef struct
45{
46 Volatile<pid_t> spinlock;
47 Volatile<SHMPTR> shm_info[SIID_LAST]; /* basic blocks of shared information.*/
48} SHM_HEADER;
49
50static SHM_HEADER shm_header;
51
52/* Static variables ***********************************************************/
53
54/* Critical section to ensure that only one thread at a time accesses shared
55memory. 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*/
63static CRITICAL_SECTION shm_critsec;
64
65/* number of locks the process currently holds (SHMLock calls without matching
66SHMRelease). Because we take the critical section while inside a
67SHMLock/SHMRelease pair, this is actually the number of locks held by a single
68thread. */
69static 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 */
73static Volatile<HANDLE> locking_thread;
74
75/* Public function implementations ********************************************/
76
77/*++
78SHMInitialize
79
80Hook this process into the PAL shared memory system; initialize the shared
81memory if no other process has done it.
82
83--*/
84BOOL 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/*++
104SHMCleanup
105
106Release all shared memory resources held; remove ourselves from the list of
107registered processes, and remove all shared memory files if no process remains
108
109Note that this function does not use thread suspension wrapper for unlink and free
110because all thread objects are deleted before this function is called
111in PALCommonCleanup.
112
113--*/
114void 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/*++
139SHMLock
140
141Restrict shared memory access to the current thread of the current process
142
143(no parameters)
144
145Return value :
146 New lock count
147
148Notes :
149see comments at the declaration of shm_critsec for rationale of critical
150section usage
151--*/
152int 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/*++
249SHMRelease
250
251Release a lock on shared memory taken with SHMLock.
252
253(no parameters)
254
255Return value :
256 New lock count
257
258--*/
259int 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/*++
313Function :
314 SHMGetInfo
315
316 Retrieve some information from shared memory
317
318Parameters :
319 SHM_INFO_ID element : identifier of element to retrieve
320
321Return value :
322 Value of specified element
323
324Notes :
325 The SHM lock should be held while manipulating shared memory
326--*/
327SHMPTR 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/*++
352Function :
353 SHMSetInfo
354
355 Place some information into shared memory
356
357Parameters :
358 SHM_INFO_ID element : identifier of element to save
359 SHMPTR value : new value of element
360
361Return value :
362 TRUE if successfull, FALSE otherwise.
363
364Notes :
365 The SHM lock should be held while manipulating shared memory
366--*/
367BOOL 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}