《Windows核心程式設計》---Interlocked原子訪問系列函式
所謂原子訪問,指的是一個執行緒在訪問某個資源的同時能夠保證沒有其他執行緒會在同一時刻訪問同一資源。Interlocked系列函式提供了這樣的操作。所有這些函式會以原子方式來操控一個值。
Interlocked函式的工作原理取決於程式碼執行的CPU平臺,如果是x86系列CPU,那麼Interlocked函式會在總線上維持一個硬體訊號,這個訊號會阻止其他CPU訪問同一個記憶體地址。我們必須確保傳給這些函式的變數地址是經過對齊的,否則這些函式可能會失敗。C執行庫提供了一個_aligned_malloc函式,我們可以使用這個函式來分配一塊對齊過的記憶體:
void * _aligned_malloc(
size_t size, //要分配的位元組數
size_t alignment //要對齊到的位元組邊界,傳給alignment的值必須是2的整數冪次方
);
Interlocked函式的另一個需要注意的點是它們執行得很快。呼叫一次Interlocked函式通常只佔用幾個CPU週期(通常小於50),而且不需要在使用者模式和核心模式之間進行切換(這個切換通常需要佔用1000個CPU週期以上)。
1)原子加減操作InterlockedExchangeAdd函式原型如下:
LONG __cdecl InterlockedExchangeAdd( //對32位值進行操作
__inout LONG
__in LONG Value //增量值,可為負值表示減法
);
LONGLONG __cdecl InterlockedExchangeAdd64( //對64位值進行操作
__inout LONGLONG volatile *Addend,
__in LONGLONG Value
);
2)InterlockedExchange函式用於原子地將32位整數設為指定的值:
LONG __cdecl InterlockedExchange(
__inout LONG volatile *Target, //指向要替換的32
__in LONG Value //替換的值
);
返回值是指向原先的32位整數值。
InterlockedExchangePointer函式原子地用於替換地址值:
PVOID __cdecl InterlockedExchangePointer(
__inout PVOID volatile *Target, //指向要替換的地址值的指標
__in PVOID Value //替換的地址值
);
返回值是原來的地址值。
對32位應用程式來說,以上兩個函式都用一個32位值替換另一個32位值,但對64位應用程式來說,InterlockedExchange替換的是32位值,而InterlockedExchangePointer替換的是64位值。
當然,還有一個函式InterlockedExchange64專門用來原子地操作64位值的:
LONGLONG __cdecl InterlockedExchange64(
__inout LONGLONG volatile *Target,
__in LONGLONG Value
);
在實現旋轉鎖時,InterlockedExchange函式極其有用:
//標識一個共享資源是否正在被使用的全域性變數
BOOL g_fResourceInUse = FALSE;
...
void ASCEFunc()
{
//等待訪問共享資源
while(InterlockedExchange(&g_fResourceInUse, TRUE) == TRUE)
sleep(0);
//訪問共享資源
...
//結束訪問
InterlockedExchange(&g_fResourceInUse, FALSE);
}
注意,在使用這項技術時要小心,因為旋轉鎖會耗費CPU時間。特別是在單CPU機器上應該避免使用旋轉鎖,如果一個執行緒不停地迴圈,那麼這會浪費寶貴的CPU時間,而且會阻止其他執行緒改變該鎖的值。
3)函式InterlockedCompareExchange函式和InterlockedCompareExchangePointer函式原型如下:
LONG __cdecl InterlockedCompareExchange(
__inout LONG volatile *Destination, //當前值
__in LONG Exchange, //
__in LONG Comparand //比較值
);
PVOID __cdecl InterlockedCompareExchangePointer(
__inout PVOID volatile *Destination,
__in PVOID Exchange,
__in PVOID Comparand
);
這兩個函式以原子方式執行一個測試和設定操作。對32位應用程式來說,這兩個函式都對32位值進行操作;在64位應用程式中,InterlockedCompareExchange對32位值進行操作而InterlockedCompareExchangePointer對64位值進行操作。函式會將當前值(Destination指向的)與引數Comparand進行比較,如果兩個值相同,那麼函式會將*Destination修改為Exchange引數指定的值。若不等,則*Destination保持不變。函式會返回*Destination原來的值。所有這些操作都是一個原子執行單元來完成的。
當然,這兩個函式的64位版本是:
LONGLONG __cdecl InterlockedCompareExchange64(
__inout LONGLONG volatile *Destination,
__in LONGLONG Exchange,
__in LONGLONG Comparand
);
4)Interlocked單向連結串列函式
InitializeSListHead函式用於建立一個空的單向連結串列棧:
void WINAPI InitializeSListHead(
__inout PSLIST_HEADER ListHead
);
InterlockedPushEntrySList函式在棧頂新增一個元素:
PSLIST_ENTRY WINAPI InterlockedPushEntrySList(
__inout PSLIST_HEADER ListHead,
__inout PSLIST_ENTRY ListEntry
);
InterlockedPopEntrySList函式移除位於棧頂的元素並將其返回:
PSLIST_ENTRY WINAPI InterlockedPopEntrySList(
__inout PSLIST_HEADER ListHead
);
InterlockedFlushSList函式用於清空單向連結串列棧:
PSLIST_ENTRY WINAPI InterlockedFlushSList(
__inout PSLIST_HEADER ListHead
);
QueryDepthSList函式用於返回棧中元素的數量:
USHORT WINAPI QueryDepthSList(
__in PSLIST_HEADER ListHead
);
單向連結串列棧中元素的結構是:
typedefstruct _SLIST_ENTRY {
struct _SLIST_ENTRY *Next;
} SLIST_ENTRY, *PSLIST_ENTRY;
注意:所有單向連結串列棧中的元素必須以MEMORY_ALLOCATION_ALIGNMENT方式對齊,使用_aligned_malloc函式即可。
例項如下:
#include<windows.h>
#include<malloc.h>
#include<stdio.h>
// Structure to be used for a list item; the first member is the
// SLIST_ENTRY structure, and additional members are used for data.
// Here, the data is simply a signature for testing purposes.
typedefstruct _PROGRAM_ITEM {
SLIST_ENTRY ItemEntry;
ULONG Signature;
} PROGRAM_ITEM, *PPROGRAM_ITEM;
int main( )
{
ULONG Count;
PSLIST_ENTRY pFirstEntry, pListEntry;
PSLIST_HEADER pListHead;
PPROGRAM_ITEM pProgramItem;
// Initialize the list header to a MEMORY_ALLOCATION_ALIGNMENT boundary.
pListHead = (PSLIST_HEADER)_aligned_malloc(sizeof(SLIST_HEADER),
MEMORY_ALLOCATION_ALIGNMENT);
if( NULL == pListHead )
{
printf("Memory allocation failed./n");
return -1;
}
InitializeSListHead(pListHead);
// Insert 10 items into the list.
for( Count = 1; Count <= 10; Count += 1 )
{
pProgramItem = (PPROGRAM_ITEM)_aligned_malloc(sizeof(PROGRAM_ITEM),
MEMORY_ALLOCATION_ALIGNMENT);
if( NULL == pProgramItem )
{
printf("Memory allocation failed./n");
return -1;
}
pProgramItem->Signature = Count;
pFirstEntry = InterlockedPushEntrySList(pListHead,
&(pProgramItem->ItemEntry));
}
// Remove 10 items from the list and display the signature.
for( Count = 10; Count >= 1; Count -= 1 )
{
pListEntry = InterlockedPopEntrySList(pListHead);
if( NULL == pListEntry )
{
printf("List is empty./n");
return -1;
}
pProgramItem = (PPROGRAM_ITEM)pListEntry;
printf("Signature is %d/n", pProgramItem->Signature);
// This example assumes that the SLIST_ENTRY structure is the
// first member of the structure. If your structure does not
// follow this convention, you must compute the starting address
// of the structure before calling the free function.
_aligned_free(pListEntry);
}
// Flush the list and verify that the items are gone.
pListEntry = InterlockedFlushSList(pListHead);
pFirstEntry = InterlockedPopEntrySList(pListHead);
if (pFirstEntry != NULL)
{
printf("Error: List is not empty./n");
return -1;
}
_aligned_free(pListHead);
return 1;
}