summaryrefslogtreecommitdiff
path: root/src/pal/src/shmemory/shmemory.cpp
blob: a12bd29c84ba413d32bec6407046382f3ad5c014 (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
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;
}