1. 程式人生 > >win7下堆管理結構分析

win7下堆管理結構分析

書接上文,上次說到gsoap在記憶體申請失敗的情況下,有個bug,申請記憶體的時候未判斷是否成功就直接使用,會造成程式的崩潰。不過,也不能單純的怪罪到gsoap上,為何我們程式的記憶體申請失敗呢,是記憶體用光了嗎?

還回到原來的崩潰分析,使用!address檢視下記憶體的使用情況,如下圖:

從圖中看到,可用記憶體僅剩0.24%,而絕大多數都被heap佔用了,足有73.42%,看到這種情況,說明程式中一定有地方new了記憶體卻沒及時釋放,以至於記憶體耗盡,引發gsoap那個bug,這樣看來,gsoap引起的崩潰,其實也是我們程式自身的問題。

在網上查到的gsoap記憶體管理方式:採用soap_malloc(用法和malloc一樣)方法分配記憶體, soap_malloc無需釋放記憶體,在gsoap內部採用連結串列的方式管理分配的記憶體,在執行soap_destroy(tsoap); soap_end(tsoap)後會自動回收記憶體。雖然如此,但由於我們的程式有個定時更新的操作,隔一段時間就會去拿一次資源,每次都會分配記憶體,而soap_end(tsoap)這個函式,只有程式退出的時候才去呼叫,因此執行時間一久,就容易把記憶體耗盡,進而引發core dump。

解決方法是:每次調webservice介面後,就呼叫這兩個函式soap_destroy(tsoap); soap_end(tsoap);清理下臨時資料。

XP下堆結構在《軟體除錯》,《windows高階除錯》這兩本書裡已經寫的很詳盡了,在此不在贅述,重點講講win7及後續系統(包括我現在用的win10)堆結構的變化。

1.堆結構變化

xp下堆結構如圖,節選自《軟體除錯》

HEAP和HEAP_SEGMENT結構起始處都有一個8位元組的HEAP_ENTRY結構,HEAP結構內包含一個64元素的HEAP_SEGMENT陣列,裡面記錄了各個HEAP_SEGMENT的資訊,對於0號段,首先是一個HEAP結構,在這個之後是該段的HEAP_SEGMENT,其餘段則最開始便是HEAP_SEGMENT。

而在win7下,則將其統一起來,win7下的HEAP結構,形如

typedef struct _HEAP_ENTRY
{
	UINT SubSegmentCode;
	USHORT PreviousSize;
	BYTE SegmentOffset;
	BYTE UnusedBytes;
}HEAP_ENTRY;

typedef struct _HEAP_SEGMENT

{
	HEAP_ENTRY Entry;
	UINT   SegmentSignature;
	UINT   SegmentFlags;
	LIST_ENTRY SegmentListEntry; //各heap_segment通過此欄位連線
	PHEAP Heap;                  //指向所屬的heap
	//...省略若干欄位
	LIST_ENTRY UCRSegmentList;
}HEAP_SEGMENT;

typedef struct _HEAP

{
	HEAP_SEGMENT Segment;
	UINT   Flags;
	UINT   ForceFlags;
	//...省略若干欄位
	LIST_ENTRY SegmentList;  //通過此欄位找到各heap_segment,從0號段開始,自然首先同HEAP最開始處那個HEAP_SEGMENT的SegmentListEntry連結
	//...省略若干欄位
	HEAP_TUNING_PARAMETERS TuningParameters;
}*PHEAP, HEAP;

可以看出,最大區別是HEAP結構記錄HEAP_SEGMENT的方式採用了連結串列,這樣不再受陣列大小的約束,同時將HEAP_SEGMENT欄位包含進HEAP,這樣各堆段的起始便統一為HEAP_SEGMENT,不再有xp下0號段與其他段那種區別,可以統一進行管理了。
2.前端分配器的變化

XP下采用旁視列表(Look Aside List,LAL)前端分配器,旁視列表其實就是一張表,位於0號段HEAP_SEGMENT結構之後,該表有128項,每一項對應於一個單向連結串列。每個單向連結串列都包含了一組固定大小空閒堆塊,從16位元組的大小開始依次遞增。當有分配請求到來的時候,即可在對應的列表中進行分配,如果在LAL中無法滿足這個請求,則將這個請求轉發到後端分配器中做進一步的處理。

而在win7下,則使用低碎片(Low Fragmentation,LF)前端分配器,