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
14 KiB
488 lines
14 KiB
/*++
|
|
|
|
Copyright (c) 2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
rundown.c
|
|
|
|
Abstract:
|
|
|
|
This module houses routine that do safe rundown of data stuctures.
|
|
|
|
The basic principle of these routines is to allow fast protection of a data structure that is torn down
|
|
by a single thread. Threads wishing to access the data structure attempt to obtain rundown protection via
|
|
calling ExAcquireRundownProtection. If this function returns TRUE then accesses are safe until the protected
|
|
thread calls ExReleaseRundownProtection. The single teardown thread calls ExWaitForRundownProtectionRelease
|
|
to mark the rundown structure as being run down and the call will return once all protected threads have
|
|
released their protection references.
|
|
|
|
Rundown protection is not a lock. Multiple threads may gain rundown protection at the same time.
|
|
|
|
The rundown structure has the following format:
|
|
|
|
Bottom bit set : This is a pointer to a rundown wait block (aligned on at least a word boundary)
|
|
Bottom bit clear : This is a count of the total number of accessors multiplied by 2 granted rundown protection.
|
|
|
|
Author:
|
|
|
|
Neill Clift (NeillC) 18-Apr-2000
|
|
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "exp.h"
|
|
|
|
#pragma hdrstop
|
|
|
|
#ifdef ALLOC_PRAGMA
|
|
// These routines are now marked as NONPAGED because they are being used
|
|
// in the paging path by file system filters.
|
|
//#pragma alloc_text(PAGE, ExfAcquireRundownProtection)
|
|
//#pragma alloc_text(PAGE, ExfReleaseRundownProtection)
|
|
//#pragma alloc_text(PAGE, ExAcquireRundownProtectionEx)
|
|
//#pragma alloc_text(PAGE, ExReleaseRundownProtectionEx)
|
|
#pragma alloc_text(PAGE, ExfWaitForRundownProtectionRelease)
|
|
#pragma alloc_text(PAGE, ExfReInitializeRundownProtection)
|
|
#pragma alloc_text(PAGE, ExfInitializeRundownProtection)
|
|
#pragma alloc_text(PAGE, ExfRundownCompleted)
|
|
#endif
|
|
|
|
//
|
|
// This is a block held on the local stack of the rundown thread.
|
|
//
|
|
typedef struct _EX_RUNDOWN_WAIT_BLOCK {
|
|
ULONG Count;
|
|
KEVENT WakeEvent;
|
|
} EX_RUNDOWN_WAIT_BLOCK, *PEX_RUNDOWN_WAIT_BLOCK;
|
|
|
|
|
|
NTKERNELAPI
|
|
VOID
|
|
FASTCALL
|
|
ExfInitializeRundownProtection (
|
|
IN PEX_RUNDOWN_REF RunRef
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize rundown protection structure
|
|
|
|
Arguments:
|
|
|
|
RunRef - Rundown block to be referenced
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
RunRef->Count = 0;
|
|
}
|
|
|
|
NTKERNELAPI
|
|
VOID
|
|
FASTCALL
|
|
ExfReInitializeRundownProtection (
|
|
IN PEX_RUNDOWN_REF RunRef
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reinitialize rundown protection structure after its been rundown
|
|
|
|
Arguments:
|
|
|
|
RunRef - Rundown block to be referenced
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
PAGED_CODE ();
|
|
|
|
ASSERT ((RunRef->Count&EX_RUNDOWN_ACTIVE) != 0);
|
|
InterlockedExchangePointer (&RunRef->Ptr, NULL);
|
|
}
|
|
|
|
NTKERNELAPI
|
|
VOID
|
|
FASTCALL
|
|
ExfRundownCompleted (
|
|
IN PEX_RUNDOWN_REF RunRef
|
|
)
|
|
/*++
|
|
Routine Description:
|
|
|
|
Mark rundown block has having completed rundown so we can wait again safely.
|
|
|
|
Arguments:
|
|
|
|
RunRef - Rundown block to be referenced
|
|
|
|
Return Value:
|
|
|
|
None
|
|
--*/
|
|
{
|
|
PAGED_CODE ();
|
|
|
|
ASSERT ((RunRef->Count&EX_RUNDOWN_ACTIVE) != 0);
|
|
InterlockedExchangePointer (&RunRef->Ptr, (PVOID) EX_RUNDOWN_ACTIVE);
|
|
}
|
|
|
|
NTKERNELAPI
|
|
BOOLEAN
|
|
FASTCALL
|
|
ExfAcquireRundownProtection (
|
|
IN PEX_RUNDOWN_REF RunRef
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reference a rundown block preventing rundown occuring if it hasn't already started
|
|
This routine is NON-PAGED because it is being called on the paging path.
|
|
|
|
Arguments:
|
|
|
|
RunRef - Rundown block to be referenced
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE - rundown protection was acquired, FALSE - rundown is active or completed
|
|
|
|
--*/
|
|
{
|
|
ULONG_PTR Value, NewValue;
|
|
|
|
Value = RunRef->Count;
|
|
do {
|
|
//
|
|
// If rundown has started return with an error
|
|
//
|
|
if (Value & EX_RUNDOWN_ACTIVE) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Rundown hasn't started yet so attempt to increment the unsage count.
|
|
//
|
|
NewValue = Value + EX_RUNDOWN_COUNT_INC;
|
|
|
|
NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr,
|
|
(PVOID) NewValue,
|
|
(PVOID) Value);
|
|
if (NewValue == Value) {
|
|
return TRUE;
|
|
}
|
|
//
|
|
// somebody else changed the variable before we did. Either a protection call came and went or rundown was
|
|
// initiated. We just repeat the whole loop again.
|
|
//
|
|
Value = NewValue;
|
|
} while (TRUE);
|
|
}
|
|
|
|
NTKERNELAPI
|
|
BOOLEAN
|
|
FASTCALL
|
|
ExAcquireRundownProtectionEx (
|
|
IN PEX_RUNDOWN_REF RunRef,
|
|
IN ULONG Count
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Reference a rundown block preventing rundown occuring if it hasn't already started
|
|
This routine is NON-PAGED because it is being called on the paging path.
|
|
|
|
Arguments:
|
|
|
|
RunRef - Rundown block to be referenced
|
|
Count - Number of references to add
|
|
|
|
Return Value:
|
|
|
|
BOOLEAN - TRUE - rundown protection was acquired, FALSE - rundown is active or completed
|
|
|
|
--*/
|
|
{
|
|
ULONG_PTR Value, NewValue;
|
|
|
|
Value = RunRef->Count;
|
|
do {
|
|
//
|
|
// If rundown has started return with an error
|
|
//
|
|
if (Value & EX_RUNDOWN_ACTIVE) {
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// Rundown hasn't started yet so attempt to increment the unsage count.
|
|
//
|
|
NewValue = Value + EX_RUNDOWN_COUNT_INC * Count;
|
|
|
|
NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr,
|
|
(PVOID) NewValue,
|
|
(PVOID) Value);
|
|
if (NewValue == Value) {
|
|
return TRUE;
|
|
}
|
|
//
|
|
// somebody else changed the variable before we did. Either a protection call came and went or rundown was
|
|
// initiated. We just repeat the whole loop again.
|
|
//
|
|
Value = NewValue;
|
|
} while (TRUE);
|
|
}
|
|
|
|
|
|
NTKERNELAPI
|
|
VOID
|
|
FASTCALL
|
|
ExfReleaseRundownProtection (
|
|
IN PEX_RUNDOWN_REF RunRef
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Dereference a rundown block and wake the rundown thread if we are the last to exit
|
|
This routine is NON-PAGED because it is being called on the paging path.
|
|
|
|
Arguments:
|
|
|
|
RunRef - Rundown block to have its reference released
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
ULONG_PTR Value, NewValue;
|
|
|
|
Value = RunRef->Count;
|
|
do {
|
|
//
|
|
// If the block is already marked for rundown then decrement the wait block count and wake the
|
|
// rundown thread if we are the last
|
|
//
|
|
if (Value & EX_RUNDOWN_ACTIVE) {
|
|
PEX_RUNDOWN_WAIT_BLOCK WaitBlock;
|
|
|
|
//
|
|
// Rundown is active. since we are one of the threads blocking rundown we have the right to follow
|
|
// the pointer and decrement the active count. If we are the last thread then we have the right to
|
|
// wake up the waiter. After doing this we can't touch the data structures again.
|
|
//
|
|
WaitBlock = (PEX_RUNDOWN_WAIT_BLOCK) (Value & (~EX_RUNDOWN_ACTIVE));
|
|
|
|
ASSERT (WaitBlock->Count > 0);
|
|
|
|
if (InterlockedDecrement ((PLONG)&WaitBlock->Count) == 0) {
|
|
//
|
|
// We are the last thread out. Wake up the waiter.
|
|
//
|
|
KeSetEvent (&WaitBlock->WakeEvent, 0, FALSE);
|
|
}
|
|
return;
|
|
} else {
|
|
//
|
|
// Rundown isn't active. Just try and decrement the count. Some other protector thread way come and/or
|
|
// go as we do this or rundown might be initiated. We detect this because the exchange will fail and
|
|
// we have to retry
|
|
//
|
|
ASSERT (Value >= EX_RUNDOWN_COUNT_INC);
|
|
|
|
NewValue = Value - EX_RUNDOWN_COUNT_INC;
|
|
|
|
NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr,
|
|
(PVOID) NewValue,
|
|
(PVOID) Value);
|
|
if (NewValue == Value) {
|
|
return;
|
|
}
|
|
Value = NewValue;
|
|
}
|
|
|
|
} while (TRUE);
|
|
}
|
|
|
|
NTKERNELAPI
|
|
VOID
|
|
FASTCALL
|
|
ExReleaseRundownProtectionEx (
|
|
IN PEX_RUNDOWN_REF RunRef,
|
|
IN ULONG Count
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Dereference a rundown block and wake the rundown thread if we are the last to exit
|
|
This routine is NON-PAGED because it is being called on the paging path.
|
|
|
|
Arguments:
|
|
|
|
RunRef - Rundown block to have its reference released
|
|
Count - Number of reference to remove
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
ULONG_PTR Value, NewValue;
|
|
|
|
Value = RunRef->Count;
|
|
do {
|
|
//
|
|
// If the block is already marked for rundown then decrement the wait block count and wake the
|
|
// rundown thread if we are the last
|
|
//
|
|
if (Value & EX_RUNDOWN_ACTIVE) {
|
|
PEX_RUNDOWN_WAIT_BLOCK WaitBlock;
|
|
|
|
//
|
|
// Rundown is active. since we are one of the threads blocking rundown we have the right to follow
|
|
// the pointer and decrement the active count. If we are the last thread then we have the right to
|
|
// wake up the waiter. After doing this we can't touch the data structures again.
|
|
//
|
|
WaitBlock = (PEX_RUNDOWN_WAIT_BLOCK) (Value & (~EX_RUNDOWN_ACTIVE));
|
|
|
|
ASSERT (WaitBlock->Count >= Count);
|
|
|
|
if (InterlockedExchangeAdd ((PLONG)&WaitBlock->Count, -(LONG)Count) == (LONG) Count) {
|
|
//
|
|
// We are the last thread out. Wake up the waiter.
|
|
//
|
|
KeSetEvent (&WaitBlock->WakeEvent, 0, FALSE);
|
|
}
|
|
return;
|
|
} else {
|
|
//
|
|
// Rundown isn't active. Just try and decrement the count. Some other protector thread way come and/or
|
|
// go as we do this or rundown might be initiated. We detect this because the exchange will fail and
|
|
// we have to retry
|
|
//
|
|
|
|
ASSERT (Value >= EX_RUNDOWN_COUNT_INC * Count);
|
|
|
|
NewValue = Value - EX_RUNDOWN_COUNT_INC * Count;
|
|
|
|
NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr,
|
|
(PVOID) NewValue,
|
|
(PVOID) Value);
|
|
if (NewValue == Value) {
|
|
return;
|
|
}
|
|
Value = NewValue;
|
|
}
|
|
|
|
} while (TRUE);
|
|
}
|
|
|
|
NTKERNELAPI
|
|
VOID
|
|
FASTCALL
|
|
ExfWaitForRundownProtectionRelease (
|
|
IN PEX_RUNDOWN_REF RunRef
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Wait till all outstanding rundown protection calls have exited
|
|
|
|
Arguments:
|
|
|
|
RunRef - Pointer to a rundown structure
|
|
|
|
Return Value:
|
|
|
|
None
|
|
|
|
--*/
|
|
{
|
|
EX_RUNDOWN_WAIT_BLOCK WaitBlock;
|
|
PKEVENT Event;
|
|
ULONG_PTR Value, NewValue;
|
|
ULONG WaitCount;
|
|
|
|
PAGED_CODE ();
|
|
|
|
//
|
|
// Fast path. this should be the normal case. If Value is zero then there are no current accessors and we have
|
|
// marked the rundown structure as rundown. If the value is EX_RUNDOWN_ACTIVE then the structure has already
|
|
// been rundown and ExRundownCompleted. This second case allows for callers that might initiate rundown
|
|
// multiple times (like handle table rundown) to have subsequent rundowns become noops.
|
|
//
|
|
|
|
Value = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr,
|
|
(PVOID) EX_RUNDOWN_ACTIVE,
|
|
(PVOID) 0);
|
|
if (Value == 0 || Value == EX_RUNDOWN_ACTIVE) {
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Slow path
|
|
//
|
|
Event = NULL;
|
|
do {
|
|
|
|
//
|
|
// Extract total number of waiters. Its biased by 2 so we can hanve the rundown active bit.
|
|
//
|
|
WaitCount = (ULONG) (Value >> EX_RUNDOWN_COUNT_SHIFT);
|
|
|
|
//
|
|
// If there are some accessors present then initialize and event (once only).
|
|
//
|
|
if (WaitCount > 0 && Event == NULL) {
|
|
Event = &WaitBlock.WakeEvent;
|
|
KeInitializeEvent (Event, SynchronizationEvent, FALSE);
|
|
}
|
|
//
|
|
// Store the wait count in the wait block. Waiting threads will start to decrement this as they exit
|
|
// if our exchange succeeds. Its possible for accessors to come and go between our initial fetch and
|
|
// the interlocked swap. This doesn't matter so long as there is the same number of outstanding accessors
|
|
// to wait for.
|
|
//
|
|
WaitBlock.Count = WaitCount;
|
|
|
|
NewValue = ((ULONG_PTR) &WaitBlock) | EX_RUNDOWN_ACTIVE;
|
|
|
|
NewValue = (ULONG_PTR) InterlockedCompareExchangePointer (&RunRef->Ptr,
|
|
(PVOID) NewValue,
|
|
(PVOID) Value);
|
|
if (NewValue == Value) {
|
|
if (WaitCount > 0) {
|
|
KeWaitForSingleObject (Event,
|
|
Executive,
|
|
KernelMode,
|
|
FALSE,
|
|
NULL);
|
|
|
|
ASSERT (WaitBlock.Count == 0);
|
|
|
|
}
|
|
return;
|
|
}
|
|
Value = NewValue;
|
|
|
|
ASSERT ((Value&EX_RUNDOWN_ACTIVE) == 0);
|
|
} while (TRUE);
|
|
}
|