| 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 | } |