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)前端分配器,