转一个solaris虚拟内存管理的wiki
Virtual Meory management Wikipedia,自由的百科全書(shū)
1. 內(nèi)存管理 1.1. 虛擬地址空間 1.1.1. 概述 Solaris的進(jìn)程地址空間分配分為兩個(gè)階段:內(nèi)核地址空間的分配和用戶地址空間的分配。內(nèi)核地址空間的分配只在系統(tǒng)啟動(dòng)時(shí)進(jìn)行一次,當(dāng)創(chuàng)建第一個(gè)內(nèi)核進(jìn)程,該進(jìn)程的地址空間就是剛剛分配好的內(nèi)核地址空間,而且之后所有的內(nèi)核進(jìn)程都共享該內(nèi)核地址空間。在Solaris中,只有內(nèi)核地址空間是經(jīng)過(guò)分配得到的,之后利用fork進(jìn)行進(jìn)程創(chuàng)建時(shí),都是直接復(fù)制父進(jìn)程的地址空間。第一個(gè)用戶進(jìn)程創(chuàng)建時(shí),它會(huì)復(fù)制父進(jìn)程的地址空間,也就是內(nèi)核地址空間。 1.1.2. 數(shù)據(jù)結(jié)構(gòu) 1.1.2.1. 重要數(shù)據(jù)結(jié)構(gòu)間關(guān)系 圖1 數(shù)據(jù)結(jié)構(gòu)關(guān)系圖 每個(gè)用戶進(jìn)程都擁有自己獨(dú)立的地址空間,而所有的內(nèi)核進(jìn)程則共享唯一的內(nèi)核地址空間。每個(gè)地址空間將若干segment driver封裝起來(lái),這些segment driver以AVL樹(shù)的形式組織起來(lái)。每個(gè)地址空間都要有hat結(jié)構(gòu),在Solaris中,硬件地址轉(zhuǎn)換(Hardware Address Translation)實(shí)現(xiàn)利用hat數(shù)據(jù)結(jié)構(gòu)來(lái)存放一個(gè)地址空間的頂級(jí)地址轉(zhuǎn)換信息。當(dāng)segment driver試圖操作硬件MMU時(shí),HAT層會(huì)被調(diào)用。如果一個(gè)segment driver試圖創(chuàng)建或者撤銷某個(gè)地址空間映射,它就會(huì)調(diào)用相應(yīng)的HAT函數(shù)。而htable結(jié)構(gòu)則用來(lái)描述硬件頁(yè)表。 1.1.2.2.as as結(jié)構(gòu)是進(jìn)程地址空間結(jié)構(gòu),每一個(gè)進(jìn)程都對(duì)應(yīng)一個(gè)as結(jié)構(gòu)的變量。as結(jié)構(gòu)在uts/common/vm/as.h中定義,它的定義如下: struct as { /*as結(jié)構(gòu)的一些屬性*/ kmutex_t a_contents; /* 保護(hù)as結(jié)構(gòu)中的一些域 */ uchar_t a_flags; /* 描述as的屬性*/ uchar_t a_vbits; /* 用來(lái)收集統(tǒng)計(jì)信息 */ kcondvar_t a_cv; /* 被as_rangelock使用*/ struct hrmstat *a_hrm; /* 維護(hù)引用信息和修改信息 */ caddr_t a_userlimit; /* 該地址空間的最高允許地址 */ size_t a_size; /* 地址空間的大小 */ /* 與hat結(jié)構(gòu)相關(guān)的字段 */ struct hat *a_hat; /* hat數(shù)據(jù)結(jié)構(gòu)*/ /*與segment相關(guān)*/ struct seg *a_seglast; /* 該地址空間中上次命中的segment */ krwlock_t a_lock; /* 保護(hù)與segment相關(guān)的一些域 */ struct seg *a_lastgap; /* 由as_gap()發(fā)現(xiàn)的最近一次的segment */ struct seg *a_lastgaphl; /* last seg saved in as_gap() either for */ /* AS_HI or AS_LO used in as_addseg() */ avl_tree_t a_segtree; /* 地址空間中的segment,以AVL樹(shù)的形式組織 */ avl_tree_t a_wpage; /* 守護(hù)頁(yè) (procfs) */ uchar_t a_updatedir; /* 映射改變時(shí),重建a_objectdir */ timespec_t a_updatetime; /* 映射上一次改變的時(shí)間 */ vnode_t **a_objectdir; /* 對(duì)象目錄 (procfs) */ size_t a_sizedir; /* 對(duì)象目錄的大小*/ struct as_callback *a_callbacks; /* callback列表*/ void *a_xhat; /* xhat提供者列表 */ }; as中的字段分成三個(gè)部分。第一個(gè)部分是用于描述as的一般性字段,包括用于保護(hù)as中域的互斥變量a_contents,描述as屬性的a_flags域,用來(lái)收集統(tǒng)計(jì)信息的a_vbits域,用來(lái)保護(hù)as_rangelock的條件變量,以及描述該地址空間大小的size域。 第二部分是與hat結(jié)構(gòu)相關(guān)的字段。每個(gè)地址空間都需要有相應(yīng)的hat結(jié)構(gòu),hat結(jié)構(gòu)中主要存放地址轉(zhuǎn)換信息,幫助MMU完成虛擬地址到物理地址的轉(zhuǎn)換。第三部分是與segment相關(guān)的字段。 a_seglast表示該地址空間中最近一次命中的segment。每次對(duì)地址空間的訪問(wèn)都需要更新該字段。 a_lock保護(hù)與segment相關(guān)的域,當(dāng)對(duì)as中某個(gè)segment相關(guān)的域操作時(shí),都需要用該讀寫鎖對(duì)這些域進(jìn)行保護(hù)。 a_lastgaphl表示在as_gap()中存放的最近一次訪問(wèn)的segment。在向地址空間增加一個(gè)segment時(shí),為了提高效率,并沒(méi)有采用avl_find()方法來(lái)尋找插入點(diǎn),而是簡(jiǎn)單地以a_lastgaphl為起點(diǎn)尋找插入點(diǎn)(如果a_lastgaphl不空)。 a_segtree表示地址空間中所有segment組成的AVL樹(shù)。在Solaris中,地址空間中的所有segment都以AVL樹(shù)的形式組織起來(lái)。這樣,對(duì)segment進(jìn)行增刪查改操作效率比較高。 a_wpage表示該地址空間的守護(hù)頁(yè)。這些守護(hù)頁(yè)也就是procfs。利用守護(hù)頁(yè)可以對(duì)內(nèi)存中的一些段進(jìn)行監(jiān)控。 a_updatedir標(biāo)示地址空間的映射發(fā)生變化,通知進(jìn)程需要重建a_objectdir。例如,當(dāng)向進(jìn)程地址空間中,新增一個(gè)segment時(shí),需要將該地址空間的a_updatedir域置為1。 a_updatetime表示最近一次地址映射發(fā)生變化的時(shí)間。 a_objectdir表示對(duì)象目錄。 a_callbacks是callback列表。該列表中主要是掛在segment driver上的回調(diào)函數(shù)列表。 1.1.2.3.seg seg結(jié)構(gòu)是描述進(jìn)程中segment的結(jié)構(gòu),每一個(gè)進(jìn)程地址空間都由若干segment組成,這些segment組成一個(gè)AVL樹(shù)。seg結(jié)構(gòu)在uts/common/vm/seg.h中,它的定義如下: struct seg { caddr_t s_base; /* 虛擬基地址 */ size_t s_size; /* 以byte計(jì)算的segment大小 */ uint_t s_szc; /* 該段所支持的最大的頁(yè)大小 */ uint_t s_flags; /* segment的標(biāo)記*/ struct as *s_as; /* 該segment從屬的地址空間 */ avl_node_t s_tree; /* 在該地址空間中,針對(duì)該segment的AVL樹(shù)鏈接 */ struct seg_ops *s_ops; /* 對(duì)該segment的操作向量*/ void *s_data; /* 針對(duì)具體實(shí)例的私有數(shù)據(jù)*/ }; 每個(gè)地址空間都包含了若干segment,這些segment由不同的segment driver管理。Seg結(jié)構(gòu)包含該segment的虛擬基地址,segment的大小,指向其所屬地址空間的指針,用來(lái)維護(hù)AVL樹(shù)的指針,以及掛在該segment driver上的回調(diào)函數(shù)和數(shù)據(jù)。可以看出,在seg結(jié)構(gòu)中既包含描述segment的屬性信息,也包含對(duì)該segment進(jìn)行操作、訪問(wèn)的函數(shù)信息。 1.1.2.4.hat hat結(jié)構(gòu)是進(jìn)程地址空間結(jié)構(gòu),每一個(gè)進(jìn)程都對(duì)應(yīng)一個(gè)as結(jié)構(gòu)的變量。as結(jié)構(gòu)在uts/common/vm/as.h中,它的定義如下: struct hat { /* hat結(jié)構(gòu)的一些屬性 */ kmutex_t hat_mutex; /* 整個(gè)hat結(jié)構(gòu)的互斥量 */ kmutex_t hat_switch_mutex; /* hat切換時(shí)的互斥量 */ struct as *hat_as; /* 指向hat所屬的地址空間 */ uint_t hat_stats; /* hat結(jié)構(gòu)的統(tǒng)計(jì)信息 */ pgcnt_t hat_pages_mapped[MAX_PAGE_LEVEL + 1]; cpuset_t hat_cpus; uint16_t hat_flags; /* hat的標(biāo)記 */ /* 維護(hù)hat鏈表的字段 */ struct hat *hat_next; /* 指向下一個(gè)hat結(jié)構(gòu) */ struct hat *hat_prev; /* 指向上一個(gè)hat結(jié)構(gòu) */ /* 與htable相關(guān)的字段 */ htable_t *hat_htable; /* 指向頂級(jí)硬件頁(yè)表的指針 */ uint_t hat_num_hash; /* htable哈希數(shù)目 */ htable_t **hat_ht_hash; htable_t *hat_ht_cached; /* 空閑的htables緩存 */ x86pte_t hat_vlp_ptes[VLP_NUM_PTES]; }; hat中的字段分成三個(gè)部分。第一部分主要描述hat結(jié)構(gòu)的屬性,包括一些互斥變量,hat所屬的地址空間指針,hat結(jié)構(gòu)的統(tǒng)計(jì)信息,及hat的標(biāo)記等。第二部分則包含了如何組織hat雙向鏈表的字段。 第三部分是一些與htable相關(guān)的字段。 hat_htable是指向頂級(jí)硬件頁(yè)表的指針。當(dāng)進(jìn)行地址轉(zhuǎn)換,調(diào)用HAT層時(shí),通過(guò)hat結(jié)構(gòu)就可以找到頂級(jí)硬件頁(yè)表,從而進(jìn)行地址轉(zhuǎn)換。 1.1.3. 情景 1.1.3.1. 內(nèi)核地址空間的分配 這個(gè)情景描述如何在系統(tǒng)啟動(dòng)時(shí)分配整個(gè)系統(tǒng)中的第一個(gè)地址空間---內(nèi)核地址空間,其中涉及到的主要函數(shù)包括:表1 內(nèi)核地址空間分配中的主要函數(shù)函數(shù)名 文件名 功能描述 kvm_init uts/i86pc/os/Startup.c 內(nèi)核虛擬地址空間的初始化工作 as_avlinit uts/common/vm/Vm_as.c 為地址空間分配所需的avl樹(shù) as_addseg uts/common/vm/Vm_seg.c 將segment添加到指定的地址空間上 as_setprot uts/common/vm/Vm_as.c 為指定區(qū)域設(shè)置映射分配內(nèi)核地址空間的工作主要在kvm_init()函數(shù)中完成。這個(gè)函數(shù)的流程如圖2所示。 圖2 kvm_init()的函數(shù)流程圖 kvm_init()首先為kas(內(nèi)核地址空間的as結(jié)構(gòu))創(chuàng)建segment AVL樹(shù)和守護(hù)頁(yè)AVL樹(shù),該過(guò)程是由as_avlinit()函數(shù)完成的。然后kvm_init()會(huì)調(diào)用seg_attach()及segkmem_create()函數(shù)來(lái)為內(nèi)核地址空間添加必要的段,如內(nèi)核代碼段,kvalloc段,內(nèi)核debugger段。seg_attach()函數(shù)的主要實(shí)現(xiàn)工作是由as_addseg()函數(shù)完成的。as_addseg()函數(shù)的流程如圖2所示。 圖3 as_addseg()的流程圖 在as_addseg()函數(shù)中,為newseg尋找插入點(diǎn)時(shí),為了提高效率,并沒(méi)有直接利用avl_find()來(lái)尋找插入點(diǎn),而是先利用as的a_lastgaphl,該域存放的是as_gap()函數(shù)最近使用的segment。如果a_lastgaphl不為空,那么則以它作為初始點(diǎn),為newseg尋找合適的插入點(diǎn),如果找到符合條件的插入點(diǎn),則調(diào)用avl_insert_here()函數(shù)將newseg插入到AVL樹(shù)中;如果a_lastgaphl為空,則利用avl_find()等函數(shù)來(lái)尋找符合條件的插入點(diǎn)。最后還需要判斷所找到的插入點(diǎn)表示的段與newseg段是否有重合,如果有重合,在sparc處理器下就需要調(diào)用seg_unmap()函數(shù)取消插入點(diǎn)所表示的段的映射,之后調(diào)用avl_insert()函數(shù)將newseg插入到AVL樹(shù)中。 利用seg_attach()及segkmem_create()函數(shù)來(lái)為內(nèi)核地址空間添加好內(nèi)核代碼段,kvalloc段及內(nèi)核debugger段之后,kvm_init()調(diào)用as_setprot()來(lái)對(duì)Red Zone、內(nèi)核代碼段及內(nèi)核數(shù)據(jù)段設(shè)置相應(yīng)的訪問(wèn)權(quán)限。在這里,為了確保Red Zone域不為訪問(wèn),將其權(quán)限置為0;將內(nèi)核代碼段的權(quán)限置為可讀/可寫/可執(zhí)行;將內(nèi)核數(shù)據(jù)段的權(quán)限置為可讀/可寫/可執(zhí)行。圖4將給出as_setprot()函數(shù)的流程: 1.1.3.2.虛擬地址空間的釋放 這個(gè)情景描述如何釋放一個(gè)虛擬地址空間,其中涉及到的主要函數(shù)包括:表2 虛擬地址空間釋放中的主要函數(shù)函數(shù)名 文件名 功能描述 as_free uts/common/vm/Vm_as.c 釋放虛擬地址空間 hat_free_end uts/i86pc/vm/Hat_i86.c 進(jìn)程地址空間正被銷毀,該函數(shù)將銷毀相應(yīng)的hat xhat_free_end_all uts/common/vm/Xhat.c 銷毀相應(yīng)的xhat 釋放虛擬地址空間的工作主要在as_free ()函數(shù)中完成。這個(gè)函數(shù)的流程如圖4所示: 圖4 as_free()的流程圖 as_free()函數(shù)將釋放一個(gè)地址空間,它的具體過(guò)程: 1. 它會(huì)調(diào)用as_do_callback()函數(shù)將所有的回調(diào)函數(shù)執(zhí)行完畢,將回調(diào)函數(shù)列表清空 2. 進(jìn)行hat結(jié)構(gòu)釋放的開(kāi)始工作 a) 首先將as的flag置為AS_BUSY,以阻止新的XHAT被附到as上 b) 調(diào)用hat_free_start來(lái)設(shè)置hat_flag為HAT_FREEING(在銷毀HAT時(shí),需要設(shè)置該標(biāo)志),為釋放hat做好準(zhǔn)備 c) 調(diào)用xhat_free_start_all,主要的工作就是將xhat鏈表中的holder都設(shè)為curthread,為釋放xhat列表做好準(zhǔn)備 3. 釋放as中的segment a) 利用SEGOP_UNMAP來(lái)取消對(duì)as中各段的映射,并會(huì)判斷其返回值err。若err=0表明正常執(zhí)行;否則err==EAGAIN(表明當(dāng)前段資源不可用),出現(xiàn)該err有兩種情況:callback未處理完,內(nèi)存被加鎖,要進(jìn)行等待。并回到步驟1重新進(jìn)行。 4. 進(jìn)行hat結(jié)構(gòu)釋放的結(jié)尾工作 a) 調(diào)用hat_free_end來(lái)真正清除相應(yīng)的hat結(jié)構(gòu)? 確保hat當(dāng)前沒(méi)有被page table stealing,然后從hat鏈表中刪除hat結(jié)構(gòu),并重置kas的hat鏈表? 將所有htables都釋放? 利用kmem_cache_free來(lái)釋放hat結(jié)構(gòu)所占用的cache b) 調(diào)用xhat_free_end_all來(lái)釋放XHAT 5. 釋放as結(jié)構(gòu)本身所占的空間 a) 釋放object directory(procfs)中的vnode b) 利用kmem_cache_fee釋放as結(jié)構(gòu) 1.1.3.3.虛擬地址空間的復(fù)制 這個(gè)情景描述如何復(fù)制一個(gè)虛擬地址空間,其中涉及到的主要函數(shù)包括:表2 虛擬地址空間復(fù)制中的主要函數(shù) 函數(shù)名 文件名 功能描述 as_dup uts/common/vm/Vm_as.c 復(fù)制虛擬地址空間 seg_alloc uts/common/vm/Vm_seg.c 分配一個(gè)段,并將它附到相應(yīng)的地址空間上 hat_dup uts/sfmmu/vm/hat_sfmmu.c 復(fù)制地址空間的地址轉(zhuǎn)換 xhat_dup_all uts/sfmmu/vm/Xhat.c 復(fù)制地址空間的xhat列表復(fù)制虛擬地址空間的工作主要在as_dup ()函數(shù)中完成。這個(gè)函數(shù)的流程如圖2所示: 圖5 as_dup流程圖 as_dup將復(fù)制一個(gè)地址空間,具體過(guò)程如下: 6. 調(diào)用as_alloc()為新的地址空間分配一個(gè)與之對(duì)應(yīng)的as數(shù)據(jù)結(jié)構(gòu) 7. 將原來(lái)地址空間所有的段都拷貝到新的地址空間中(利用一個(gè)循環(huán)) a) 調(diào)用seg_alloc()來(lái)分配一個(gè)段,并將這個(gè)新分配的段映射到新的地址空間中 b) 如果上面的seg_alloc()工作失敗,則會(huì)調(diào)用as_free()將新的地址空間釋放 c) 調(diào)用SEGOP_DUP()將原來(lái)段的操作復(fù)制到剛剛分配好的段中 d) 若復(fù)制段操作時(shí)發(fā)生錯(cuò)誤,需調(diào)用seg_free()將剛剛分配的段釋放,然后調(diào)用as_fee()將新的地址空間釋放 8. 調(diào)用hat_dup()進(jìn)行hat的復(fù)制工作 9. 調(diào)用xhat_dup_all()進(jìn)行xhat的復(fù)制工作這里需要指出,hat_dup()和xhat_dup_all()只進(jìn)行了一些字段取值的驗(yàn)證,并沒(méi)有去進(jìn)行實(shí)際的復(fù)制工作,這里hat和xhat都采用copy-on-write的策略完成復(fù)制。也就是說(shuō),只有真正去使用hat或xhat時(shí),才會(huì)去進(jìn)行復(fù)制工作。 1.2. 匿名內(nèi)存 1.2.1. 概述 Solaris匿名內(nèi)存是由segvn管理的,但并不直接與文件相關(guān)聯(lián),匿名內(nèi)存用于進(jìn)程的棧,堆以及寫入時(shí)拷貝頁(yè)。匿名頁(yè)是通過(guò)anon層接口被創(chuàng)建的。當(dāng)一個(gè)段第一次收到一個(gè)頁(yè)錯(cuò)誤,它分配一個(gè)anon映像結(jié)構(gòu)(該結(jié)構(gòu)說(shuō)明anon頭部在哪里)并在匿名映射的amp域里置入指向該anon頭部的指針。然后分配插槽數(shù)組,需要足夠大以至于能放下段內(nèi)潛在的其他頁(yè)。插槽數(shù)組采用一次間接尋址和兩次間接尋址(一維數(shù)組和二維數(shù)組),這取決于需要插槽的數(shù)目。 32位系統(tǒng)由于要支持大于16MB的段,需要兩次間接尋址;64位系統(tǒng),因?yàn)橹羔樀拈L(zhǎng)度更長(zhǎng),當(dāng)支持大于8MB時(shí)需要兩次間接尋址。當(dāng)只用一次間接尋址時(shí),anon頭部的 anon_chunk直接引用了anon的插槽數(shù)組。當(dāng)我們使用兩次間接尋址時(shí),anon_chunk被分成兩大塊:針對(duì)32位系統(tǒng),由2048個(gè)插槽組成的插槽塊和針對(duì)64位系統(tǒng),由1024個(gè)插槽組成的插槽塊。這一分配過(guò)程由anon層接口anon_create實(shí)現(xiàn)。每一個(gè)anon插槽指向一個(gè)anon結(jié)構(gòu),anon結(jié)構(gòu)描述了與地址空間內(nèi)一頁(yè)大小的區(qū)域內(nèi)容一致的虛擬內(nèi)存頁(yè)。 使用匿名內(nèi)存會(huì)有很多的優(yōu)點(diǎn)。例如,當(dāng)創(chuàng)建一個(gè)進(jìn)程時(shí),被創(chuàng)建的進(jìn)程的所有地址都映射到物理內(nèi)存的相同位(相同的頁(yè))。但是如果子進(jìn)程在此時(shí)要對(duì)內(nèi)存做些不同的操作(例如子進(jìn)程管理內(nèi)存中的一個(gè)數(shù)組),vm子系統(tǒng)會(huì)將這些頁(yè)復(fù)制,并在子進(jìn)程中改變映射指向新的頁(yè)。這些新頁(yè)即為匿名內(nèi)存,子進(jìn)程可以正常的修改數(shù)組,而不必知道這個(gè)數(shù)組已經(jīng)有了一個(gè)新的物理內(nèi)存了。這保證了存儲(chǔ)部分對(duì)子進(jìn)程的透明。 1.2.2. 數(shù)據(jù)結(jié)構(gòu) 1.2.2.1.重要數(shù)據(jù)結(jié)構(gòu)之間的關(guān)系 1.2.2.2.anon結(jié)構(gòu) struct anon { struct vnode *an_vp; /* vnode of anon page */ struct vnode *an_pvp; /* vnode of physical backing store */ anoff_t an_off; /* offset of anon page */ anoff_t an_poff; /* offset in vnode */ struct anon *an_hash; /* hash table of anon slots */ int an_refcnt; /* # of people sharing slot */ }; 每個(gè)匿名頁(yè),不論其在內(nèi)存還是在對(duì)換區(qū),都有一個(gè)anon結(jié)構(gòu)。這個(gè)結(jié)構(gòu)(也稱為插槽)提供了匿名頁(yè)和其后備存儲(chǔ)器之間的一個(gè)間接級(jí)的映射。(an_vp,an_off)為這個(gè)插槽指向匿名頁(yè)的vnode,(an_pvp,an_poff)指向這個(gè)插槽代表的頁(yè)的物理存儲(chǔ)器的位置。An_hash是anon插槽的一個(gè)散列表。這個(gè)列表由相關(guān)的匿名頁(yè)的(an_vp,an_off)進(jìn)行散列,并且提供一個(gè)方法用于從一個(gè)匿名頁(yè)轉(zhuǎn)到相關(guān)的匿名anon插槽。An_refcnt用于一個(gè)引用計(jì)數(shù),記錄了在寫入時(shí)拷貝的情況下,需要建立的各個(gè)分散的拷貝的數(shù)目。一個(gè)大于零的refcnt保護(hù)了插槽的存在。在anon_alloc被調(diào)用時(shí),refcnt初始化為1 。 1.2.2.3.anon_hdr結(jié)構(gòu) struct anon_hdr { kmutex_t serial_lock; /* serialize array chunk allocation */ pgcnt_t size; /* number of pointers to (anon) pages */ void **array_chunk; /* pointers to anon pointers or chunks of */ /* anon pointers */ int flags; /* ANON_ALLOC_FORCE force preallocation of */ /* whole anon array */ }; anon數(shù)組指針在塊中分配。每個(gè)塊都有anon指針的pagesize/sizeof(u_long*),anon_hdr結(jié)構(gòu)指向匿名數(shù)組,控制著匿名插槽的分配。 anon數(shù)組是二維指針數(shù)組比一個(gè)塊要大。第一級(jí)指針指向anon數(shù)組的塊,第二級(jí)包含anon指針的塊。如果anon數(shù)組比一個(gè)塊要小則建立整個(gè)的anon數(shù)組。如果anon數(shù)組比一個(gè)塊要大則僅僅第一維數(shù)組被分配。則另一維數(shù)組只在它們被anon指針初始化時(shí)分配。serial_lock,分配一個(gè)連續(xù)的數(shù)組塊。Size,記錄指向匿名頁(yè)的指針的數(shù)目。array_chunk,指向anon指針的指針。 1.2.2.4.anon_map結(jié)構(gòu) struct anon_map { krwlock_t a_rwlock; /* 保護(hù)anon_map結(jié)構(gòu)和anon數(shù)組*/ size_t size; /* anon數(shù)組大小*/ struct anon_hdr *ahp; /*anon數(shù)組的頭指針, 包含anon指針數(shù)組*/ size_t swresv; /* 為anon_map結(jié)構(gòu)而保存的交換空間 */ uint_t refcnt; /* 這個(gè)結(jié)構(gòu)的引用計(jì)數(shù) */ ushort_t a_szc; /*在共享的進(jìn)程中最大的 szc */ void *locality; /* lgroup locality info */ }; 這是匿名內(nèi)存中最核心的數(shù)據(jù)結(jié)構(gòu)。 Anon_map結(jié)構(gòu)用于各種各樣的anon從客戶端來(lái)進(jìn)行匿名內(nèi)存的管理。當(dāng)匿名內(nèi)存被共享時(shí),不同的共享客戶端將會(huì)指向同一個(gè)anon_map結(jié)構(gòu)。同樣的如果一個(gè)段在anon_map結(jié)構(gòu)存在中間沒(méi)有被安排,則新創(chuàng)建的段將仍會(huì)共享anon_map結(jié)構(gòu),盡管這兩個(gè)段用到的是anon數(shù)組的不同范圍。 1.2.3.情景 1.2.3.1.匿名內(nèi)存的分配 所用的函數(shù): anonmap_alloc()為給定交換區(qū)的相關(guān)段分配并初始化一個(gè)anon_map結(jié)構(gòu). anon_create()創(chuàng)建指針列表. anon_get_slot()從列表中返回指定的匿名索引的指針。 anon_alloc()分配一個(gè)anon插槽,上鎖后返回該插槽. Anon_zero()分配一個(gè)私有的用零填充的anon頁(yè) Anon_set_ptr()用一給定的指針設(shè)置列表項(xiàng),該指針為一指定偏移量的指針. 流程圖如下: 詳細(xì)的說(shuō)明: 1. 進(jìn)入anonmap_alloc()函數(shù),為給定交換區(qū)的相關(guān)段分配并初始化一個(gè)anon_map結(jié)構(gòu) 2. 進(jìn)入anon_create()函數(shù),來(lái)創(chuàng)建指針列表,指向匿名數(shù)組(一維或二維) 3. 創(chuàng)建工作結(jié)束,進(jìn)入匿名數(shù)組的操作,首先通過(guò)page_get_pagecnt()函數(shù)來(lái)得到可用匿名插槽的數(shù)目,如果可用插槽數(shù)目為0則將其標(biāo)記為忙碌,若大于0則調(diào)用anon_array_lock()來(lái)鎖定插槽區(qū)域,獲取第一個(gè)插槽,并將此插槽置位忙碌,之后用anon_get_slot()來(lái)獲取一個(gè)匿名插槽。 4. 調(diào)用Anon_zero()來(lái)分配一個(gè)私有的用零填充的anon頁(yè),在此函數(shù)中,用anon_alloc()來(lái)分配匿名插槽和它的鎖。通過(guò)[vp,offset]來(lái)尋找頁(yè)(page_lookup()),沒(méi)有找到,所以要調(diào)用page_lookup_create()來(lái)建立一個(gè)頁(yè),并將其置位vp,offset。 5. 從頁(yè)空閑列表中根據(jù)所給的vp和offset選擇一個(gè)最合適的頁(yè)(page_get_freelist()),并將此頁(yè)上鎖(page_trylock()),并將此頁(yè)從空閑列表中移出(page_sub()),插入到散列表中(page_hashin())。請(qǐng)求此頁(yè)的i/o鎖(page_io_lock()),并將此頁(yè)插入到引用列表中(page_add)。 6. 調(diào)用Anon_zero()來(lái)分配一個(gè)私有的用零填充的anon頁(yè),在此函數(shù)中,用anon_alloc()來(lái)分配匿名插槽和它的鎖。通過(guò)[vp,offset]來(lái)尋找頁(yè)(page_lookup()),沒(méi)有找到,所以要調(diào)用page_lookup_create()來(lái)建立一個(gè)頁(yè),并將其置位vp,offset。 7. 從頁(yè)空閑列表中根據(jù)所給的vp和offset選擇一個(gè)最合適的頁(yè)(page_get_freelist()),并將此頁(yè)上鎖(page_trylock()),并將此頁(yè)從空閑列表中移出(page_sub()),插入到散列表中(page_hashin())。請(qǐng)求此頁(yè)的i/o鎖(page_io_lock()),并將此頁(yè)插入到引用列表中(page_add)。 8. 將此頁(yè)添0(pagezero()),將此頁(yè)鎖的等級(jí)從獨(dú)占鎖降為共享鎖(page_downgrade()). 9. 若有匿名頁(yè),也將此匿名頁(yè)添0(anon_zero()),并用給定的指針設(shè)置列表項(xiàng)(anon_set_ptr()),通過(guò)hat層從實(shí)際的內(nèi)存空間中分配此頁(yè)(在對(duì)換區(qū)域中)。退出匿名內(nèi)存數(shù)組的操作(anon_array_exit())。 10. 至此,所有的操作完成。一個(gè)問(wèn)題要注意:為什么anon結(jié)構(gòu)要設(shè)置成一個(gè)數(shù)組?答案:每個(gè)anon結(jié)構(gòu)代表了一個(gè)內(nèi)存頁(yè)。而每一個(gè)段可能比一頁(yè)的大小要大,所以需要不止一個(gè)的anon結(jié)構(gòu)去描述它,所以我們需要一個(gè)數(shù)組。 1.2.3.2.匿名內(nèi)存的釋放 涉及到的函數(shù): anonmap_free() 釋放anon結(jié)構(gòu) anon_release() 釋放匿名數(shù)組的指針 lgrp_shm_policy_fini()為anon_map結(jié)構(gòu)釋放共享內(nèi)存的決策樹(shù)和為零的本地空間。此函數(shù)在Lgrp.c文件中。流程圖: 詳細(xì)解釋: 1. 判斷anon_map結(jié)構(gòu)中的ahp指針是否為空,若為空,則說(shuō)明此anon頁(yè)已經(jīng)被釋放,錯(cuò)誤。若不為空則進(jìn)入下一步。 2. 判斷匿名頁(yè)是否還有進(jìn)程在引用,若refcnt=0則說(shuō)明沒(méi)有進(jìn)程在引用,則可以釋放。若refcnt!=0則錯(cuò)誤。 3. 釋放在物理內(nèi)存中的頁(yè)面 4. 釋放匿名數(shù)組,要分為兩種情況,即一維數(shù)組和二維數(shù)組的情況。 5. 清空對(duì)象緩存。 1.2.3.3. 分配一個(gè)anon插槽 函數(shù)anon_alloc() 流程圖: 詳細(xì)說(shuō)明:anon_alloc參數(shù)為vnode和其偏移。首先聲明一個(gè)anon結(jié)構(gòu)類型變量ap,ap是一個(gè)anon插槽。為ap在緩存中分配空間,若失敗則調(diào)用swap_alloc進(jìn)行分配,若成功則將ap的兩個(gè)分量an_vp和an_off置為參數(shù)的值,并將ap的其他分量都初始化。根據(jù)an_vp和an_off進(jìn)行散列,并插入散列表中并上鎖,最后返回指針ap。 1.2.3.4.匿名數(shù)組的拷貝 函數(shù):anon_copy_ptr():拷貝anon數(shù)組sahp到另一個(gè)anon數(shù)組dahp. 流程圖: 詳細(xì)過(guò)程:拷貝anon數(shù)組sahp到另一個(gè)anon數(shù)組dahp。幾個(gè)參數(shù)的意義,s_idx指sahp數(shù)組塊的大小,d_idx指dahp數(shù)組塊的大小,npages指復(fù)制需要的頁(yè)面數(shù)。函數(shù)的執(zhí)行過(guò)程:如果兩個(gè)數(shù)組都是一維的,要對(duì)sahp和dahp所指向的空間進(jìn)行測(cè)試,(1)如果其小于ANON_CHUNK_SIZE的值才是合法值,然后調(diào)用bcopy函數(shù)對(duì)數(shù)組進(jìn)行復(fù)制。(2)如果兩個(gè)數(shù)組都是二維的則相對(duì)要麻煩一些。同一維數(shù)組一樣,首先要測(cè)試sahp和dahp所指向的空間的大小是否合法,然后不斷測(cè)試npage的值,當(dāng)其值大于零的時(shí)候則進(jìn)入循環(huán),相當(dāng)于調(diào)用若干次的bcopy對(duì)若干個(gè)一維數(shù)組進(jìn)行復(fù)制,最終得到二維數(shù)組的復(fù)制。(3)如果至少有一個(gè)數(shù)組是二維的,則在npage大于零的情況下,要求anon索引的指針不能為空,用anon_get_ptr函數(shù)得到索引指針并且放在ap中,并通過(guò)anon_set_ptr函數(shù)直接復(fù)制給dahp。 1.2.3.5.設(shè)置指針數(shù)組結(jié)構(gòu) 函數(shù):anon_create() 流程圖: 詳細(xì)說(shuō)明:這個(gè)函數(shù)可以分配和回收指針,返回和設(shè)置給定的偏移的指針數(shù)組的入口。首先在內(nèi)核內(nèi)存中分配數(shù)組指針的空間,對(duì)數(shù)組指針的互斥鎖進(jìn)行初始化。若數(shù)組是一維的,整個(gè)指針數(shù)組占npages個(gè)頁(yè),則在內(nèi)核內(nèi)存中分配這npages個(gè)頁(yè)的anon指針的空間(用函數(shù)kmem_zalloc),若沒(méi)有分配成功則調(diào)用kmem_free尋找內(nèi)核中的空閑區(qū)。若是二維數(shù)組,則只是在計(jì)算指針數(shù)組大小的時(shí)候麻煩一下,其余均一樣。最后返回這個(gè)數(shù)組的指針(anon_hdr*類型)。這里要說(shuō)一下kmem_free函數(shù)的算法,將內(nèi)核內(nèi)存中空閑的塊建立為一個(gè)堆,從根部開(kāi)始在其中查找與給定塊最相近的空閑塊,如果找到,則將給定的塊寫入到空閑塊中。 1.2.3.6.匿名頁(yè)引用計(jì)數(shù)的操作 函數(shù):Anon_decref():對(duì)anon頁(yè)的引用減1,如果引用計(jì)數(shù)為零,則將其和其相關(guān)頁(yè)釋放。流程圖: 詳細(xì)過(guò)程:在散列表中找到ap指向的匿名頁(yè),并通過(guò)臨界區(qū)訪問(wèn)此頁(yè),以保證互斥。(1)若引用此頁(yè)的計(jì)數(shù)不為零,則對(duì)其進(jìn)行減1操作,如果減1以后對(duì)此匿名頁(yè)的引用變?yōu)榱?#xff0c;則調(diào)用函數(shù)swap_xlate(),將一個(gè)匿名插槽轉(zhuǎn)換為其相關(guān)的vnode和vnode中的偏移,并查找此匿名插槽的頁(yè),如果找到,則調(diào)用VN_DISPOSE函數(shù)將與之相關(guān)的匿名頁(yè)歸還到空閑列表中,以表示此頁(yè)真正成為空閑頁(yè)了。(2)若引用計(jì)數(shù)為零,則直接從散列表中將ap所指的匿名頁(yè)移除,如果ap_pvp所指的物理頁(yè)不為null則也要調(diào)用函數(shù)swap_phys_free將物理頁(yè)釋放。 1.2.3.7.匿名內(nèi)存中數(shù)據(jù)的復(fù)制函數(shù):anon_dup():復(fù)制anon頁(yè)size字節(jié)長(zhǎng)度的內(nèi)容。流程圖: 詳細(xì)過(guò)程:首先,調(diào)用btopr函數(shù)將要復(fù)制的長(zhǎng)度size轉(zhuǎn)換為以頁(yè)為單位的度量即npages個(gè)頁(yè)。每復(fù)制一頁(yè)就將npages減1,找到index處開(kāi)始第一個(gè)合法的anon指針賦給ap。然后,調(diào)用anon_set_ptr函數(shù)將old的內(nèi)容復(fù)制到new處,并對(duì)每一個(gè)匿名頁(yè)的引用數(shù)都加1。函數(shù)中用off來(lái)記錄每頁(yè)中的偏移,進(jìn)行復(fù)制。須注意,對(duì)頁(yè)的引用計(jì)數(shù)進(jìn)行修改時(shí)要保證互斥進(jìn)行,代碼如下: mutex_enter(ahm); ap->an_refcnt++; mutex_exit(ahm); 1.2.3.8.釋放一組anon頁(yè) 函數(shù):anon_free():釋放一組anon頁(yè),長(zhǎng)度為size字節(jié),并清空指向這些anon表項(xiàng)的指針。流程圖: 詳細(xì)過(guò)程:首先將size字節(jié)的長(zhǎng)度轉(zhuǎn)換為以頁(yè)為單位,npages個(gè)頁(yè)。即轉(zhuǎn)換為釋放這npages個(gè)頁(yè)的函數(shù)。找出index后的第一個(gè)合法的anon指針ap,ap指向的是要釋放的第一頁(yè)。將此頁(yè)的列表項(xiàng)設(shè)置為null,調(diào)用anon_decref函數(shù)減少一個(gè)此頁(yè)的引用計(jì)數(shù)。當(dāng)該引用的計(jì)數(shù)為0時(shí),釋放該頁(yè)和與之關(guān)聯(lián)的頁(yè)(如果有的話)。將npages減1再進(jìn)入循環(huán),直至npages等于零,表明所有的匿名頁(yè)已被釋放。 1.3.Swap文件系統(tǒng) 1.3.1.概述現(xiàn)代操作系統(tǒng)都實(shí)現(xiàn)了“虛擬內(nèi)存”這一技術(shù),在功能上突破了物理內(nèi)存的限制,使程序可以操縱大于實(shí)際物理內(nèi)存的空間。有兩個(gè)基本的虛擬內(nèi)存管理模型:交換(swapping)和按需換頁(yè)(demand pages)。交換模型的內(nèi)存管理粒度是進(jìn)程,當(dāng)物理內(nèi)存不足時(shí),最不活躍的進(jìn)程被換出內(nèi)存。按需換頁(yè)的內(nèi)存管理粒度是頁(yè)面,在內(nèi)存匱乏時(shí),只有最不經(jīng)常使用的頁(yè)面被換出。Solaris中使用了這兩種虛擬內(nèi)存管理方法。通常情況下使用按需換頁(yè)方式,在內(nèi)存嚴(yán)重不足的時(shí)候采用交換的方式。在進(jìn)行虛擬內(nèi)存管理的時(shí)候,在系統(tǒng)的磁盤空間中,必須專門劃分出一個(gè)部分作為系統(tǒng)的Swap空間。Swap空間的作用可簡(jiǎn)單描述為:當(dāng)系統(tǒng)的物理內(nèi)存不夠用的時(shí)候,就需要將物理內(nèi)存中的一部分空間釋放出來(lái),以供當(dāng)前運(yùn)行的程序使用。那些被釋放的空間可能來(lái)自一些很長(zhǎng)時(shí)間沒(méi)有什么操作的程序,這些被釋放的空間被臨時(shí)保存到Swap空間中,等到那些程序要運(yùn)行時(shí),再?gòu)腟wap中恢復(fù)保存的數(shù)據(jù)到內(nèi)存中。這樣,系統(tǒng)總是在物理內(nèi)存不夠時(shí),才進(jìn)行Swap交換。需要聲明的是,并不是所有從物理內(nèi)存中交換出來(lái)的數(shù)據(jù)都會(huì)被放到Swap空間中,有相當(dāng)一部分的數(shù)據(jù)直接交換到操作系統(tǒng)的文件系統(tǒng)中了。例如,有的程序會(huì)打開(kāi)一些文件,對(duì)文件進(jìn)行讀寫(其實(shí)每個(gè)程序都至少打開(kāi)一個(gè)文件,那就是運(yùn)行程序本身),當(dāng)這些程序的內(nèi)存空間需要交換出去時(shí),文件部分的數(shù)據(jù)就沒(méi)有必要放到Swap空間中了,如果是讀文件操作,那么內(nèi)存數(shù)據(jù)直接就釋放了,不需要交換出來(lái),因?yàn)橄麓涡枰獣r(shí),直接從文件系統(tǒng)就能恢復(fù);如果是寫文件,只需要將變化的數(shù)據(jù)保存到文件中,以便恢復(fù)。但是那些用malloc( )和new等函數(shù)生成的對(duì)象的數(shù)據(jù)則不同,需要Swap空間,因?yàn)樗鼈冊(cè)谖募到y(tǒng)中沒(méi)有相應(yīng)的“儲(chǔ)備”文件,因此被稱為“匿名”(Anonymous)的內(nèi)存數(shù)據(jù),這類數(shù)據(jù)還包括堆棧中的一些狀態(tài)和變量數(shù)據(jù)等,所以說(shuō),Swap空間是“匿名”數(shù)據(jù)的交換空間。內(nèi)存的每個(gè)物理頁(yè)面都由它的vnode和offset指定。當(dāng)所要尋找的頁(yè)面不在內(nèi)存中時(shí),則vnode和offset指出該頁(yè)在后備存儲(chǔ)器中的位置。對(duì)于一個(gè)文件來(lái)說(shuō),物理頁(yè)緩存了文件的vnode和offset.交換空間就像一個(gè)后備存儲(chǔ)器,存儲(chǔ)內(nèi)存的匿名頁(yè),因此,當(dāng)內(nèi)存不足時(shí),可以把內(nèi)存的某頁(yè)交換到磁盤中,以增加內(nèi)存空閑空間。因?yàn)榻粨Q空間是作為匿名內(nèi)存的后備存儲(chǔ)器,所以我們必須首先確定是否有足夠的交換空間,以便可以把頁(yè)面交換出去。因此,在創(chuàng)建一個(gè)可寫入的映象前,我們要先申請(qǐng)交換空間。當(dāng)有足夠的內(nèi)存空間可裝入進(jìn)程的內(nèi)容時(shí),Solaris內(nèi)核允許匿名內(nèi)存可不申請(qǐng)交換空間。這意味著在某些情況下,一個(gè)系統(tǒng)可以在很少或沒(méi)有交換空間下運(yùn)行。通常情況下,Swap空間應(yīng)大于或等于物理內(nèi)存的大小,最小不應(yīng)小于64M,通常Swap空間的大小應(yīng)是物理內(nèi)存的2-2.5倍。對(duì)于傳統(tǒng)的UNIX,在可寫入的虛擬內(nèi)存中,其每個(gè)頁(yè)大小的單元都需要一個(gè)同等大小的交換空間。比如,在傳統(tǒng)的UNIX系統(tǒng)里,一個(gè)malloc要求分配8 Mbytes空間,那同時(shí),它也要申請(qǐng)8 Mbytes的交換磁盤空間,盡管這些交換空間它可能從未用到。一般地,粗略估計(jì)進(jìn)程所要的空間的大小是其所使用的物理頁(yè)的兩倍,這就導(dǎo)致了交換空間要等于兩倍大小的內(nèi)存空間。而swapfs層允許Solaris在分配時(shí)更加謹(jǐn)慎,我們只需要讓交換空間等于虛擬內(nèi)存的大小即可,該虛擬內(nèi)存比機(jī)器中可用的可分頁(yè)物理內(nèi)存大。 Solaris的交換使用swapfs來(lái)實(shí)現(xiàn)交換區(qū)域分配,以提高空間的使用效率。swapfs文件系統(tǒng)位于anon層和物理交換設(shè)備之間,是一個(gè)虛擬的文件系統(tǒng)。即使沒(méi)有分配物理交換空間,swapfs文件系統(tǒng)也使每個(gè)頁(yè)面如同有真實(shí)的后備交換空間。 swapfs文件系統(tǒng)使用一個(gè)全局變量:availrmen來(lái)跟蹤系統(tǒng)中可用且可分頁(yè)的物理內(nèi)存,并把添加到可使用的交換空間中。當(dāng)我們申請(qǐng)?zhí)摂M交換時(shí),我們只是簡(jiǎn)單地減少了可用的虛擬內(nèi)存的總量。只要有充足的內(nèi)存和物理交換空間可用,則該交換分配就是成功的。物理交換空間直到需要使用時(shí)才會(huì)分配。當(dāng)我們創(chuàng)建一個(gè)私有段時(shí),我們申請(qǐng)交換分區(qū)和分配anon結(jié)構(gòu)。在這狀態(tài)中,直到一個(gè)真正的內(nèi)存頁(yè)作為ZFOD或copy-on-write的結(jié)果被創(chuàng)建時(shí),anon結(jié)構(gòu)的分配等才會(huì)真正發(fā)生。當(dāng)一個(gè)物理頁(yè)默認(rèn)加入時(shí),用vnode/offset對(duì)它進(jìn)行標(biāo)記。在Solaris中,當(dāng)段驅(qū)動(dòng)調(diào)用anon_alloc()去獲取一個(gè)新的匿名頁(yè)時(shí),該匿名頁(yè)被分配swapfs vnode和offset。anon_alloc()函數(shù)通過(guò)swafs_getvp()進(jìn)入swapfs,然后調(diào)用swapfs_getpage()創(chuàng)建一個(gè)帶有vnode/offset的新頁(yè)面。an_vp和an_off被初始化成swapfs虛擬交換設(shè)備的vnode和offset,這兩個(gè)an_vp和an_off是在anon結(jié)構(gòu)中用來(lái)標(biāo)明該頁(yè)的后備存儲(chǔ)的地址的。 在沒(méi)有page-out請(qǐng)求之前,并不需要任何物理交換空間。當(dāng)段請(qǐng)求虛擬交換空間時(shí),可用的虛擬交換空間的總量減少,但是,因?yàn)槲覀冞€不需要把頁(yè)置換到物理交換區(qū),所以,物理交換空間并沒(méi)有被分配。當(dāng)發(fā)生第一個(gè)page-out請(qǐng)求時(shí),真正的交換分區(qū)才被分配。這時(shí),頁(yè)掃描器為該頁(yè)檢查vnode,然后調(diào)用putpage()方法。因?yàn)樵擁?yè)的vnode是swapfs vnode,因而調(diào)用swapfs_putpage()把該頁(yè)置換到交換設(shè)備。swapfs_putpage()分配物理交換分區(qū)的一個(gè)頁(yè)大小的塊給該頁(yè)面,然后把a(bǔ)non槽中的vnode an_pvp和an_poff設(shè)置為指向物理交換設(shè)備,接著該頁(yè)就被置換到交換設(shè)備中了。 另外,系統(tǒng)中有可能包含多個(gè)Swap分區(qū),分區(qū)的數(shù)量對(duì)性能也有很大的影響。因?yàn)镾wap交換的操作是磁盤I/O的操作,如果有多個(gè)Swap交換區(qū),Swap空間的分配會(huì)以輪流的方式操作于所有的Swap,這樣會(huì)大大均衡I/O的負(fù)載,加快Swap交換的速度。 1.3.2.數(shù)據(jù)結(jié)構(gòu) 1.3.2.1.重要數(shù)據(jù)結(jié)構(gòu)關(guān)系 1.3.2.2.swapinfo 每個(gè)交換空間都有一個(gè)swapinfo結(jié)構(gòu)來(lái)記錄該區(qū)域的相關(guān)信息。這些結(jié)構(gòu)連成一個(gè)線性表,決定交換空間在邏輯交換設(shè)備中的順序。每個(gè)結(jié)構(gòu)包含一個(gè)指針,指向相應(yīng)的位圖,同時(shí)也說(shuō)明該交換空間的大小,和它相應(yīng)的vnode。Swapinfo結(jié)構(gòu)在/uts/common/sys/swap.h中,它的定義如下: struct swapinfo { ulong_t si_soff; /*指明文件開(kāi)始的偏移量*/ ulong_t si_eoff; /* 指明文件結(jié)束的偏移量*/ struct vnode *si_vp; /*指向一個(gè)結(jié)點(diǎn)*/ struct swapinfo *si_next; /*指向下一個(gè)交換空間 */ int si_allocs; /*該交換空間的分配結(jié)果*/ short si_flags; /* 標(biāo)記*/ pgcnt_t si_npgs; /* 交換空間的頁(yè)面數(shù) */ pgcnt_t si_nfpgs; /* 交換空間的空閑頁(yè)面數(shù)*/ int si_pnamelen; /*交換文件的名字長(zhǎng)度加一 */ char *si_pname; /* 交換文件的名字*/ ssize_t si_mapsize; /*為位圖分配的字節(jié) */ uint_t *si_swapslots; /*插槽的位圖,未置位標(biāo)明該插槽為空*/ pgcnt_t si_hint; /*空閑頁(yè)的第一頁(yè) first page to check if free */ ssize_t si_checkcnt; /*尋找空的插槽 # of checks to find freeslot */ ssize_t si_alloccnt; /* 用來(lái)獲取ave結(jié)果used to find ave checks */ }; ulong_t si_soff和ulong_t si_eoff分別指明文件在vnode中的開(kāi)始地址和結(jié)束地址。 vnode *si_vp指向某設(shè)備的vnode。 swapinfo *si_next指向下一個(gè)交換空間,從而把所有的交換空間串連起來(lái)。 si_allocs指出交換空間的分配結(jié)果,si_flags是一個(gè)標(biāo)記,以后會(huì)有詳細(xì)的定義。 pgcnt_t si_npgs和pgcnt_t si_nfpgs分別指出交換空間的頁(yè)面數(shù)和空閑頁(yè)面數(shù) si_pnamelen的值為交換文件的名字長(zhǎng)度加一 si_pname指出交換文件的名字 si_mapsize指出交換空間對(duì)應(yīng)的位圖的比特?cái)?shù),系統(tǒng)中,每個(gè)物理交換空間都有一個(gè)相對(duì)應(yīng)的位圖,該位圖用來(lái)表示它的物理容量。位圖記錄了哪個(gè)交換槽已經(jīng)被使用或尚未使用。分配時(shí)是通過(guò)遍歷位圖來(lái)找到第一個(gè)空閑槽。因而,在交換設(shè)備中的偏移量和插槽所支持的頁(yè)面地址之間不存在線性關(guān)系。相反,這是一個(gè)一對(duì)一的映像。 si_swapslots插槽的位圖,未置位則標(biāo)明該插槽可用。 si_hint指出空閑頁(yè)的第一頁(yè); si_checkcnt尋找空的插槽 si_alloccnt用來(lái)獲取ave結(jié)果,ave為一個(gè)宏,表明空間分配了多長(zhǎng)時(shí)間。 1.3.2.3. swapre swapre結(jié)構(gòu)指出將要進(jìn)入或移出交換空間的資源的路徑。Swapre結(jié)構(gòu)在/uts/common/sys/swap.h中,其定義如下: typedef struct swapres { char *sr_name; /* 特定資源的路徑名*/ off_t sr_start; /*被交換資源的起始偏移地址g*/ off_t sr_length; /*交換空間的長(zhǎng)度 */ } swapres_t; sr_name指出將要移入或移出交換空間的資源的路徑名稱 sr_start 指出被交換資源的起始偏移地址 sr_length指出交換空間的長(zhǎng)度 1.3.2.4.swapent swapent結(jié)構(gòu)保存交換文件(相當(dāng)于交換設(shè)備,暫存換出頁(yè)面)的名字,將要進(jìn)行交換的頁(yè)數(shù)等,它在/uts/common/sys/swap.h中,其結(jié)構(gòu)定義如下: typedef struct swapent { char *ste_path; /* 獲取交換文件的名字 */ off_t ste_start; /* 交換的起始?jí)K*/ off_t ste_length; /*交換空間的長(zhǎng)度 */ long ste_pages; /* 可交換的頁(yè)數(shù)*/ long ste_free; /*空閑的交換頁(yè)數(shù) */ int ste_flags } swapent_t; ste_path獲取交換文件的名字 ste_start指出交換的起始?jí)K ste_length交換空間的長(zhǎng)度 ste_pages可以進(jìn)行交換的頁(yè)數(shù) ste_free 空閑的交換頁(yè)數(shù) 1.3.2.5.swaptable swaptable結(jié)構(gòu)是一個(gè)數(shù)組,存儲(chǔ)swapent,指出有多少個(gè)交換文件。它在/uts/common/sys/swap.h中,其結(jié)構(gòu)定義如下: typedef struct swaptable { int swt_n; /*指出有多少個(gè)交換文件*/ struct swapent swt_ent[1]; /* array of swt_n swapents */ } swaptbl_t; 1.3.3.情景 1.3.3.1.從設(shè)備中分配交換頁(yè) 表6 添加交換文件時(shí)用到的主要函數(shù)函數(shù)名 文件名 功能描述 swap_phys_alloc uts/common/vm/Vm_swap.c 分配指定大小的連續(xù)頁(yè) swap_getoff uts/common/vm/Vm_swap.c 獲取設(shè)備中空閑頁(yè)的開(kāi)始偏移量從物理設(shè)備中分配交換頁(yè)是通過(guò)函數(shù)swap_phys_alloc來(lái)完成的, 這個(gè)函數(shù)的流程如圖所示: 圖11 swap_phys_alloc函數(shù)流程圖 分配指定大小的,連續(xù)的物理交換頁(yè)。分配成功返回1,若一個(gè)頁(yè)面都分配不了,則返回0。首先對(duì)swapinfo表加鎖。然后在列表中逐個(gè)搜索,搜索有空閑頁(yè)的設(shè)備。如果調(diào)用者表明交換頁(yè)不從某個(gè)設(shè)備中分配,則交換頁(yè)應(yīng)從其他設(shè)備中分配,于是應(yīng)尋找不相同的設(shè)備,如果找到,轉(zhuǎn)到found;如果調(diào)用者沒(méi)有這種需求,則直接轉(zhuǎn)到found。sip指向所找到設(shè)備的swapinfo。調(diào)用swap_getoff,找到設(shè)備空閑頁(yè)的偏移地址soff。設(shè)備的空閑頁(yè)數(shù)減掉1。如果soff為-1,出錯(cuò)。開(kāi)始從設(shè)備中分配所需的頁(yè)面數(shù)。若分配過(guò)程中,設(shè)備的頁(yè)已分配完或剩下的比特?cái)?shù)不夠一頁(yè)大小,則完成分配過(guò)程,盡管可能還未分配夠要求的頁(yè)數(shù)。把設(shè)備的vnode,偏移量soff,剛才分配的比特?cái)?shù)len等返回給調(diào)用者。如果設(shè)備分配的交換頁(yè)數(shù)超過(guò)swap_maxconfig(一個(gè)宏,在anon_init中定義其大小),為了把負(fù)載平衡分配到各個(gè)設(shè)備,把該設(shè)備的si_allocs設(shè)為0,并且如果該設(shè)備排在鏈表的最后,把silast的值改為swapinfo鏈表的開(kāi)頭結(jié)點(diǎn)。保存分配的信息,完成程序。 1.3.3.2.釋放物理交換頁(yè) 表7 釋放物理頁(yè)時(shí)用到的主要函數(shù) 函數(shù)名 文件名 功能描述 swap_phys_free uts/common/vm/Vm_swap.c 釋放指定設(shè)備的物理頁(yè)從指定設(shè)備中釋放頁(yè)是通過(guò)函數(shù)swap_phys_free來(lái)完成的, 這個(gè)函數(shù)的流程如圖所示: 圖12 swap_phys_free函數(shù)流程圖釋放一個(gè)物理頁(yè)。調(diào)用者給出欲釋放頁(yè)所在設(shè)備的vnode,頁(yè)的偏移量和頁(yè)大小。調(diào)用者給出的欲刪頁(yè)大小,并不符合設(shè)備中頁(yè)的規(guī)格,即調(diào)用者眼里的一頁(yè)和設(shè)備中的一頁(yè)大小是不一樣的。 首先,在swapinfo鏈表中開(kāi)始查找。如果某個(gè)結(jié)點(diǎn)的vnode和指定vnode相同,且給出的偏移量超出結(jié)點(diǎn)范圍,則計(jì)算在指定偏移量之前的頁(yè)數(shù)pagenumber, 而npage是pangnumber和釋放頁(yè)數(shù)之和。 釋放每一頁(yè)。如果某頁(yè)對(duì)應(yīng)的位圖表示該頁(yè)未曾使用,則打印:“釋放空閑頁(yè)。”把該頁(yè)對(duì)應(yīng)的位圖清0,設(shè)備的空閑頁(yè)數(shù)加一。 1.3.3.3.釋放一個(gè)正在使用的交換頁(yè) 表8 釋放一個(gè)正在使用的交換頁(yè)用到的主要函數(shù)函數(shù)名 文件名 功能描述 swapslot_free uts/common/vm/Vm_swap.c 釋放指定設(shè)備的正在使用的物理頁(yè) VOP_GETPAGE /on/usr/src/uts/common/sys/vnode.h 找到指定頁(yè),并設(shè)置該頁(yè)屬性 swap_anon uts/common/vm/Vm_swap.c 找尋和指定vnode,off相關(guān)的anon swap_phys_free uts/common/vm/Vm_swap.c 釋放指定的交換頁(yè)頁(yè) hat_setmod /on/usr/src/uts/common/vm/hat.h 為指定頁(yè)設(shè)置指定的屬性從設(shè)備中釋放正在使用的交換頁(yè)是通過(guò)函數(shù)swapslot_free來(lái)完成的, 這個(gè)函數(shù)的流程如圖所示: 圖13 swapslot_free函數(shù)流程圖釋放某個(gè)設(shè)備正被使用的交換頁(yè),調(diào)用者需提供該設(shè)備的vnode,欲釋放頁(yè)在設(shè)備中的偏移量,和設(shè)備的swapinfo結(jié)點(diǎn)。使用VOP_GETPAGE獲取欲釋放的頁(yè),如果VOP_GETPAGE返回錯(cuò)誤信息,轉(zhuǎn)出錯(cuò)處理。對(duì)找到的頁(yè)加鎖,添加互斥量。找到欲釋放頁(yè)所在的anon結(jié)構(gòu),把a(bǔ)non結(jié)構(gòu)返回給ap;如果找不到相應(yīng)的anon,出錯(cuò)處理;如果ap的后備存儲(chǔ)的vnode和指定設(shè)備的vnode相同,且ap在設(shè)備中的偏移量沒(méi)超出設(shè)備的范圍,則調(diào)用swap_phys_free釋放該頁(yè)。把a(bǔ)p的an_pvp和an_poff置為空,調(diào)用hat_setmod對(duì)該頁(yè)屬性進(jìn)行設(shè)置。 1.3.3.4.添加交換文件 表4 添加交換文件時(shí)用到的主要函數(shù) 函數(shù)名 文件名 功能描述 swapadd uts/common/vm/Vm_swap.c 增加新的交換設(shè)備到列表中,并在更新anoninfo計(jì)數(shù)器前轉(zhuǎn)移分配給它 common_specvp uts/common/fs/specfs/specsubr.c 對(duì)給定設(shè)備的vnode,函數(shù)返回和該設(shè)備vnode相關(guān)聯(lián)的通用vnode格式 mutex_enter /on/usr/src/lib/libzpool/common/kernel.c 對(duì)文件上鎖 mutex_exit /on/usr/src/lib/libzpool/common/kernel.c 對(duì)文件開(kāi)鎖 kmem_zalloc /on/usr/src/uts/common/os/kmem.c 該函數(shù)在這里為swapinfo 結(jié)構(gòu)分配指定大小的內(nèi)存空間添加交換文件是通過(guò)函數(shù)swapadd來(lái)完成的,這個(gè)函數(shù)的流程如圖 7所示: 圖 8 swapadd函數(shù)流程圖 交換文件是用于交換的有固定長(zhǎng)度的、規(guī)則的文件,交換設(shè)備相當(dāng)于磁盤分區(qū),交換設(shè)備和交換文件的作用一樣,設(shè)備掛靠到系統(tǒng)上時(shí),系統(tǒng)就把設(shè)備當(dāng)作一個(gè)文件來(lái)操作,添加交換文件可看作添加設(shè)備,以下的交換文件和交換設(shè)備都可統(tǒng)一看作交換文件。系統(tǒng)中所說(shuō)的塊和頁(yè)其實(shí)是一樣的,都是頁(yè)的意思。 在函數(shù)的開(kāi)始,先把交換設(shè)備的vnode轉(zhuǎn)化成文件系統(tǒng)通用的的vnode格式,這通過(guò)函數(shù)common_specvp實(shí)現(xiàn)。common_specvp把返回的vnode賦給cvp,cvp就表示了欲添加設(shè)備的vnode。 改變?cè)O(shè)備vnode的 標(biāo)志位。先對(duì)設(shè)備加鎖,然后計(jì)算wasswap和vnode的v_flag,其中,wasswap表明設(shè)備是否是交換設(shè)備,計(jì)算完后開(kāi)鎖。對(duì)互斥量swap_lock加鎖,調(diào)用VOP_OPEN打開(kāi)欲添加的設(shè)備,如果成功,VOP_OPEN會(huì)為設(shè)備返回一個(gè)新的vnode;如果打開(kāi)不成功,則恢復(fù)剛才改變的wasswap值。開(kāi)鎖。獲取交換設(shè)備的屬性,如果設(shè)備的大小為零,或無(wú)法確定大小,則進(jìn)行出錯(cuò)處理。如果設(shè)備大小超過(guò)系統(tǒng)所能接受的范圍,則把它的大小限定為MAXOFF32_T(0x7fffffff),因?yàn)?2位的操作系統(tǒng)不支持超過(guò)32位尋址的交換設(shè)備。判斷該交換設(shè)備是否可寫入。這通過(guò)VOP_SETATTR設(shè)置設(shè)備屬性來(lái)決定,如果成功設(shè)置,則設(shè)備可寫,否則進(jìn)入出錯(cuò)處理。判斷設(shè)備是否可進(jìn)行頁(yè)I/O,如果不支持文件系統(tǒng)操作,則轉(zhuǎn)出錯(cuò)處理。一般地,如果在根文件系統(tǒng)上進(jìn)行交換,不要把和miniroot文件系統(tǒng)相應(yīng)的交換塊放在空閑的交換列表中。因此,如果加入的設(shè)備作為根文件,則可用塊的起始地址要進(jìn)行計(jì)算,具體前面多少塊不能用由klustsize(外部定義)決定。如果設(shè)備不是根文件,則起始地址由調(diào)用者指定,如果未指定,則從第二張頁(yè)面開(kāi)始,因?yàn)榈谝粡堩?yè)面存儲(chǔ)設(shè)備的標(biāo)志。計(jì)算開(kāi)始的偏移地址soff,如果大于設(shè)備的大小,則轉(zhuǎn)出錯(cuò)處理。計(jì)算尾端的偏移地址eoff,如果大于設(shè)備大小,轉(zhuǎn)出錯(cuò)處理。開(kāi)始的偏移量soff和尾端的偏移量eoff進(jìn)行頁(yè)面對(duì)齊,如果soff>=eoff,轉(zhuǎn)出錯(cuò)處理。調(diào)用kmem_zalloc函數(shù)分配給swapinfo結(jié)構(gòu)相應(yīng)大小的內(nèi)存。分配的空間的指針?lè)祷亟onsip,則nsip指出交換設(shè)備的相關(guān)交換信息。對(duì)nsip的部分變量進(jìn)行賦值:它的vnode,起始偏移量,尾端偏移量,對(duì)應(yīng)的設(shè)備名稱。。。計(jì)算設(shè)備插槽(插槽相當(dāng)于頁(yè))相應(yīng)位圖需要的字節(jié)數(shù),給位圖分配相應(yīng)的內(nèi)存,并對(duì)每一位進(jìn)行置位,然后檢查是否可以把交換設(shè)備添加到系統(tǒng)中。首先對(duì)swapinfo進(jìn)行加鎖,由mutex_enter(&swapinfo_lock)完成。接著檢查全局變量swapinfo中是否已有該設(shè)備的vnode,如果找到的vnode和欲添加的vnode的偏移量一樣,只是先前被刪去的,把它恢復(fù)即可,然后解鎖,跳出程序;如果找到的vnode的偏移量的范圍被設(shè)備的偏移量完全覆蓋,則出錯(cuò)。把設(shè)備加到列表中。判斷k_anoninfo中申請(qǐng)的頁(yè)數(shù)是否大于加上鎖的頁(yè)數(shù),如小于,出錯(cuò);再判斷k_anoninfo中頁(yè)數(shù)的總量是否大于申請(qǐng)的數(shù)目,如果小于,出錯(cuò)處理。把設(shè)備的頁(yè)數(shù)加到k_anoninfo的總數(shù)上,然后把cpu中相關(guān)線程的ani_count加上設(shè)備的頁(yè)數(shù)。如果在k_anoninfo中申請(qǐng)的頁(yè)面數(shù)大于已加鎖的,說(shuō)明有一些申請(qǐng)為滿足,現(xiàn)在加入了一個(gè)設(shè)備,可把這個(gè)設(shè)備的頁(yè)面分配給請(qǐng)求者。如果系統(tǒng)中尚未有備份裝置,把剛添加的設(shè)備初始化為備份裝置。 1.3.3.5.刪除交換文件 表5 刪除交換文件時(shí)用到的主要函數(shù) 函數(shù)名 文件名 功能描述 swapdel uts/common/vm/Vm_swap.c 刪除某個(gè)設(shè)備刪除交換文件是通過(guò)函數(shù)swapdel這個(gè)函數(shù)的流程如圖 9所示: 圖 10swapdel函數(shù)流程圖 刪除交換文件是通過(guò)swapdel函數(shù)來(lái)完成的,調(diào)用者必須傳給swapdel欲刪除文件的vnode和刪除區(qū)域的起始?jí)K地址。首先,函數(shù)把設(shè)備文件的vnode轉(zhuǎn)換成文件系統(tǒng)通用的vnode格式,這通過(guò)函數(shù)common_specvp實(shí)現(xiàn)。common_specvp把返回的vnode賦給cvp,cvp就表示了欲刪除設(shè)備的vnode。進(jìn)行頁(yè)面對(duì)齊,獲取設(shè)備的開(kāi)始偏移量soff。對(duì)全局變量swapinfo上鎖,在swapinfo中尋找設(shè)備的vnode。如果未找到,轉(zhuǎn)出錯(cuò)處理。設(shè)備的信息賦給osip變量。對(duì)匿名內(nèi)存k_anoninfo結(jié)構(gòu)的信息進(jìn)行判斷。如果申請(qǐng)的內(nèi)存頁(yè)數(shù)小于被鎖的內(nèi)存交換頁(yè)數(shù),跳出程序;如果可申請(qǐng)的磁盤交換頁(yè)數(shù)小于已申請(qǐng)的磁盤交換頁(yè)數(shù),跳出程序。如果系統(tǒng)中的所有的空閑頁(yè)數(shù)目小于欲刪除設(shè)備的頁(yè)數(shù)目,說(shuō)明設(shè)備正在被使用,或系統(tǒng)無(wú)法騰出空間裝載在刪除設(shè)備中的內(nèi)容,轉(zhuǎn)出錯(cuò)處理。如果刪除設(shè)備后,請(qǐng)求的磁盤交換空間不足,可申請(qǐng)內(nèi)存交換空間補(bǔ)足。先計(jì)算差額,然后從availrmem中分配內(nèi)存。如果全局變量k_anoninfo中申請(qǐng)的內(nèi)存交換空間小于被鎖住的,跳出程序;如果可分配的磁盤交換空間小于申請(qǐng)的數(shù)目,跳出程序;從系統(tǒng)中減去所刪除設(shè)備的頁(yè)數(shù)。對(duì)設(shè)備信息Osip的的標(biāo)志位進(jìn)行置位,防止再?gòu)脑撛O(shè)備分配交換空間。準(zhǔn)備釋放該設(shè)備的物理交換頁(yè)。在系統(tǒng)中,每個(gè)匿名頁(yè)都有一個(gè)anon結(jié)構(gòu)。這個(gè)anon結(jié)構(gòu)(slot)提供了匿名頁(yè)和其對(duì)應(yīng)后備存儲(chǔ)的關(guān)系。對(duì)整個(gè)anon哈希表進(jìn)行遍歷,找出有交換頁(yè)在欲刪除設(shè)備的anon slot,更新anon slot。在每個(gè)頁(yè)釋放后,都要返回anon slot相應(yīng)的桶的開(kāi)始,因?yàn)樵卺尫彭?yè)的時(shí)候,并沒(méi)有對(duì)整個(gè)哈希表加鎖,所以在釋放時(shí)哈希表可能會(huì)被別的進(jìn)程改變。對(duì)哈希表一個(gè)個(gè)桶逐個(gè)遍歷。首先,獲取全局變量anon_hash的初始地址,然后對(duì)第一個(gè)桶進(jìn)行分析,先對(duì)其加鎖。然后找這個(gè)桶中的每一個(gè)anon結(jié)構(gòu),如果某個(gè)anon的后備存儲(chǔ)為欲刪設(shè)備的頁(yè),測(cè)試該頁(yè)對(duì)應(yīng)的位圖情況,如果該頁(yè)已被使用,則把該頁(yè)對(duì)應(yīng)vnode的v_count加1,把該頁(yè)從slot中釋放,然后把該頁(yè)對(duì)應(yīng)的v_count減1。如果釋放成功,返回當(dāng)前桶,繼續(xù)查找。如果釋放失敗,要把該頁(yè)恢復(fù),全局變量k_anoninfo和availrmem等的相關(guān)值要重新加上該頁(yè),然后轉(zhuǎn)出錯(cuò)處理程序。遍歷完哈希表后,判斷是否完全完成釋放,這時(shí)應(yīng)有空閑頁(yè)數(shù)和設(shè)備的頁(yè)數(shù)相等,否則終止程序。把設(shè)備從swapinfo列表中刪除。如果設(shè)備處于列表中的最后一個(gè),則修改指針silast;釋放設(shè)備對(duì)應(yīng)位圖的內(nèi)存,釋放設(shè)備swapinfo對(duì)應(yīng)的內(nèi)存。如果設(shè)備屬于后備裝置,釋放。釋放設(shè)備的vnode,程序完成。 1.4.物理頁(yè)面管理 1.5.Vmem分配器 1.6.內(nèi)核內(nèi)存的初始化與布局 1.7.內(nèi)核內(nèi)存分配 1.7.1.概述 Solaris的內(nèi)核內(nèi)存分配器分成兩個(gè)層次。下層是后備分配器,它負(fù)責(zé)從內(nèi)核地址空間分配整塊的內(nèi)存供上層分配器使用,它分配的內(nèi)存塊大小一般是一個(gè)頁(yè)或者是頁(yè)的整數(shù)倍。上層是slab分配器,它從后備分配器那里獲得整塊內(nèi)存,然后把整塊內(nèi)存分成小的內(nèi)存塊,分配給內(nèi)核程序。一整塊內(nèi)存被稱為一個(gè)slab,這也是slab分配器名字的由來(lái)。后備分配器是一個(gè)vmem分配器,這是一個(gè)通用的資源分配器。后面的章節(jié)會(huì)對(duì)vmem分配器進(jìn)行分析,這一節(jié)主要是分析slab分配器。 slab分配器吸收了面向?qū)ο蟮乃枷?#xff0c;它可以直接給用戶分配已經(jīng)初始化好的對(duì)象,而且在釋放對(duì)象的時(shí)候,它也會(huì)調(diào)用相應(yīng)的析構(gòu)函數(shù)來(lái)銷毀對(duì)象。Slab分配器還采用了緩存的技術(shù),每種類型的對(duì)象有一個(gè)單獨(dú)的緩存,以前分配的對(duì)象在釋放時(shí)并不是馬上銷毀,而是放回這個(gè)緩存中。以后再分配時(shí),如果緩存中有空閑對(duì)象,就直接從緩存中分配,沒(méi)有的話再去創(chuàng)建一個(gè)新對(duì)象。采用緩存的辦法避免了每次分配對(duì)象都要進(jìn)行一次初始化,從而加快了分配的速度。用戶在創(chuàng)建對(duì)象緩存的時(shí)候需要指定對(duì)象的規(guī)格,包括對(duì)象的尺寸、對(duì)齊邊界、構(gòu)造函數(shù)和析構(gòu)函數(shù)。除了可以分配初始化好的對(duì)象,slab分配器還實(shí)現(xiàn)了傳統(tǒng)的內(nèi)存分配接口,可以分配任意大小的未初始化內(nèi)存。在多CPU平臺(tái)中,不同的CPU在同時(shí)向內(nèi)存分配器請(qǐng)求分配內(nèi)存的時(shí)候會(huì)發(fā)生沖突。為了防止沖突破壞內(nèi)存分配器的一致性,一個(gè)CPU在分配內(nèi)存的時(shí)候必須對(duì)分配器加鎖,防止其他CPU同時(shí)分配內(nèi)存。這樣的后果是不同CPU的內(nèi)存分配操作必須順序進(jìn)行,后一個(gè)CPU必須在前一個(gè)CPU分配完后才能開(kāi)始分配,這就會(huì)造成效率的降低。CPU數(shù)量越多,這個(gè)問(wèn)題越嚴(yán)重。為了解決這個(gè)問(wèn)題,Solaris為每一個(gè)CPU配置了一個(gè)自己的局部緩存。CPU在分配內(nèi)存的時(shí)候,首先從自己的緩存中分配,如果自己的緩存已空再?gòu)娜志彺嬷蟹峙?#xff0c;這樣就降低了CPU沖突的概率。 CPU的局部緩存借用了自動(dòng)步槍的原理,一個(gè)CPU是一支步槍,緩存中的對(duì)象是步槍的子彈,每分配一個(gè)對(duì)象就相當(dāng)于打出一發(fā)子彈。子彈被放到彈夾(magazine)中,一個(gè)CPU的局部緩存中有兩個(gè)彈夾。分配對(duì)象時(shí)如果所有彈夾都被打空,就從全局分配器中換上一個(gè)滿彈夾。CPU釋放一個(gè)對(duì)象的時(shí)候相當(dāng)于又獲得了一發(fā)子彈,它把這發(fā)子彈壓入彈夾中,供以后使用。釋放對(duì)象時(shí)如果CPU局部緩存中所有彈夾都是滿的,則用從全局分配器中換上一個(gè)空彈夾。為了存放全局的彈夾,slab分配器又引入了一個(gè)depot層,這個(gè)層相當(dāng)于一個(gè)彈藥庫(kù)。depot層中有兩個(gè)鏈表,一個(gè)是空彈夾鏈表,一個(gè)是滿彈夾鏈表,每個(gè)CPU從彈藥庫(kù)中換彈夾時(shí)就是對(duì)這兩個(gè)鏈表進(jìn)行操作。因此slab分配器可以看作是由三個(gè)層組成。最下層是slab層,它與頁(yè)分配器進(jìn)行交互,申請(qǐng)或釋放整塊的內(nèi)存,并把整塊內(nèi)存劃分成小塊,供上層使用。中間是depot層,它是一個(gè)全局的彈夾管理器。最上面是CPU層,主要處理CPU的局部緩存。 Solaris中的slab分配器還考慮到了對(duì)CPU高速cache的影響。如果所有的對(duì)象都是從相同的對(duì)齊邊界開(kāi)始(例如512字節(jié)對(duì)齊邊界),那么不同對(duì)象映射到同一個(gè)CPU緩存線(cache line)的概率就會(huì)增加,相應(yīng)地也會(huì)增加cache的沖突和失效率,從而造成系統(tǒng)性能的下降。Solaris中引入了一個(gè)簡(jiǎn)單的染色機(jī)制來(lái)解決這個(gè)問(wèn)題。在對(duì)象緩存中,一個(gè)slab中對(duì)象的起始地址由一個(gè)染色值(color)決定,染色值不同,對(duì)象的起始地址也不同,所以映射到同一個(gè)緩存線的概率就會(huì)降低。一個(gè)對(duì)象緩存有一組可用的染色值,在創(chuàng)建slab的時(shí)候,這組染色值被循環(huán)使用,使得具有相同染色值的slab數(shù)量盡可能少。這樣就減少了cache沖突的次數(shù),提高了系統(tǒng)的整體性能。 1.7.2.數(shù)據(jù)結(jié)構(gòu) 1.7.2.1.重要數(shù)據(jù)結(jié)構(gòu)間關(guān)系 圖14 數(shù)據(jù)結(jié)構(gòu)關(guān)系圖 slab分配器中主要的實(shí)體是對(duì)象緩存。對(duì)象緩存的控制結(jié)構(gòu)是kmem_cache,從kmem_cache出發(fā)可以訪問(wèn)到緩存相關(guān)的數(shù)據(jù)結(jié)構(gòu),如kmem_slab、kmem_magazine等。系統(tǒng)中所有的對(duì)象緩存被串成一個(gè)雙向鏈表,通過(guò)全局變量kmem_null_cache可以訪問(wèn)這個(gè)鏈表。一個(gè)對(duì)象緩存中可以包括多個(gè)slab,所有的slab鏈成一個(gè)雙向循環(huán)鏈表,通過(guò)kmem_cache中的cache_freelist字段可以引用這個(gè)鏈表。slab的控制結(jié)構(gòu)是kmem_slab。一個(gè)slab被分成多個(gè)對(duì)象,每一個(gè)對(duì)象用一個(gè)kmem_bufctl結(jié)構(gòu)來(lái)控制。slab中空閑的對(duì)象鏈成一個(gè)單向鏈表,kmem_slab中的slab_head字段指向這個(gè)鏈表的表頭。對(duì)象緩存中有兩個(gè)彈夾的鏈表,一個(gè)是空彈夾鏈表,一個(gè)是滿彈夾鏈表。彈夾的鏈表用kmem_maglist結(jié)構(gòu)表示,鏈表中每一個(gè)彈夾用kmem_magazine結(jié)構(gòu)表示。一個(gè)彈夾中可以裝載多個(gè)對(duì)象,對(duì)每一個(gè)裝載的對(duì)象,kmem_magazine中保存一個(gè)指向該對(duì)象起始地址的指針。每一個(gè)CPU有自己的局部緩存,這個(gè)局部緩存由kmem_cpu_cache結(jié)構(gòu)控制。kmem_cache結(jié)構(gòu)中有一個(gè)kmem_cpu_cache結(jié)構(gòu)的數(shù)組,其中每一個(gè)元素表示一個(gè)CPU的局部緩存。每個(gè)CPU的局部緩存中包含兩個(gè)彈夾,一個(gè)是當(dāng)前裝載的彈夾,一個(gè)是前一個(gè)裝載的彈夾。 1.7.2.2.kmem_cache kmem_cache結(jié)構(gòu)是對(duì)象緩存的控制結(jié)構(gòu),每一個(gè)對(duì)象緩存都對(duì)應(yīng)一個(gè)kmem_cache結(jié)構(gòu)的變量。kmem_cache結(jié)構(gòu)在uts/common/sys/kmem_impl.h中定義,它的定義如下: struct kmem_cache { /* 以下變量用于統(tǒng)計(jì) */ uint64_t cache_slab_create; /* slab創(chuàng)建的次數(shù) */ uint64_t cache_slab_destroy; /* slab銷毀的次數(shù) */ uint64_t cache_slab_alloc; /* slab層分配的次數(shù) */ uint64_t cache_slab_free; /* slab層釋放的次數(shù) */ uint64_t cache_alloc_fail; /* 分配失敗的總次數(shù) */ uint64_t cache_buftotal; /* 總的對(duì)象個(gè)數(shù) */ uint64_t cache_bufmax; /* 出現(xiàn)過(guò)的最大對(duì)象個(gè)數(shù) */ uint64_t cache_rescale; /* 重新調(diào)整hash表的次數(shù) */ uint64_t cache_lookup_depth; /* hash查找的深度 */ uint64_t cache_depot_contention; /* depot層互斥沖突的次數(shù) */ uint64_t cache_depot_contention_prev; /* depot層互斥沖突次數(shù)的前一個(gè)快照 */ /* 對(duì)象緩存的屬性 */ char cache_name[KMEM_CACHE_NAMELEN + 1]; /* 緩存名稱 */ size_t cache_bufsize; /* 對(duì)象大小 */ size_t cache_align; /* 對(duì)象的對(duì)齊邊界 */ int (*cache_constructor)(void *, void *, int); /* 構(gòu)造函數(shù) */ void (*cache_destructor)(void *, void *); /* 析構(gòu)函數(shù) */ void (*cache_reclaim)(void *); /* 回收函數(shù) */ void *cache_private; /* 構(gòu)造函數(shù)、析構(gòu)函數(shù)和回收函數(shù)的參數(shù) */ vmem_t *cache_arena; /* slab的后備分配器 */ int cache_cflags; /* 緩存的創(chuàng)建標(biāo)識(shí) */ int cache_flags; /* 緩存的狀態(tài)信息 */ uint32_t cache_mtbf; /* induced alloc failure rate */ uint32_t cache_pad1; /* to align cache_lock */ kstat_t *cache_kstat; /* exported statistics */ kmem_cache_t *cache_next; /* 系統(tǒng)中后一個(gè)緩存 */ kmem_cache_t *cache_prev; /* 系統(tǒng)中前一個(gè)緩存 */ /* Slab層 */ kmutex_t cache_lock; /* 保護(hù)slab層的互斥鎖 */ size_t cache_chunksize; /* 對(duì)象在slab中的實(shí)際大小 */ size_t cache_slabsize; /* 一個(gè)slab的大小 */ size_t cache_bufctl; /* buf起始地址到kmem_bufctl的偏移 */ size_t cache_buftag; /* buf起始地址到kmem_buftag的偏移 */ size_t cache_verify; /* 需要驗(yàn)證的字節(jié)數(shù) */ size_t cache_contents; /* bytes of saved content */ size_t cache_color; /* 創(chuàng)建下一個(gè)slab時(shí)用的染色值 */ size_t cache_mincolor; /* 可用的最小染色值 */ size_t cache_maxcolor; /* 可用的最大染色值 */ size_t cache_hash_shift; /* hash運(yùn)算時(shí)地址移位的位數(shù) */ size_t cache_hash_mask; /* hash表的掩碼 */ kmem_slab_t *cache_freelist; /* 空閑slab鏈表 */ kmem_slab_t cache_nullslab; /* 空閑鏈表尾部的標(biāo)記 */ kmem_cache_t *cache_bufctl_cache; /* 分配kmem_bufctls結(jié)構(gòu)的緩存 */ kmem_bufctl_t **cache_hash_table; /* hash表的基地址 */ void *cache_pad2; /* to align depot_lock */ /* Depot層 */ kmutex_t cache_depot_lock; /* 保護(hù)depot層的互斥鎖 */ kmem_magtype_t *cache_magtype; /* 彈夾類型 */ void *cache_pad3; /* to align cache_cpu */ kmem_maglist_t cache_full; /* 滿彈夾列表 */ kmem_maglist_t cache_empty; /* 空彈夾列表 */ /* CPU層 */ kmem_cpu_cache_t cache_cpu[1]; /* CPU局部緩存控制結(jié)構(gòu)數(shù)組 */ }; kmem_cache中的字段分成五個(gè)部分。第一個(gè)部分是用于統(tǒng)計(jì)的字段,系統(tǒng)每次執(zhí)行與該緩存相關(guān)的操作時(shí)會(huì)更新這些字段,它們的值用于做系統(tǒng)負(fù)載、性能等方面的統(tǒng)計(jì)。第二個(gè)部分是表示緩存屬性的字段。 cache_bufsize和cache_align保存了緩存中對(duì)象的大小和對(duì)齊邊界。 cache_constructor、cache_destructor和cache_reclaim是回調(diào)函數(shù),它們分別是構(gòu)造函數(shù)、析構(gòu)函數(shù)和回收函數(shù),這些函數(shù)由用戶在創(chuàng)建緩存的時(shí)候指定。cache_private是調(diào)用這三個(gè)回調(diào)函數(shù)時(shí)用到的參數(shù)。 cache_arena是比緩存低一級(jí)的分配器,緩存從這里獲取整塊內(nèi)存(一般是頁(yè)的整數(shù)倍)來(lái)創(chuàng)建slab,然后再劃分成對(duì)象,釋放slab時(shí)slab對(duì)應(yīng)的內(nèi)存也歸還到這里。系統(tǒng)中所有的緩存通過(guò)cache_next和cache_prev字段鏈成一個(gè)雙向循環(huán)鏈表,這個(gè)鏈表的表頭是kmem_null_cache,這是一個(gè)kmem_cache類型的全局變量,定義在uts/common/os/kmem.c中。第三個(gè)部分是與slab層相關(guān)的字段。 cache_chunksize表示一個(gè)對(duì)象在slab中的實(shí)際大小。一個(gè)對(duì)象在slab中除了它本身要占用空間外,為了對(duì)齊還要占用一些空間,另外可能還會(huì)有一些調(diào)試信息,所有這些空間合稱為一個(gè)chunk,而cache_chunksize就是它的大小。 cache_slabsize是緩存中一個(gè)slab的大小,這個(gè)大小與cache_chunksize有關(guān)。一般小對(duì)象(chunksize小于512字節(jié))對(duì)應(yīng)的slab大小都是一個(gè)整頁(yè)(4K字節(jié))。在slab中,如果一個(gè)小對(duì)象是空閑的,而且這個(gè)對(duì)象的大小可以放下kmem_bufctl和kmem_buftag結(jié)構(gòu),那么這個(gè)對(duì)象對(duì)應(yīng)的內(nèi)存塊會(huì)被用來(lái)存放這兩個(gè)控制結(jié)構(gòu)。kmem_bufctl和kmem_buftag結(jié)構(gòu)位于內(nèi)存塊的末尾,cache_bufctl和cache_buftag字段分別表示對(duì)象的起始地址到這兩個(gè)結(jié)構(gòu)的起始地址之間的距離。如果對(duì)象太大,使得一個(gè)slab可能跨越多個(gè)頁(yè),或者對(duì)象太小,不能放下kmem_bufctl結(jié)構(gòu),那么即便對(duì)象是空閑的,kmem_bufctl也不能放在對(duì)象所占的內(nèi)存塊中,而是要另外為kmem_bufctl分配一塊內(nèi)存。在為kmem_bufctl分配內(nèi)存的時(shí)候,把kmem_bufctl也看作是小對(duì)象,用的是與其它對(duì)象相同的分配方法。cache_bufctl_cache字段就是指向用于分配kmem_bufctl結(jié)構(gòu)的對(duì)象緩存。 cache_hash_table是一個(gè)kmem_bufctl結(jié)構(gòu)的hash表。當(dāng)kmem_bufctl結(jié)構(gòu)不位于對(duì)象所占的內(nèi)存塊中時(shí),就不能通過(guò)對(duì)象的起始地址計(jì)算出kmem_bufctl的地址。為了能快速找到對(duì)象的kmem_bufctl,Solaris為kmem_bufctl建了一個(gè)hash表,表的索引值就是對(duì)象的起始地址,這樣就可以通過(guò)起始地址快速在表中它的kmem_bufctl。 kmem_hash_shift和kmem_hash_mask字段與hash函數(shù)有關(guān)。slab中hash函數(shù)的過(guò)程是先把kmem_bufctl的地址右移kmem_hash_shift位,然后再與kmem_hash_mask做與操作,得到的值就是kmem_bufctl結(jié)構(gòu)在hash表中的索引。 cache_color、cache_mincolor和cache_maxcolor這三個(gè)字段用于對(duì)slab進(jìn)行染色。cache_color是創(chuàng)建下一個(gè)slab時(shí)用到的染色值,cache_mincolor是可用的最小染色值,cache_maxcolor是可用的最大染色值。一個(gè)對(duì)象緩存中所有的slab串成一個(gè)雙向循環(huán)鏈表,kmem_nullslab標(biāo)識(shí)這個(gè)鏈表的結(jié)尾。這個(gè)鏈表分成三個(gè)部分,所有對(duì)象都已被分配的slab位于最前面的部分,部分被分配的slab位于中間,而完全空閑的slab位于尾部。部分被分配的部分和完全空閑的部分合稱為空閑列表,cache_freelist指向這個(gè)鏈表的表頭。kmem_nullslab是整個(gè)鏈表的尾部,因此它也是空閑鏈表的尾部。第四個(gè)部分是depot層相關(guān)的字段。 cache_magtype表示彈夾的類型。彈夾分成不同的類型,不同類型的彈夾中可以存放的對(duì)象數(shù)各不相同,可裝載對(duì)象的尺寸范圍也不同。這個(gè)部分中還有兩個(gè)鏈表字段,一個(gè)是滿彈夾鏈表cache_full,一個(gè)是空彈夾鏈表cache_empty,這兩個(gè)鏈表中存放的是全局的彈夾。第五個(gè)部分是與CPU層相關(guān)的字段。這個(gè)部分中只有一個(gè)字段cache_cpu,這是一個(gè)kmem_cpu_cache結(jié)構(gòu)的數(shù)組,數(shù)組中有多少個(gè)元素由CPU的個(gè)數(shù)決定。每一個(gè)元素對(duì)應(yīng)一個(gè)CPU,表示該CPU的局部緩存。在kmem_cache結(jié)構(gòu)的聲明中,這個(gè)數(shù)組只有一個(gè)元素,但是在創(chuàng)建緩存的時(shí)候,系統(tǒng)會(huì)根據(jù)CPU的數(shù)目給kmem_cache結(jié)構(gòu)多分配一些內(nèi)存,保證數(shù)組中可以放下所有CPU局部緩存的控制結(jié)構(gòu)。 1.7.2.3.kmem_slab kmem_slab結(jié)構(gòu)是slab的控制結(jié)構(gòu),它在uts/common/sys/kmem_impl.h中定義,定義如下: typedef struct kmem_slab { struct kmem_cache *slab_cache; /* 所屬對(duì)象緩存的控制結(jié)構(gòu)指針 */ void *slab_base; /* 第一個(gè)對(duì)象的起始地址 */ struct kmem_slab *slab_next; /* 空閑鏈表中的下一個(gè)slab */ struct kmem_slab *slab_prev; /* 空閑鏈表中的上一個(gè)slab */ struct kmem_bufctl *slab_head; /* 空閑對(duì)象鏈表的表頭 */ long slab_refcnt; /* 已經(jīng)分配的對(duì)象數(shù) */ long slab_chunks; /* 共有多少個(gè)對(duì)象 */ } kmem_slab_t; slab_cache字段是一個(gè)指向這個(gè)slab所屬對(duì)象緩存控制結(jié)構(gòu)的指針。 slab_base字段表示這個(gè)slab中第一個(gè)對(duì)象的起始地址。前面提到,slab的第一個(gè)對(duì)象不一定位于slab的起始位置,它的地址與創(chuàng)建slab時(shí)分配的染色值有關(guān)。slab_base保存了第一個(gè)對(duì)象的地址,在實(shí)現(xiàn)中它就等于slab的起始地址加上染色值。 slab_next和slab_prev用來(lái)構(gòu)建空閑slab鏈表。一個(gè)slab中所有空閑對(duì)象的控制結(jié)構(gòu)組成一個(gè)單向鏈表,slab_head就指向這個(gè)鏈表的表頭。 slab_refcnt是引用數(shù),實(shí)際上就表示這個(gè)slab中已經(jīng)有多少個(gè)對(duì)象已經(jīng)被分配出去。 slab_chunks表示slab中包括多少個(gè)chunk。一個(gè)chunk對(duì)應(yīng)一個(gè)對(duì)象,因此這個(gè)字段實(shí)際上也指出了slab中有多少個(gè)對(duì)象。 1.7.2.4.kmem_bufctl kmem_bufctl是對(duì)象所占內(nèi)存塊的控制結(jié)構(gòu),它在uts/common/sys/kmem_impl.h中定義,定義如下: typedef struct kmem_bufctl { struct kmem_bufctl *bc_next; /* 空閑列表中下一個(gè)內(nèi)存塊 */ void *bc_addr; /* 內(nèi)存塊的起始地址 */ struct kmem_slab *bc_slab; /* 所屬slab的控制結(jié)構(gòu)指針 */ } kmem_bufctl_t; 這個(gè)結(jié)構(gòu)中只有三個(gè)字段。bc_next表示空閑列表中的下一個(gè)內(nèi)存塊;bc_addr表示這個(gè)內(nèi)存塊的起始地址,實(shí)際上也是對(duì)象的地址;bc_slab表示這個(gè)內(nèi)存塊所屬slab的控制結(jié)構(gòu)的指針。 1.7.2.5.kmem_magazine、kmem_maglist和kmem_magtype 這三個(gè)數(shù)據(jù)結(jié)構(gòu)是在管理彈夾的時(shí)候用到的主要數(shù)據(jù)結(jié)構(gòu),它們都是在uts/common/sys/kmem_impl.h中定義,定義如下: typedef struct kmem_magazine { void *mag_next; /* 彈夾鏈表中的下一個(gè)彈夾 */ void *mag_round[1]; /* 一發(fā)或多發(fā)子彈 */ } kmem_magazine_t; /* 每個(gè)CPU進(jìn)行分配時(shí)所用的彈夾類型 */ typedef struct kmem_magtype { int mt_magsize; /* 彈夾尺寸(有多少發(fā)子彈) */ int mt_align; /* 彈夾對(duì)齊邊界 */ size_t mt_minbuf; /* 適用的最小內(nèi)存塊尺寸 */ size_t mt_maxbuf; /* 允許調(diào)整的最大內(nèi)存塊尺寸 */ kmem_cache_t *mt_cache; /* 用于分配彈夾數(shù)據(jù)結(jié)構(gòu)的對(duì)象緩存 */ } kmem_magtype_t; /* 用于depot層的彈夾列表 */ typedef struct kmem_maglist { kmem_magazine_t *ml_list; /* 彈夾鏈表 */ long ml_total; /* 總彈夾數(shù) */ long ml_min; /* 上次更新后彈夾數(shù)的最小值 */ long ml_reaplimit; /* 最多可以釋放的彈夾數(shù) */ uint64_t ml_alloc; /* 從這個(gè)鏈表中分配了多少個(gè)彈夾 */ } kmem_maglist_t; kmem_magazine是彈夾的控制結(jié)構(gòu),它有兩個(gè)字段。mag_next字段指向彈夾鏈表中的下一個(gè)彈夾。mag_round是一個(gè)數(shù)組,其中每一個(gè)位置可以裝一發(fā)子彈。如果一個(gè)位置裝了一發(fā)子彈,那么這個(gè)位置的指針就指向一個(gè)空閑對(duì)象。在聲明中這個(gè)數(shù)組只有一個(gè)元素,但在實(shí)際分配內(nèi)存的時(shí)候,系統(tǒng)會(huì)根據(jù)彈夾中能裝的子彈數(shù)分配出足夠的內(nèi)存。 kmem_magtype表示彈夾的類型。 mt_magsize表示彈夾中可以裝多少發(fā)子彈,kmem_magazine中mag_round數(shù)組的大小就是由這個(gè)字段來(lái)決定。 mt_align表示彈夾的對(duì)齊邊界。 mt_minbuf和mt_maxbuf限定了這個(gè)彈夾類型的適用范圍。一種類型的彈夾并不是可以用于所有的對(duì)象,它所能裝對(duì)象的尺寸是有一定限制的,這兩個(gè)字段就給出了尺寸范圍。這兩個(gè)字段表示的尺寸不是對(duì)象本身的大小,而是前面所說(shuō)的chunksize,也就是對(duì)象加上對(duì)齊和調(diào)試信息之后實(shí)際所占的內(nèi)存塊大小。一個(gè)彈夾中裝載的對(duì)象的chunksize必須大于mt_minbuf。mt_maxbuf的用法與mt_minbuf的用法有所不同,它用于調(diào)整彈夾的類型。一個(gè)CPU在運(yùn)行的時(shí)候可以調(diào)整它所用的彈夾類型,如果它發(fā)現(xiàn)當(dāng)前類型中的彈夾裝載的子彈數(shù)太少,就會(huì)選擇子彈數(shù)更多的彈夾類型。在調(diào)整彈夾的時(shí)候,需要判斷對(duì)象的chunksize是否比當(dāng)前彈夾類型的mt_maxbuf小。如果chunksize小于mt_maxbuf,則允許調(diào)整,CPU可以選擇下一級(jí)別的彈夾類型,否則的話就不能進(jìn)行彈夾的調(diào)整。 mt_cache表示分配這種類型的彈夾使用哪一個(gè)對(duì)象緩存,分配不同類型的彈夾用到的對(duì)象緩存可能是不同的,但是分配同一類型的彈夾用的都是同一個(gè)對(duì)象緩存。系統(tǒng)中共定義了九種彈夾類型,每個(gè)彈夾必定屬于其中一種類型。這九種類型在uts/common/os/kmem.c中定義,定義如下: static kmem_magtype_t kmem_magtype[] = { { 1, 8, 3200, 65536}, { 3, 16, 256, 32768}, { 7, 32, 64, 16384}, { 15, 64, 0, 8192 }, { 31, 64, 0, 4096 }, { 47, 64, 0, 2048 }, { 63, 64, 0, 1024 }, { 95, 64, 0, 512 }, { 143, 64, 0, 0 }, }; 在這個(gè)定義中,只給出了每種類型前四個(gè)字段的值,最后一個(gè)字段,也就是mt_cache字段的值沒(méi)有給出。mt_cache字段指向分配彈夾所用的對(duì)象緩存,這些對(duì)象緩存是在uts/common/os/kmem.c中的kmem_cache_init函數(shù)中創(chuàng)建的,所以mt_cache字段也是在kmem_cache_init函數(shù)中賦的值。 kmem_maglist表示一個(gè)彈夾鏈表。 ml_list是鏈表的表頭,ml_total是鏈表中的總彈夾數(shù)。 ml_min表示從上次更新到當(dāng)前時(shí)間彈夾鏈表中出現(xiàn)的彈夾數(shù)最小值。Solaris會(huì)定期更新對(duì)象緩存,也就是做對(duì)象緩存維護(hù),包括更新對(duì)象緩存的統(tǒng)計(jì)信息、更新depot層的工作集和調(diào)整彈夾大小。每次在更新depot層工作集的時(shí)候,它會(huì)把depot層的空彈夾鏈表和滿彈夾鏈表中的ml_min設(shè)成鏈表的當(dāng)前總彈夾數(shù)。隨著對(duì)象的分配和回收,彈夾鏈表中的彈夾數(shù)也在不斷變化,而ml_min保存了自上次更新以后出現(xiàn)過(guò)的最小彈夾數(shù)。 ml_reaplimit用于回收depot層的彈夾。這里有一個(gè)彈夾工作集的概念。在系統(tǒng)穩(wěn)定工作的時(shí)候,所需的內(nèi)存會(huì)保持在一個(gè)穩(wěn)定的值,因此depot層中所需的彈夾數(shù)也會(huì)在一個(gè)穩(wěn)定的范圍內(nèi),這時(shí),所需的彈夾就構(gòu)成一個(gè)彈夾工作集。例如在一段時(shí)間內(nèi),一個(gè)彈夾鏈表的彈夾數(shù)在30到40之間浮動(dòng),那么彈夾工作集就是10個(gè)彈夾(40 - 30),其余的彈夾一直閑置在彈夾鏈表中,在回收的時(shí)候就要回收這些閑置的彈夾。實(shí)際上ml_min就是閑置的彈夾數(shù),因?yàn)樗硎玖艘欢螘r(shí)間內(nèi)不同彈夾的最小值,在更新depot工作集的時(shí)候,Solaris會(huì)把ml_min的值賦給ml_reaplimit。在下次更新depot工作集之前,ml_reaplimit的值就不會(huì)再改變了,但是彈夾鏈表中的彈夾數(shù)是會(huì)發(fā)生變化的,所以在實(shí)際回收的時(shí)候,回收的彈夾數(shù)會(huì)取ml_reaplimit和當(dāng)前彈夾總數(shù)中的最小值。 1.7.2.6.kmem_cpu_cache kmem_cpu_cache結(jié)構(gòu)是CPU局部緩存的控制結(jié)構(gòu),它在uts/common/sys/kmem_impl.h中定義,定義如下: typedef struct kmem_cpu_cache { kmutex_t cc_lock; /* 保護(hù)CPU局部緩存的互斥鎖 */ uint64_t cc_alloc; /* 這個(gè)局部緩存中已分配的對(duì)象數(shù) */ uint64_t cc_free; /* 這個(gè)局部緩存中的空閑對(duì)象數(shù) */ kmem_magazine_t *cc_loaded; /* 當(dāng)前裝載的彈夾 */ kmem_magazine_t *cc_ploaded; /* 前一個(gè)裝載的彈夾 */ int cc_rounds; /* 當(dāng)前裝載的彈夾中還有多少發(fā)子彈 */ int cc_prounds; /* 前一個(gè)裝載的彈夾中還有多少發(fā)子彈 */ int cc_magsize; /* 滿彈夾中有多少發(fā)子彈 */ int cc_flags; /* 全局對(duì)象緩存cache_flags字段的拷貝 */ char cc_pad[KMEM_CPU_PAD]; /* 用于邊界對(duì)齊 */ } kmem_cpu_cache_t; cc_alloc表示當(dāng)前CPU的局部緩存中已經(jīng)分配了多少個(gè)對(duì)象,cc_free表示還有多少個(gè)空閑對(duì)象。 cc_loaded和cc_ploaded表示CPU裝載的兩個(gè)彈夾。當(dāng)從depot層裝載一個(gè)新彈夾的時(shí)候,cc_loaded指向新裝載的彈夾,cc_ploaded指向cc_loaded原來(lái)所指的彈夾,而cc_ploaded原來(lái)所指的彈夾被歸還到depot層。保存兩個(gè)彈夾的目的是為了防止抖動(dòng)。如果只有一個(gè)彈夾,現(xiàn)在考慮彈夾中只有一個(gè)對(duì)象的情況,如果CPU要連續(xù)分配兩個(gè)對(duì)象,那么第一個(gè)對(duì)象可以直接從彈夾中分配,而分配第二個(gè)對(duì)象時(shí)就需要局部緩存從depot層裝載一個(gè)滿彈夾,并把當(dāng)前的空彈夾還給depot層,再?gòu)臐M彈夾中分配;下一步CPU要連續(xù)釋放兩個(gè)對(duì)象,由于彈夾中還有一個(gè)空位,所以第一個(gè)對(duì)象可以直接存到彈夾中,但是釋放第二個(gè)對(duì)象時(shí)就需要從depot層裝載一個(gè)空彈夾,并把滿彈夾還給depot層,然后把空閑對(duì)象放到空彈夾中。這時(shí),CPU的局部緩存中的彈夾又變成只有一個(gè)對(duì)象的情況。如果這種操作反復(fù)進(jìn)行,那么局部緩存就需要反復(fù)地與depot層交互,從而出現(xiàn)抖動(dòng)。如果有兩個(gè)彈夾的話,只要其中有一個(gè)彈夾中有空閑對(duì)象,就可以分配對(duì)象,而只要其中一個(gè)彈夾有空位,就可以釋放對(duì)象,這就避免了抖動(dòng)的情況。 cc_rounds和cc_prounds分別表示當(dāng)前裝載的彈夾和前一個(gè)裝載的彈夾中的空閑對(duì)象數(shù),而cc_magsize表示彈夾中最多可以裝載多少個(gè)對(duì)象。 cc_flags是局部緩存所在的對(duì)象緩存中cache_flags字段的拷貝,在這里放一份拷貝是為了操作方便。 1.7.3.情景 1.7.3.1.創(chuàng)建對(duì)象緩存 表9 創(chuàng)建對(duì)象緩存時(shí)用到的主要函數(shù)函數(shù)名 文件名 功能描述 kmem_cache_create uts/common/os/kmem.c 在內(nèi)核空間分配一個(gè)已初始化的對(duì)象創(chuàng)建對(duì)象緩存是通過(guò)kmem_cache_create函數(shù)完成的,這個(gè)函數(shù)的流程如圖2所示: 圖15 kmem_cache_create函數(shù)流程圖創(chuàng)建對(duì)象緩存的時(shí)候需要給對(duì)象緩存起一個(gè)名字,這個(gè)名字要符合C語(yǔ)言的命名規(guī)范,kmem_cache_create函數(shù)首先會(huì)調(diào)用strident_valid函數(shù)來(lái)驗(yàn)證緩存的名字。然后要為對(duì)象緩存的控制結(jié)構(gòu)分配空間,這通過(guò)調(diào)用vmem_xalloc函數(shù)來(lái)完成。vmem_xalloc函數(shù)從kmem_cache_arena區(qū)域分配一塊內(nèi)存,內(nèi)存的大小通過(guò)KMEM_CACHE_SIZE宏來(lái)計(jì)算。這個(gè)宏根據(jù)系統(tǒng)中CPU的個(gè)數(shù)調(diào)整內(nèi)存塊的大小,保證位于控制結(jié)構(gòu)末尾的局部緩存數(shù)組可以容下所有CPU的局部緩存。新分配的內(nèi)存塊中所有的字節(jié)都被初始化為0。接下來(lái)就要設(shè)置控制結(jié)構(gòu)的各個(gè)字段了,這大致分成設(shè)置對(duì)象緩存屬性、設(shè)置slab層、設(shè)置depot層和設(shè)置CPU層幾個(gè)部分,下面就部分字段進(jìn)行說(shuō)明。 cache_align字段根據(jù)align參數(shù)來(lái)設(shè)置。首先要檢查align的值,如果它的值為0,就把它設(shè)成默認(rèn)的對(duì)齊邊界(8字節(jié),在KMEM_ALIGN宏中定義)。另外align的值必須是2的冪,而且它不能大于下層分配器的基本分配單位,否則會(huì)報(bào)錯(cuò)。接下來(lái)kmem_cache_create函數(shù)會(huì)對(duì)cflags進(jìn)行檢驗(yàn)和設(shè)置,保證里面的各個(gè)標(biāo)識(shí)位都是合理的,然后把cflags的值賦給cache_cflags字段。 cache_constructor、cache_destructor等字段由調(diào)用者通過(guò)參數(shù)來(lái)設(shè)置。 cache_chunksize字段的值需要根據(jù)cache_bufsize字段的值來(lái)計(jì)算。首先把cache_bufsize按對(duì)齊邊界對(duì)齊;如果cache_flags中的KMF_BUFTAG位被置位,就再加上kmem_buftag結(jié)構(gòu)的大小;最后再按對(duì)齊邊界對(duì)齊一次,得到的值就是chunk的大小。有了chunk的大小,就可以算出cache_bufctl、cache_buftag、cache_contents等字段的值。然后要找一個(gè)合適的slab大小。如果cache_chunksize小于下層分配器基本分配單位的1/KMEM_VOID_FRACTION(KMEM_VOID_FRACTION的值為8),而且cache_cflags的 KMF_NOHASH位被置位,則slab的大小就等于基本分配單位的大小。否則的話就分別考慮slab中分別包括1個(gè)到8個(gè)chunk的情況,從中找出內(nèi)存浪費(fèi)最少的一個(gè)情況,slab的大小就等于這種情況下的chunk數(shù)乘以chunk大小,再按基本分配單位對(duì)齊。 depot層的設(shè)置主要就是考慮選擇一個(gè)合適的彈夾類型,選擇的原則就是從彈夾類型鏈表中找第一個(gè)適用的彈夾類型(mt_minbuf字段小于chunk大小的類型),找到后就把這個(gè)類型賦給cache_magtype字段。設(shè)置完各個(gè)字段的值后,kmem_cache_create函數(shù)會(huì)把新建的對(duì)象緩存添加到全局對(duì)象緩存鏈表中,把它插在kmem_null_cache之前。最后,如果kmem_ready的值非0,也就是內(nèi)核內(nèi)存的初始化已經(jīng)完成,說(shuō)明用于分配彈夾的對(duì)象緩存已經(jīng)創(chuàng)建完畢,這時(shí)就開(kāi)啟對(duì)象緩存的彈夾機(jī)制。 1.7.3.2.分配對(duì)象 這個(gè)情景描述在內(nèi)核空間分配一個(gè)已經(jīng)初始化好的對(duì)象,其中涉及到的主要函數(shù)包括:表10 內(nèi)核對(duì)象分配中的主要函數(shù)函數(shù)名 文件名 功能描述 kmem_cache_alloc uts/common/os/kmem.c 在內(nèi)核空間分配一個(gè)已初始化的對(duì)象 kmem_depot_alloc uts/common/os/kmem.c 從depot層申請(qǐng)一個(gè)滿彈夾 kmem_depot_free uts/common/os/kmem.c 把CPU換下的空彈夾加入到depot層的空彈夾鏈表中 kmem_slab_alloc uts/common/os/kmem.c 在slab層創(chuàng)建一個(gè)對(duì)象,并把它分配給調(diào)用者 kmem_slab_create uts/common/os/kmem.c 為一個(gè)對(duì)象緩存創(chuàng)建一個(gè)新的slab 內(nèi)核程序要分配一個(gè)對(duì)象的時(shí)候,需要調(diào)用kmem_cache_alloc函數(shù)。這個(gè)函數(shù)的流程如圖2所示: 圖16 kmem_cache_alloc函數(shù)流程圖 kmem_cache_alloc首先從CPU自己的緩存中分配。如果CPU的兩個(gè)彈夾中都沒(méi)有可分配的對(duì)象,則調(diào)用kmem_depot_alloc函數(shù)從depot層的滿彈夾鏈表中裝載一個(gè)滿彈夾,然后把滿彈夾設(shè)為當(dāng)前彈夾,重新執(zhí)行分配過(guò)程。裝載滿彈夾時(shí)會(huì)替換下CPU前一個(gè)裝載的彈夾,被換下的彈夾一定是一個(gè)空彈夾,所以要調(diào)用kmem_depot_free函數(shù)把這個(gè)彈夾放入depot層的空彈夾鏈表中。如果獲取滿彈夾失敗,則說(shuō)明滿彈夾列表為空,depot層也沒(méi)有可以分配的對(duì)象,這時(shí)就調(diào)用kmem_slab_alloc函數(shù)直接從slab層分配一個(gè)對(duì)象。kmem_slab_alloc分配的對(duì)象是未初始化的對(duì)象,最后還要用構(gòu)造函數(shù)對(duì)這個(gè)對(duì)象進(jìn)行初始化。從slab層分配對(duì)象時(shí)調(diào)用的是kmem_slab_alloc函數(shù),這個(gè)函數(shù)的流程如圖3所示: 圖17 kmem_slab_alloc函數(shù)流程圖 這個(gè)函數(shù)中需要說(shuō)明的是如何獲取slab中一個(gè)空閑內(nèi)存塊的地址。獲取地址的代碼如下: if (cp->cache_flags & KMF_HASH) { /* * Add buffer to allocated-address hash table. */ buf = bcp->bc_addr; hash_bucket = KMEM_HASH(cp, buf); bcp->bc_next = *hash_bucket; *hash_bucket = bcp; if ((cp->cache_flags & (KMF_AUDIT | KMF_BUFTAG)) == KMF_AUDIT) { KMEM_AUDIT(kmem_transaction_log, cp, bcp); } } else { buf = KMEM_BUF(cp, bcp); } 從kmem_slab結(jié)構(gòu)中只能獲取空閑內(nèi)存塊的控制結(jié)構(gòu),也就是一個(gè)kmem_bufctl變量,然后需要從kmem_bufctl得出空閑塊的地址。在kmem_cache結(jié)構(gòu)的介紹中提到內(nèi)存塊的控制結(jié)構(gòu)可能放在內(nèi)存塊中,也可能另外為它分一塊內(nèi)存,如果另外分配內(nèi)存的話,就需要一個(gè)存放控制結(jié)構(gòu)的hash表。kmem_cache結(jié)構(gòu)的cache_flag字段中有一位會(huì)標(biāo)出是否要用到hash表。在從kmem_bufctl結(jié)構(gòu)獲得內(nèi)存塊地址的時(shí)候,要對(duì)這一位進(jìn)行判斷。如果用到hash表,則kmem_bufctl的bc_addr字段就指向內(nèi)存塊的地址;如果kmem_bufctl就在內(nèi)存塊中,就可以從控制結(jié)構(gòu)直接計(jì)算出內(nèi)存塊的地址。這個(gè)計(jì)算是通過(guò)KMEM_BUF宏完成的,這個(gè)宏在uts/common/sys/kmem_impl.h中定義。在沒(méi)有空閑slab時(shí)需要調(diào)用kmem_slab_create函數(shù)創(chuàng)建一個(gè)新的slab,它的流程如圖4所示: 圖18 kmem_slab_create函數(shù)流程 這個(gè)函數(shù)在為kmem_slab控制結(jié)構(gòu)分配內(nèi)存的時(shí)候,會(huì)根據(jù)對(duì)象緩存是否使用hash表來(lái)決定是為控制結(jié)構(gòu)另外分配一塊內(nèi)存還是利用slab末尾的內(nèi)存。同樣,在為每個(gè)小內(nèi)存塊的控制結(jié)構(gòu)分配內(nèi)存的時(shí)候,也要根據(jù)是否使用hash表來(lái)決定是為控制結(jié)構(gòu)另外分配內(nèi)存還是直接利用小內(nèi)存塊本身的內(nèi)存。如果不使用hash表,則不需要另外分配內(nèi)存。為了節(jié)省空間,在流程圖中為小內(nèi)存塊設(shè)置控制結(jié)構(gòu)這一塊簡(jiǎn)略表示了。為slab控制結(jié)構(gòu)分配內(nèi)存的代碼如下: if (cache_flags & KMF_HASH) { if ((sp = kmem_cache_alloc(kmem_slab_cache, kmflag)) == NULL) goto slab_alloc_failure; chunks = (slabsize - color) / chunksize; } else { sp = KMEM_SLAB(cp, slab); chunks = (slabsize - sizeof (kmem_slab_t) - color) / chunksize; } 代碼中為控制結(jié)構(gòu)另外分配內(nèi)存時(shí)調(diào)用的也是kmem_cache_alloc函數(shù),它從kmem_slab對(duì)應(yīng)的對(duì)象緩存中分配。kmem_slab的對(duì)象緩存是不使用hash表的,否則在分配kmem_slab對(duì)象的時(shí)候有可能引起對(duì)kmem_slab對(duì)象緩存的遞歸分配,最后導(dǎo)致內(nèi)存分配失敗。 KMEM_SLAB宏用來(lái)根據(jù)slab的地址計(jì)算slab控制結(jié)構(gòu)的地址。相應(yīng)地,在分配小內(nèi)存塊控制結(jié)構(gòu)的時(shí)候,KMEM_BUFCTL宏用來(lái)根據(jù)小內(nèi)存塊的地址計(jì)算小內(nèi)存塊控制結(jié)構(gòu)的地址。 1.7.3.3.釋放對(duì)象 表11 釋放對(duì)象時(shí)用到的主要函數(shù)函數(shù)名 文件名 功能描述 kmem_cache_free uts/common/os/kmem.c 釋放一個(gè)已初始化的對(duì)象 kmem_slab_free uts/common/os/kmem.c 在slab層釋放一個(gè)內(nèi)存塊 kmem_slab_destroy uts/common/os/kmem.c 銷毀一個(gè)slab 釋放對(duì)象是與分配對(duì)象相反的過(guò)程。釋放對(duì)象時(shí)調(diào)用的是kmem_cache_free,這個(gè)函數(shù)在uts/common/os/kmem.c中定義,它的流程如圖5所示: 圖19 kmem_alloc_free函數(shù)流程圖如果CPU當(dāng)前裝載的彈夾中有空位,那么就把被釋放的對(duì)象放到空位中,也就是讓對(duì)象數(shù)組中的一個(gè)空閑指針指向被釋放的對(duì)象。如果當(dāng)前裝載的彈夾是滿的,那么就看前一個(gè)裝載的彈夾是否還有空位。如果有空位的話就把當(dāng)前裝載的彈夾和前一個(gè)裝載的彈夾交換一下位置,再重新做釋放操作。如果局部緩存中的兩個(gè)彈夾都是滿的,那么就需要調(diào)用kmem_depot_alloc函數(shù)從depot層獲取一個(gè)空彈夾,并通過(guò)調(diào)用kmem_depot_free函數(shù)把一個(gè)滿彈夾還給depot層,然后再重新做釋放操作。如果depot層的空彈夾鏈表中已經(jīng)沒(méi)有空彈夾,就調(diào)用kmem_cache_alloc函數(shù)從彈夾的對(duì)象緩存中分配一個(gè)新彈夾(空彈夾),然后重新做釋放操作。這也就是說(shuō)一個(gè)對(duì)象緩存在剛創(chuàng)建的時(shí)候,depot層中的滿彈夾鏈表和空彈夾鏈表都是空的,以后用到的彈夾都是在釋放對(duì)象時(shí)創(chuàng)建的。彈夾在被創(chuàng)建后,除非出現(xiàn)內(nèi)存緊張或者需要調(diào)整彈夾尺寸的情況,它們不會(huì)被銷毀,而是隨著申請(qǐng)對(duì)象和釋放對(duì)象操作的交替進(jìn)行,慢慢分布到depot層的空彈夾鏈表或滿彈夾鏈表中。如果創(chuàng)建新彈夾失敗,就無(wú)法把被釋放的對(duì)象緩存到depot層,這時(shí)就調(diào)用析構(gòu)函數(shù)銷毀對(duì)象,再調(diào)用kmem_slab_free函數(shù)把對(duì)象所占的內(nèi)存塊還給slab層。 kmem_slab_free函數(shù)的功能是釋放一個(gè)已分配的未初始化內(nèi)存塊,它在uts/common/os/kmem.c中定義,它的流程如圖6所示: 圖20 kmem_slab_free函數(shù)流程圖 kmem_slab_free首先要得到內(nèi)存塊的控制結(jié)構(gòu)和slab的控制結(jié)構(gòu)。如果這個(gè)slab使用hash表存儲(chǔ)控制結(jié)構(gòu),則查詢hash表,否則直接從內(nèi)存塊的地址算出控制結(jié)構(gòu)的地址。由于這個(gè)slab釋放了一個(gè)內(nèi)存塊,也就是說(shuō)這個(gè)slab現(xiàn)在肯定包含有空閑內(nèi)存塊,所以如果這個(gè)slab以前不在對(duì)象緩存的空閑slab鏈表中,現(xiàn)在就把它加進(jìn)去。然后把被釋放的內(nèi)存塊插入到這個(gè)slab的空閑內(nèi)存塊鏈表的頭部。如果釋放完這個(gè)內(nèi)存塊后slab中所有的內(nèi)存塊都是空閑的,就可以回收這個(gè)slab所占的內(nèi)存空間。回收slab時(shí)首先要把這個(gè)slab從空閑slab鏈表中移除,如果這個(gè)slab恰好是空閑鏈表的表頭,那么還要修改表頭,讓表頭指向下一個(gè)空閑slab。然后就可以調(diào)用kmem_slab_destroy函數(shù)銷毀slab了。 kmem_slab_destroy函數(shù)比較簡(jiǎn)單,它完成的工作包括兩步:第一步,如果slab的控制結(jié)構(gòu)占用了另外的空間,就釋放這些空間;第二步,調(diào)用底層分配器的釋放函數(shù)(vmem_free),釋放slab所占的內(nèi)存。 1.7.3.4.分配與釋放未初始化內(nèi)存塊 表12分配與釋放未初始化內(nèi)存塊時(shí)用到的主要函數(shù)函數(shù)名 文件名 功能描述 kmem_alloc uts/common/os/kmem.c 分配一塊任意大小的未初始化內(nèi)存 kmem_zalloc uts/common/os/kmem.c 分配一塊任意大小的內(nèi)存塊,內(nèi)存塊全部初始化為0 kmem_free uts/common/os/kmem.c 釋放一個(gè)未初始化內(nèi)存塊 Solaris的slab內(nèi)存分配器除了可以分配已初始化的對(duì)象,它還可以像普通的分配器一樣分配和釋放任意大小的未初始化的內(nèi)存,完成這兩個(gè)操作的函數(shù)分別是kmem_alloc和kmem_free。 kmem_alloc函數(shù)在uts/common/os/kmem.c中定義。在調(diào)用kmem_alloc函數(shù)的時(shí)候需要給出所需內(nèi)存塊的尺寸,kmem_alloc會(huì)根據(jù)尺寸進(jìn)行不同的分配操作。對(duì)于小于16k(在常量KMEM_MAXBUF中定義)的內(nèi)存塊,slab分配器采用了與分配對(duì)象相同的方法,它把未初始化的內(nèi)存看作構(gòu)造函數(shù)和析構(gòu)函數(shù)都為空的對(duì)象。系統(tǒng)在初始化的時(shí)候創(chuàng)建了32個(gè)用于分配未初始化內(nèi)存的對(duì)象緩存,它們對(duì)應(yīng)的內(nèi)存塊尺寸在kmem_alloc_sizes數(shù)組中給出。 static const int kmem_alloc_sizes[] = { 1 * 8, 2 * 8, 3 * 8, 4 * 8, 5 * 8, 6 * 8, 7 * 8, 4 * 16, 5 * 16, 6 * 16, 7 * 16, 4 * 32, 5 * 32, 6 * 32, 7 * 32, 4 * 64, 5 * 64, 6 * 64, 7 * 64, 4 * 128, 5 * 128, 6 * 128, 7 * 128, P2ALIGN(8192 / 7, 64), P2ALIGN(8192 / 6, 64), P2ALIGN(8192 / 5, 64), P2ALIGN(8192 / 4, 64), P2ALIGN(8192 / 3, 64), P2ALIGN(8192 / 2, 64), P2ALIGN(8192 / 1, 64), 4096 * 3, 8192 * 2, }; 為了實(shí)現(xiàn)內(nèi)存塊尺寸到32個(gè)對(duì)象緩存的映射,Solaris聲明了一個(gè)全局?jǐn)?shù)組kmem_alloc_table。數(shù)組中有2048個(gè)元素(KMEM_MAXBUF >> KMEM_ALIGH_SHIFT),每個(gè)元素是一個(gè)指向?qū)ο缶彺娴闹羔槨?shù)組中每個(gè)元素對(duì)應(yīng)一個(gè)內(nèi)存塊尺寸,尺寸從前往后依次遞增,每次遞增的值是8字節(jié)(也就是最小對(duì)齊邊界,在常量KMEM_ALIGN中定義)。第一個(gè)元素對(duì)應(yīng)8字節(jié)的內(nèi)存塊,第二個(gè)元素對(duì)應(yīng)16字節(jié)的內(nèi)存塊,……,最后一個(gè)元素對(duì)應(yīng)16k字節(jié)的內(nèi)存塊。在初始化的時(shí)候,系統(tǒng)令kmem_alloc_table數(shù)組中某一元素指向大于它所對(duì)應(yīng)內(nèi)存塊尺寸的第一個(gè)對(duì)象緩存。kmem_alloc_table數(shù)組元素和32個(gè)對(duì)象緩存的對(duì)應(yīng)關(guān)系如圖5所示。 圖21 kmem_alloc_talbe數(shù)組與32個(gè)對(duì)象緩存之間的關(guān)系如果要分配一定尺寸的一塊內(nèi)存,分配器首先在kmem_alloc_table數(shù)組中找到大于該尺寸的第一個(gè)元素,再根據(jù)該元素中的指針找到相應(yīng)的對(duì)象緩存,然后就調(diào)用前面所講的kmem_cache_alloc函數(shù)從對(duì)象緩存中分配一塊內(nèi)存。如果要分配大于16k字節(jié)的內(nèi)存塊,就不能用上面提到的這套機(jī)制了,這時(shí)solaris就調(diào)用vmem_alloc函數(shù)直接從kmem_oversize_arena區(qū)域分配所需的內(nèi)存。 kmem_free函數(shù)的功能是釋放未初始化內(nèi)存塊,它的原理與kmem_alloc差不多,只不過(guò)它執(zhí)行了一個(gè)相反的過(guò)程。kmem_free函數(shù)也是根據(jù)內(nèi)存塊的大小執(zhí)行不同的釋放操作,如果內(nèi)存塊的尺寸小于16k,它就調(diào)用kmem_cache_free函數(shù)釋放內(nèi)存塊,否則它就直接調(diào)用vmem_free函數(shù)把內(nèi)存塊歸還到kmem_oversize_arena區(qū)域中去。
總結(jié)
以上是生活随笔為你收集整理的转一个solaris虚拟内存管理的wiki的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: python 计算器 eval ctf_
- 下一篇: pb 日期相关函数