1 引言
? 眾所周知,操作系統使用伙伴系統管理內存,不僅會造成大量的內存碎片,同時處理效率也較低下。SLAB是一種內存管理機制,其擁有較高的處理效率,同時也有效的避免內存碎片的產生,其核心思想是預分配。其按照SIZE對內存進行分類管理的,當申請一塊大小為SIZE的內存時,分配器就從SIZE集合中分配一個內存塊(BLOCK)出去,當釋放一個大小為SIZE的內存時,則將該內存塊放回到原有集合,而不是釋放給操作系統。當又要申請相同大小的內存時,可以復用之前被回收的內存塊(BLOCK),從而避免了內存碎片的產生。[注:因SLAB處理過程的細節較多,在此只是做一個原理上的講解]
?
2 總體結構
圖1 SLAB內存結構
?
3 處理流程
- SLAB頭:包含SLAB管理的匯總信息,如最小分配單元(min_size)、最小分配單元對應的位移(min_shift)、頁數組地址(pages)、空閑頁鏈表(free)、可分配空間的起始地址(start)、內存塊結束地址(end)等等信息(如代碼1所示),在內存的管理過程中,內存的分配、回收、定位等等操作都依賴于這些數據。
- SLOT數組:SLOT數組各成員分別負責固定大小的內存塊(BLOCK)的分配和回收。在nginx中SLOT[0]~SLOT[7]分別負責區間在[1~8]、[9~16]、[17~32]、[33~64]、[65~128]、[129~256]、[257~512]、[513~1024]字節大小內存的分配,但為方便內存塊(BLOCK)的分配和回收,每個內存塊(BLOCK)的大小為各區間的上限(8、16、32、64、128、256、512、1024)。比如說:假如應用進程請求申請5個字節的空間,因5處在[1~8]的區間內,因此由SLOT[0]負責該內存的分配,但區間[1~8]的上限為8,因此即使申請5個字節,卻依然分配8字節給應用進程。以此類推:假如申請12字節,12處于區間[9~16]之間,取上限16,因此由SLOT[1]分配16個字節給應用進程;假如申請50字節,50處于區間[33~64]之間,取上限64,因此由SLOT[2]分配64個字節給應用進程;假如申請84字節,84處于區間[65~128]之間,取上限128,因此由SLOT[3]分配128個字節;...;假如申請722字節,722處于區間[513~1024]之間,取上限1024,因此由SLOT[7]分配1024字節。
- PAGES數組:PAGES數組各成員分別負責可分配空間中各頁的查詢、分配和回收,其處理流程可參考3.2節的說明。
- 可分配空間:SLAB在邏輯上將可分配空間劃分成M個內存頁,每頁大小為4K。每頁內存與PAGES數組成員一一對應,由PAGES數組各成員負責各內存頁的分配和回收。
- 被浪費空間:按照每頁4K的大小對空間進行劃分時,滿足4K的空間,將作為可分配空間被PAGES數組進行管理,而最后剩余的不足4K的內存將會被舍棄,也就是被浪費了!
3.1 初始化流程
? 初始化階段主要完成對SLOT頭、SLOT數組、PAGES數組、可分配空間和被浪費空間的區域分化,各區域的劃分可參考圖1和各模塊功能的說明。nginx中slab結構體如下所示:
- typedef?struct?{??
- ????size_t????????????min_size;?????/*?最小分配單元?*/??
- ????size_t????????????min_shift;????/*?最小分配單元對應的位移?*/??
- ??
- ????ngx_slab_page_t??*pages;????????/*?頁數組?*/??
- ????ngx_slab_page_t???free;?????????/*?空閑頁鏈表?*/??
- ??
- ????u_char???????????*start;????????/*?可分配空間的起始地址?*/??
- ????u_char???????????*end;??????????/*?內存塊的結束地址?*/??
- ??
- ????...?????????????????????????????/*?其他變量成員(省略)?*/??
- }ngx_slab_pool_t??
代碼1 SLAB頭部結構體
3.2 頁的管理
3.2.1 頁的分配
1)分配之前
? 在SLAB初始化之后,所有頁可以看成是一個連續的整體,其內存結構如下圖所示:
圖2 頁的結構(分配之前)
2)申請一頁
? 當申請一頁時,則將pages[0]從free鏈表中分離出去,如下圖所示:
圖3 頁的結構(申請一頁)
3)申請二頁
? 當再申請二頁時,則將page[3]和pages[4]作為一個整體從free鏈表中分離出去,如下圖所示:
圖4 頁的結構(申請二頁)
3.2.2 頁的回收
1)回收一頁
? 當頁被回收時,被回收的頁并不會和未被分配的頁進行合并,而是通過鏈表串聯起來,這樣將造成運行的時間越長,要申請到超過一頁大小的空間也會變得越來越難。正是因為這個原因,所以slab機制不適合用來反復分配和回收超過一頁大小的內存空間。如下圖所示:[切記:slab機制不適合用來反復分配和回收超過一頁大小的內存空間]
圖5 頁的結構(回收一頁)
2)回收二頁
? 當頁被回收時,被回收的頁并不會和未被分配的頁進行合并,而是通過鏈表串聯起來,如下圖所示:
圖6 頁的結構(回收二頁)
3.4 SLOT的管理
? SLOT數組的作用可以參考第三章開頭的闡述。SLOT數組各成員相當于鏈表頭,在SLOT的分配和回收過程中,通過鏈表來組織用于分配各SIZE(1~1024)的PAGE。如,在某時刻,可能存在如下狀態:
圖7? SLOT和PAGES的關系
3.4.1 頁的管理
1)初始狀態
? 在SLAB初始化后,slot鏈表頭的下一個節點都為NULL,如下圖所示:
圖8 SLOT初始狀態
2)添加一頁
? SLOT[2]負責32(17~32)字節空間的分配和回收,假設現申請分配24字節(17~32之間)的空間,因此將從slot[2]中分配。但在初始狀態下slot[2]的下一頁為NULL,因此需要向頁管理模塊申請一頁pages[x]內存,再將該頁加入到slot[2]的鏈表中,添加之后的內存結構如下圖所示:
圖9 slot[2]增加一頁
3)暫離鏈表
? SLOT[2]中的每一頁有128(4K/32=128)個單元,當一頁分配了128次時,表示該頁可分配單元分配完畢,此時該頁將會暫時從鏈表中剔除出去,以防止下次申請時,做無效的遍歷。如下圖所示:
圖10 slot[2]第一頁被使用完
?
4)再添一頁
? 當再次申請17~32字節時,此時slot[2]的后續鏈表為空,因此需要再次向頁管理申請一頁pages[y]內存,再將該頁加入到slot[2]的鏈表中,如下圖所示。如果該頁又被分配完,則進行3)的處理。
圖11 slot[2]再添一頁
5)重入鏈表
? 當所有單元被用完的頁pages[x]中的一個單元被回收時,頁pages[x]中將有1個單元可以再次被分配使用,此時應該將pages[x]重新加入到slot[2]的鏈表中,以便下次分配時可以從頁pages[x]中進行查找。此時內存組織形式如下圖所示:
圖12 頁pages[x]重入鏈表
6)回收整頁
? 當頁pages[x]所有單元被釋放后,則該頁將會被全部回收:該頁將從slot[2]的鏈表中被剔除,并將頁pages[x]重新加入到free鏈表。此時的內存結構圖如下圖所示:
圖13 回收頁pages[x]
?
3.4.2 SLOT的分配
1)頁內結構
? 被加入到SLOT數組鏈表的頁在邏輯上劃分為很多的內存單元,每一小內存單元的使用情況是通過位圖進行標記的,1表示被占用,0表示未被占用。如:第20位bit的值為1時,表示第20個內存單元被占用。假如此SLOT鏈表的PAGE正好可以劃分為32塊,則其邏輯組織結構如下圖所示:
圖14 PAGE內結構
2)分配單元
? 假如此時在SLOT[s]鏈表的頁中連續申請4個內存單元,則其前4個內存單元將首先被占用,則此時的位圖結構如下圖所示:
圖15 分配單元
3) 釋放單元
? 假如此時釋放SLOT[s]鏈表頁中第3個內存單元,則此時的位圖結構如下圖所示:
圖16 釋放單元
?
轉自:"祁峰"的CSDN博客