Linux常用內(nèi)核態(tài)內(nèi)存分配方式總結(jié) 一、 alloc_pages類 此類函數(shù)主要包括: struct page * alloc_page(unsigned int gfp_mask)——分配一頁物理內(nèi)存并返回該頁物理內(nèi)存的page結(jié)構(gòu)指針。 struct page * alloc_pages(unsigned int gfp_mask, unsigned int order)——分配 個(gè)連續(xù)的物理頁并返回分配的第一個(gè)物理頁的page結(jié)構(gòu)指針。 unsigned long get_free_page(unsigned int gfp_mask)——分配一頁物理內(nèi)存并將該物理頁全部清零,最后返回一個(gè)虛擬(線形)地址。 unsigned long __get_free_page(unsigned int gfp_mask)——Allocates a single page and returns a virtual address。 Unsigned long __get_free_pages(unsigned int gfp_mask, unsigned int order)——Allocates 2order number of pages and returns a virtual address。 struct page * __get_dma_pages(unsigned int gfp_mask, unsigned int order)——Allocates 2order number of pages from the DMA zone and returns a struct page。 此類函數(shù)主要通過伙伴分配系統(tǒng)進(jìn)行分配,它們是linux內(nèi)核最基本的內(nèi)存分配函數(shù),一次請(qǐng)求能分配的最大物理頁數(shù)由變量MAX_ORDER決定。 相應(yīng)的內(nèi)存釋放函數(shù)如下: void __free_pages(struct page *page, unsigned int order)
二、 kmalloc 這個(gè)函數(shù)建立在slab分配器之上,主要用于分配范圍在 字節(jié)— 字節(jié)大小以內(nèi)的小內(nèi)存區(qū)域。并且此函數(shù)分配的內(nèi)存在線形地址和物理地址上都是連續(xù)的,它不能分配到所謂的高端內(nèi)存區(qū)域內(nèi)的內(nèi)存,高端內(nèi)存區(qū)域內(nèi)的內(nèi)存必須由專門的方式來獲得。 由于使用伙伴分配系統(tǒng)分配小塊內(nèi)存會(huì)帶來太多的碎片,因此linux保留了2列cache專門用于小塊內(nèi)存分配。這兩列cache一列專門用于DMA分配,起名為size-N(DMA) cache,另一列實(shí)用于通用的內(nèi)存分配,起名為size-N cache,其中N為對(duì)應(yīng)cache的大小。對(duì)于每個(gè)大小為cs_size的cache linux是采用以下結(jié)構(gòu)體來描述的: typedef struct cache_sizes { size_t cs_size; kmem_cache_t *cs_cachep; kmem_cache_t *cs_dmacachep; }cache_sizes_t;由以上結(jié)構(gòu)體可知:linux是以大小來同時(shí)描述size-N(DMA) cache 和size-N cache的。 由于這些cache的個(gè)數(shù)是有限的,linux在編譯階段就初始化了一個(gè)稱為cache_size的靜態(tài)數(shù)組來描述這些cache,該數(shù)組定義大致如下: static cache_sizes_t cache_sizes[] = { #if PAGE_SIZE == 4096 { 32, NULL, NULL}, #endif { 64, NULL, NULL}, { 128, NULL, NULL}, { 256, NULL, NULL}, { 512, NULL, NULL}, { 1024, NULL, NULL}, { 2048, NULL, NULL}, { 4096, NULL, NULL}, { 8192, NULL, NULL}, { 16384, NULL, NULL}, { 32768, NULL, NULL}, { 65536, NULL, NULL}, {131072, NULL, NULL}, { 0, NULL, NULL} }由該數(shù)組可知,linux在編譯時(shí)已建立好對(duì)各個(gè)cache的描述,接下來的工作就是在系統(tǒng)啟動(dòng)時(shí)分配好cache并將對(duì)應(yīng)的各個(gè)cache指針寫入該數(shù)組中即可。 有了以上數(shù)組,kmalloc的工作就變得簡(jiǎn)單了:根據(jù)用戶提供的內(nèi)存分配大小查找該數(shù)組,若找到合適大小cache,則調(diào)用slab分配器接口分配所需內(nèi)存,若找不到合適大小,則返回NULL。 對(duì)應(yīng)與kmalloc的內(nèi)存釋放函數(shù)為kfree:它釋放內(nèi)存時(shí)首先確保所釋放的內(nèi)存指針不為NULL,然后在確保指針指向的內(nèi)存區(qū)域在slab內(nèi),最后才是調(diào)用slab分配器的接口回收該指針指向的內(nèi)存區(qū)域。 三、 vmalloc 與kmalloc不同的時(shí),vmalloc分配的內(nèi)存只是在線形地址上是連續(xù)的,它不保證分配的內(nèi)存在物理上也連續(xù)。vmalloc的主要目的是用于非連續(xù)物理內(nèi)存分配。 采用vmalloc的主要原因在于:由于伙伴分配系統(tǒng)會(huì)產(chǎn)生外碎片,我們很難找到大塊的在物理上連續(xù)的物理內(nèi)存,因此linux采用vmalloc來解決這個(gè)問題。 原理:linux在內(nèi)核虛擬地址空間中保留了從VMALLOC_START到VMALLOC_END之間的區(qū)域用于vmalloc分配內(nèi)存,在vmalloc分配內(nèi)存時(shí),最終還是要調(diào)用伙伴分配器的接口來一頁一頁地(因此不要求頁與頁之間連續(xù))分配物理內(nèi)存,當(dāng)分配到物理內(nèi)存后再通過修改頁表來使連續(xù)的虛擬地址空間對(duì)應(yīng)到不連續(xù)的物理地址空間。由此我們知道vmalloc分配的物理內(nèi)存都是物理頁的整數(shù)倍的,另外由于為vmalloc保留的線形地址空間是有限的,因此我們能夠通過vmalloc獲得物理內(nèi)存也是有限的。 通過vmalloc分配出去的線形空間是通過vm_struct結(jié)構(gòu)體來描述的,它的定義如下: struct vm_struct { unsigned long flags; void * addr; unsigned long size; struct vm_struct * next; }; 已分配出去的線形地址空間通過結(jié)構(gòu)體的next域連接起來,它們按地址排序,當(dāng)要分配一塊新的地址空間時(shí)可通過查找這個(gè)連表并結(jié)合保留給vmalloc的線形空間來獲得新的可用區(qū)域信息。這個(gè)結(jié)構(gòu)體本身所需的空間是通過kmalloc獲得的。 與vmalloc對(duì)應(yīng)的內(nèi)存釋放函數(shù)名為vfree。
接下來是2個(gè)只用于高端內(nèi)存分配的函數(shù) 四、 kmap 在內(nèi)核線形地址空間頂部linux保留了一段區(qū)域用于kmap函數(shù)映射高端內(nèi)存,這個(gè)地址空間范圍為從PKMAP_BASE到FIXADDR_START。 以下摘自《深入linux虛擬內(nèi)存管理》: Space is reserved at the top of the kernel page tables from PKMAP_BASE to FIXADDR_START for a PKMap. The size of the space reserved varies slightly. On the x86, PKMAP_BASE is at 0xFE000000, and the address of FIXADDR_START is a compile time constant that varies with configure options, but that is typically only a few pages located near the end of the linear address space. This means that there is slightly below 32MiB of page table space for mapping pages from high memory into usable space. For mapping pages, a single page set of PTEs is stored at the beginning of the PKMap area to allow 1,024 high pages to be mapped into low memory for short periods with the function kmap() and to be unmapped with kunmap(). The pool seems very small, but the page is only mapped by kmap() for a very short time. Comments in the code indicate that there was a plan to allocate contiguous page table entries to expand this area, but it has remained just that, comments in the code, so a large portion of the PKMap is unused. The page table entry for use with kmap() is called pkmap_page_table, which is located at PKMAP_BASE and which is set up during system initialization. On the x86, this takes place at the end of the pagetable_init() function. The pages for the PGD and PMD entries are allocated by the boot memory allocator to ensure they exist. The current state of the page table entries is managed by a simple array called pkmap_count, which has LAST_PKMAP entries in it. On an x86 system without PAE, this is 1,024, and, with PAE, it is 512. More accurately, albeit not expressed in code, the LAST_PKMAP variable is equivalent to PTRS_PER_PTE. Each element is not exactly a reference count, but it is very close. If the entry is 0, the page is free and has not been used since the last TLB flush. If it is 1, the slot is unused, but a page is still mapped there waiting for a TLB flush. Flushes are delayed until every slot has been used at least once because a global flush is required for all CPUs when the global page tables are modified and is extremely expensive. Any higher value is a reference count of n-1 users of the page. 五、 kmap_atomic The use of kmap_atomic() is discouraged, but slots are reserved for each CPU for when they are necessary, such as when bounce buffers are used by devices from interrupt. There are a varying number of different requirements an architecture has for atomic high memory mapping, which are enumerated by km_type. The total number of uses is KM_TYPE_NR. On the x86, there are a total of six different uses for atomic kmaps. KM_TYPE_NR entries per processor are reserved at boot time for atomic mapping at the location FIX_KMAP_BEGIN and ending at FIX_KMAP_END. Obviously, a user of an atomic kmap may not sleep or exit before calling kunmap_atomic() because the next process on the processor may try to use the same entry and fail. The function kmap_atomic() has the very simple task of mapping the requested page to the slot set aside in the page tables for the requested type of operation and processor. The function kunmap_atomic() is interesting because it will only clear the PTE with pte_clear() if debugging is enabled. It is considered unnecessary to bother unmapping atomic pages because the next call to kmap_atomic() will simply replace it and make TLB flushes unnecessary.
linux內(nèi)核地址空間與用戶地址空間的差別 本文僅限在i386平臺(tái)下討論一般情況。 1、用戶線性地址空間范圍0-3G,內(nèi)核線性空間范圍3G-4G。
1. 地址空間的管理 物理地址都是有內(nèi)核管理的, node-->zone-->mem_map-->page, 所有的物理頁面都在mem_map數(shù)組中的頁幀對(duì)應(yīng), 然后不同的page有分為DMA,normal,highmem三個(gè)zone。 內(nèi)核線性地址空間, 實(shí)際上只是低端內(nèi)存才有線性地址,0---896MB部分。 內(nèi)核虛擬地址, 低端內(nèi)存的虛擬地址與線性地址是一樣的。 高端內(nèi)存只有在映射了以后才有虛擬地址 用戶空間地址, tast_struct ---> mmap --> mm_struct ---> vm_area_struct 2.內(nèi)存的申請(qǐng)或使用 物理內(nèi)存的分配, 在內(nèi)核中最終都要調(diào)用__alloc_pages().它是最核心的分配函數(shù),申請(qǐng)大小最大不超過2的MAX_ORDER次冪,在現(xiàn)在好像最大定義為4MB。 線性地址, kmalloc和get_free_pages,線性地址, 對(duì)應(yīng)的物理內(nèi)存就是低端內(nèi)存,kmalloc是基于slab的分配技術(shù), 最大不能超過128KB。 虛擬地址, vmalloc申請(qǐng), 他只是在內(nèi)核中建立類似與用戶空間的vm_area的一個(gè)虛擬內(nèi)存空間到vmlist中, 最終的物理內(nèi)存分配還是基于缺頁的。 用戶空間的虛擬內(nèi)存, malloc之類的, 最終在內(nèi)核中都是do_map()和do_brk()。實(shí)際上也只是建立了一塊虛擬空間,最終的物理內(nèi)存還是在缺頁異常時(shí)分配的。 3. 內(nèi)存的交換問題 在page結(jié)構(gòu)和用戶層的vm_area_struct結(jié)構(gòu)中, 都包含locked和reserved標(biāo)志。通過合適的途徑設(shè)置這些標(biāo)志, 可以是頁面鎖存在物理內(nèi)存中, 不被交換出去。 4. 設(shè)備內(nèi)存可以通過ioremap映射到內(nèi)核虛擬地址空間, 也可以通過mmap方法映射到用戶空間。 Linux 內(nèi)存管理:缺頁異常的幾種原因給定一個(gè)線性地址,MMU 通過頁目錄表、頁表的轉(zhuǎn)換,找到對(duì)應(yīng)的物理地址。在這個(gè)過程中,如果因某種原因?qū)е聼o法訪問到最終的物理內(nèi)存單元,CPU 會(huì)產(chǎn)生一次缺頁異常,從而進(jìn)入缺頁異常處理程序。 總結(jié)一下,缺頁異常的原因有以下幾種: 1、導(dǎo)致缺頁異常的線性地址根本不在進(jìn)程的“虛存區(qū)間”中,段錯(cuò)誤。(棧擴(kuò)展是一種例外情況) 2、地址在“虛存區(qū)間”中,但“虛存區(qū)間”的訪問權(quán)限不夠;例如“區(qū)間”是只讀的,而你想寫,段錯(cuò)誤 3、權(quán)限也夠了,但是映射關(guān)系沒建立;先建立映射關(guān)系再說 4、映射關(guān)系也建立了,但是頁面不在內(nèi)存中??隙ㄊ菗Q出到交換分區(qū)中了,換進(jìn)來再說 5、頁面也在內(nèi)存中。但頁面的訪問權(quán)限不夠。例如頁面是只讀的,而你想寫。這通常就是 “寫時(shí)拷貝COW” 的情況。 6、缺頁異常發(fā)生在“內(nèi)核動(dòng)態(tài)映射空間”。這是由于進(jìn)程進(jìn)入內(nèi)核后,訪問一個(gè)通過 vmalloc() 獲得線性地址而引起的異常。對(duì)這種情況,需要將內(nèi)核頁目錄表、頁表中對(duì)應(yīng)的映射關(guān)系拷貝到進(jìn)程的頁目錄表和頁表中。 Linux虛擬內(nèi)存組織結(jié)構(gòu)淺析 眾所周知,linux內(nèi)核支持絕大多數(shù)體系結(jié)構(gòu),因此linux內(nèi)核必須采取一種與具體體系結(jié)構(gòu)無關(guān)的方法來描述物理內(nèi)存的組織結(jié)構(gòu),這個(gè)問題就是本系列文章要討論的話題。
要理解linux虛擬內(nèi)存在邏輯上的組織結(jié)構(gòu),我們首先要明白兩個(gè)概念:UMA(Uniform Memory Access)、NUMA(Non Uniform Memory Access)。UMA指一致性內(nèi)存訪問,這是單CPU機(jī)器常用的體系結(jié)構(gòu),在這種結(jié)構(gòu)下,CPU訪問系統(tǒng)內(nèi)存的任何存儲(chǔ)位置的代價(jià)都是一樣的;而NUMA是指非一致性內(nèi)存訪問,常用于多CPU機(jī)器,在這種體系結(jié)構(gòu)中,不同內(nèi)存相對(duì)于不同的CPU而言所處的位置不一樣,最典型的就是每個(gè)CPU都有自己的本地內(nèi)存(Local Memory),不同CPU之間通過總線連接起來,如圖1所示。在這種結(jié)構(gòu)中,CPU訪問本地內(nèi)存的代價(jià)比訪問遠(yuǎn)端的內(nèi)存代價(jià)要小。
為了支持NUMA,Linux將物理內(nèi)存劃分成不同的節(jié)點(diǎn)(node),節(jié)點(diǎn)用結(jié)構(gòu)體pg_data_t表示,以上圖為例,圖中每個(gè)CPU的本地物理內(nèi)存都稱為一個(gè)節(jié)點(diǎn);即使在UMA結(jié)構(gòu)中也有節(jié)點(diǎn)的概念,此時(shí)系統(tǒng)中就只有一個(gè)節(jié)點(diǎn);系統(tǒng)中的多個(gè)節(jié)點(diǎn)被連接起來保存在一個(gè)稱為pgdat_list的鏈表上。每個(gè)節(jié)點(diǎn)又被劃分成不同的區(qū)(zone),節(jié)點(diǎn)是通過其結(jié)構(gòu)體內(nèi)的數(shù)組node_zones來跟蹤節(jié)點(diǎn)內(nèi)的區(qū)的。區(qū)是指一個(gè)節(jié)點(diǎn)內(nèi)一段連續(xù)的物理內(nèi)存范圍。Linux中主要有3個(gè)區(qū):ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM,它們的劃分如下:ZONE_DMA位于物理內(nèi)存開始的一段區(qū)域內(nèi),主要用來供一些ISA設(shè)備使用;ZONE_NORMAL位于ZONE_DMA后面,這個(gè)區(qū)域被內(nèi)核直接映射到線性地址的高端部分;ZONE_HIGHMEM指系統(tǒng)中剩下的物理內(nèi)存,這個(gè)區(qū)域不能直接被內(nèi)核映射。為了清晰起見,我們可以看看在x86平臺(tái)上的區(qū)是如何劃分的: ZONE_HIGHMEM —— 物理內(nèi)存起始16M ZONE_NORMAL —— 16M—896M ZONE_HIGHMEM —— 896M—物理內(nèi)存結(jié)束
最后,在每個(gè)區(qū)中都有一個(gè)指向mem_map數(shù)組中某個(gè)單元的指針zone_mem_map,這是干什么的呢?通過查看源代碼我們可以知道,mem_map是類型為struct page的數(shù)組,而Linux內(nèi)核正是利用struct page結(jié)構(gòu)體來描述每個(gè)物理內(nèi)存頁的,在系統(tǒng)啟動(dòng)時(shí),內(nèi)核就會(huì)為整個(gè)系統(tǒng)的內(nèi)存建立好一個(gè)全局的頁描述數(shù)組mem_map,在以后的運(yùn)行過程中,Linux內(nèi)核最終就是根據(jù)這個(gè)全局內(nèi)存描述數(shù)組來控制對(duì)物理內(nèi)存的分配、回收等操作的。明白了mem_map的作用,我們?cè)賮砜纯疵總€(gè)區(qū)中的zone_mem_map指針,這個(gè)指針指向的是mem_map中的某一個(gè)單元,而這個(gè)單元的內(nèi)容恰恰描述了這個(gè)區(qū)內(nèi)的第一頁物理內(nèi)存,這樣Linux就把節(jié)點(diǎn)、區(qū)、page結(jié)構(gòu)聯(lián)系起來了,它們共同組織起來完成了對(duì)系統(tǒng)所有物理內(nèi)存的描述。因此,我們現(xiàn)在就可以清晰地得出Linux在邏輯上是如何描述所有的物理內(nèi)存了,
在前一篇文章中我們介紹了Linux虛擬內(nèi)存在邏輯上的組織結(jié)構(gòu),現(xiàn)在就讓我們從源代碼入手,從程序級(jí)仔細(xì)看看各個(gè)數(shù)據(jù)結(jié)構(gòu)體的內(nèi)部組成如何,源代碼來自于最新的kernel2.6.26.5,分析過程中主要參考了《Understanding the linux virtual memory》這本書,有興趣的朋友可以去閱讀一下。 一、節(jié)點(diǎn)的數(shù)據(jù)表示 在內(nèi)核中,節(jié)點(diǎn)由結(jié)構(gòu)體pg_data_t來表示,它是由結(jié)構(gòu)體pglist_data通過typedef來定義的,位于文件<linux/mmzone.h>,其內(nèi)容如下: typedef struct pglist_data { struct zone node_zones[MAX_NR_ZONES]; struct zonelist node_zonelists[MAX_ZONELISTS]; int nr_zones; #ifdef CONFIG_FLAT_NODE_MEM_MAP struct page *node_mem_map; #endif struct bootmem_data *bdata; #ifdef CONFIG_MEMORY_HOTPLUG spinlock_t node_size_lock; #endif unsigned long node_start_pfn; unsigned long node_present_pages; /* total number of physical pages */ unsigned long node_spanned_pages; /* total size of physical page range, including holes */ int node_id; wait_queue_head_t kswapd_wait; struct task_struct *kswapd; int kswapd_max_order; } pg_data_t; 各字段含義如下: node_zones:包含本節(jié)點(diǎn)內(nèi)區(qū)的描述結(jié)構(gòu)體,一般而言MAX_NR_ZONES為3。 node_zonelists:這個(gè)數(shù)組指明了在分配內(nèi)存時(shí)區(qū)的選擇順序。 nr_zones:指明本節(jié)點(diǎn)內(nèi)區(qū)的個(gè)數(shù),這一般都是1到3中的一個(gè)數(shù)字。并不是所有的節(jié)點(diǎn)都有3個(gè)區(qū),比如有些節(jié)點(diǎn)就沒有ZONE_DMA區(qū)。 node_mem_map:指向全局內(nèi)存描述數(shù)組中的某一項(xiàng),該元素描述了本節(jié)點(diǎn)的第一頁物理內(nèi)存。 bdata:指向bootmem_data結(jié)構(gòu)體,該結(jié)構(gòu)體用于系統(tǒng)啟動(dòng)時(shí)的內(nèi)存分配器分配、管理該節(jié)點(diǎn)的內(nèi)存。該內(nèi)存分配器在系統(tǒng)啟動(dòng)完畢后不再使用。 node_size_lock:該自旋鎖用于保護(hù)node_start_pfn、node_present_pages以及node_spanned_pages,當(dāng)你要取得這些變量的值時(shí),必須首先獲取這個(gè)自旋鎖。當(dāng)你調(diào)用pfn_valid()前也應(yīng)當(dāng)獲得這個(gè)自旋鎖。并且注意必須在zone->lock和zone->size_seqlock前獲取這個(gè)自旋鎖。 node_start_pfn:這個(gè)是該節(jié)點(diǎn)內(nèi)第一頁物理內(nèi)存在全局?jǐn)?shù)組mem_map中的序號(hào)。 node_present_pages:當(dāng)前該節(jié)點(diǎn)內(nèi)可獲得的物理頁總數(shù)。 node_spanned_pages:節(jié)點(diǎn)內(nèi)可訪問的所有物理頁數(shù),包含可能存在的空洞頁。 node_id:當(dāng)前節(jié)點(diǎn)的編號(hào),從0開始。 kswapd_wait:kswapd線程的等待隊(duì)列。kswapd是一個(gè)內(nèi)核線程,當(dāng)系統(tǒng)中內(nèi)存頁較少時(shí)它負(fù)責(zé)回收物理內(nèi)存頁,這個(gè)線程常常處于睡眠狀態(tài)。 kswapd:指向kswapd線程的進(jìn)程描述符。 kswapd_max_order:kswapd線程要釋放內(nèi)存塊大小取對(duì)數(shù)后的值。 二、區(qū)的數(shù)據(jù)表示 每個(gè)區(qū)由結(jié)構(gòu)體struct zone來描述,其中包含了頁使用統(tǒng)計(jì)、空閑頁數(shù)、瑣等信息,這個(gè)結(jié)構(gòu)體在<linux/mmzone.h>中定義: struct zone { unsigned long pages_min, pages_low, pages_high; unsigned long lowmem_reserve[MAX_NR_ZONES]; #ifdef CONFIG_NUMA int node; unsigned long min_unmapped_pages; unsigned long min_slab_pages; struct per_cpu_pageset *pageset[NR_CPUS]; #else struct per_cpu_pageset pageset[NR_CPUS]; #endif spinlock_t lock; #ifdef CONFIG_MEMORY_HOTPLUG seqlock_t span_seqlock; #endif struct free_area free_area[MAX_ORDER]; #ifndef CONFIG_SPARSEMEM unsigned long *pageblock_flags; #endif /* CONFIG_SPARSEMEM */ spinlock_t lru_lock; struct list_head active_list; struct list_head inactive_list; unsigned long nr_scan_active; unsigned long nr_scan_inactive; unsigned long pages_scanned; /* since last reclaim */ unsigned long flags; atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS]; int prev_priority; wait_queue_head_t * wait_table; unsigned long wait_table_hash_nr_entries; unsigned long wait_table_bits; struct pglist_data *zone_pgdat; unsigned long zone_start_pfn; unsigned long spanned_pages; unsigned long present_pages; const char *name; } ____cacheline_internodealigned_in_smp; 各字段含義如下: pages_min:管理區(qū)中保留的頁數(shù),當(dāng)區(qū)中空閑的頁框數(shù)達(dá)到這個(gè)值時(shí)kswapd線程會(huì)被喚醒并以同步的方式回收區(qū)中的頁框。 pages_low:回收頁框的下限,當(dāng)區(qū)中空閑的頁框數(shù)達(dá)到這個(gè)值時(shí),伙伴分配器會(huì)喚起kswapd線程開始回收頁框。 pages_high:回收頁框的上限,當(dāng)kswapd線程被喚醒回收頁框時(shí),它會(huì)一直回收頁框直到區(qū)中空閑的頁框數(shù)達(dá)到這個(gè)值為止。 lowmem_reserve:指明在靠近內(nèi)存低端的區(qū)內(nèi)需要保留的頁框數(shù),這個(gè)主要是為了防止這樣一種情況:當(dāng)內(nèi)存低端的頁框全部被分配后,如果這時(shí)候有新的頁分配請(qǐng)求就可能會(huì)產(chǎn)生OOM,即使此刻在高端內(nèi)存我們還有大量空閑的頁框。 node:指明這個(gè)區(qū)所屬的節(jié)點(diǎn)編號(hào)。 pageset:用于實(shí)現(xiàn)每cpu高速緩存的數(shù)組,每個(gè)cpu在該數(shù)組內(nèi)占有一項(xiàng)。由于內(nèi)核經(jīng)常請(qǐng)求和釋放單個(gè)頁框,因此每cpu高速緩存包含一些預(yù)先分配的頁框,它們用于滿足本地cpu發(fā)出的單一內(nèi)存請(qǐng)求。 lock:保護(hù)該描述符的自旋鎖。 span_seqlock:保護(hù)zone_start_pfn、spanned_pages和present_pages的順序鎖,之所以使用順序鎖來保護(hù)這3個(gè)變量是因?yàn)槲覀兘?jīng)常需要在沒有獲得zone->lock的情況下讀取它們的值,同時(shí)我們很少更改這些值。 free_area:標(biāo)識(shí)出區(qū)中空閑頁框快,這個(gè)結(jié)構(gòu)是伙伴分配器的主要數(shù)據(jù)結(jié)構(gòu),伙伴分配器就是根據(jù)該結(jié)構(gòu)體來對(duì)區(qū)中內(nèi)存進(jìn)行分配、回收等操作的。 pageblock_flags:一個(gè)內(nèi)存塊的標(biāo)志,該內(nèi)存塊大小為pageblock_nr_pages。 lru_lock:活動(dòng)及非活動(dòng)頁鏈表使用的自旋鎖,主要在頁框回收時(shí)使用。 active_list:區(qū)的活動(dòng)頁鏈表。 inactive_list:區(qū)的非活動(dòng)頁鏈表。 nr_scan_active:回收頁框要掃描的活動(dòng)頁數(shù)目。 nr_scan_inactive:回收頁框要掃描的非活動(dòng)頁數(shù)目。 pages_scanned:頁框回收時(shí)使用的計(jì)數(shù)器。 flags:區(qū)的標(biāo)志,目前有三個(gè)標(biāo)志可以設(shè)置: ZONE_ALL_UNRECLAIMABLE——區(qū)中所有頁框被鎖定不能回收 ZONE_RECLAIM_LOCKED——不能并發(fā)回收區(qū)中頁框 ZONE_OOM_LOCKED——區(qū)處于OOM的區(qū)鏈表中 vm_stat:包含區(qū)的統(tǒng)計(jì)信息。 prev_priority:區(qū)掃描優(yōu)先級(jí),主要用于頁框回收過程中。 wait_table:進(jìn)程等待隊(duì)列的hash表,表中的進(jìn)程在等待區(qū)中的某一頁內(nèi)存。 wait_table_hash_nr_entries:等待隊(duì)列hash表數(shù)組的大小。 wait_table_bits:等待隊(duì)列hash表大小,1<<wait_table_bits。 zone_pgdat:指向區(qū)所屬節(jié)點(diǎn)的指針。 zone_start_pfn:區(qū)中第一個(gè)頁框在mem_map數(shù)組中的編號(hào)。 spanned_pages:區(qū)中的頁框數(shù),包含空洞。 present_pages:區(qū)中包含的頁框數(shù),不包含空洞。 name:指向區(qū)的名稱,這個(gè)域很少使用。 三、頁的數(shù)據(jù)表示 這個(gè)結(jié)構(gòu)體是描述物理內(nèi)存的最小單位,它描述了每個(gè)物理頁框的屬性,定義于文件<linux/mm_types.h>: struct page { unsigned long flags; atomic_t _count; union { atomic_t _mapcount; struct { u16 inuse; u16 objects; }; }; union { struct { unsigned long private; struct address_space *mapping; }; #if NR_CPUS >= CONFIG_SPLIT_PTLOCK_CPUS spinlock_t ptl; #endif struct kmem_cache *slab; struct page *first_page; }; union { pgoff_t index; /* Our offset within mapping. */ void *freelist; /* SLUB: freelist req. slab lock */ }; struct list_head lru; #if defined(WANT_PAGE_VIRTUAL) void *virtual; #endif #ifdef CONFIG_CGROUP_MEM_RES_CTLR unsigned long page_cgroup; #endif }; 各字段含義如下: flags:一組原子型標(biāo)志,有些標(biāo)志可以異步更新,這個(gè)域功能繁多,有興趣的可以進(jìn)一步看看源代碼。 _count:頁框的引用計(jì)數(shù)器。如果該字段為-1,則相應(yīng)頁框空閑,并可被分配給任一進(jìn)程或內(nèi)核本身;如果該字段大于或等于0,則說明該頁框被分配給了一個(gè)或多個(gè)進(jìn)程,或用于存放一些內(nèi)核數(shù)據(jù)結(jié)構(gòu)。 _mapcount:本頁框?qū)?yīng)的頁表項(xiàng)數(shù)目,本頁框若未被映射則為-1。 inuse、objects:這2個(gè)標(biāo)志在SLUB分配器中使用。 private:可用于正在使用該頁的內(nèi)核成分使用,如果頁是空閑的則該字段由伙伴系統(tǒng)使用。 mapping:當(dāng)頁被插入頁高速緩存中時(shí)使用。 slab:指向slab的指針。 first_page: index、freelist:作為不同的含義被幾種內(nèi)核成分使用。 lru:將頁鏈接到最近最少使用雙向鏈表中。 virtual:指向本頁框的內(nèi)核虛擬地址。 page_cgroup:這個(gè)域只有在內(nèi)存資源控制器起作用的情況下使用,用于統(tǒng)計(jì)處于內(nèi)存資源控制器下的頁框。
1 名詞解釋:
(2)頁描述符:描述每一個(gè)頁框的狀態(tài)信息,所有的也描述符都保存在mem_map[ ]數(shù)組中,每個(gè)描述符32個(gè)字節(jié) (3)節(jié)點(diǎn):系統(tǒng)物理內(nèi)存被劃分為多個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)內(nèi)cpu訪問頁面的時(shí)間是相同的,對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu):節(jié)點(diǎn)描述符 (4)管理區(qū):每個(gè)節(jié)點(diǎn)又分為多個(gè)管理區(qū) 對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu): 管理區(qū)描述符
2 頁表管理
(1)啟動(dòng)階段8M頁表的映射過程 (2)剩余頁表的映射過程 幾個(gè)比較重要的地址轉(zhuǎn)換: 虛擬地址轉(zhuǎn)換成物理地址: virt_to_phsy(address){ __pa(address) } 虛擬地址轉(zhuǎn)換頁描述符的地址: virt_to_page( kaddr ) { return mem_map + __pa(kaddr) >>12 } 3 用戶進(jìn)程的地址空間 從內(nèi)核看來,整個(gè)4G的地址空間是這樣的。
進(jìn)程可用的地址空間是被一個(gè)叫mm_struct(進(jìn)程地址空間描述符)結(jié)構(gòu)體來管理的,同一個(gè)進(jìn)程內(nèi)的多個(gè)線程是共享這個(gè)數(shù)據(jù)結(jié)構(gòu)的。 同時(shí),對(duì)于用戶進(jìn)程來說,每一個(gè)進(jìn)程有一個(gè)獨(dú)一無二的mm_struct,但是內(nèi)核線程確不是必須的。下面是操作mm_struct的一些函數(shù)。 當(dāng)然如果在進(jìn)程創(chuàng)建的時(shí)候指定子進(jìn)程共享父進(jìn)程的虛擬地址空間的話,比如: if ( clone_flags & CLONE_VM ) { atomic_inc(&old_mm->mm_user) mm = &oldmm; goto good_mm; }
還有一點(diǎn)許喲阿注意的就是系統(tǒng)中的第一個(gè)mm_struct是需要靜態(tài)初始化的,以后的所有的mm_strcut都是通過拷貝生成的。
mmap函數(shù),內(nèi)存映射函數(shù) 該函數(shù)的主要功能是在進(jìn)程地址空間中創(chuàng)建一個(gè)線性區(qū)。有兩種類型的內(nèi)存映射:共享型和私有型。二者的主要區(qū)別可以理解成是否對(duì)其他進(jìn)程可見。共享型每次對(duì)線性區(qū)的讀寫都會(huì)修改 磁盤文件,一個(gè)進(jìn)程修改共享型的線性區(qū),其他映射這一線性區(qū)的所有進(jìn)程都是可見的。與內(nèi)存映射相關(guān)的數(shù)據(jù)結(jié)構(gòu): (1)與所映射的文件相關(guān)的索引節(jié)點(diǎn)對(duì)象 (2)所映射文件的address_space對(duì)象 (3)不同進(jìn)程對(duì)同一文件進(jìn)行不同映射所使用的文件對(duì)象 (4)對(duì)文件進(jìn)行每一不同映射所使用的vm_area_struct (5)對(duì)文件進(jìn)行映射的線性區(qū)所分配的每個(gè)頁框?qū)?yīng)的描述符
從圖上能看出一個(gè)文件對(duì)應(yīng)一個(gè)inode,對(duì)應(yīng)一個(gè)address_space,對(duì)應(yīng)多個(gè)struct file, 對(duì)應(yīng)多個(gè)vm_area_file ,對(duì)應(yīng)多個(gè)page(頁框),當(dāng)然也對(duì)應(yīng)多個(gè)page(頁描述符)、 mm_struct 內(nèi)存描述符中的兩棵樹: 當(dāng)前進(jìn)程內(nèi)所有線性區(qū)的一個(gè)鏈表和所有線性區(qū)的紅黑樹 mmap和mm_rb都可以訪問線性區(qū)。事實(shí)上,它們都指向了同一個(gè)vm_area_struct結(jié)構(gòu),只是鏈接的方式不同 address_space中的兩棵樹:基數(shù)和優(yōu)先級(jí)搜索數(shù)。
address_space的i_mmap指向了組織構(gòu)成這個(gè)文件的所有的線性區(qū)描述符的基樹 要注意一個(gè)問題: (1)共享內(nèi)存映射的頁通常保存在也高速緩存中,私有內(nèi)存映射的頁只要還沒有修改,也保存在頁高速緩存中。當(dāng)進(jìn)程試圖修改一個(gè)私有映射的頁時(shí),內(nèi)核就把該頁框進(jìn)行復(fù)制,并在進(jìn)程頁表中用復(fù)制的頁替換原來的頁,這就是寫時(shí)復(fù)制的基礎(chǔ)。復(fù)制后的頁框就不會(huì)放在頁告訴緩存中了,原因是它不再是表示磁盤上那個(gè)文件的有效數(shù)據(jù)。 (2)線性區(qū)的開始和結(jié)束地址都是4K對(duì)齊的 進(jìn)程獲得新線性區(qū)的一些典型情況 : 剛剛創(chuàng)建的新進(jìn)程 創(chuàng)建內(nèi)存映射: 要想創(chuàng)建一個(gè)映射,就要調(diào)用mmap, mmap()最終會(huì)調(diào)用do_mmap() static inline unsigned long do_mmap(struct file *file, unsigned long addr, unsigned long len, unsigned long prot, unsigned long flag,unsigned long offset) file:要映射的文件描述符,知道映射哪個(gè)文件才行 offset:文件內(nèi)的偏移量,指定要映射文件的一部分,當(dāng)然也可以是全部 len: 要映射文件的那一部分的長(zhǎng)度 flag:一組標(biāo)志,顯示的指定映射的那部分是MAP_SHARED或MAP_PRIVATE prot: 一組權(quán)限,指定對(duì)線性區(qū)訪問的一種或多種訪問權(quán)限 addr: 一個(gè)可選的線性地址,表示從這個(gè)地址之后的某個(gè)位置創(chuàng)建線性區(qū)
基本的過程是: (1)先為要映射的文件申請(qǐng)一段線性區(qū),調(diào)用內(nèi)存描述符的get_unmapped_area() (2)做一些權(quán)限和標(biāo)志位檢查 (3)將文件對(duì)象的地址struct file地址賦值給線性區(qū)描述符vm_area_struct.vm_file (4)調(diào)用mmap方法,這個(gè)方法最后調(diào)用generic_file_mmap() 其他線性區(qū)處理函數(shù) (1)find_vma() : 查找一個(gè)線性地址所屬的線性區(qū)或后繼線性區(qū) (2)find_vmm_interrection(): 查找一個(gè)與給定區(qū)間重疊的線性區(qū) (3)get_unmapped_area() : 查找一個(gè)空閑的線性區(qū) (4)insert_vm_struct () : 向進(jìn)程的內(nèi)存描述符中插入一個(gè)線性區(qū) 缺頁異常處理程序 (1)背景知識(shí) 內(nèi)核中的函數(shù)以直接了當(dāng)?shù)姆绞将@得動(dòng)態(tài)內(nèi)存,內(nèi)核是操作系統(tǒng)中優(yōu)先級(jí)最高的成分,內(nèi)核信任自己,采用面級(jí)內(nèi)存分配和小內(nèi)存分配以及非連續(xù)線性區(qū)得到內(nèi)存 用戶態(tài)進(jìn)程分配內(nèi)存時(shí),請(qǐng)求被認(rèn)為是不緊迫的,用戶進(jìn)程不可信任,因此,當(dāng)用戶態(tài)進(jìn)程請(qǐng)求動(dòng)態(tài)內(nèi)存時(shí),并沒有立即獲得實(shí)際的物理頁框,而僅僅獲得對(duì)一個(gè) 新的線性地址區(qū)間的使用權(quán)這個(gè)線性地址區(qū)間會(huì)成為進(jìn)程地址空間的一部分,稱作線性區(qū)(memory areas)。 這樣,當(dāng)用戶進(jìn)程真正向這些線性區(qū)寫的時(shí)候,就會(huì) 產(chǎn)生缺頁異常,在缺頁異常處理程序中獲得真正的物理內(nèi)存。 (2)缺頁異常處理程序需要區(qū)分引起缺頁的兩種情況:編程錯(cuò)引起的缺頁和屬于進(jìn)程的地址空間尚未分配到物理頁框 簡(jiǎn)單流程圖:
詳細(xì)流程圖:
linux為什么要分為三個(gè)區(qū):ZONE_DMA ZONE_NORMAL ZONE_HIGHMEM? (1)isa總線的歷史遺留問題,只能訪問內(nèi)存的前16M的空間 (2)大容量的RAM使得線性地址空間太小,并不是所有的物理空間都能映射到唯一的線性地址空間 如何確定某個(gè)頁框?qū)儆谀膫€(gè)節(jié)點(diǎn)或管理區(qū)? 是由每個(gè)頁框描述符中的flag的高位索引的,比如page_zone()函數(shù)就是接收頁描述符的地址作為參數(shù),返回頁描述符中flag的高位,并到zone_table[ ]數(shù)組中確定相應(yīng)的管理區(qū)描述符的地址 slab算法是用來滿足對(duì)以頁框?yàn)閱挝坏恼?qǐng)求而設(shè)置的,簡(jiǎn)單介紹以下slab算法的原理
對(duì)于以頁為單位的請(qǐng)求發(fā)送到管理區(qū)分配器,然后管理區(qū)分配器搜索它所管轄的管理區(qū),找一個(gè)滿足請(qǐng)求的分配區(qū),然后再由這個(gè)管理區(qū)中的伙伴系統(tǒng)去處理,為了加快這個(gè) 過程,每個(gè)分區(qū)中還提供了一個(gè)每cpu頁框高速緩存,來處理單個(gè)頁框的請(qǐng)求。 這個(gè)過程中有四個(gè)請(qǐng)求頁框的函數(shù)和宏: (1)alloc_pages, alloc_page返回分配的第一個(gè)頁框的頁描述符的地址 (2)__get_free_pages , __get_free_page 返回分配的第一個(gè)頁框的線性地址 其實(shí)二者是相同,因?yàn)橛袑iT用來處理線性地址到頁描述符地址轉(zhuǎn)換的函數(shù) virt_to_page() 實(shí)現(xiàn)從線性地址到頁描述符地址的轉(zhuǎn)換
Linux是一個(gè)遵循POSIX(Portable Operating System Interface)標(biāo)準(zhǔn)的操作系統(tǒng),它繼承了UNIX系統(tǒng)優(yōu)秀的設(shè)計(jì)思想,擁有簡(jiǎn)練、容錯(cuò)強(qiáng)、高效而且穩(wěn)定的內(nèi)核。此外Linux還具備其他操作系統(tǒng)所不能比擬的優(yōu)點(diǎn)。①:完全免費(fèi);②:內(nèi)核源代碼完全公開。 Linux2.4內(nèi)核擁有一個(gè)功能完備的內(nèi)存管理子系統(tǒng),它增加了對(duì)NUMA(非均勻存儲(chǔ)結(jié)構(gòu))體系結(jié)構(gòu)的支持并且使用了基于區(qū)(ZONE)的物理內(nèi)存管理方法,從而保持了物理上連續(xù)分布、而邏輯上統(tǒng)一的內(nèi)存模式和傳統(tǒng)的共享內(nèi)存編程模型,使得系統(tǒng)的性能得以極大的擴(kuò)展。這樣Linux不僅能夠滿足傳統(tǒng)的桌面應(yīng)用,而且還能滿足高端服務(wù)器市場(chǎng)的需要。目前,Linux不僅在Internet服務(wù)器上表現(xiàn)出色,而且還可以勝任大型數(shù)據(jù)庫系統(tǒng)的服務(wù)器。
二:Linux存儲(chǔ)管理的基本框架 Linux內(nèi)核采用虛擬頁式存儲(chǔ)管理,采用三次映射機(jī)制實(shí)現(xiàn)從線性地址到物理地址的映射。其中PGD為頁面目錄,PMD為中間目錄,PT為頁面表。具體的映射過程為: ⑴從CR3寄存器中找到PGD基地址; ⑵以線性地址的最高位段為下標(biāo),在PGD中找到指向PMD的指針; ⑶以線性地址的次位段為下標(biāo),在PMD中找到指向PT的指針; ⑷同理,在PT中找到指向頁面的指針; ⑸線性地址的最后位段,為在此頁中的偏移量,這樣就完成了從線性地址到物理地址的映射過程。 32位的微機(jī)平臺(tái)如Intel的X86采用段頁式的兩層映射機(jī)制,而64位的微處理器采用三級(jí)分頁。對(duì)于傳統(tǒng)的32位平臺(tái),Linux采用讓PMD(中間目錄)全0來消除中間目錄域,這樣就把Linux邏輯上的三層映射模型落實(shí)到X86結(jié)構(gòu)物理上的二層映射,從而保證了Linux對(duì)多種硬件平臺(tái)的支持。
三:Linux對(duì)虛擬內(nèi)存的管理 虛擬內(nèi)存不僅可以解決內(nèi)存容量的問題,還可以提供以下附加的功能:大地址空間;進(jìn)程保護(hù);內(nèi)存映射;靈活的物理內(nèi)存分配;共享虛擬內(nèi)存。 Linux對(duì)虛擬內(nèi)存的管理以進(jìn)程為基礎(chǔ)。32位的線性地址映射的4G的虛擬空間中,從0XC0000000到0XFFFFFFFF的1G空間為所用進(jìn)程所共享的內(nèi)核空間,每個(gè)進(jìn)程都有自己的3G用戶空間。 Linux的虛擬內(nèi)存管理需要各種機(jī)制的支持,首先內(nèi)存管理程序通過映射機(jī)制把用戶程序的邏輯地址映射到物理地址,在用戶程序運(yùn)行時(shí)時(shí)如果發(fā)現(xiàn)程序中要用的虛擬地址沒有對(duì)應(yīng)的物理地址,就發(fā)出請(qǐng)頁要求①:如果有空閑的內(nèi)存可供分配,就請(qǐng)求分配內(nèi)存②,并把正在使用的物理頁記錄在頁緩存中③,如果沒有足夠的內(nèi)存分配,就調(diào)用交換機(jī)制,騰出一部分內(nèi)存④⑤。另外在地址映射中要通過TLB(翻譯后援存儲(chǔ)器)來尋找物理頁⑧,交換機(jī)制中要用到交換緩存⑥,并且把物理頁內(nèi)容交換到交換文件中也要修改頁表來映射文件地址⑦。 一個(gè)進(jìn)程的虛擬地址映射靠三個(gè)數(shù)據(jù)結(jié)構(gòu)來描述:mm_struct、vm_area_struct、page。其中mm_struct結(jié)構(gòu)用來描述一個(gè)進(jìn)程的虛擬內(nèi)存;vm_area_struct描述一個(gè)進(jìn)程的虛擬地址區(qū)域,在這個(gè)區(qū)域中的所有頁面具有相同的訪問權(quán)限和一些屬性;page描述一個(gè)具體的物理頁面。 當(dāng)進(jìn)程通過系統(tǒng)調(diào)用動(dòng)態(tài)分配內(nèi)存時(shí),Linux首先分配一個(gè)vm_area_struct結(jié)構(gòu),并鏈接到進(jìn)程的虛擬內(nèi)存鏈表,當(dāng)后續(xù)指令訪問這一內(nèi)存區(qū)域時(shí),產(chǎn)生缺頁異常。系統(tǒng)處理時(shí),通過分析缺頁原因、操作權(quán)限之后,如果頁面在交換文件中,則進(jìn)入do_page_fault()中恢復(fù)映射的代碼,重新建立映射關(guān)系。如果因?yàn)轫撁娌辉賰?nèi)存中,則Linux會(huì)分配新的物理頁,并建立映射關(guān)系。 當(dāng)物理內(nèi)存出現(xiàn)不足時(shí),就需要換出一些頁面。Linux采用LRU(Least Recently Used最近最少使用)頁面置換算法選擇需要從系統(tǒng)中換出的頁面。系統(tǒng)中每個(gè)頁面都有一個(gè)“age”屬性,這個(gè)屬性會(huì)在頁面被訪問的時(shí)候改變。Linux根據(jù)這個(gè)屬性選擇要回收的頁面,同時(shí)為了避免頁面“抖動(dòng)”(即剛釋放的頁面又被訪問),將頁面的換出和內(nèi)存頁面的釋放分兩步來做,而在真正釋放的時(shí)候僅僅只寫回“臟”頁面。這一任務(wù)由交換守護(hù)進(jìn)程kswapd完成。free_pages_high,free_pages_low是衡量系統(tǒng)中現(xiàn)有空閑頁的標(biāo)準(zhǔn),當(dāng)系統(tǒng)中空閑頁的數(shù)量少于free_pages_high,甚至少于free_pages_low時(shí),kswapd進(jìn)程會(huì)采用三種方法來減少系統(tǒng)正在使用的物理頁的數(shù)量。①調(diào)用shrink_mmap()減少buffer cache和page cache的大?。虎谡{(diào)用shm_swap()將system V共享內(nèi)存頁交換到物理內(nèi)存;③調(diào)用swap_out()交換或丟棄頁。 圖1.3給出了頁面置換管理框圖。其中①代表:refill_inactive_scan(),它的任務(wù)是掃描活躍頁面隊(duì)列,從中找到可以轉(zhuǎn)入不活躍狀態(tài)的頁面;②代表:page_launder()它把已經(jīng)轉(zhuǎn)入不活躍狀態(tài)的“臟”頁面“洗凈”,使它們成為立即可以分配的頁面;③代表:reclaim_page()用于從頁面管理區(qū)的不活躍凈頁面隊(duì)列中回收頁面。 kswapd是被定期喚醒的,首先檢查內(nèi)存中可供分配或周轉(zhuǎn)的物理頁面是否短缺,若需要回收頁面,則按順序循環(huán)檢查緩沖區(qū)、共享內(nèi)存、進(jìn)程獨(dú)占的內(nèi)存,遇到滿足條件的頁面,即將它釋放。如果已釋放了足夠的頁面,kswapd重新睡眠,直到下一次被重新喚醒。
四:Linux對(duì)物理內(nèi)存的管理 Linux2.4內(nèi)核加入了對(duì)NUMA的支持,如果系統(tǒng)是NUMA結(jié)構(gòu)的處理機(jī)系統(tǒng),則物理內(nèi)存被劃分為三個(gè)層次來管理:存儲(chǔ)節(jié)點(diǎn)(Node),管理區(qū)(Zone),頁面(Page)。處理器的本地內(nèi)存組成的區(qū)域叫做一個(gè)節(jié)點(diǎn)(Node),它通過pglist_data數(shù)據(jù)結(jié)構(gòu)來描述。各個(gè)節(jié)點(diǎn)的物理內(nèi)存根據(jù)不同的作用又分為ZONE_DMA、ZONE_NORMAL、ZONE_HIGH,ZONE_DMA面積小,且專供DMA使用,ZONE_NORMAL則供大多數(shù)的程序使用,對(duì)于ZONE_HIGH僅僅只有頁面緩存以及用戶進(jìn)程能夠使用該區(qū)域的空間。每個(gè)管理區(qū)對(duì)應(yīng)一個(gè)free_area數(shù)組來組織空閑頁面隊(duì)列,該數(shù)組的每一項(xiàng)描述某一種頁塊的信息,第一個(gè)元素描述大小為1頁的內(nèi)存塊的信息,第二個(gè)元素描述大小為2 頁的內(nèi)存塊的信息,依此類推,所描述的頁塊大小以 2 的倍數(shù)增加。free_area 數(shù)組的定義如下: Typedef struct free_area_struct{ Struct list_head free_list; Unsigned int *map; }free_area_t; list_head是一個(gè)雙向指針結(jié)構(gòu),在這里用于將物理頁塊結(jié)構(gòu)mem_map_t 連結(jié)成一個(gè)雙向鏈表,而map則是記錄這種頁塊組分配情況的位圖,例如,位圖的第N位為1,表明第N個(gè)頁塊是空閑的。 頁分配代碼使用向量表free_area來分配和回收物理頁。系統(tǒng)初始化時(shí),free_area數(shù)組也被賦了初值。也就是說,系統(tǒng)中所有可用的空閑物理頁塊都已經(jīng)被加到了free_area數(shù)組中。 Linux使用Buddy最先匹配算法來進(jìn)行頁面的分配和回收,并且必須按2的冪次方進(jìn)行分配。如圖1.4所示,比如要分配大小為2k的空閑塊,如果系統(tǒng)中有足夠的空閑塊,頁面分配代碼首先在free_area中查找相應(yīng)大小的空閑塊,如果找到則分配。如果沒有則查找下一尺寸(2倍于請(qǐng)求大?。┑捻撁鎵K,繼續(xù)這一過程直到找到可以分配的頁面,按要求分配之后,將剩余的空閑塊仍然按照2的冪次方劃分后鏈入適當(dāng)?shù)目臻e塊中。與分配算法相反,頁面回收時(shí)總是試圖將相鄰的空閑頁面組合成更大尺寸的空閑塊。這里先給出“伙伴”要滿足的三個(gè)條件:①兩個(gè)塊大小相同;②兩個(gè)塊物理地址連續(xù);③兩個(gè)塊從同一大塊中分離出來。在用戶釋放內(nèi)存時(shí),判斷“伙伴”是否是空閑塊。若否,則只要將釋放的空閑塊簡(jiǎn)單的插入相應(yīng)的free_area中。若是,則需要在free_area中刪除其伙伴關(guān)系,然后再判斷合并后的空閑塊的伙伴關(guān)系,依次重復(fù),直到歸并后的空閑塊沒有伙伴關(guān)系或合并到最大塊時(shí)將其插入到free_area中。
五:緩存和刷新機(jī)制 為了更好的發(fā)揮系統(tǒng)性能,Linux采用了一系列和內(nèi)存管理相關(guān)的高速緩存機(jī)制: ①緩沖區(qū)高速緩存:包含了從設(shè)備中讀取的數(shù)據(jù)塊或?qū)懭朐O(shè)備的數(shù)據(jù)塊。緩沖區(qū)高速緩存由設(shè)備標(biāo)示號(hào)和塊索引,因此可以快速找到數(shù)據(jù)塊。如果數(shù)據(jù)可以在緩沖區(qū)中高速緩存中找到,則不需要從物理塊設(shè)備上讀取,從而加快了訪問速度。 ②頁高速緩存:這一高速緩存用來加速對(duì)磁盤上的映像和數(shù)據(jù)訪問,它用來緩存某個(gè)文件的邏輯內(nèi)容,并通過文件VFS索引節(jié)點(diǎn)和偏移量訪問。當(dāng)頁從磁盤讀到物理內(nèi)存時(shí),就緩存在頁高速緩存。 ③交換高速緩存:用于多個(gè)近程共享的頁面被換出到交換區(qū)的情況。當(dāng)頁面交換到交換文件之后,如果有進(jìn)程再次訪問,它會(huì)被重新調(diào)入內(nèi)存。
六:小結(jié) Linux是近年來應(yīng)用的比較多的一個(gè)操作系統(tǒng),廣泛應(yīng)用于各個(gè)行業(yè)。而且由于全世界計(jì)算機(jī)愛好者的支持,Linux也成為世界上發(fā)展最快的操作系統(tǒng)。在Linux2.6內(nèi)核中,對(duì)存儲(chǔ)管理子系統(tǒng)進(jìn)行了一系列的改進(jìn),提高了系統(tǒng)的可擴(kuò)展性,包含了對(duì)大型服務(wù)器如NUMA服務(wù)器和Intel服務(wù)器的良好支持。此外,Linux2.6還提供了對(duì)無MMU的支持??梢奓inux正在不斷的加強(qiáng)對(duì)高端服務(wù)器領(lǐng)域以及嵌入式領(lǐng)域的支持。 Linux在其發(fā)展過程中不斷的在完善和優(yōu)化內(nèi)存管理單元的功能和性能。針對(duì)具體領(lǐng)域,我們可以根據(jù)自己的需要定制Linux內(nèi)核。而內(nèi)存管理單元作為L(zhǎng)inux操作系統(tǒng)的核心部分,在整個(gè)系統(tǒng)的運(yùn)行過程中發(fā)揮著舉足輕重的作用。 |
|