日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

转一个solaris虚拟内存管理的wiki

發(fā)布時(shí)間:2023/12/20 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 转一个solaris虚拟内存管理的wiki 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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)建或者撤銷(xiāo)某個(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í),都需要用該讀寫(xiě)鎖對(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)限置為可讀/可寫(xiě)/可執(zhí)行;將內(nèi)核數(shù)據(jù)段的權(quán)限置為可讀/可寫(xiě)/可執(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)程地址空間正被銷(xiāo)毀,該函數(shù)將銷(xiāo)毀相應(yīng)的hat xhat_free_end_all uts/common/vm/Xhat.c 銷(xiāo)毀相應(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(在銷(xiāo)毀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)程的棧,堆以及寫(xiě)入時(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)(也稱(chēng)為插槽)提供了匿名頁(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ù),記錄了在寫(xiě)入時(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)類(lèi)型變量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*類(lèi)型)。這里要說(shuō)一下kmem_free函數(shù)的算法,將內(nèi)核內(nèi)存中空閑的塊建立為一個(gè)堆,從根部開(kāi)始在其中查找與給定塊最相近的空閑塊,如果找到,則將給定的塊寫(xiě)入到空閑塊中。 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)的磁盤(pán)空間中,必須專(zhuān)門(mén)劃分出一個(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)行讀寫(xiě)(其實(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ù);如果是寫(xiě)文件,只需要將變化的數(shù)據(jù)保存到文件中,以便恢復(fù)。但是那些用malloc( )和new等函數(shù)生成的對(duì)象的數(shù)據(jù)則不同,需要Swap空間,因?yàn)樗鼈冊(cè)谖募到y(tǒng)中沒(méi)有相應(yīng)的“儲(chǔ)備”文件,因此被稱(chēng)為“匿名”(Anonymous)的內(nèi)存數(shù)據(jù),這類(lèi)數(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è)交換到磁盤(pán)中,以增加內(nèi)存空閑空間。因?yàn)榻粨Q空間是作為匿名內(nèi)存的后備存儲(chǔ)器,所以我們必須首先確定是否有足夠的交換空間,以便可以把頁(yè)面交換出去。因此,在創(chuàng)建一個(gè)可寫(xiě)入的映象前,我們要先申請(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,在可寫(xiě)入的虛擬內(nèi)存中,其每個(gè)頁(yè)大小的單元都需要一個(gè)同等大小的交換空間。比如,在傳統(tǒng)的UNIX系統(tǒng)里,一個(gè)malloc要求分配8 Mbytes空間,那同時(shí),它也要申請(qǐng)8 Mbytes的交換磁盤(pán)空間,盡管這些交換空間它可能從未用到。一般地,粗略估計(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交換的操作是磁盤(pán)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指出將要移入或移出交換空間的資源的路徑名稱(chēng) 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)于磁盤(pán)分區(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è)備是否可寫(xiě)入。這通過(guò)VOP_SETATTR設(shè)置設(shè)備屬性來(lái)決定,如果成功設(shè)置,則設(shè)備可寫(xiě),否則進(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è)備名稱(chēng)。。。計(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)的磁盤(pán)交換頁(yè)數(shù)小于已申請(qǐng)的磁盤(pán)交換頁(yè)數(shù),跳出程序。如果系統(tǒng)中的所有的空閑頁(yè)數(shù)目小于欲刪除設(shè)備的頁(yè)數(shù)目,說(shuō)明設(shè)備正在被使用,或系統(tǒng)無(wú)法騰出空間裝載在刪除設(shè)備中的內(nèi)容,轉(zhuǎn)出錯(cuò)處理。如果刪除設(shè)備后,請(qǐng)求的磁盤(pán)交換空間不足,可申請(qǐng)內(nèi)存交換空間補(bǔ)足。先計(jì)算差額,然后從availrmem中分配內(nèi)存。如果全局變量k_anoninfo中申請(qǐng)的內(nèi)存交換空間小于被鎖住的,跳出程序;如果可分配的磁盤(pán)交換空間小于申請(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)存被稱(chēng)為一個(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)銷(xiāo)毀對(duì)象。Slab分配器還采用了緩存的技術(shù),每種類(lèi)型的對(duì)象有一個(gè)單獨(dú)的緩存,以前分配的對(duì)象在釋放時(shí)并不是馬上銷(xiāo)毀,而是放回這個(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銷(xiāo)毀的次數(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]; /* 緩存名稱(chēng) */ 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; /* 彈夾類(lèi)型 */ 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類(lèi)型的全局變量,定義在uts/common/os/kmem.c中。第三個(gè)部分是與slab層相關(guān)的字段。 cache_chunksize表示一個(gè)對(duì)象在slab中的實(shí)際大小。一個(gè)對(duì)象在slab中除了它本身要占用空間外,為了對(duì)齊還要占用一些空間,另外可能還會(huì)有一些調(diào)試信息,所有這些空間合稱(chēng)為一個(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位于尾部。部分被分配的部分和完全空閑的部分合稱(chēng)為空閑列表,cache_freelist指向這個(gè)鏈表的表頭。kmem_nullslab是整個(gè)鏈表的尾部,因此它也是空閑鏈表的尾部。第四個(gè)部分是depot層相關(guān)的字段。 cache_magtype表示彈夾的類(lèi)型。彈夾分成不同的類(lèi)型,不同類(lèi)型的彈夾中可以存放的對(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í)所用的彈夾類(lèi)型 */ 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表示彈夾的類(lèi)型。 mt_magsize表示彈夾中可以裝多少發(fā)子彈,kmem_magazine中mag_round數(shù)組的大小就是由這個(gè)字段來(lái)決定。 mt_align表示彈夾的對(duì)齊邊界。 mt_minbuf和mt_maxbuf限定了這個(gè)彈夾類(lèi)型的適用范圍。一種類(lèi)型的彈夾并不是可以用于所有的對(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)整彈夾的類(lèi)型。一個(gè)CPU在運(yùn)行的時(shí)候可以調(diào)整它所用的彈夾類(lèi)型,如果它發(fā)現(xiàn)當(dāng)前類(lèi)型中的彈夾裝載的子彈數(shù)太少,就會(huì)選擇子彈數(shù)更多的彈夾類(lèi)型。在調(diào)整彈夾的時(shí)候,需要判斷對(duì)象的chunksize是否比當(dāng)前彈夾類(lèi)型的mt_maxbuf小。如果chunksize小于mt_maxbuf,則允許調(diào)整,CPU可以選擇下一級(jí)別的彈夾類(lèi)型,否則的話就不能進(jìn)行彈夾的調(diào)整。 mt_cache表示分配這種類(lèi)型的彈夾使用哪一個(gè)對(duì)象緩存,分配不同類(lèi)型的彈夾用到的對(duì)象緩存可能是不同的,但是分配同一類(lèi)型的彈夾用的都是同一個(gè)對(duì)象緩存。系統(tǒng)中共定義了九種彈夾類(lèi)型,每個(gè)彈夾必定屬于其中一種類(lèi)型。這九種類(lèi)型在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è)定義中,只給出了每種類(lèi)型前四個(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è)合適的彈夾類(lèi)型,選擇的原則就是從彈夾類(lèi)型鏈表中找第一個(gè)適用的彈夾類(lèi)型(mt_minbuf字段小于chunk大小的類(lèi)型),找到后就把這個(gè)類(lèi)型賦給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 銷(xiāo)毀一個(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ì)被銷(xiāo)毀,而是隨著申請(qǐng)對(duì)象和釋放對(duì)象操作的交替進(jìn)行,慢慢分布到depot層的空彈夾鏈表或滿彈夾鏈表中。如果創(chuàng)建新彈夾失敗,就無(wú)法把被釋放的對(duì)象緩存到depot層,這時(shí)就調(diào)用析構(gòu)函數(shù)銷(xiāo)毀對(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ù)銷(xiāo)毀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)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

av黄免费看 | 国产精品视频线看 | 国产一区免费 | 日韩欧美精品一区二区 | 色夜视频 | 婷婷丁香狠狠爱 | 国产人在线成免费视频 | 久久国产网站 | 一级性视频| av中文在线 | 精久久久久 | 成人天堂网 | 国产白浆在线观看 | 亚洲,播放| 久久久午夜精品理论片中文字幕 | 九月婷婷人人澡人人添人人爽 | 国产1区2区 | 国产视频二区三区 | 色综合色综合久久综合频道88 | 精品成人a区在线观看 | 日韩精品中文字幕在线 | 人人澡人人干 | 免费高清无人区完整版 | 天天爽夜夜爽精品视频婷婷 | 国产精品久久久久永久免费 | 日日摸日日爽 | 91九色在线视频观看 | 久久久久五月 | 色姑娘综合天天 | 天天操夜夜操国产精品 | 中国美女一级看片 | 日韩av高清在线观看 | 国产精品亚洲片在线播放 | 午夜精品视频免费在线观看 | 国产色小视频 | 国产在线视频一区 | 国产福利一区二区在线 | 黄色性av | 日本在线h| 欧美aaa一级| 伊人伊成久久人综合网小说 | 国产精品日韩在线观看 | 日韩精品中文字幕在线播放 | 久久久久久久久久久久久国产精品 | 精品在线视频一区二区三区 | 国产精品麻豆99久久久久久 | 国产精品精品国产婷婷这里av | 久久免费视频1 | www天天操| 在线v片免费观看视频 | 综合久久影院 | 国产精品美女久久久久久久久久久 | 国产精品久久久久久久久久久久午 | 亚洲精品久久久久58 | 国产精品久久久久久久久久免费 | 狠狠干夜夜操天天爽 | 国产精品高潮呻吟久久久久 | 日本精品久久久一区二区三区 | 国产精品女视频 | 日韩极品在线 | 97理论电影 | 欧美性生活小视频 | 狠狠狠狠狠狠狠狠干 | www.天天操| 免费在线观看av网站 | japanese黑人亚洲人4k | 久久精品国产久精国产 | 欧美极品久久 | 在线电影91 | 在线成人免费 | 亚洲电影久久 | 中文字幕一二 | 视频在线91| av久久在线| 日日干天夜夜 | 欧美三级在线播放 | 亚洲婷婷伊人 | 色香蕉在线视频 | 一区二区视频在线观看免费 | 国产成人av网址 | 国产精品久久二区 | 婷婷开心久久网 | 国产亚洲综合性久久久影院 | 韩国精品在线 | zzijzzij亚洲成熟少妇 | 97碰碰视频 | 国产精品私人影院 | 激情黄色一级片 | 色综合www| 免费看国产曰批40分钟 | 天天操天天是 | 91免费高清| 91完整版 | 六月丁香久久 | 免费av免费观看 | 国精产品999国精产 久久久久 | 欧美激情视频在线免费观看 | 欧美a视频在线观看 | 久久久69 | 久久久免费视频播放 | 伊人狠狠色丁香婷婷综合 | 日色在线视频 | 国产精品av免费 | 99免费国产 | 色国产精品一区在线观看 | 亚洲精品一区二区精华 | 伊人六月 | 久久99久久99精品免观看软件 | 91国内在线视频 | 亚洲五月 | 91女神的呻吟细腰翘臀美女 | 中文字幕制服丝袜av久久 | 99精品国产一区二区三区不卡 | 狠狠色2019综合网 | 激情五月六月婷婷 | 国产高清成人 | 国产精品成人一区二区三区吃奶 | avove黑丝| 五月婷婷开心 | 18做爰免费视频网站 | 波多野结衣视频一区 | 伊人资源站 | 日韩久久激情 | 96国产在线 | 91中文视频 | 久久国产亚洲视频 | 插婷婷| 国产日韩欧美视频在线观看 | 精品在线观看免费 | 日韩视频中文字幕在线观看 | 正在播放久久 | 亚洲国产免费看 | 成人网在线免费视频 | 四虎影视av | 国产精品亚州 | 免费日韩 精品中文字幕视频在线 | 日韩在线观看a | 亚洲女人天堂成人av在线 | 中文字幕欲求不满 | 国产男女免费完整视频 | 国产高清在线一区 | 欧美少妇18p| a黄在线观看 | 精品在线你懂的 | 97超碰福利久久精品 | 狠狠色丁香九九婷婷综合五月 | 97精品国产97久久久久久春色 | 精品亚洲免a | 成人资源在线播放 | 亚洲国产中文字幕在线 | 中文在线字幕观看电影 | www.久久久精品 | 欧美激情精品久久久久 | 亚洲无吗av | 欧美午夜理伦三级在线观看 | 日日干日日操 | 9999免费视频 | 欧美日韩国产在线精品 | 国产成人精品一区二区三区网站观看 | 美女黄色网在线播放 | 91成人在线网站 | 97视频中文字幕 | 97超碰色偷偷 | 波多野结衣在线观看一区 | 国产白浆视频 | 国产精品久久久久久久久久白浆 | 91网址在线观看 | 超碰九九 | 久久最新 | 欧美日韩一区二区三区在线免费观看 | 高潮毛片无遮挡高清免费 | 在线成人国产 | 国产一级黄色免费看 | 丁香六月天 | 国产一线二线三线在线观看 | 丁香九月激情综合 | 婷婷国产v亚洲v欧美久久 | 久久黄色免费观看 | 丁香九月激情综合 | 欧美日韩国产色综合一二三四 | 久爱综合 | 天天色播 | 欧美日韩在线看 | 91av福利视频 | 1000部18岁以下禁看视频 | 国产在线观看xxx | 狠狠色香婷婷久久亚洲精品 | 少妇视频在线播放 | 国产亚洲欧美日韩高清 | 中文字幕一区在线观看视频 | 91av美女| 91成人黄色 | 激情xxxx | 91精品国产高清自在线观看 | 欧美性视频网站 | 久久免费a | 亚洲干| 日韩国产精品久久 | 久99热| 久久网页 | 精品成人免费 | 欧美精品久久久 | av在线电影网站 | 一区二区国产精品 | 日韩中文字幕在线不卡 | 欧美日韩一区二区在线观看 | 在线观看一区 | 91视频在线国产 | 九九热在线视频 | 1024在线看片 | 国产99色 | 欧美福利在线播放 | 少妇bbw撒尿 | 麻豆你懂的 | 欧美成人精品欧美一级乱黄 | 成人日批视频 | 免费一级片久久 | 91免费看片黄| 亚洲综合日韩在线 | 91香蕉国产在线观看软件 | 国产美女被啪进深处喷白浆视频 | 欧美a视频在线观看 | www.天天操.com | 嫩小bbbb摸bbb摸bbb | 欧美午夜精品久久久久 | 操操综合 | 国产精品综合久久久久久 | 免费av在线 | 综合色站 | 91麻豆免费版 | 国产精品久久久久久久av电影 | 日韩影视精品 | 狠狠色伊人亚洲综合成人 | 91九色在线视频 | 黄色小说免费观看 | 国产精品久久久久久久久久白浆 | 成年人在线观看 | 久久久黄色 | 在线观影网站 | 97精品超碰一区二区三区 | 又黄又爽又刺激 | 午夜在线观看影院 | 一区三区在线欧 | 在线观看亚洲精品 | 最新中文字幕在线资源 | 一区免费观看 | 精品999久久久 | 激情五月婷婷综合网 | 午夜精品久久久久久久99婷婷 | 久久免费大片 | 欧美日韩在线免费观看 | 亚洲区色 | 天天爽天天爽天天爽 | 日韩精品一区二区免费视频 | 国产精品麻豆91 | 一区三区视频 | 91最新在线观看 | 超碰在线最新地址 | a视频在线观看 | 人人插人人草 | 免费观看黄色12片一级视频 | 婷婷av网站| 91在线国内视频 | 丁香色婷| 日韩,精品电影 | 992tv人人草 黄色国产区 | av看片在线观看 | 欧美激情精品一区 | 国产91在线免费视频 | 开心色婷婷| 少妇自拍av | 夜夜躁日日躁 | 97在线观看视频国产 | 九九免费在线视频 | 久久伦理电影 | 永久av免费在线观看 | 色婷婷综合久久久久 | 亚洲美女精品区人人人人 | 综合色站导航 | 欧美日韩中文在线视频 | 天干啦夜天干天干在线线 | 99精品视频在线免费观看 | 久久精品伊人 | 欧美日韩在线精品一区二区 | 国产又粗又猛又黄又爽视频 | 国产视频一区在线 | 麻豆av一区二区三区在线观看 | 视频一区二区在线 | 欧美成人精品欧美一级乱 | 久久视频6| 丝袜美腿亚洲综合 | 国产精品18久久久久vr手机版特色 | 久久视频精品在线观看 | 色久av| a v在线视频 | 日韩精品视频在线观看免费 | 91豆麻精品91久久久久久 | 在线看片91 | 久久99精品久久久久久清纯直播 | 成人小电影在线看 | 亚洲人在线7777777精品 | 国产精品久久久久久久午夜 | 中文在线天堂资源 | 欧美激情精品一区 | 国产在线超碰 | 天天舔夜夜操 | 免费在线观看视频一区 | 天天操网址 | 久久免费国产视频 | www.99热精品 | 中文字幕韩在线第一页 | 亚洲激情久久 | 久久国产精品一区二区三区 | 久久黄色小说视频 | 操操日日 | 91在线porny国产在线看 | 国产又粗又硬又爽视频 | 久久久精品99 | 中文在线| 国产黄色大片 | 国产亚洲综合精品 | 九九免费精品视频在线观看 | 久久网页 | 欧美日韩高清在线观看 | 久久久久久久久久亚洲精品 | 91精品国产高清自在线观看 | 99热在线免费观看 | 久久国产精品一二三区 | 91av播放| 久久久久国产精品www | 欧美性脚交 | 国产精品爽爽久久久久久蜜臀 | 久久日韩精品 | 国产精品乱码高清在线看 | 麻豆播放 | 久久久影院一区二区三区 | 久操中文字幕在线观看 | 亚洲情影院 | 国产精品一区二区av日韩在线 | av免费看在线 | 国产丝袜在线 | 国产成人香蕉 | 亚洲精品av在线 | 国产精品99久久久久久宅男 | 黄网站免费大全入口 | 97成人在线免费视频 | 97精品一区| 在线观看一区视频 | 日p视频| 97精品国产一二三产区 | 欧美精品天堂 | 中文电影网| 激情综合五月天 | 天天做天天干 | 欧美精品中文 | 欧美一级片免费播放 | 日日夜夜天天久久 | 黄污在线看 | 久久国产精品偷 | 久草在线观看资源 | 国产一区在线播放 | 91手机电影| av在线电影网站 | 鲁一鲁影院 | 91香蕉国产在线观看软件 | 日日干天天爽 | 一区二区高清在线 | 久在线观看 | 国产一级一片免费播放放 | av中文天堂在线 | 日韩精品欧美一区 | 亚洲欧洲精品一区二区精品久久久 | 国产xx在线 | 夜夜操狠狠操 | 天堂av影院 | 国产在线观看a | www蜜桃视频 | 黄色aaaaa| 四虎影视成人精品 | 亚洲一区精品人人爽人人躁 | 在线观av | 精品国产伦一区二区三区 | 一区二区电影网 | 免费看国产视频 | 最近更新好看的中文字幕 | 亚洲精品三级 | 成人av资源网 | 久草在线观看资源 | 中文字幕日本电影 | 久久天堂影院 | 久草在线资源观看 | 成人小电影在线看 | 激情深爱 | 久久久久久久18 | 高潮久久久 | 久久福利电影 | 亚洲精品免费观看 | 欧美精品中文字幕亚洲专区 | 日产乱码一二三区别在线 | 日韩一区二区三区免费视频 | 久操视频在线播放 | 国产精品一区一区三区 | 人人澡人人添人人爽一区二区 | av福利在线看 | 97超碰国产在线 | 日韩网站中文字幕 | 99色视频 | 亚洲天天在线日亚洲洲精 | 婷婷色九月 | 亚洲成av人片一区二区梦乃 | 国产精品第一 | 69国产成人综合久久精品欧美 | av大片网站 | 91视视频在线直接观看在线看网页在线看 | 国产精品久久久久久久午夜 | 18av在线视频 | 99久国产 | 91插插插免费视频 | 国产又黄又猛又粗 | 五月婷婷在线视频 | 免费在线观看黄色网 | 中文字幕一区二区三区乱码在线 | 97在线免费观看 | www视频免费在线观看 | 午夜精品中文字幕 | 午夜精品视频一区二区三区在线看 | 91精品国产99久久久久久红楼 | 精品久久一区二区三区 | 国产精品久久久久影院日本 | 美女视频一区 | 亚洲精品综合一二三区在线观看 | 成人小电影在线看 | 亚洲精欧美一区二区精品 | 日韩一级成人av | 黄色片网站av | 欧美日韩国产一区二区在线观看 | 在线播放 日韩专区 | 91av99| www.夜色.com | 国产精品久久久久久久久久久久午夜片 | a色视频| 日韩专区av | 91九色成人蝌蚪首页 | 在线观看不卡的av | 91九色视频网站 | 91一区二区三区久久久久国产乱 | 国产系列精品av | 久久久久久久综合色一本 | 亚洲精品国产第一综合99久久 | 五月综合在线观看 | 一区二区精品在线 | 婷婷国产一区二区三区 | 日本三级吹潮在线 | 激情综合亚洲 | 日本性xxx | 91视频啪| 日韩理论电影在线观看 | 久久午夜网| 在线精品国产 | 国产精品视频免费观看 | 99视频免费观看 | 中文字幕888 | 国产老太婆免费交性大片 | 国产自产在线视频 | 国产综合小视频 | 国产精品18久久久久久vr | 久久毛片视频 | 国产成人一区二区三区 | 91在线免费播放 | 精品91久久久久 | 久草资源在线观看 | 夜夜高潮夜夜爽国产伦精品 | 六月丁香在线观看 | 黄色片免费在线 | 国产精品ssss在线亚洲 | 日韩色综合网 | 国产精品一区二区久久精品爱微奶 | 久久精品久久精品久久精品 | 国产午夜精品一区二区三区四区 | 一区二区三区在线观看中文字幕 | 免费黄色av| 我爱av激情网 | av大片免费看 | 夜夜操天天干 | 久久久久免费精品 | 四虎www.| 久久久精品影视 | 99草视频 | 久久午夜精品视频 | 日韩精品一区电影 | 亚洲激精日韩激精欧美精品 | 激情久久久久久久久久久久久久久久 | 日本爱爱免费视频 | 日韩资源在线 | 天天综合91 | 91精品91| 亚洲成人av影片 | 国产免费观看久久 | 久热精品国产 | 亚洲精品国产区 | 日韩中文字幕在线看 | 99热精品在线观看 | 国产探花视频在线播放 | 国产精品丝袜久久久久久久不卡 | 国产午夜精品一区二区三区 | 国产一区二区三区在线 | 婷婷视频 | 岛国av在线 | 欧美在线你懂的 | 91热精品| 色婷婷狠狠18 | 久久视频在线免费观看 | 精品一区二区视频 | 久久99久久精品国产 | 九九视频精品免费 | 色噜噜色噜噜 | 一级理论片在线观看 | 天天爱天天操 | 国产中文在线播放 | 亚洲永久精品国产 | 在线婷婷 | 99色在线观看视频 | 91精品国自产在线偷拍蜜桃 | 久久久久二区 | 国产不卡av在线播放 | 97精品国产97久久久久久久久久久久 | 亚洲一级片免费观看 | 日韩一级片观看 | 国产专区欧美专区 | 69精品人人人人 | 人人玩人人添人人 | 国产剧情一区在线 | 国产成人亚洲精品自产在线 | 国产精品系列在线播放 | 久久精品欧美一 | 九色精品免费永久在线 | 一区二区三区国产精品 | 91色吧| 成年人在线观看视频免费 | 天天人人 | a级片久久久| av中文字幕网 | 青青河边草手机免费 | 91在线公开视频 | 久久视了 | 久草国产在线观看 | 久久精品久久久久电影 | 久久亚洲热 | 色美女在线 | 亚洲精品国精品久久99热一 | 国产美女精品视频免费观看 | 免费av影视 | 精品不卡av | 精品欧美一区二区三区久久久 | 一区二区三区四区在线免费观看 | 免费在线观看视频一区 | 国产日本在线观看 | 丁香av| 99在线观看视频网站 | 久久精品国产亚洲 | 久久人人添人人爽添人人88v | 日韩在线三区 | 超碰国产人人 | 久草亚洲视频 | 97国产一区二区 | 国产成人精品综合久久久久99 | 午夜精品视频在线 | 成人免费看片网址 | 狠狠狠狠狠色综合 | www.99久久.com | 久久爱www. | 日韩午夜精品福利 | 免费观看www小视频的软件 | 香蕉视频国产在线观看 | 91污污| 午夜视频在线观看一区二区 | 久久香蕉国产精品麻豆粉嫩av | 美女一级毛片视频 | 在线免费观看一区二区三区 | av大片免费看| 精品久久电影 | 激情五月六月婷婷 | 国产精品密入口果冻 | 亚洲国产中文字幕 | 亚洲aⅴ乱码精品成人区 | 最新中文字幕在线资源 | 91av电影| 久久精品国产亚洲a | 国产最顶级的黄色片在线免费观看 | av888av.com| 亚洲国产资源 | 久久中文网 | 亚洲精品视频在线免费播放 | 久久成人高清视频 | 天天色天天操天天爽 | 一级特黄av | 天天综合网~永久入口 | 成人久久视频 | av中文字幕网站 | 丁香婷五月 | 激情图片久久 | 黄色网大全 | 国产一区二区三区久久久 | 91成人免费视频 | 91视频免费| 久香蕉| 亚洲欧美偷拍另类 | 精品国产欧美 | 中文在线字幕观看电影 | 最近免费在线观看 | 中文字幕在线观看三区 | 中文字幕文字幕一区二区 | 亚洲理论视频 | 日韩精品国产一区 | 欧美 激情 国产 91 在线 | 久久成人亚洲欧美电影 | 日韩无在线| 免费人成网 | 日韩视频二区 | 中文字幕在线免费看线人 | 久久久久久草 | 夜夜嗨av色一区二区不卡 | 色99网| 黄色片亚洲 | 91麻豆精品国产91久久久无限制版 | 国内精品久久久久久久久久久 | 日日操日日插 | 欧美视频在线观看免费网址 | 亚洲精品99久久久久中文字幕 | 波多野结衣一区三区 | av大全在线免费观看 | 九九热精品在线 | 欧美精品在线视频 | 91丨九色丨蝌蚪丨对白 | 在线a亚洲视频播放在线观看 | www.888av| 五月开心六月伊人色婷婷 | 激情综合网五月婷婷 | 在线视频一区二区 | 日韩精品久久久久 | 亚洲综合网 | 亚洲精品日韩一区二区电影 | 精品国产乱码久久久久久1区2匹 | 精品视频 | 一区二区三区韩国免费中文网站 | 97在线视频免费播放 | 国产特级毛片aaaaaa高清 | 久久久国产精品一区二区中文 | 日本系列中文字幕 | 亚洲精品国产自产拍在线观看 | 日韩电影在线一区 | 九九视频免费观看视频精品 | 黄色av免费电影 | 色吊丝在线永久观看最新版本 | 日韩激情第一页 | 久久中文字幕视频 | 手机看片午夜 | 有码一区二区三区 | 日本激情动作片免费看 | 久草在线免费资源 | 午夜在线免费观看 | 一区二区视频欧美 | 一区二区三区四区精品 | 91精品爽啪蜜夜国产在线播放 | 久久精品国产第一区二区三区 | a级黄色片视频 | 久久夜色精品国产亚洲aⅴ 91chinesexxx | 黄色毛片视频 | 亚洲日本一区二区在线 | 色婷婷色 | 麻豆国产露脸在线观看 | 99c视频高清免费观看 | 久久精品三级 | 精品久久久久久亚洲综合网站 | av在线观 | 五月婷影院 | 日日夜夜天天干 | 欧美极度另类 | 黄p网站在线观看 | 国产真实精品久久二三区 | 欧美一级免费 | 99精品国产成人一区二区 | 日韩在线免费看 | 99久久久久国产精品免费 | 国产欧美在线一区二区三区 | 国产一区二区三区在线 | 日韩亚洲国产精品 | 九七视频在线 | 69亚洲视频 | www好男人| 天天干天天做天天爱 | 久久中文字幕在线视频 | 中文字幕亚洲字幕 | 免费看污片 | 国产手机在线观看视频 | 精品女同一区二区三区在线观看 | 亚洲黄色小说网 | bbbb操bbbb| 久久激情久久 | 在线免费观看不卡av | 天天操 夜夜操 | 玖玖在线免费视频 | 91亚洲精品国偷拍 | 亚洲午夜久久久久久久久 | 911免费视频 | 亚洲色综合 | 精品视频免费在线 | 911精品美国片911久久久 | 国产成人精品网站 | 不卡电影免费在线播放一区 | 在线v片免费观看视频 | 国产一区免费 | 久久久这里有精品 | 亚洲视频第一页 | 国产精品一区欧美 | 欧美日韩免费视频 | 久久无码精品一区二区三区 | 国产主播大尺度精品福利免费 | 91av在线免费观看 | 国产视频在线观看一区 | 中文字幕免费在线看 | 五月天亚洲婷婷 | 黄色软件在线观看 | 久久精品久久99 | 超碰在线色 | 国产精品综合久久久久 | 国产在线观看不卡 | 少妇18xxxx性xxxx片 | 国产精品美| 欧美日本不卡视频 | 色婷婷综合五月 | 中文字幕在线观看免费观看 | av再线观看 | 五月激情婷婷丁香 | 特级毛片爽www免费版 | 成人中心免费视频 | 99久久精品久久久久久清纯 | 欧美日韩视频在线播放 | 成人资源在线观看 | 欧美精品免费一区二区 | 国产亚洲人成网站在线观看 | 四虎最新域名 | 五月婷亚洲 | 精品女同一区二区三区在线观看 | 免费在线成人av电影 | 在线观看成人 | 欧美一级视频在线观看 | 岛国大片免费视频 | 超碰97在线资源 | 免费在线观看一区 | 久久视频6 | 91高清视频 | 精品99免费视频 | 97电影网手机版 | 国产资源免费在线观看 | 国内精品久久久久国产 | 字幕网资源站中文字幕 | 国产精品久久久久久久久久久久 | 天天操天天操天天 | 国产黄av | 狠狠狠色丁香婷婷综合久久88 | 一区免费在线 | 美女久久网站 | 最新日韩视频 | 亚洲精选视频免费看 | 免费观看视频的网站 | 黄色特级一级片 | 五月天六月婷 | 日韩肉感妇bbwbbwbbw | 天天操狠狠操网站 | 国产日韩欧美综合在线 | 日韩电影中文字幕在线观看 | 欧美激情视频免费看 | 久久精品欧美一 | 久久精品国产v日韩v亚洲 | www天天干com | 深夜免费福利在线 | 五月婷婷六月丁香 | 中文字幕欲求不满 | 青春草免费在线视频 | 在线国产不卡 | 欧美日韩一区二区三区视频 | 国产又粗又猛又色又黄网站 | 久久久久久久久艹 | www.成人sex| 天天爽天天做 | 亚州国产精品视频 | 亚洲一区二区天堂 | 97超碰资源网 | 久久精品影片 | 久久你懂的 | 91资源在线播放 | 福利视频一二区 | 国产精品99久久久久久有的能看 | 成人av电影在线 | 日本精品久久久一区二区三区 | 黄色大片国产 | 日韩在线视频国产 | 婷婷综合av| 国产香蕉97碰碰久久人人 | 亚洲黄色一级电影 | 免费av观看| www视频免费在线观看 | 天天色天天爱天天射综合 | 天天艹| 国产精品理论在线观看 | 丁香六月激情婷婷 | 日韩有码第一页 | 欧美在线91| 麻豆视频免费 | 97国产 | 亚洲免费在线观看视频 | 日日摸日日添日日躁av | 99热超碰| 中文字幕在线观看一区二区三区 | 日日爱网址 | 日b视频在线观看网址 | 国产91免费在线观看 | 男女日麻批 | 亚洲h视频在线 | 精品国产人成亚洲区 | 亚洲精品国产自产拍在线观看 | 黄色成人在线观看 | 日本中文字幕在线看 | 亚洲国产偷 | 奇米777777| 69国产成人综合久久精品欧美 | 成 人 a v天堂 | 亚洲精品播放 | 亚洲成人第一区 | 日韩高清不卡在线 | 久久久99精品免费观看app | 手机看片 | 久久久免费少妇 | 国产精品网红直播 | 99久久精品国产一区二区成人 | 国产午夜麻豆影院在线观看 | 成人毛片100免费观看 | 欧美极品在线播放 | 西西44人体做爰大胆视频 | 99久久日韩精品免费热麻豆美女 | 免费av小说 | 麻豆成人小视频 | 96av麻豆蜜桃一区二区 | 黄色毛片大全 | 久久久亚洲麻豆日韩精品一区三区 | 日韩精品一区二区三区免费观看视频 | 中文字幕第一 | 亚洲综合色婷婷 | 亚洲尺码电影av久久 | 成人网在线免费视频 | 亚洲精品资源在线观看 | 久久久久99精品国产片 | 色婷婷国产精品一区在线观看 | 日本色小说视频 | 欧美网站黄色 | 久久成人国产精品一区二区 | 久久久精品国产一区二区电影四季 | 婷婷色在线播放 | 九九视频在线 | 天天在线免费视频 | 国产午夜三级 | 欧美999| 国内精品久久久 | 久久久久久久久影视 | 亚洲欧美va| 久久中文欧美 | 日日夜夜天天操 | 亚洲国产精品视频在线观看 | 色综合天天狠天天透天天伊人 | 色婷婷六月天 | av在线影片 | 六月丁香色婷婷 | 日韩在线观看的 | 国内丰满少妇猛烈精品播 | 日韩精品免费一区 | 亚洲精品www久久久 www国产精品com | av三级在线看 | 国产高清无线码2021 | 国产成人精品一区二区三区福利 | 蜜臀av麻豆 | 69成人在线 | 91麻豆国产福利在线观看 | 亚洲国内精品在线 | 一区二区三区免费在线观看视频 | www.五月天婷婷.com | 久久国产美女 | 99久久99视频| 久久伊人综合 | 国产男女爽爽爽免费视频 | 色综合天天 | 日韩丝袜| 国产高清成人 | 成人精品福利 | 一区二区三区精品在线视频 | 日韩在线视频免费播放 | 黄色福利网站 | 色多视频在线观看 | 香蕉免费在线 | 欧美日韩免费网站 | 欧洲高潮三级做爰 | 日韩网站在线观看 | 黄色免费大片 | 日韩av成人| 丁香激情综合 | 手机av在线网站 | 久久精品视频国产 | 韩国在线视频一区 | 欧美在线视频精品 | 久久精品香蕉 | 操久久网| 久久精品亚洲综合专区 | 日韩在线观看一区二区 | 91香蕉视频污在线 | 国产精品网红福利 | 成人va天堂| 一区二区不卡 | 97在线观看免费观看高清 | 91av在线免费看 | 黄色av一区二区 | 亚洲爱视频| 免费午夜视频在线观看 | 国产群p | 久久亚洲区| 国产老太婆免费交性大片 | 99精品成人| 国产手机视频在线观看 | 久久99电影| 69久久夜色精品国产69 | 国产成人一区二区三区电影 | 91久久久久久久 | 超碰97免费在线 | 97天堂| 亚洲不卡av一区二区三区 | 天天干天天看 | 欧美成人黄色片 | 免费观看一区二区三区视频 | 99色在线视频 | 99热免费在线 | 91av九色| 色综合天天狠狠 | 国产精品系列在线 | 日韩av影视 | 精品国产一区二区三区久久久久久 | 成人在线观看日韩 | 久久精品1区 | 免费毛片一区二区三区久久久 | 91激情视频在线播放 | 丁香六月久久综合狠狠色 | 正在播放久久 | 国产精品12 | 国产精品18久久久久久首页狼 | 久久免费看av | 欧美日韩国产二区 | 啪啪免费视频网站 | 91九色视频在线观看 | 国产日韩欧美在线免费观看 | 99热手机在线| 久久综合中文字幕 | 国产专区视频在线 | 日韩成人邪恶影片 | 激情网第四色 | 久久只精品99品免费久23小说 | 精品九九久久 | 五月婷综合 | 91视频麻豆视频 | 美女视频久久黄 | 深夜国产福利 | 国产又粗又硬又爽视频 | 成人免费视频网站在线观看 | 一区二区视频免费在线观看 | 五月开心婷婷网 | 啪啪免费观看网站 | 久久婷婷影视 | 国产色婷婷精品综合在线手机播放 | 又黄又刺激视频 | 五月天中文在线 | 亚洲精品国产精品国自产观看 | 亚洲精品国精品久久99热一 | 深爱激情五月网 | 亚洲春色综合另类校园电影 | 日日摸日日爽 | 精品视频123区在线观看 | 日韩一区二区免费在线观看 | 九九激情视频 | 日韩欧美电影在线 | 00av视频| 午夜视频在线观看一区 | 国产区av在线 | 国产一区高清在线 | 激情网五月婷婷 | 天天草综合网 | 亚洲精品在线观看免费 | 99免费在线视频观看 | 久久免费精彩视频 | 国产精品扒开做爽爽的视频 | 狠狠色丁香婷婷综合久小说久 | 婷婷丁香av |