Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

690 lines
21 KiB

  1. /*++
  2. Copyright (c) 2000 Microsoft Corporation
  3. Module Name:
  4. pushlock.c
  5. Abstract:
  6. This module houses routines that do push locking.
  7. Push locks are capable of being acquired in both shared and exclusive mode.
  8. Properties include:
  9. They can not be acquired recursively.
  10. They are small (the size of a pointer) and can be used when embeded in pagable data.
  11. Acquire and release is done lock free. On lock contention the waiters are chained
  12. through the lock and local stack space.
  13. This is the structure of a push lock:
  14. E == Exclusive bit
  15. W == Waiters present
  16. SC == Share count
  17. P == Pointer to wait block
  18. +----------+---+---+
  19. | SC | E | W | E, W are single bits W == 0
  20. +----------+---+---+
  21. +--------------+---+
  22. | P | W | W == 1. Pointer is the address of a chain of stack local wait blocks
  23. +--------------+---+
  24. The non-contented acquires and releases are trivial. Interlocked operations make the following
  25. transformations.
  26. (SC=0,E=0,W=0) === Exclusive acquire ===> (SC=0,E=1,W=0)
  27. (SC=n,E=0,W=0) === Shared acquire ===> (SC=n+1,E=0,W=0)
  28. (SC=0,E=1,W=0) === Exclusive release ===> (SC=0,E=0,W=0)
  29. (SC=n,E=0,W=0) === Shared release ===> (SC=n-1,E=0,W=0) n > 0
  30. Contention causes the acquiring thread to produce a local stack based wait block and to
  31. enqueue it to the front of the list.
  32. (SC=n,E=e,W=0) === Exclusive acquire ===> (P=LWB(SSC=n,E=e),W=1) LWB = local wait block,
  33. SSC = Saved share count,
  34. n > 0 or e == 1.
  35. (SC=0,E=1,W=0) === Shared acquire ===> (P=LWB(SSC=0,E=0),W=1) LWB = local wait block,
  36. SSC = Saved share count.
  37. After contention has causes one or more threads to queue up wait blocks releases are more
  38. complicated. This following rights are granted to a releasing thread (shared or exclusive).
  39. 1) Shared release threads are allowed to search the wait list until they hit a wait block
  40. with a non-zero share count (this will be a wait block marked exclusive). This thread is
  41. allowed to use an interlocked operation to decrement this value. If this thread
  42. transitioned the value to zero then it obtains the rights of an exclusive release thread
  43. 2) Exclusive threads are allowed to search the wait list until they find a continuous chain
  44. of shared wait blocks or they find the last wait block is an exclusive waiter. This thread
  45. may then break the chain at this point or update the header to show a single exclusive
  46. owner or multiple shared owners. Breaking the list can be done with normal assignment
  47. but updating the header requires interlocked exchange compare.
  48. Author:
  49. Neill Clift (NeillC) 30-Sep-2000
  50. Revision History:
  51. --*/
  52. #include "exp.h"
  53. #pragma hdrstop
  54. #ifdef ALLOC_PRAGMA
  55. #pragma alloc_text(PAGE, ExBlockPushLock)
  56. #pragma alloc_text(PAGE, ExfAcquirePushLockExclusive)
  57. #pragma alloc_text(PAGE, ExfAcquirePushLockShared)
  58. #pragma alloc_text(PAGE, ExfReleasePushLock)
  59. #pragma alloc_text(PAGE, ExfUnblockPushLock)
  60. #pragma alloc_text(PAGE, ExAllocateCacheAwarePushLock)
  61. #pragma alloc_text(PAGE, ExFreeCacheAwarePushLock)
  62. #pragma alloc_text(PAGE, ExAcquireCacheAwarePushLockExclusive)
  63. #pragma alloc_text(PAGE, ExReleaseCacheAwarePushLockExclusive)
  64. #endif
  65. NTKERNELAPI
  66. VOID
  67. FASTCALL
  68. ExfAcquirePushLockExclusive (
  69. IN PEX_PUSH_LOCK PushLock
  70. )
  71. /*++
  72. Routine Description:
  73. Acquire a push lock exclusively
  74. Arguments:
  75. PushLock - Push lock to be acquired
  76. Return Value:
  77. None
  78. --*/
  79. {
  80. EX_PUSH_LOCK OldValue, NewValue;
  81. EX_PUSH_LOCK_WAIT_BLOCK WaitBlock;
  82. OldValue = *PushLock;
  83. while (1) {
  84. //
  85. // If the lock is already held exclusively/shared or there are waiters then
  86. // we need to wait.
  87. //
  88. if (OldValue.Value == 0) {
  89. NewValue.Value = OldValue.Value + EX_PUSH_LOCK_EXCLUSIVE;
  90. NewValue.Ptr = InterlockedCompareExchangePointer (&PushLock->Ptr,
  91. NewValue.Ptr,
  92. OldValue.Ptr);
  93. if (NewValue.Ptr == OldValue.Ptr) {
  94. break;
  95. }
  96. } else {
  97. KeInitializeEvent (&WaitBlock.WakeEvent, SynchronizationEvent, FALSE);
  98. WaitBlock.Exclusive = TRUE;
  99. //
  100. // Move the sharecount to our wait block if need be.
  101. //
  102. if (OldValue.Waiting) {
  103. WaitBlock.Next = (PEX_PUSH_LOCK_WAIT_BLOCK)
  104. (OldValue.Value - EX_PUSH_LOCK_WAITING);
  105. WaitBlock.ShareCount = 0;
  106. } else {
  107. WaitBlock.Next = NULL;
  108. WaitBlock.ShareCount = (ULONG) OldValue.Shared;
  109. }
  110. NewValue.Ptr = ((PUCHAR) &WaitBlock) + EX_PUSH_LOCK_WAITING;
  111. ASSERT ((NewValue.Value & EX_PUSH_LOCK_WAITING) != 0);
  112. NewValue.Ptr = InterlockedCompareExchangePointer (&PushLock->Ptr,
  113. NewValue.Ptr,
  114. OldValue.Ptr);
  115. if (NewValue.Ptr == OldValue.Ptr) {
  116. KeWaitForSingleObject (&WaitBlock.WakeEvent,
  117. Executive,
  118. KernelMode,
  119. FALSE,
  120. NULL);
  121. ASSERT ((WaitBlock.ShareCount == 0) && (WaitBlock.Next == NULL));
  122. break;
  123. }
  124. }
  125. OldValue = NewValue;
  126. }
  127. }
  128. NTKERNELAPI
  129. VOID
  130. FASTCALL
  131. ExfAcquirePushLockShared (
  132. IN PEX_PUSH_LOCK PushLock
  133. )
  134. /*++
  135. Routine Description:
  136. Acquire a push lock shared
  137. Arguments:
  138. PushLock - Push lock to be acquired
  139. Return Value:
  140. None
  141. --*/
  142. {
  143. EX_PUSH_LOCK OldValue, NewValue;
  144. EX_PUSH_LOCK_WAIT_BLOCK WaitBlock;
  145. OldValue = *PushLock;
  146. while (1) {
  147. //
  148. // If the lock is already held exclusively or there are waiters then we need to wait
  149. //
  150. if (OldValue.Exclusive || OldValue.Waiting) {
  151. KeInitializeEvent (&WaitBlock.WakeEvent, SynchronizationEvent, FALSE);
  152. WaitBlock.Exclusive = 0;
  153. WaitBlock.ShareCount = 0;
  154. //
  155. // Chain the next block to us if there is one.
  156. //
  157. if (OldValue.Waiting) {
  158. WaitBlock.Next = (PEX_PUSH_LOCK_WAIT_BLOCK)
  159. (OldValue.Value - EX_PUSH_LOCK_WAITING);
  160. } else {
  161. WaitBlock.Next = NULL;
  162. }
  163. NewValue.Ptr = ((PUCHAR) &WaitBlock) + EX_PUSH_LOCK_WAITING;
  164. ASSERT ((NewValue.Value & EX_PUSH_LOCK_WAITING) != 0);
  165. NewValue.Ptr = InterlockedCompareExchangePointer (&PushLock->Ptr,
  166. NewValue.Ptr,
  167. OldValue.Ptr);
  168. if (NewValue.Ptr == OldValue.Ptr) {
  169. KeWaitForSingleObject (&WaitBlock.WakeEvent,
  170. Executive,
  171. KernelMode,
  172. FALSE,
  173. NULL);
  174. ASSERT (WaitBlock.ShareCount == 0);
  175. break;
  176. }
  177. } else {
  178. //
  179. // We only have shared accessors at the moment. We can just update the lock to include this thread.
  180. //
  181. NewValue.Value = OldValue.Value + EX_PUSH_LOCK_SHARE_INC;
  182. ASSERT (!(NewValue.Waiting || NewValue.Exclusive));
  183. NewValue.Ptr = InterlockedCompareExchangePointer (&PushLock->Ptr,
  184. NewValue.Ptr,
  185. OldValue.Ptr);
  186. if (NewValue.Ptr == OldValue.Ptr) {
  187. break;
  188. }
  189. }
  190. OldValue = NewValue;
  191. }
  192. }
  193. NTKERNELAPI
  194. VOID
  195. FASTCALL
  196. ExfReleasePushLock (
  197. IN PEX_PUSH_LOCK PushLock
  198. )
  199. /*++
  200. Routine Description:
  201. Release a push lock that was acquired exclusively or shared
  202. Arguments:
  203. PushLock - Push lock to be released
  204. Return Value:
  205. None
  206. --*/
  207. {
  208. EX_PUSH_LOCK OldValue, NewValue;
  209. PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock, NextWaitBlock, ReleaseWaitList, Previous;
  210. ULONG ShareCount;
  211. OldValue = *PushLock;
  212. while (1) {
  213. if (!OldValue.Waiting) {
  214. //
  215. // Either we hold the lock exclusive or shared but not both.
  216. //
  217. ASSERT (OldValue.Exclusive ^ (OldValue.Shared > 0));
  218. //
  219. // We must hold the lock exclusive or shared. We make the assuption that
  220. // the exclusive bit is just below the share count here.
  221. //
  222. NewValue.Value = (OldValue.Value - EX_PUSH_LOCK_EXCLUSIVE) &
  223. ~EX_PUSH_LOCK_EXCLUSIVE;
  224. NewValue.Ptr = InterlockedCompareExchangePointer (&PushLock->Ptr,
  225. NewValue.Ptr,
  226. OldValue.Ptr);
  227. if (NewValue.Ptr == OldValue.Ptr) {
  228. break;
  229. }
  230. //
  231. // Either we gained a new waiter or another shared owner arrived or left
  232. //
  233. ASSERT (NewValue.Waiting || (NewValue.Shared > 0 && !NewValue.Exclusive));
  234. OldValue = NewValue;
  235. } else {
  236. //
  237. // There are waiters chained to the lock. We have to release the share count,
  238. // last exclusive or last chain of shared waiters.
  239. //
  240. WaitBlock = (PEX_PUSH_LOCK_WAIT_BLOCK)
  241. (OldValue.Value - EX_PUSH_LOCK_WAITING);
  242. ReleaseWaitList = WaitBlock;
  243. Previous = NULL;
  244. ShareCount = 0;
  245. do {
  246. if (WaitBlock->Exclusive) {
  247. //
  248. // This is an exclusive waiter. If this was the first exclusive waited to a shared acquire
  249. // then it will have the saved share count. If we acquired the lock shared then the count
  250. // must contain a bias for this thread. Release that and if we are not the last shared
  251. // accessor then exit. A later shared release thread will wake the exclusive
  252. // waiter.
  253. //
  254. if (WaitBlock->ShareCount != 0) {
  255. if (InterlockedDecrement ((PLONG)&WaitBlock->ShareCount) != 0) {
  256. return;
  257. }
  258. }
  259. //
  260. // Reset count of share acquires waiting.
  261. //
  262. ShareCount = 0;
  263. } else {
  264. //
  265. // This is a shared waiter. Record the number of these to update the head or the
  266. // previous exclusive waiter.
  267. //
  268. ShareCount++;
  269. }
  270. NextWaitBlock = WaitBlock->Next;
  271. if (NextWaitBlock != NULL) {
  272. if (NextWaitBlock->Exclusive) {
  273. //
  274. // The next block is exclusive. This may be the entry to free.
  275. //
  276. Previous = WaitBlock;
  277. ReleaseWaitList = NextWaitBlock;
  278. } else {
  279. //
  280. // The next block is shared. If the chain start is exclusive then skip to this one
  281. // as the exclusive isn't the thread we will wake up.
  282. //
  283. if (ReleaseWaitList->Exclusive) {
  284. Previous = WaitBlock;
  285. ReleaseWaitList = NextWaitBlock;
  286. }
  287. }
  288. }
  289. WaitBlock = NextWaitBlock;
  290. } while (WaitBlock != NULL);
  291. //
  292. // If our release chain is everything then we have to update the header
  293. //
  294. if (Previous == NULL) {
  295. NewValue.Value = 0;
  296. NewValue.Exclusive = ReleaseWaitList->Exclusive;
  297. NewValue.Shared = ShareCount;
  298. ASSERT (((ShareCount > 0) ^ (ReleaseWaitList->Exclusive)) && !NewValue.Waiting);
  299. NewValue.Ptr = InterlockedCompareExchangePointer (&PushLock->Ptr,
  300. NewValue.Ptr,
  301. OldValue.Ptr);
  302. if (NewValue.Ptr != OldValue.Ptr) {
  303. //
  304. // We are releasing so we could have only gained another waiter
  305. //
  306. ASSERT (NewValue.Waiting);
  307. OldValue = NewValue;
  308. continue;
  309. }
  310. } else {
  311. //
  312. // Truncate the chain at this position and save the share count for all the shared owners to
  313. // decrement later.
  314. //
  315. Previous->Next = NULL;
  316. ASSERT (Previous->ShareCount == 0);
  317. Previous->ShareCount = ShareCount;
  318. //
  319. // We are either releasing multiple share accessors or a single exclusive
  320. //
  321. ASSERT ((ShareCount > 0) ^ ReleaseWaitList->Exclusive);
  322. }
  323. //
  324. // Release the chain of threads we located.
  325. //
  326. do {
  327. NextWaitBlock = ReleaseWaitList->Next;
  328. //
  329. // All the chain should have the same type (Exclusive/Shared).
  330. //
  331. ASSERT (NextWaitBlock == NULL || (ReleaseWaitList->Exclusive == NextWaitBlock->Exclusive));
  332. ASSERT (!ReleaseWaitList->Exclusive || (ReleaseWaitList->ShareCount == 0));
  333. KeSetEventBoostPriority (&ReleaseWaitList->WakeEvent, NULL);
  334. ReleaseWaitList = NextWaitBlock;
  335. } while (ReleaseWaitList != NULL);
  336. break;
  337. }
  338. }
  339. }
  340. NTKERNELAPI
  341. VOID
  342. FASTCALL
  343. ExBlockPushLock (
  344. IN PEX_PUSH_LOCK PushLock,
  345. IN PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock
  346. )
  347. /*++
  348. Routine Description:
  349. Block on a push lock
  350. Arguments:
  351. PushLock - Push lock to block on
  352. WaitBlock - Wait block to queue for waiting
  353. Return Value:
  354. None
  355. --*/
  356. {
  357. EX_PUSH_LOCK OldValue, NewValue;
  358. //
  359. // Push the wait block on the list.
  360. //
  361. KeInitializeEvent (&WaitBlock->WakeEvent, SynchronizationEvent, FALSE);
  362. OldValue = *PushLock;
  363. while (1) {
  364. //
  365. // Chain the next block to us if there is one.
  366. //
  367. WaitBlock->Next = OldValue.Ptr;
  368. NewValue.Ptr = InterlockedCompareExchangePointer (&PushLock->Ptr,
  369. WaitBlock,
  370. OldValue.Ptr);
  371. if (NewValue.Ptr == OldValue.Ptr) {
  372. return;
  373. }
  374. OldValue = NewValue;
  375. }
  376. }
  377. NTKERNELAPI
  378. VOID
  379. FASTCALL
  380. ExfUnblockPushLock (
  381. IN PEX_PUSH_LOCK PushLock,
  382. IN PEX_PUSH_LOCK_WAIT_BLOCK WaitBlock OPTIONAL
  383. )
  384. /*++
  385. Routine Description:
  386. Unblock on a push lock
  387. Arguments:
  388. PushLock - Push lock to block on
  389. WaitBlock - Wait block previously queued for waiting or NULL if there wasn't one
  390. Return Value:
  391. None
  392. --*/
  393. {
  394. EX_PUSH_LOCK OldValue;
  395. PEX_PUSH_LOCK_WAIT_BLOCK tWaitBlock;
  396. BOOLEAN FoundOurBlock=FALSE;
  397. //
  398. // Pop the entire chain and wake them all up.
  399. //
  400. OldValue.Ptr = InterlockedExchangePointer (&PushLock->Ptr,
  401. NULL);
  402. while (OldValue.Ptr != NULL) {
  403. tWaitBlock = OldValue.Ptr;
  404. OldValue.Ptr = tWaitBlock->Next;
  405. if (tWaitBlock == WaitBlock) {
  406. FoundOurBlock = TRUE;
  407. } else{
  408. KeSetEvent (&tWaitBlock->WakeEvent, 0, FALSE);
  409. }
  410. }
  411. if (WaitBlock != NULL && !FoundOurBlock) {
  412. KeWaitForSingleObject (&WaitBlock->WakeEvent,
  413. Executive,
  414. KernelMode,
  415. FALSE,
  416. NULL);
  417. }
  418. }
  419. NTKERNELAPI
  420. PEX_PUSH_LOCK_CACHE_AWARE
  421. ExAllocateCacheAwarePushLock (
  422. VOID
  423. )
  424. /*++
  425. Routine Description:
  426. Allocate a cache aware (cache friendly) push lock
  427. Arguments:
  428. None
  429. Return Value:
  430. None
  431. --*/
  432. {
  433. PEX_PUSH_LOCK_CACHE_AWARE PushLockCacheAware;
  434. PEX_PUSH_LOCK_CACHE_AWARE_PADDED PaddedPushLock;
  435. ULONG i, j;
  436. PushLockCacheAware = ExAllocatePoolWithTag (PagedPool,
  437. sizeof (EX_PUSH_LOCK_CACHE_AWARE),
  438. 'pclP');
  439. if (PushLockCacheAware != NULL) {
  440. //
  441. // If we are a non-numa machine then allocate the padded push locks as a single block
  442. //
  443. if (KeNumberNodes == 1) {
  444. PaddedPushLock = ExAllocatePoolWithTag (PagedPool,
  445. sizeof (EX_PUSH_LOCK_CACHE_AWARE_PADDED)*
  446. EX_PUSH_LOCK_FANNED_COUNT,
  447. 'lclP');
  448. if (PaddedPushLock == NULL) {
  449. ExFreePool (PushLockCacheAware);
  450. return NULL;
  451. }
  452. for (i = 0; i < EX_PUSH_LOCK_FANNED_COUNT; i++) {
  453. PaddedPushLock->Single = TRUE;
  454. ExInitializePushLock (&PaddedPushLock->Lock);
  455. PushLockCacheAware->Locks[i] = &PaddedPushLock->Lock;
  456. PaddedPushLock++;
  457. }
  458. } else {
  459. //
  460. // Allocate a different block for each lock and set affinity
  461. // so the allocation comes from that nodes memory.
  462. //
  463. for (i = 0; i < EX_PUSH_LOCK_FANNED_COUNT; i++) {
  464. KeSetSystemAffinityThread(AFFINITY_MASK(i % KeNumberProcessors));
  465. PaddedPushLock = ExAllocatePoolWithTag (PagedPool,
  466. sizeof (EX_PUSH_LOCK_CACHE_AWARE_PADDED),
  467. 'lclP');
  468. if (PaddedPushLock == NULL) {
  469. for (j = 0; j < i; j++) {
  470. ExFreePool (PushLockCacheAware->Locks[j]);
  471. }
  472. KeRevertToUserAffinityThread ();
  473. ExFreePool (PushLockCacheAware);
  474. return NULL;
  475. }
  476. PaddedPushLock->Single = FALSE;
  477. ExInitializePushLock (&PaddedPushLock->Lock);
  478. PushLockCacheAware->Locks[i] = &PaddedPushLock->Lock;
  479. }
  480. KeRevertToUserAffinityThread ();
  481. }
  482. }
  483. return PushLockCacheAware;
  484. }
  485. NTKERNELAPI
  486. VOID
  487. ExFreeCacheAwarePushLock (
  488. PEX_PUSH_LOCK_CACHE_AWARE PushLock
  489. )
  490. /*++
  491. Routine Description:
  492. Frees a cache aware (cache friendly) push lock
  493. Arguments:
  494. PushLock - Cache aware push lock to be freed
  495. Return Value:
  496. None
  497. --*/
  498. {
  499. ULONG i;
  500. if (!CONTAINING_RECORD (PushLock->Locks[0], EX_PUSH_LOCK_CACHE_AWARE_PADDED, Lock)->Single) {
  501. for (i = 0; i < EX_PUSH_LOCK_FANNED_COUNT; i++) {
  502. ExFreePool (PushLock->Locks[i]);
  503. }
  504. } else {
  505. ExFreePool (PushLock->Locks[0]);
  506. }
  507. ExFreePool (PushLock);
  508. }
  509. NTKERNELAPI
  510. VOID
  511. ExAcquireCacheAwarePushLockExclusive (
  512. IN PEX_PUSH_LOCK_CACHE_AWARE PushLock
  513. )
  514. /*++
  515. Routine Description:
  516. Acquire a cache aware push lock exclusive.
  517. Arguments:
  518. PushLock - Cache aware push lock to be acquired
  519. Return Value:
  520. None
  521. --*/
  522. {
  523. PEX_PUSH_LOCK *Start, *End;
  524. //
  525. // Exclusive acquires must obtain all the slots exclusive.
  526. // Take the first slot exclusive and then we can take the
  527. // rest of the slots in any order we want.
  528. // There is no deadlock here. A->B->C does not deadlock with A->C->B.
  529. //
  530. Start = &PushLock->Locks[1];
  531. End = &PushLock->Locks[EX_PUSH_LOCK_FANNED_COUNT - 1];
  532. ExAcquirePushLockExclusive (PushLock->Locks[0]);
  533. while (Start <= End) {
  534. if (ExTryAcquirePushLockExclusive (*Start)) {
  535. Start++;
  536. } else {
  537. ExAcquirePushLockExclusive (*End);
  538. End--;
  539. }
  540. }
  541. }
  542. NTKERNELAPI
  543. VOID
  544. ExReleaseCacheAwarePushLockExclusive (
  545. IN PEX_PUSH_LOCK_CACHE_AWARE PushLock
  546. )
  547. /*++
  548. Routine Description:
  549. Release a cache aware push lock exclusive.
  550. Arguments:
  551. PushLock - Cache aware push lock to be released
  552. Return Value:
  553. None
  554. --*/
  555. {
  556. PEX_PUSH_LOCK *Start, *End;
  557. //
  558. // Release the locks in order
  559. //
  560. End = &PushLock->Locks[EX_PUSH_LOCK_FANNED_COUNT];
  561. for (Start = &PushLock->Locks[0];
  562. Start < End;
  563. Start++) {
  564. ExReleasePushLockExclusive (*Start);
  565. }
  566. }