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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
/*++
Module Name:
shmemory/shmemory.c
Abstract:
Implementation of shared memory infrastructure for IPC
--*/
#include "pal/dbgmsg.h"
#include "pal/shmemory.h"
#include "pal/critsect.h"
#include "pal/process.h"
#if HAVE_YIELD_SYSCALL
#include <sys/syscall.h>
#endif /* HAVE_YIELD_SYSCALL */
SET_DEFAULT_DEBUG_CHANNEL(SHMEM);
/* Type definitions ***********************************************************/
/*
SHM_HEADER
Global information about the shared memory system
The spinlock is used to ensure that only one process accesses shared memory at
the same time. A process can only take the spinlock if its contents is 0, and
it takes the spinlock by placing its PID in it. (this allows a process to catch
the special case where it tries to take a spinlock it already owns.
*/
typedef struct
{
Volatile<pid_t> spinlock;
Volatile<SHMPTR> shm_info[SIID_LAST]; /* basic blocks of shared information.*/
} SHM_HEADER;
static SHM_HEADER shm_header;
/* Static variables ***********************************************************/
/* Critical section to ensure that only one thread at a time accesses shared
memory. Rationale :
-Using a spinlock means that processes must busy-wait for the lock to be
available. The critical section ensures taht only one thread will busy-wait,
while the rest are put to sleep.
-Since the spinlock only contains a PID, it isn't possible to make a difference
between threads of the same process. This could be resolved by using 2
spinlocks, but this would introduce more busy-wait.
*/
static CRITICAL_SECTION shm_critsec;
/* number of locks the process currently holds (SHMLock calls without matching
SHMRelease). Because we take the critical section while inside a
SHMLock/SHMRelease pair, this is actually the number of locks held by a single
thread. */
static Volatile<LONG> lock_count;
/* thread ID of thread holding the SHM lock. used for debugging purposes :
SHMGet/SetInfo will verify that the calling thread holds the lock */
static Volatile<HANDLE> locking_thread;
/* Public function implementations ********************************************/
/*++
SHMInitialize
Hook this process into the PAL shared memory system; initialize the shared
memory if no other process has done it.
--*/
BOOL SHMInitialize(void)
{
InternalInitializeCriticalSection(&shm_critsec);
TRACE("Now initializing global shared memory system\n");
InterlockedExchange((LONG *)&shm_header.spinlock, 0);
/* SHM information array starts with NULLs */
memset((void *)shm_header.shm_info, 0, SIID_LAST*sizeof(SHMPTR));
TRACE("Global shared memory initialization complete.\n");
lock_count = 0;
locking_thread = 0;
return TRUE;
}
/*++
SHMCleanup
Release all shared memory resources held; remove ourselves from the list of
registered processes, and remove all shared memory files if no process remains
Note that this function does not use thread suspension wrapper for unlink and free
because all thread objects are deleted before this function is called
in PALCommonCleanup.
--*/
void SHMCleanup(void)
{
pid_t my_pid;
TRACE("Starting shared memory cleanup\n");
SHMLock();
SHMRelease();
/* We should not be holding the spinlock at this point. If we are, release
the spinlock. by setting it to 0 */
my_pid = gPID;
_ASSERT_MSG(shm_header.spinlock != my_pid,
"SHMCleanup called while the current process still owns the lock "
"[owner thread=%u, current thread: %u]\n",
locking_thread.Load(), THREADSilentGetCurrentThreadId());
/* Now for the interprocess stuff. */
DeleteCriticalSection(&shm_critsec);
TRACE("SHMCleanup complete!\n");
}
/*++
SHMLock
Restrict shared memory access to the current thread of the current process
(no parameters)
Return value :
New lock count
Notes :
see comments at the declaration of shm_critsec for rationale of critical
section usage
--*/
int SHMLock(void)
{
/* Hold the critical section until the lock is released */
PALCEnterCriticalSection(&shm_critsec);
_ASSERTE((0 == lock_count && 0 == locking_thread) ||
(0 < lock_count && (HANDLE)pthread_self() == locking_thread));
if(lock_count == 0)
{
pid_t my_pid, tmp_pid;
int spincount = 1;
TRACE("First-level SHM lock : taking spinlock\n");
// Store the id of the current thread as the (only) one that is
// trying to grab the spinlock from the current process
locking_thread = (HANDLE)pthread_self();
my_pid = gPID;
while(TRUE)
{
//
// Try to grab the spinlock
//
tmp_pid = InterlockedCompareExchange((LONG *) &shm_header.spinlock, my_pid,0);
if (0 == tmp_pid)
{
// Spinlock acquired: break out of the loop
break;
}
/* Check if lock holder is alive. If it isn't, we can reset the
spinlock and try to take it again. If it is, we have to wait.
We use "spincount" to do this check only every 8th time through
the loop, for performance reasons.*/
if( (0 == (spincount&0x7)) &&
(-1 == kill(tmp_pid,0)) &&
(errno == ESRCH))
{
TRACE("SHM spinlock owner (%08x) is dead; releasing its lock\n",
tmp_pid);
InterlockedCompareExchange((LONG *) &shm_header.spinlock, 0, tmp_pid);
}
else
{
/* another process is holding the lock... we want to yield and
give the holder a chance to release the lock
The function sched_yield() only yields to a thread in the
current process; this doesn't help us much, anddoens't help
at all if there's only 1 thread. There doesn't seem to be
any clean way to force a yield to another process, but the
FreeBSD syscall "yield" does the job. We alternate between
both methods to give other threads of this process a chance
to run while we wait.
*/
#if HAVE_YIELD_SYSCALL
if(spincount&1)
{
#endif /* HAVE_YIELD_SYSCALL */
sched_yield();
#if HAVE_YIELD_SYSCALL
}
else
{
/* use the syscall first, since we know we'l need to yield
to another process eventually - the lock can't be held
by the current process, thanks to the critical section */
syscall(SYS_yield, 0);
}
#endif /* HAVE_YIELD_SYSCALL */
}
// Increment spincount
spincount++;
}
_ASSERT_MSG(my_pid == shm_header.spinlock,
"\n(my_pid = %u) != (header->spinlock = %u)\n"
"tmp_pid = %u\n"
"spincount = %d\n"
"locking_thread = %u\n",
(DWORD)my_pid, (DWORD)shm_header.spinlock,
(DWORD)tmp_pid,
(int)spincount,
(HANDLE)locking_thread);
}
lock_count++;
TRACE("SHM lock level is now %d\n", lock_count.Load());
return lock_count;
}
/*++
SHMRelease
Release a lock on shared memory taken with SHMLock.
(no parameters)
Return value :
New lock count
--*/
int SHMRelease(void)
{
/* prevent a thread from releasing another thread's lock */
PALCEnterCriticalSection(&shm_critsec);
if(lock_count==0)
{
ASSERT("SHMRelease called without matching SHMLock!\n");
PALCLeaveCriticalSection(&shm_critsec);
return 0;
}
lock_count--;
_ASSERTE(lock_count >= 0);
/* If lock count is 0, this call matches the first Lock call; it's time to
set the spinlock back to 0. */
if(lock_count == 0)
{
pid_t my_pid, tmp_pid;
TRACE("Releasing first-level SHM lock : resetting spinlock\n");
my_pid = gPID;
/* Make sure we don't touch the spinlock if we don't own it. We're
supposed to own it if we get here, but just in case... */
tmp_pid = InterlockedCompareExchange((LONG *) &shm_header.spinlock, 0, my_pid);
if (tmp_pid != my_pid)
{
ASSERT("Process 0x%08x tried to release spinlock owned by process "
"0x%08x! \n", my_pid, tmp_pid);
PALCLeaveCriticalSection(&shm_critsec);
return 0;
}
/* indicate no thread (in this process) holds the SHM lock */
locking_thread = 0;
}
TRACE("SHM lock level is now %d\n", lock_count.Load());
/* This matches the EnterCriticalSection from SHMRelease */
PALCLeaveCriticalSection(&shm_critsec);
/* This matches the EnterCriticalSection from SHMLock */
PALCLeaveCriticalSection(&shm_critsec);
return lock_count;
}
/*++
Function :
SHMGetInfo
Retrieve some information from shared memory
Parameters :
SHM_INFO_ID element : identifier of element to retrieve
Return value :
Value of specified element
Notes :
The SHM lock should be held while manipulating shared memory
--*/
SHMPTR SHMGetInfo(SHM_INFO_ID element)
{
SHMPTR retval = 0;
if(element < 0 || element >= SIID_LAST)
{
ASSERT("Invalid SHM info element %d\n", element);
return 0;
}
/* verify that this thread holds the SHM lock. No race condition: if the
current thread is here, it can't be in SHMLock or SHMUnlock */
if( (HANDLE)pthread_self() != locking_thread )
{
ASSERT("SHMGetInfo called while thread does not hold the SHM lock!\n");
}
retval = shm_header.shm_info[element];
TRACE("SHM info element %d is %08x\n", element, retval );
return retval;
}
/*++
Function :
SHMSetInfo
Place some information into shared memory
Parameters :
SHM_INFO_ID element : identifier of element to save
SHMPTR value : new value of element
Return value :
TRUE if successfull, FALSE otherwise.
Notes :
The SHM lock should be held while manipulating shared memory
--*/
BOOL SHMSetInfo(SHM_INFO_ID element, SHMPTR value)
{
if(element < 0 || element >= SIID_LAST)
{
ASSERT("Invalid SHM info element %d\n", element);
return FALSE;
}
/* verify that this thread holds the SHM lock. No race condition: if the
current thread is here, it can't be in SHMLock or SHMUnlock */
if( (HANDLE)pthread_self() != locking_thread )
{
ASSERT("SHMGetInfo called while thread does not hold the SHM lock!\n");
}
TRACE("Setting SHM info element %d to %08x; used to be %08x\n",
element, value, shm_header.shm_info[element].Load() );
shm_header.shm_info[element] = value;
return TRUE;
}
|