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.

488 lines
13 KiB

  1. /*++
  2. Copyright (c) 2000 Microsoft Corporation
  3. Module Name:
  4. rundown.c
  5. Abstract:
  6. This module houses routine that do safe rundown of data stuctures.
  7. The basic principle of these routines is to allow fast protection of a data structure that is torn down
  8. by a single thread. Threads wishing to access the data structure attempt to obtain rundown protection via
  9. calling ExAcquireRundownProtection. If this function returns TRUE then accesses are safe until the protected
  10. thread calls ExReleaseRundownProtection. The single teardown thread calls ExWaitForRundownProtectionRelease
  11. to mark the rundown structure as being run down and the call will return once all protected threads have
  12. released their protection references.
  13. Rundown protection is not a lock. Multiple threads may gain rundown protection at the same time.
  14. The rundown structure has the following format:
  15. Bottom bit set : This is a pointer to a rundown wait block (aligned on at least a word boundary)
  16. Bottom bit clear : This is a count of the total number of accessors multiplied by 2 granted rundown protection.
  17. Author:
  18. Neill Clift (NeillC) 18-Apr-2000
  19. Revision History:
  20. --*/
  21. #include "exp.h"
  22. #pragma hdrstop
  23. #ifdef ALLOC_PRAGMA
  24. #pragma alloc_text(PAGE, ExAcquireRundownProtection)
  25. #pragma alloc_text(PAGE, ExReleaseRundownProtection)
  26. #pragma alloc_text(PAGE, ExAcquireRundownProtectionEx)
  27. #pragma alloc_text(PAGE, ExReleaseRundownProtectionEx)
  28. #pragma alloc_text(PAGE, ExWaitForRundownProtectionRelease)
  29. #pragma alloc_text(PAGE, ExReInitializeRundownProtection)
  30. #pragma alloc_text(PAGE, ExfInitializeRundownProtection)
  31. #pragma alloc_text(PAGE, ExRundownCompleted)
  32. #endif
  33. //
  34. // This is a block held on the local stack of the rundown thread.
  35. //
  36. typedef struct _EX_RUNDOWN_WAIT_BLOCK {
  37. ULONG Count;
  38. KEVENT WakeEvent;
  39. } EX_RUNDOWN_WAIT_BLOCK, *PEX_RUNDOWN_WAIT_BLOCK;
  40. NTKERNELAPI
  41. VOID
  42. FASTCALL
  43. ExfInitializeRundownProtection (
  44. IN PEX_RUNDOWN_REF RunRef
  45. )
  46. /*++
  47. Routine Description:
  48. Initialize rundown protection structure
  49. Arguments:
  50. RunRef - Rundown block to be referenced
  51. Return Value:
  52. None
  53. --*/
  54. {
  55. RunRef->Count = 0;
  56. }
  57. NTKERNELAPI
  58. VOID
  59. FASTCALL
  60. ExReInitializeRundownProtection (
  61. IN PEX_RUNDOWN_REF RunRef
  62. )
  63. /*++
  64. Routine Description:
  65. Reinitialize rundown protection structure after its been rundown
  66. Arguments:
  67. RunRef - Rundown block to be referenced
  68. Return Value:
  69. None
  70. --*/
  71. {
  72. PAGED_CODE ();
  73. ASSERT ((RunRef->Count&EX_RUNDOWN_ACTIVE) != 0);
  74. InterlockedExchangePointer (&RunRef->Ptr, NULL);
  75. }
  76. NTKERNELAPI
  77. VOID
  78. FASTCALL
  79. ExRundownCompleted (
  80. IN PEX_RUNDOWN_REF RunRef
  81. )
  82. /*++
  83. Routine Description:
  84. Mark rundown block has having completed rundown so we can wait again safely.
  85. Arguments:
  86. RunRef - Rundown block to be referenced
  87. Return Value:
  88. None
  89. --*/
  90. {
  91. PAGED_CODE ();
  92. ASSERT ((RunRef->Count&EX_RUNDOWN_ACTIVE) != 0);
  93. InterlockedExchangePointer (&RunRef->Ptr, (PVOID) EX_RUNDOWN_ACTIVE);
  94. }
  95. NTKERNELAPI
  96. BOOLEAN
  97. FASTCALL
  98. ExAcquireRundownProtection (
  99. IN PEX_RUNDOWN_REF RunRef
  100. )
  101. /*++
  102. Routine Description:
  103. Reference a rundown block preventing rundown occuring if it hasn't already started
  104. Arguments:
  105. RunRef - Rundown block to be referenced
  106. Return Value:
  107. BOOLEAN - TRUE - rundown protection was acquired, FALSE - rundown is active or completed
  108. --*/
  109. {
  110. ULONG_PTR Value, NewValue;
  111. PAGED_CODE ();
  112. Value = RunRef->Count;
  113. do {
  114. //
  115. // If rundown has started return with an error
  116. //
  117. if (Value & EX_RUNDOWN_ACTIVE) {
  118. return FALSE;
  119. }
  120. //
  121. // Rundown hasn't started yet so attempt to increment the unsage count.
  122. //
  123. NewValue = Value + EX_RUNDOWN_COUNT_INC;
  124. NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr,
  125. (PVOID) NewValue,
  126. (PVOID) Value);
  127. if (NewValue == Value) {
  128. return TRUE;
  129. }
  130. //
  131. // somebody else changed the variable before we did. Either a protection call came and went or rundown was
  132. // initiated. We just repeat the whole loop again.
  133. //
  134. Value = NewValue;
  135. } while (TRUE);
  136. }
  137. NTKERNELAPI
  138. BOOLEAN
  139. FASTCALL
  140. ExAcquireRundownProtectionEx (
  141. IN PEX_RUNDOWN_REF RunRef,
  142. IN ULONG Count
  143. )
  144. /*++
  145. Routine Description:
  146. Reference a rundown block preventing rundown occuring if it hasn't already started
  147. Arguments:
  148. RunRef - Rundown block to be referenced
  149. Count - Number of references to add
  150. Return Value:
  151. BOOLEAN - TRUE - rundown protection was acquired, FALSE - rundown is active or completed
  152. --*/
  153. {
  154. ULONG_PTR Value, NewValue;
  155. PAGED_CODE ();
  156. Value = RunRef->Count;
  157. do {
  158. //
  159. // If rundown has started return with an error
  160. //
  161. if (Value & EX_RUNDOWN_ACTIVE) {
  162. return FALSE;
  163. }
  164. //
  165. // Rundown hasn't started yet so attempt to increment the unsage count.
  166. //
  167. NewValue = Value + EX_RUNDOWN_COUNT_INC * Count;
  168. NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr,
  169. (PVOID) NewValue,
  170. (PVOID) Value);
  171. if (NewValue == Value) {
  172. return TRUE;
  173. }
  174. //
  175. // somebody else changed the variable before we did. Either a protection call came and went or rundown was
  176. // initiated. We just repeat the whole loop again.
  177. //
  178. Value = NewValue;
  179. } while (TRUE);
  180. }
  181. NTKERNELAPI
  182. VOID
  183. FASTCALL
  184. ExReleaseRundownProtection (
  185. IN PEX_RUNDOWN_REF RunRef
  186. )
  187. /*++
  188. Routine Description:
  189. Dereference a rundown block and wake the rundown thread if we are the last to exit
  190. Arguments:
  191. RunRef - Rundown block to have its reference released
  192. Return Value:
  193. None
  194. --*/
  195. {
  196. ULONG_PTR Value, NewValue;
  197. PAGED_CODE ();
  198. Value = RunRef->Count;
  199. do {
  200. //
  201. // If the block is already marked for rundown then decrement the wait block count and wake the
  202. // rundown thread if we are the last
  203. //
  204. if (Value & EX_RUNDOWN_ACTIVE) {
  205. PEX_RUNDOWN_WAIT_BLOCK WaitBlock;
  206. //
  207. // Rundown is active. since we are one of the threads blocking rundown we have the right to follow
  208. // the pointer and decrement the active count. If we are the last thread then we have the right to
  209. // wake up the waiter. After doing this we can't touch the data structures again.
  210. //
  211. WaitBlock = (PEX_RUNDOWN_WAIT_BLOCK) (Value & (~EX_RUNDOWN_ACTIVE));
  212. ASSERT (WaitBlock->Count > 0);
  213. if (InterlockedDecrement ((PLONG)&WaitBlock->Count) == 0) {
  214. //
  215. // We are the last thread out. Wake up the waiter.
  216. //
  217. KeSetEvent (&WaitBlock->WakeEvent, 0, FALSE);
  218. }
  219. return;
  220. } else {
  221. //
  222. // Rundown isn't active. Just try and decrement the count. Some other protector thread way come and/or
  223. // go as we do this or rundown might be initiated. We detect this because the exchange will fail and
  224. // we have to retry
  225. //
  226. NewValue = Value - EX_RUNDOWN_COUNT_INC;
  227. NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr,
  228. (PVOID) NewValue,
  229. (PVOID) Value);
  230. if (NewValue == Value) {
  231. return;
  232. }
  233. Value = NewValue;
  234. }
  235. } while (TRUE);
  236. }
  237. NTKERNELAPI
  238. VOID
  239. FASTCALL
  240. ExReleaseRundownProtectionEx (
  241. IN PEX_RUNDOWN_REF RunRef,
  242. IN ULONG Count
  243. )
  244. /*++
  245. Routine Description:
  246. Dereference a rundown block and wake the rundown thread if we are the last to exit
  247. Arguments:
  248. RunRef - Rundown block to have its reference released
  249. Count - Number of reference to remove
  250. Return Value:
  251. None
  252. --*/
  253. {
  254. ULONG_PTR Value, NewValue;
  255. PAGED_CODE ();
  256. Value = RunRef->Count;
  257. do {
  258. //
  259. // If the block is already marked for rundown then decrement the wait block count and wake the
  260. // rundown thread if we are the last
  261. //
  262. if (Value & EX_RUNDOWN_ACTIVE) {
  263. PEX_RUNDOWN_WAIT_BLOCK WaitBlock;
  264. //
  265. // Rundown is active. since we are one of the threads blocking rundown we have the right to follow
  266. // the pointer and decrement the active count. If we are the last thread then we have the right to
  267. // wake up the waiter. After doing this we can't touch the data structures again.
  268. //
  269. WaitBlock = (PEX_RUNDOWN_WAIT_BLOCK) (Value & (~EX_RUNDOWN_ACTIVE));
  270. ASSERT (WaitBlock->Count >= Count);
  271. if (InterlockedExchangeAdd ((PLONG)&WaitBlock->Count, -(LONG)Count) == (LONG) Count) {
  272. //
  273. // We are the last thread out. Wake up the waiter.
  274. //
  275. KeSetEvent (&WaitBlock->WakeEvent, 0, FALSE);
  276. }
  277. return;
  278. } else {
  279. //
  280. // Rundown isn't active. Just try and decrement the count. Some other protector thread way come and/or
  281. // go as we do this or rundown might be initiated. We detect this because the exchange will fail and
  282. // we have to retry
  283. //
  284. ASSERT (Value >= EX_RUNDOWN_COUNT_INC * Count);
  285. NewValue = Value - EX_RUNDOWN_COUNT_INC * Count;
  286. NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr,
  287. (PVOID) NewValue,
  288. (PVOID) Value);
  289. if (NewValue == Value) {
  290. return;
  291. }
  292. Value = NewValue;
  293. }
  294. } while (TRUE);
  295. }
  296. NTKERNELAPI
  297. VOID
  298. FASTCALL
  299. ExWaitForRundownProtectionRelease (
  300. IN PEX_RUNDOWN_REF RunRef
  301. )
  302. /*++
  303. Routine Description:
  304. Wait till all outstanding rundown protection calls have exited
  305. Arguments:
  306. RunRef - Pointer to a rundown structure
  307. Return Value:
  308. None
  309. --*/
  310. {
  311. EX_RUNDOWN_WAIT_BLOCK WaitBlock;
  312. PKEVENT Event;
  313. ULONG_PTR Value, NewValue;
  314. ULONG WaitCount;
  315. PAGED_CODE ();
  316. //
  317. // Fast path. this should be the normal case. If Value is zero then there are no current accessors and we have
  318. // marked the rundown structure as rundown. If the value is EX_RUNDOWN_ACTIVE then the structure has already
  319. // been rundown and ExRundownCompleted. This second case allows for callers that might initiate rundown
  320. // multiple times (like handle table rundown) to have subsequent rundowns become noops.
  321. //
  322. Value = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr,
  323. (PVOID) EX_RUNDOWN_ACTIVE,
  324. (PVOID) 0);
  325. if (Value == 0 || Value == EX_RUNDOWN_ACTIVE) {
  326. return;
  327. }
  328. //
  329. // Slow path
  330. //
  331. Event = NULL;
  332. do {
  333. //
  334. // Extract total number of waiters. Its biased by 2 so we can hanve the rundown active bit.
  335. //
  336. WaitCount = (ULONG) (Value >> EX_RUNDOWN_COUNT_SHIFT);
  337. //
  338. // If there are some accessors present then initialize and event (once only).
  339. //
  340. if (WaitCount > 0 && Event == NULL) {
  341. Event = &WaitBlock.WakeEvent;
  342. KeInitializeEvent (Event, SynchronizationEvent, FALSE);
  343. }
  344. //
  345. // Store the wait count in the wait block. Waiting threads will start to decrement this as they exit
  346. // if our exchange succeeds. Its possible for accessors to come and go between our initial fetch and
  347. // the interlocked swap. This doesn't matter so long as there is the same number of outstanding accessors
  348. // to wait for.
  349. //
  350. WaitBlock.Count = WaitCount;
  351. NewValue = ((ULONG_PTR) &WaitBlock) | EX_RUNDOWN_ACTIVE;
  352. NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr,
  353. (PVOID) NewValue,
  354. (PVOID) Value);
  355. if (NewValue == Value) {
  356. if (WaitCount > 0) {
  357. KeWaitForSingleObject (Event,
  358. Executive,
  359. KernelMode,
  360. FALSE,
  361. NULL);
  362. ASSERT (WaitBlock.Count == 0);
  363. }
  364. return;
  365. }
  366. Value = NewValue;
  367. ASSERT ((Value&EX_RUNDOWN_ACTIVE) == 0);
  368. } while (TRUE);
  369. }