字元串與連結清單Review
1.字元串操作:
char *str = { “my first string” }; //ANSI字元串
wchar_t *wstr = { L“my first string” };//Unicode字元串
差別:
ANSI( char)是用一個位元組來表示的,最多256個符号。
Unicode( w_char )是用兩個位元組來表示的,能代表更多的字元。
字元串在核心中的定義:
UNICODE:( 寬字元串 )
Typedef struct _UNICODE_STRING{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
注:并不保證Buffer指向的字元串是以空結束的。(例取空UNICODE_STRING的Buffer會使核心崩潰)
最好使用微軟提供的Rtl系列函數來操作字元串。
ANSI:( 窄字元串 )
Typedef struct _STRING{
USHORT Length;
USHORT MaximumLength;
PSTR Buffer;
} ANSI_STRING , *PANSI_STRING;
字元串在核心中的初始化:
以下代碼會導緻藍屏:
UNICODE_STRING str = { 0 };//清空UNICODE_STRING,意味着Buffer也為0 ;
//會導緻空指針異常,str.Buffer為0。緩沖區大小為零啊。
//為避免空指針異常,應該為str. Buffer指定一個固定大小的緩沖區,如:
WCHAR strBuf[ 128 ] = { 0 };
str. Buffer=strBuf;
wcscpy(str. Buffer , L”my first string !”);
str. Length = str. MaximumLength = wcslen( L”my first string!” ) *sizeof( WCHAR );
更友善的,WDK提供了一個API來幫助開發者簡單的初始化一個UNICODE_STRING:
VOID RtlInitUnicodeString(
IN OUT PUNICODE_STRING DestinationString;//需要初始化的字元串變量
IN PCWSTR SourceString; //字元串
);
如:
UNICODE_STRING str = { 0 };
RtlInitUnicodeString( &str, L”my first string!” );
字元串的拷貝:
字元串不再是以空結束的,是以不能使用wcscpy()來拷貝。
注:拷貝目标字元串的Buffer指向的緩沖區必須有足夠的空間,倘若Buffer空間不夠,那麼字元串會拷貝不完全。(也就是說拷貝的目标STRING都必須有足夠 大小的已初始化Buffer)
UNICODE_STRING src = RTL_CONSTANT_STRING( L”My source string!” );
//dst的copy前操作:
UNICODE_STRING dst ;
WCHAR dst_buf [256];
RtlInitEmtyUnicodeString( &dst ,dst_buf , 256*sizeof(WCHAR) );
//copy操作:
RtlCopyUnicodeString( &dst ,&src );
字元串的連接配接:
1.source Append to UNICODE_STRING
NTSTATUS RtlAppendUnicodeToString(
IN OUT PUNICODE_STRING Destination,//Pointer to the buffered string.
IN PCWSTR Source
);
2.UNICODE Append to UNICODE_STRING
NTSTATUS RtlAppendUnicodeStringToString(
IN OUT PUNICODE_STRING Destination,//Pointer to a buffered Unicode string.
IN PUNICODE_STRING Source//Pointer to the buffered string to be concatenated
);
字元串的列印:
在WDK中用RtlStringCbPrintW()替代C語言中的列印函數(sprintf,swprintf,_snprintf..).
//在ntstrsafe.h中
NTSTATUS RtlStringCbPrintfW(
OUT LPWSTR pszDest,//Supplies a pointer to a caller-supplied buffer(字元串)
IN size_t cbDest, //Supplies the size of the destination buffer, in bytes
IN LPCWSTR pszFormat,//Supplies a pointer to a null-terminated text string
....
....
);
其在目标緩沖區記憶體不足時仍然可以列印,但多餘部分會被截去。
2.記憶體與連結清單:
在驅動中最常調用的是ExAllocatePoolWithTag();
如:dst .Buffer = (PWCHAR)ExAllocatePoolWithTag( NonPagedPool ,
Src .Length,
MEM_TAG,
);
PVOID ExAllocatePoolWithTag(
IN POOL_TYPE PoolType,//Specifies the type of pool memory to allocate.
IN SIZE_T NumberOfBytes,//Specifies the number of bytes to allocate.
IN ULONG Tag //Specifies the pool tag for the allocated memory.
);
第一個參數表明配置設定的記憶體是鎖定記憶體,永遠存在于真實實體記憶體上,不會被分頁交換到硬碟。
第二個參數是要配置設定的長度。
第三個參數是一個記憶體配置設定辨別。(TAG)
Windows NT把核心模式位址空間分成分頁記憶體池和非分頁記憶體池。(使用者模式位址空間總是分頁的)必須駐留的代碼和資料放在非分頁池;不必常駐的代碼和資料放在分頁池中。Windows NT為決定代碼和資料是否需要駐留非分頁池提供了一個簡單規則。
非分頁内容的空間是很小的,是以一般的東西都會放入分頁記憶體中。
執行在高于或等于DISPATCH_LEVEL級上的代碼必須存在于非分頁記憶體中。
Windows規定有些虛拟記憶體可以交換到檔案中,這類記憶體被稱為分頁記憶體。
有些虛拟記憶體永遠不會交換到檔案中,這些記憶體叫非分頁記憶體。
其中,主要的兩個差別就是分頁記憶體和非分頁記憶體。
分頁記憶體是低中斷級别的例程可以通路的。
而非分頁記憶體則是各個中斷級别的例程都可以使用的。
差別在于:
分頁記憶體是虛拟記憶體,在實體上未必總是能得到。
作業系統實作虛拟記憶體的主要方法就是通過分頁機制。在Win32中,實體位址空間,二維虛拟位址空間和實際記憶體位址是三個不同的概念。作業系統通過段選擇構成二維虛拟位址空間,每個程序有一個4G的位址空間,然後作業系統的記憶體管理器件把每個程序映射到一維實體位址空間的不同部分,但是因為我們實際機器 上大都沒有4G記憶體,是以,實際記憶體空間是實體位址空間的子集。
分頁管理器把位址空間劃分成4K大小的頁面(非Intel X86體系與之不同),當程序通路某個頁面時,作業系統首先在Cache中查找頁面,如果該頁面不在記憶體中,則産生一個缺頁中斷(Page Fault),程序就會被阻塞,直至要通路的頁面從外存調入記憶體中。
我們知道,在處理低優先級的中斷時,仍可以發生高優先級的中斷。既然缺頁過程也是一個中斷過程,那麼就産生一個問題,即,缺頁中斷和其他中斷的優先級的問題。如果在高于缺頁中斷的中斷優先級上再發生缺頁中斷,核心就會崩潰。是以在DISPATCH_LEVEL級别以上,絕對不能使用分頁記憶體,一旦使用分頁記憶體,就有發生缺頁中斷的可能,前面說過,這樣會導緻核心崩潰。
利用ExFreePool();釋放申請的記憶體池記憶體
使用LIST_ENTRY:
一個雙向連結清單結構,總是在使用中插入到已經存在的資料結構中。
為了讓一個struct成為一個節點,則可以将LIST_ENTRY放入struct結構内。
Typedef struct {
LIST_ENTRY list_entry;
PFILE_OBJECT file_object;
UNICODE_STRING file_name;
LARGE_INTEGER file_length;
} MY_FILE_INFOR, * PWY_FILE_INFOR;
若LIST_ENTRY作為連結清單的頭,那麼在使用之前,必須在調用前用InitializeListHead初始化。
将LIST_ENTRY插入到MY_FILE_INFOR結構頭部,這樣看來,整個MY_FILE_INFOR看起來就像是一個LIST_ENTRY。
但是,并非所有情況都可以那樣。如:MS的許多結構喜歡一開頭就是結構的長度,是以在通過LIST_ENTRY結構的位址擷取所在節點的位址時,有個位址偏移計算的過程。
For(p = my_list_head.Flink; p != &my_list_head.Flink; p = p->Flink){
PMY_FILE_INOR elem =
CONTATINING_RECORD(p, MY_FILE_INFOR, list_entry);
//....
}
CONTAINING_RECORD是WDK中已經定義的一個宏,作用是通過一個LIST_ENTRY結構的指針,找到這個結構所在節點的指針。定義如下:
#define CONTAINING_RECORD(address, type, field)
( (type *) ( \
(PCHAR)(address) - \
(ULONG_PTR)(&( (type *)0 )->field) ) \
)
注:
當自定義資料結構的第一個字段是LIST_ENTRY時,RemoveHeadList傳回的指針可以當做使用者自定義的指針。如:
PLIST_ENTRY pEntry = RemoveHeadList(&head);
PMYDATASTRUCT pMyData = (PMYDATASTRUCT) pEntry;
當自定義資料結構的第一個字段不是LIST_ENTRY時,RemoveHeadList傳回的指針不可以當做使用者自定義的指針。
此時需要通過自定義資料結構初始化的變量位址逆向算出自定義資料的指針。一般通過其變量在自定義資料中的偏移量,用變量減去這個偏移量,就會得到使用者自定義結構的指針的位址。
PLIST_ENTRY pEntry = RemoveHeadList(&head);
PIRP pIrp = CONTAINING_RECORD(pEntry,
MYDATASTRUCT,
ListEntry
);
在WDK中,DDK提供了宏CONTAINING_RECORD:
第一個參數指的是RemoveHeadList傳回的PLIST_ENTRY結構;
第二個參數是資料結構的名字;
第三個參數是資料結構中的字段。
(見《windows核心驅動詳解》第五章windows記憶體管理P129)
自旋鎖:
解決基本多線程問題,就使用鎖。
注:
一般為全局變量,
http://blog.sina.com.cn/s/blog_612ae57d0100fsey.html
http://blog.csdn.net/chenyujing1234/article/details/7764288
如下:
KSPIN_LOCK my_spin_lock;
KeInitializeSpinLock( &my_spin_lock );//初始化一個自旋鎖
KIRQL irql;//中斷請求級别
//interrupt request level (IRQL)
The hardware priority level at which a given kernel-mode routineruns, masking off interrupts with an equivalent or lower IRQL onthe processor. A routine can be preempted by an interrupt with ahigher IRQL.
//提高中斷級别
KeAcquireSpinLock( &my_spin_lock, &irql );
//.......to do some thing
KeReleaseSpinLock( &my_spin_lock, irql );
在雙向連結清單中使用自旋鎖:
LIST_ENTRY my_list_head;
KSPIN_LOCK my_list_lock;
Void List_Initialize()//連結清單初始化函數
{
InitializeListHead( &my_list_head );
KeInitializeSpinLock( &my_list_lock );
}
//連結清單一旦完成初始化,之後就可以用一系列加鎖的操作來代替普通操作。比如,插入一個節點。
普通操作:InsertHeadList( &my_list_head, (PLIST_ENTRY)&my_file_infor );
加鎖操作:
(插入操作)
ExInterLockedInsertHeadList(
&my_list_head,
(PLIST_ENTRY)& my_file_infor,
&my_list_lock
);
(移除操作)從連結清單中移除第一個節點并傳回
My_file_infor = ExInterLockedRemovedHeadList(
&my_list_head,
&my_list_lock
);
使用隊列自旋鎖提高性能:
初始化自旋鎖也是KeInitializeSpinLock.
KLOCK_QUEUE_HANDLE資料結構唯一的表示一個隊列自旋鎖
擷取鎖:VOID KeAcquireInStackQueuedSpinLock(
IN PKSPIN_LOCK SpinLock,
IN PKLOCK_QUEUE_HANDLE LockHandle);
釋放鎖:VOID KeReleaseInStackQueueSpinLock(
IN PKLOCE_QUEUE_HANDLE LockHandle
);