Linux內存模型


http://blog.csdn.net/sunyubo458/article/details/6090946

 

了解linux的內存模型,或許不能讓你大幅度提高編程能力,但是作為一個基本知識點應該熟悉。坐火車外出旅行時,即時你對沿途的地方一無所知,仍然可以到達目標地。但是你對整個路途都很比較清楚的話,每到一個站都知道自己在哪里,知道當地的風土人情,對比一下所見所想,旅程可能更有趣一些。 

類似的,了解linux的內存模型,你知道每塊內存,每個變量,在系統中處於什么樣的位置。這同樣會讓你心情愉快,知道這些,有時還會讓你的生活輕更松些。看看變量的地址,你可以大致斷定這是否是一個有效的地址。一個變量被破壞了,你可以大致推斷誰是犯罪嫌疑人。 

Linux的內存模型,一般為:

地址

作用

說明

>=0xc000 0000

內核虛擬存儲器

用戶代碼不可見區域

<0xc000 0000

Stack(用戶棧)

ESP指向棧頂

 

 

 

空閑內存

>=0x4000 0000

文件映射區

 

<0x4000 0000

 

 

 

空閑內存

 

 

Heap(運行時堆)

通過brk/sbrk系統調用擴大堆,向上增長。

 

.data、.bss(讀寫段)

從可執行文件中加載

>=0x0804 8000

.init、.text、.rodata(只讀段)

從可執行文件中加載

<0x0804 8000

保留區域

 

 

很多書上都有類似的描述,本圖取自於《深入理解計算機系統》p603,略做修改。本圖比較清析,很容易理解,但仍然有兩點不足。下面補充說明一下:

 

1.         第一點是關於運行時堆的

為說明這個問題,我們先運行一個測試程序,並觀察其結果:

 

[c-sharp]  view plain copy
 
  1. #include <stdio.h>  
  2.   
  3. intmain(intargc, char* argv[])  
  4. {  
  5.     int first = 0;  
  6.     int* p0 = malloc(1024);  
  7.     int* p1 = malloc(1024 * 1024);  
  8.     int* p2 = malloc(512 * 1024 * 1024 );  
  9.     int* p3 = malloc(1024 * 1024 * 1024 );  
  10.     printf("main=%p print=%p/n", main, printf);  
  11.     printf("first=%p/n", &first);  
  12.     printf("p0=%p p1=%p p2=%p p3=%p/n", p0, p1, p2, p3);  
  13.     getchar();  
  14.     return 0;  
  15. }  

 

運行后,輸出結果為:

main=0x8048404 print=0x8048324

first=0xbfcd1264

p0=0x9253008 p1=0xb7ec0008 p2=0x97ebf008 p3=0x57ebe008

  main和print兩個函數是代碼段(.text)的,其地址符合表一的描述。

l         first是第一個臨時變量,由於在first之前還有一些環境變量,它的值並非0xbfffffff,而是0xbfcd1264,這是正常的。

l         p0是在堆中分配的,其地址小於0x4000 0000,這也是正常的。

l         但p1和p2也是在堆中分配的,而其地址竟大於0x4000 0000,與表一描述不符。 

原因在於:運行時堆的位置與內存管理算法相關,也就是與malloc的實現相關。關於內存管理算法的問題,我們在后繼文章中有詳細描述,這里只作簡要說明。在glibc實現的內存管理算法中,Malloc小塊內存是在小於0x4000 0000的內存中分配的,通過brk/sbrk不斷向上擴展,而分配大塊內存,malloc直接通過系統調用mmap實現,分配得到的地址在文件映射區,所以其地址大於0x4000 0000。 

從maps文件中可以清楚的看到一點:

00514000-00515000 r-xp 00514000 00:00 0

00624000-0063e000 r-xp 00000000 03:01 718192     /lib/ld-2.3.5.so

0063e000-0063f000 r-xp 00019000 03:01 718192     /lib/ld-2.3.5.so

0063f000-00640000 rwxp 0001a000 03:01 718192     /lib/ld-2.3.5.so

00642000-00766000 r-xp 00000000 03:01 718193     /lib/libc-2.3.5.so

00766000-00768000 r-xp 00124000 03:01 718193     /lib/libc-2.3.5.so

00768000-0076a000 rwxp 00126000 03:01 718193     /lib/libc-2.3.5.so

0076a000-0076c000 rwxp 0076a000 00:00 0

08048000-08049000 r-xp 00000000 03:01 1307138    /root/test/mem/t.exe

08049000-0804a000 rw-p 00000000 03:01 1307138    /root/test/mem/t.exe

09f5d000-09f7e000 rw-p 09f5d000 00:00 0          [heap]

57e2f000-b7f35000 rw-p 57e2f000 00:00 0

b7f44000-b7f45000 rw-p b7f44000 00:00 0

bfb2f000-bfb45000 rw-p bfb2f000 00:00 0          [stack]

 

2.         第二是關於多線程的。

現在的應用程序,多線程的居多。表一所描述的模型無法適用於多線程環境。按表一所述,程序最多擁有上G的棧空間,事實上,在多線程情況下,能用的棧空間是非常有限的。為了說明這個問題,我們再看另外一個測試:

 

[c-sharp]  view plain copy
 
  1. #include <stdio.h>  
  2. #include <pthread.h>  
  3. void* thread_proc(void* param)  
  4. {  
  5.     int first = 0;  
  6.     int* p0 = malloc(1024);  
  7.     int* p1 = malloc(1024 * 1024);  
  8.     printf("(0x%x): first=%p/n",    pthread_self(), &first);  
  9.     printf("(0x%x): p0=%p p1=%p /n", pthread_self(), p0, p1);  
  10.     return 0;  
  11. }  
  12.  
  13. #define N 5  
  14. intmain(intargc, char* argv[])  
  15. {  
  16.     intfirst = 0;  
  17.     inti= 0;  
  18.     void* ret = NULL;  
  19.     pthread_t tid[N] = {0};  
  20.     printf("first=%p/n", &first);  
  21.     for(i = 0; i < N; i++)  
  22.     {  
  23.         pthread_create(tid+i, NULL, thread_proc, NULL);  
  24.     }  
  25.     for(i = 0; i < N; i++)  
  26.     {  
  27.         pthread_join(tid[i], &ret);  
  28.     }  
  29.     return 0;  
  30. }  

 

運行后,輸出結果為:

first=0xbfd3d35c

(0xb7f2cbb0): first=0xb7f2c454

(0xb7f2cbb0): p0=0x84d52d8 p1=0xb4c27008

(0xb752bbb0): first=0xb752b454

(0xb752bbb0): p0=0x84d56e0 p1=0xb4b26008

(0xb6b2abb0): first=0xb6b2a454

(0xb6b2abb0): p0=0x84d5ae8 p1=0xb4a25008

(0xb6129bb0): first=0xb6129454

(0xb6129bb0): p0=0x84d5ef0 p1=0xb4924008

(0xb5728bb0): first=0xb5728454

(0xb5728bb0): p0=0x84d62f8 p1=0xb7e2c008

 

我們看一下:

主線程與第一個線程的棧之間的距離:0xbfd3d35c - 0xb7f2c454=0x7e10f08=126M

第一個線程與第二個線程的棧之間的距離:0xb7f2c454 - 0xb752b454=0xa01000=10M

其它幾個線程的棧之間距離均為10M。

也就是說,主線程的棧空間最大為126M,而普通線程的棧空間僅為10M,超這個范圍就會造成棧溢出。

系統為進程分配數據空間有三種形式。

靜態分配

整塊靜態分配空間,包括其中的所有數據實體,都是在進程創建時由系統一次性分配的(同時為UNIX稱為Text的代碼分配空間)。這塊空間在進程運行期間保持不變。

初始化的和未初始化的實體分別放在初始化數據段和未初始化數據段(BSS)。后者和前者不同,在.o文件a.out文件里都不存在(只有構架信息),在進程的虛擬空間里才展開。

extern變量和static變量采用靜態分配。

在進程創建時做靜態分配,分配正文(text)段、數據段和棧空間。

正文和初始化數據是按a.out照樣復制過來;未初始化數據按構架信息展開,填以0或空;棧空間的大小由鏈接器開關(具體哪個開關忘了)決定。

棧分配

整個棧空間已在進程創建時分配好。棧指針SP的初值的設定,確定了棧空間的大小。鏈接器的某個開關可以設定棧空間的大小。在進程運行期間,棧空間的大小不變。但是,在進程剛啟動時,棧空間是空的,里面沒有實體。在進程運行期間,對具體實體的棧分配是進程自行生成(壓棧)和釋放(彈出)實體,系統並不參與。

auto變量和函數參數采用棧分配。

只要壓入的實體的總長度不超過棧空間尺寸,棧分配就與系統無關。如果超過了,就會引發棧溢出錯誤。

堆分配

當進程需要生成實體時,向系統申請分配空間;不再需要該實體時,可以向系統申請回收這塊空間。

堆分配使用特定的函數(如malloc()等)或操作符(new)。所生成的實體都是匿名的,只能通過指針去訪問。

對實體來說,棧分配和堆分配都是動態分配:實體都是在進程運行中生成和消失。而靜態分配的所有實體都是在進程創建時全部分配好的,在運行中一直存在。

同為動態分配,棧分配與堆分配是很不相同的。前者是在進程創建時由系統分配整塊棧空間,以后實體通過壓棧的方式產生,並通過彈出的方式取消。不管是否產生實體,產生多少實體,棧空間總是保持原來的大小。后者並沒有預設的空間,當需要產生實體時,才向系統申請正好能容納這個實體的空間。當不再需要該實體時,可以向系統申請回收這塊空間。因此,堆分配是真正的動態分配。

顯然,堆分配的空間利用率最高。

棧分配和靜態分配也有共性:整塊空間是在進程創建時由系統分配的。但是,后者同時分配了所有實體的空間,而前者在進程啟動時是空的。另外,棧上的實體和數據段里的實體都是有名實體,可以通過標識符來訪問。

 

靜態分配

棧分配

堆分配

整塊空間生成

進程創建時

進程創建時

用一點分配一點

實體生成時間

進程創建時

進程運行時

進程運行時

實體生成者

操作系統

進程

進程申請/系統實施

生命期

永久

臨時

完全可控

有名/匿名

有名

有名

匿名

訪問方式

能以標識訪問

能以標識訪問

只能通過指針訪問

空間可否回收

不可

不可

可以

 

棧溢出的后果是比較嚴重的,或者出現Segmentation fault錯誤,或者出現莫名其妙的錯誤。

 

linux物理內存描述

http://blog.csdn.net/bullbat/article/details/7166736

linux使用於廣泛的體系結構,因此需要用一種與體系結構無關的方式來描述內存。linux用VM描述和管理內存。在VM中獸葯的普遍概念就是非一致內存訪問。對於大型機器而言,內存會分成許多簇,依據簇與處理器“距離”的不同,訪問不同的簇會有不同的代價。

每個簇都被認為是一個節點(pg_data_t),每個節點被分成很多的成為管理區(zone)的塊,用於表示內存中的某個范圍。除了ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM以外,linux2.6.32中引入了ZONE_MOVABLE,用於適應大塊連續內存的分配。

每個物理頁面由一個page結構體描述,所有的結構都存儲在一個全局的mem_map數組中(非平板模式),該數組通常存放在ZONE_NORMAL的首部,或者就在校內存系統中為裝入內核映像而預留的區域之后。

節點

內存的每個節點都有pg_data_t描述,在分配一個頁面時,linux采用節點局部分配的策略,從最靠近運行中的CPU的節點分配內存。由於進程往往是在同一個CPU上運行,因此從當前節點得到的內存很可能被用到。

 

[cpp]  view plain copy print ?
 
  1. /* 
  2.  * The pg_data_t structure is used in machines with CONFIG_DISCONTIGMEM 
  3.  * (mostly NUMA machines?) to denote a higher-level memory zone than the 
  4.  * zone denotes. 
  5.  * 
  6.  * On NUMA machines, each NUMA node would have a pg_data_t to describe 
  7.  * it's memory layout. 
  8.  * 
  9.  * Memory statistics and page replacement data structures are maintained on a 
  10.  * per-zone basis. 
  11.  */  
  12. struct bootmem_data;  
  13. typedef struct pglist_data {  
  14.      /*該節點內的內存區。可能的區域類型用zone_type表示。 */  
  15.     struct zone node_zones[MAX_NR_ZONES];  
  16.      /* 該節點的備用內存區。當節點沒有可用內存時,就從備用區中分配內存。*/  
  17.     struct zonelist node_zonelists[MAX_ZONELISTS];  
  18.       /*可用內存區數目,即node_zones數據中保存的最后一個有效區域的索引*/  
  19.     int nr_zones;  
  20. #ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */  
  21.      /* 在平坦型的內存模型中,它指向本節點第一個頁面的描述符。 */  
  22.     struct page *node_mem_map;  
  23. #ifdef CONFIG_CGROUP_MEM_RES_CTLR  
  24.     /*cgroup相關*/  
  25.     struct page_cgroup *node_page_cgroup;  
  26. #endif  
  27. #endif  
  28.   /** 
  29.           * 在內存子系統初始化以前,即boot階段也需要進行內存管理。 
  30.           * 此結構用於這個階段的內存管理。 
  31.           */  
  32.     struct bootmem_data *bdata;  
  33. #ifdef CONFIG_MEMORY_HOTPLUG  
  34.     /* 
  35.      * Must be held any time you expect node_start_pfn, node_present_pages 
  36.      * or node_spanned_pages stay constant.  Holding this will also 
  37.      * guarantee that any pfn_valid() stays that way. 
  38.      * 
  39.      * Nests above zone->lock and zone->size_seqlock. 
  40.      */  
  41.         /*當系統支持內存熱插撥時,用於保護本結構中的與節點大小相關的字段。 
  42.             哪調用node_start_pfn,node_present_pages,node_spanned_pages相關的代碼時,需要使用該鎖。 
  43.           */  
  44.     spinlock_t node_size_lock;  
  45. #endif  
  46.     /*起始頁面幀號,指出該節點在全局mem_map中 
  47.     的偏移*/  
  48.     unsigned long node_start_pfn;  
  49.     unsigned long node_present_pages; /* total number of physical pages */  
  50.     unsigned long node_spanned_pages; /* total size of physical page range, including holes */  
  51.     /*節點編號*/                          
  52.     int node_id;  
  53.     /*等待該節點內的交換守護進程的等待隊列。將節點中的頁幀換出時會用到。*/  
  54.     wait_queue_head_t kswapd_wait;  
  55.     /*負責該節點的交換守護進程。*/  
  56.     struct task_struct *kswapd;  
  57.     /*由頁交換子系統使用,定義要釋放的區域大小。*/  
  58.     int kswapd_max_order;  
  59. } pg_data_t;  

管理區

 

每個管理區由一個zone結構體描述,對於管理區的類型描述如下

 

[cpp]  view plain copy print ?
 
  1. enum zone_type {  
  2. #ifdef CONFIG_ZONE_DMA  
  3.     /* 
  4.      * ZONE_DMA is used when there are devices that are not able 
  5.      * to do DMA to all of addressable memory (ZONE_NORMAL). Then we 
  6.      * carve out the portion of memory that is needed for these devices. 
  7.      * The range is arch specific. 
  8.      * 
  9.      * Some examples 
  10.      * 
  11.      * Architecture     Limit 
  12.      * --------------------------- 
  13.      * parisc, ia64, sparc  <4G 
  14.      * s390         <2G 
  15.      * arm          Various 
  16.      * alpha        Unlimited or 0-16MB. 
  17.      * 
  18.      * i386, x86_64 and multiple other arches 
  19.      *          <16M. 
  20.      */  
  21.     ZONE_DMA,  
  22. #endif  
  23. #ifdef CONFIG_ZONE_DMA32  
  24.     /* 
  25.      * x86_64 needs two ZONE_DMAs because it supports devices that are 
  26.      * only able to do DMA to the lower 16M but also 32 bit devices that 
  27.      * can only do DMA areas below 4G. 
  28.      */  
  29.     ZONE_DMA32,  
  30. #endif  
  31.     /* 
  32.      * Normal addressable memory is in ZONE_NORMAL. DMA operations can be 
  33.      * performed on pages in ZONE_NORMAL if the DMA devices support 
  34.      * transfers to all addressable memory. 
  35.      */  
  36.     ZONE_NORMAL,  
  37. #ifdef CONFIG_HIGHMEM  
  38.     /* 
  39.      * A memory area that is only addressable by the kernel through 
  40.      * mapping portions into its own address space. This is for example 
  41.      * used by i386 to allow the kernel to address the memory beyond 
  42.      * 900MB. The kernel will set up special mappings (page 
  43.      * table entries on i386) for each page that the kernel needs to 
  44.      * access. 
  45.      */  
  46.     ZONE_HIGHMEM,  
  47. #endif  
  48.     /* 
  49.           這是一個偽內存段。為了防止形成物理內存碎片, 
  50.           可以將虛擬地址對應的物理地址進行遷移。 
  51.           */  
  52.     ZONE_MOVABLE,  
  53.     __MAX_NR_ZONES  
  54. };  

里面的英文注釋已經寫的很詳細了。

 

管理區用於跟蹤諸如頁面使用情況統計數,空閑區域信息和鎖信息等。

[cpp]  view plain copy print ?
 
  1. struct zone {  
  2.     /* Fields commonly accessed by the page allocator */  
  3.   
  4.     /* zone watermarks, access with *_wmark_pages(zone) macros */  
  5.     /*本管理區的三個水線值:高水線(比較充足)、低水線、MIN水線。*/  
  6.     unsigned long watermark[NR_WMARK];  
  7.   
  8.     /* 
  9.      * We don't know if the memory that we're going to allocate will be freeable 
  10.      * or/and it will be released eventually, so to avoid totally wasting several 
  11.      * GB of ram we must reserve some of the lower zone memory (otherwise we risk 
  12.      * to run OOM on the lower zones despite there's tons of freeable ram 
  13.      * on the higher zones). This array is recalculated at runtime if the 
  14.      * sysctl_lowmem_reserve_ratio sysctl changes. 
  15.      */  
  16.       /** 
  17.           * 當高端內存、normal內存區域中無法分配到內存時,需要從normal、DMA區域中分配內存。 
  18.           * 為了避免DMA區域被消耗光,需要額外保留一些內存供驅動使用。 
  19.           * 該字段就是指從上級內存區退到回內存區時,需要額外保留的內存數量。 
  20.           */  
  21.     unsigned long       lowmem_reserve[MAX_NR_ZONES];  
  22.   
  23. #ifdef CONFIG_NUMA  
  24.     /*所屬的NUMA節點。*/  
  25.     int node;  
  26.     /* 
  27.      * zone reclaim becomes active if more unmapped pages exist. 
  28.      */  
  29.      /*當可回收的頁超過此值時,將進行頁面回收。*/  
  30.     unsigned long       min_unmapped_pages;  
  31.     /*當管理區中,用於slab的可回收頁大於此值時,將回收slab中的緩存頁。*/  
  32.     unsigned long       min_slab_pages;  
  33.     /* 
  34.           * 每CPU的頁面緩存。 
  35.           * 當分配單個頁面時,首先從該緩存中分配頁面。這樣可以: 
  36.           *避免使用全局的鎖 
  37.           * 避免同一個頁面反復被不同的CPU分配,引起緩存行的失效。 
  38.           * 避免將管理區中的大塊分割成碎片。 
  39.           */  
  40.     struct per_cpu_pageset  *pageset[NR_CPUS];  
  41. #else  
  42.     struct per_cpu_pageset  pageset[NR_CPUS];  
  43. #endif  
  44.     /* 
  45.      * free areas of different sizes 
  46.      */  
  47.      /*該鎖用於保護伙伴系統數據結構。即保護free_area相關數據。*/  
  48.     spinlock_t      lock;  
  49. #ifdef CONFIG_MEMORY_HOTPLUG  
  50.     /* see spanned/present_pages for more description */  
  51.     /*用於保護spanned/present_pages等變量。這些變量幾乎不會發生變化,除非發生了內存熱插撥操作。 
  52.            這幾個變量並不被lock字段保護。並且主要用於讀,因此使用讀寫鎖。*/  
  53.     seqlock_t       span_seqlock;  
  54. #endif  
  55.     /*伙伴系統的主要變量。這個數組定義了11個隊列,每個隊列中的元素都是大小為2^n的頁面*/  
  56.     struct free_area    free_area[MAX_ORDER];  
  57.   
  58. #ifndef CONFIG_SPARSEMEM  
  59.     /* 
  60.      * Flags for a pageblock_nr_pages block. See pageblock-flags.h. 
  61.      * In SPARSEMEM, this map is stored in struct mem_section 
  62.      */  
  63.      /*本管理區里的頁面標志數組*/  
  64.     unsigned long       *pageblock_flags;  
  65. #endif /* CONFIG_SPARSEMEM */  
  66.   
  67.     /*填充的未用字段,確保后面的字段是緩存行對齊的*/  
  68.     ZONE_PADDING(_pad1_)  
  69.   
  70.     /* Fields commonly accessed by the page reclaim scanner */  
  71.     /* 
  72.           * lru相關的字段用於內存回收。這個字段用於保護這幾個回收相關的字段。 
  73.           * lru用於確定哪些字段是活躍的,哪些不是活躍的,並據此確定應當被寫回到磁盤以釋放內存。 
  74.           */  
  75.     spinlock_t      lru_lock;     
  76.     /* 匿名活動頁、匿名不活動頁、文件活動頁、文件不活動頁鏈表頭*/  
  77.     struct zone_lru {  
  78.         struct list_head list;  
  79.     } lru[NR_LRU_LISTS];  
  80.     /*頁面回收狀態*/  
  81.     struct zone_reclaim_stat reclaim_stat;  
  82.     /*自從最后一次回收頁面以來,掃過的頁面數*/  
  83.     unsigned long       pages_scanned;     /* since last reclaim */  
  84.     unsigned long       flags;         /* zone flags, see below */  
  85.   
  86.     /* Zone statistics */  
  87.     atomic_long_t       vm_stat[NR_VM_ZONE_STAT_ITEMS];  
  88.   
  89.     /* 
  90.      * prev_priority holds the scanning priority for this zone.  It is 
  91.      * defined as the scanning priority at which we achieved our reclaim 
  92.      * target at the previous try_to_free_pages() or balance_pgdat() 
  93.      * invokation. 
  94.      * 
  95.      * We use prev_priority as a measure of how much stress page reclaim is 
  96.      * under - it drives the swappiness decision: whether to unmap mapped 
  97.      * pages. 
  98.      * 
  99.      * Access to both this field is quite racy even on uniprocessor.  But 
  100.      * it is expected to average out OK. 
  101.      */  
  102.     int prev_priority;  
  103.   
  104.     /* 
  105.      * The target ratio of ACTIVE_ANON to INACTIVE_ANON pages on 
  106.      * this zone's LRU.  Maintained by the pageout code. 
  107.      */  
  108.     unsigned int inactive_ratio;  
  109.   
  110.     /*為cache對齊*/  
  111.     ZONE_PADDING(_pad2_)  
  112.     /* Rarely used or read-mostly fields */  
  113.   
  114.     /* 
  115.      * wait_table       -- the array holding the hash table 
  116.      * wait_table_hash_nr_entries   -- the size of the hash table array 
  117.      * wait_table_bits  -- wait_table_size == (1 << wait_table_bits) 
  118.      * 
  119.      * The purpose of all these is to keep track of the people 
  120.      * waiting for a page to become available and make them 
  121.      * runnable again when possible. The trouble is that this 
  122.      * consumes a lot of space, especially when so few things 
  123.      * wait on pages at a given time. So instead of using 
  124.      * per-page waitqueues, we use a waitqueue hash table. 
  125.      * 
  126.      * The bucket discipline is to sleep on the same queue when 
  127.      * colliding and wake all in that wait queue when removing. 
  128.      * When something wakes, it must check to be sure its page is 
  129.      * truly available, a la thundering herd. The cost of a 
  130.      * collision is great, but given the expected load of the 
  131.      * table, they should be so rare as to be outweighed by the 
  132.      * benefits from the saved space. 
  133.      * 
  134.      * __wait_on_page_locked() and unlock_page() in mm/filemap.c, are the 
  135.      * primary users of these fields, and in mm/page_alloc.c 
  136.      * free_area_init_core() performs the initialization of them. 
  137.      */  
  138.     wait_queue_head_t   * wait_table;  
  139.     unsigned long       wait_table_hash_nr_entries;  
  140.     unsigned long       wait_table_bits;  
  141.   
  142.     /* 
  143.      * Discontig memory support fields. 
  144.      */  
  145.      /*管理區屬於的節點*/  
  146.     struct pglist_data  *zone_pgdat;  
  147.     /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */  
  148.     /*管理區的頁面在mem_map中的偏移*/  
  149.     unsigned long       zone_start_pfn;  
  150.   
  151.     /* 
  152.      * zone_start_pfn, spanned_pages and present_pages are all 
  153.      * protected by span_seqlock.  It is a seqlock because it has 
  154.      * to be read outside of zone->lock, and it is done in the main 
  155.      * allocator path.  But, it is written quite infrequently. 
  156.      * 
  157.      * The lock is declared along with zone->lock because it is 
  158.      * frequently read in proximity to zone->lock.  It's good to 
  159.      * give them a chance of being in the same cacheline. 
  160.      */  
  161.     unsigned long       spanned_pages;  /* total size, including holes */  
  162.     unsigned long       present_pages;  /* amount of memory (excluding holes) */  
  163.   
  164.     /* 
  165.      * rarely used fields: 
  166.      */  
  167.     const char      *name;  
  168. } ____cacheline_internodealigned_in_smp;  

沒有說明的地方,內核中的英文注釋已經寫得很清楚了。

 

頁面

系統中每個物理頁面都有一個相關聯的page用於記錄該頁面的狀態。

 

[cpp]  view plain copy print ?
 
  1. /* 
  2.  * Each physical page in the system has a struct page associated with 
  3.  * it to keep track of whatever it is we are using the page for at the 
  4.  * moment. Note that we have no way to track which tasks are using 
  5.  * a page, though if it is a pagecache page, rmap structures can tell us 
  6.  * who is mapping it. 
  7.  */  
  8. struct page {  
  9.     unsigned long flags;        /* Atomic flags, some possibly 
  10.                      * updated asynchronously */  
  11.     atomic_t _count;        /* Usage count, see below. */  
  12.     union {  
  13.         atomic_t _mapcount; /* Count of ptes mapped in mms, 
  14.                      * to show when page is mapped 
  15.                      * & limit reverse map searches. 
  16.                      */  
  17.         struct {        /* SLUB */  
  18.             u16 inuse;  
  19.             u16 objects;  
  20.         };  
  21.     };  
  22.     union {  
  23.         struct {  
  24.         unsigned long private;      /* Mapping-private opaque data: 
  25.                          * usually used for buffer_heads 
  26.                          * if PagePrivate set; used for 
  27.                          * swp_entry_t if PageSwapCache; 
  28.                          * indicates order in the buddy 
  29.                          * system if PG_buddy is set. 
  30.                          */  
  31.         struct address_space *mapping;  /* If low bit clear, points to 
  32.                          * inode address_space, or NULL. 
  33.                          * If page mapped as anonymous 
  34.                          * memory, low bit is set, and 
  35.                          * it points to anon_vma object: 
  36.                          * see PAGE_MAPPING_ANON below. 
  37.                          */  
  38.         };  
  39. #if USE_SPLIT_PTLOCKS  
  40.         spinlock_t ptl;  
  41. #endif  
  42.         struct kmem_cache *slab;    /* SLUB: Pointer to slab */  
  43.     /* 如果屬於伙伴系統,並且不是伙伴系統中的第一個頁 
  44.     則指向第一個頁*/  
  45.         struct page *first_page;    /* Compound tail pages */  
  46.     };  
  47.     union {/*如果是文件映射,那么表示本頁面在文件中的位置(偏移)*/  
  48.         pgoff_t index;      /* Our offset within mapping. */  
  49.         void *freelist;     /* SLUB: freelist req. slab lock */  
  50.     };  
  51.     struct list_head lru;       /* Pageout list, eg. active_list 
  52.                      * protected by zone->lru_lock ! 
  53.                      */  
  54.     /* 
  55.      * On machines where all RAM is mapped into kernel address space, 
  56.      * we can simply calculate the virtual address. On machines with 
  57.      * highmem some memory is mapped into kernel virtual memory 
  58.      * dynamically, so we need a place to store that address. 
  59.      * Note that this field could be 16 bits on x86 ... ;) 
  60.      * 
  61.      * Architectures with slow multiplication can define 
  62.      * WANT_PAGE_VIRTUAL in asm/page.h 
  63.      */  
  64. #if defined(WANT_PAGE_VIRTUAL)  
  65.     void *virtual;          /* Kernel virtual address (NULL if 
  66.                        not kmapped, ie. highmem) */  
  67. #endif /* WANT_PAGE_VIRTUAL */  
  68. #ifdef CONFIG_WANT_PAGE_DEBUG_FLAGS  
  69.     unsigned long debug_flags;  /* Use atomic bitops on this */  
  70. #endif  
  71.   
  72. #ifdef CONFIG_KMEMCHECK  
  73.     /* 
  74.      * kmemcheck wants to track the status of each byte in a page; this 
  75.      * is a pointer to such a status block. NULL if not tracked. 
  76.      */  
  77.     void *shadow;  
  78. #endif  
  79. };  

linux中主要的結構描述體現了linux物理內存管理的設計。后面會介紹linux內存管理的各個細節。


 

版權聲明:本文為博主原創文章,未經博主允許不得轉載。

linux內存管理概述

 

linux內存管理建立在基本的分頁機制基礎上,在linux內核中RAM的某些部分將會永久的分配給內核,並用來存放內核代碼以及靜態內核數據結構。RAM的其余部分稱為動態內存,這不僅是進程所需的寶貴資源,也是內核本身所需的寶貴資源。實際上,整個系統的性能取決於如何有效地管理動態內存。因此,現在所有多任務操作系統都在經歷優化對動態內存的使用,也就是說,盡可能做到當要時分配,不需要時釋放。

內存管理是os中最復雜的管理機制之一。linux中采用了很多有效的管理方法,包括頁表管理、高端內存(臨時映射區、固定映射區、永久映射區、非連續內存區)管理、為減小外部碎片的伙伴系統、為減小內部碎片的slab機制、伙伴系統未建立之前的頁面分配制度以及緊急內存管理等等。這些在后面的具體部分會詳細進行分析總結。

本來想自己畫張圖,但當我看到這張圖,我決定不畫了。這張圖來自http://bbs.chinaunix.net/thread-2018659-2-1.html,畫的很好,基本上說明了linux內存概況。

版權聲明:本文為博主原創文章,未經博主允許不得轉載。

 

linux物理內存探測

http://blog.csdn.net/bullbat/article/details/7167870

linux在被bootloader加載到內存后, cpu最初執行的linux內核代碼是/header.S文件中的start_of_setup函數,這個函數在做了一些准備工作后會跳轉到boot目下文件main.c的main函數執行,在這個main函數中我們可以第一次看到與內存管理相關的代碼,這段代碼調用detect_memeory()函數檢測系統物理內存

在header.S中執行下面匯編代碼:

 

[cpp]  view plain copy print ?
 
  1. start_of_setup:  
  2.        .....  
  3. # Jump to C code (should not return)  
  4.     calll   main  
  5.        .....  

跳到boot目錄下的main.c文件中

[cpp]  view plain copy print ?
 
  1. void main(void)  
  2. {  
  3.         ......  
  4.     /* Detect memory layout */  
  5.     detect_memory();/*內存探測函數*/  
  6.     ......  
  7. }  

 

[cpp]  view plain copy print ?
 
  1. int detect_memory(void)  
  2. {  
  3.     int err = -1;  
  4.   
  5.     if (detect_memory_e820() > 0)  
  6.         err = 0;  
  7.   
  8.     if (!detect_memory_e801())  
  9.         err = 0;  
  10.   
  11.     if (!detect_memory_88())  
  12.         err = 0;  
  13.   
  14.     return err;  
  15. }  

由上面的代碼可知,linux內核會分別嘗試調用detect_memory_e820()、detcct_memory_e801()、detect_memory_88()獲得系統物理內存布局,這3個函數內部其實都會以內聯匯編的形式調用bios中斷以取得內存信息,該中斷調用形式為int 0x15,同時調用前分別把AX寄存器設置為0xe820h、0xe801h、0x88h,關於0x15號中斷有興趣的可以去查詢相關手冊。下面分析detect_memory_e820()的代碼,其它代碼基本一樣。

 

 

[cpp]  view plain copy print ?
 
  1. #define SMAP    0x534d4150  /* ASCII "SMAP" */  
  2. /*由於歷史原因,一些i/o設備也會占據一部分內存 
  3. 物理地址空間,因此系統可以使用的物理內存空 
  4. 間是不連續的,系統內存被分成了很多段,每個段 
  5. 的屬性也是不一樣的。int 0x15 查詢物理內存時每次 
  6. 返回一個內存段的信息,因此要想返回系統中所有 
  7. 的物理內存,我們必須以迭代的方式去查詢。 
  8. detect_memory_e820()函數把int 0x15放到一個do-while循環里, 
  9. 每次得到的一個內存段放到struct e820entry里,而 
  10. struct e820entry的結構正是e820返回結果的結構!而像 
  11. 其它啟動時獲得的結果一樣,最終都會被放到 
  12. boot_params里,e820被放到了 boot_params.e820_map。 
  13. */  
  14. static int detect_memory_e820(void)  
  15. {  
  16.     int count = 0;/*用於記錄已檢測到的物理內存數目*/  
  17.     struct biosregs ireg, oreg;  
  18.     struct e820entry *desc = boot_params.e820_map;  
  19.     static struct e820entry buf; /* static so it is zeroed */  
  20.   
  21.     initregs(&ireg);/*初始化ireg中的相關寄存器*/  
  22.     ireg.ax  = 0xe820;  
  23.     ireg.cx  = sizeof buf;/*e820entry數據結構大小*/  
  24.     ireg.edx = SMAP;/*標識*/  
  25.     ireg.di  = (size_t)&buf;/*int15返回值的存放處*/  
  26.   
  27.     /* 
  28.      * Note: at least one BIOS is known which assumes that the 
  29.      * buffer pointed to by one e820 call is the same one as 
  30.      * the previous call, and only changes modified fields.  Therefore, 
  31.      * we use a temporary buffer and copy the results entry by entry. 
  32.      * 
  33.      * This routine deliberately does not try to account for 
  34.      * ACPI 3+ extended attributes.  This is because there are 
  35.      * BIOSes in the field which report zero for the valid bit for 
  36.      * all ranges, and we don't currently make any use of the 
  37.      * other attribute bits.  Revisit this if we see the extended 
  38.      * attribute bits deployed in a meaningful way in the future. 
  39.      */  
  40.   
  41.     do {  
  42.         /*在執行這條內聯匯編語句時輸入的參數有: 
  43.         eax寄存器=0xe820 
  44.         dx寄存器=’SMAP’ 
  45.         edi寄存器=desc 
  46.         ebx寄存器=next 
  47.         ecx寄存器=size 
  48.          
  49.         返回給c語言代碼的參數有: 
  50.         id=eax寄存器 
  51.         rr=edx寄存器 
  52.         ext=ebx寄存器 
  53.         size=ecx寄存器 
  54.         desc指向的內存地址在執行0x15中斷調用時被設置 
  55.         */  
  56.         intcall(0x15, &ireg, &oreg);  
  57.         /*選擇下一個*/  
  58.         ireg.ebx = oreg.ebx; /* for next iteration... */  
  59.   
  60.         /* BIOSes which terminate the chain with CF = 1 as opposed 
  61.            to %ebx = 0 don't always report the SMAP signature on 
  62.            the final, failing, probe. */  
  63.         if (oreg.eflags & X86_EFLAGS_CF)  
  64.             break;  
  65.   
  66.         /* Some BIOSes stop returning SMAP in the middle of 
  67.            the search loop.  We don't know exactly how the BIOS 
  68.            screwed up the map at that point, we might have a 
  69.            partial map, the full map, or complete garbage, so 
  70.            just return failure. */  
  71.         if (oreg.eax != SMAP) {  
  72.             count = 0;  
  73.             break;  
  74.         }  
  75.   
  76.         *desc++ = buf;/*將buf賦值給desc*/  
  77.         count++;/*探測數加一*/  
  78.     }   
  79.     while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map));  
  80.     /*將內存塊數保持到變量中*/  
  81.     return boot_params.e820_entries = count;  
  82. }  

其中存放中斷返回值得結構如下

 

 

[cpp]  view plain copy print ?
 
  1. struct e820entry {  
  2.     __u64 addr; /* start of memory segment */  
  3.     __u64 size; /* size of memory segment */  
  4.     __u32 type; /* type of memory segment */  
  5. } __attribute__((packed));  

在內核初始化跳入start_kernel函數后執行以下初始化

 

start_kernel()->setup_arch()->setup_memory_map()

 

[cpp]  view plain copy print ?
 
  1. /*調用x86_init.resources.memory_setup()實現對e820內存圖的優化, 
  2. 將e820中得值保存在e820_saved中,打印內存圖 
  3. */  
  4. void __init setup_memory_map(void)  
  5. {  
  6.     char *who;  
  7.     /*調用x86體系下的memory_setup函數*/  
  8.     who = x86_init.resources.memory_setup();  
  9.     /*保存到e820_saved中*/  
  10.     memcpy(&e820_saved, &e820, sizeof(struct e820map));  
  11.     printk(KERN_INFO "BIOS-provided physical RAM map:\n");  
  12.     /*打印輸出*/  
  13.     e820_print_map(who);  
  14. }  

 

在x86_init.c中定義了x86下的memory_setup函數

[cpp]  view plain copy print ?
 
  1. struct x86_init_ops x86_init __initdata = {  
  2.   
  3.     .resources = {  
  4.         ……  
  5.         .memory_setup       = default_machine_specific_memory_setup,  
  6.     },  
  7.         ……  
  8. };  
[cpp]  view plain copy print ?
 
  1. char *__init default_machine_specific_memory_setup(void)  
  2. {  
  3.     char *who = "BIOS-e820";  
  4.     u32 new_nr;  
  5.     /* 
  6.      * Try to copy the BIOS-supplied E820-map. 
  7.      * 
  8.      * Otherwise fake a memory map; one section from 0k->640k, 
  9.      * the next section from 1mb->appropriate_mem_k 
  10.      */  
  11.     new_nr = boot_params.e820_entries;  
  12.     /*將重疊的去除*/  
  13.     sanitize_e820_map(boot_params.e820_map,  
  14.             ARRAY_SIZE(boot_params.e820_map),  
  15.             &new_nr);  
  16.     /*去掉重疊的部分后得到的內存個數*/  
  17.     boot_params.e820_entries = new_nr;  
  18.     /*將其賦值到全局變量e820中,小於0時,為出錯處理*/  
  19.     if (append_e820_map(boot_params.e820_map, boot_params.e820_entries)  
  20.       < 0) {  
  21.         ……  
  22.     }  
  23.   
  24.     /* In case someone cares... */  
  25.     return who;  
  26. }  

append_e820_map調用__append_e820_map實現

 

[cpp]  view plain copy print ?
 
  1. static int __init __append_e820_map(struct e820entry *biosmap, int nr_map)  
  2. {  
  3.     while (nr_map) {/*循環nr_map次調用,添加內存塊到e820*/  
  4.         u64 start = biosmap->addr;  
  5.         u64 size = biosmap->size;  
  6.         u64 end = start + size;  
  7.         u32 type = biosmap->type;  
  8.         /* Overflow in 64 bits? Ignore the memory map. */  
  9.         if (start > end)  
  10.             return -1;  
  11.         /*添加函數*/  
  12.         e820_add_region(start, size, type);  
  13.         biosmap++;  
  14.         nr_map--;  
  15.     }  
  16.     return 0;  
  17. }  

 

[cpp]  view plain copy print ?
 
  1. void __init e820_add_region(u64 start, u64 size, int type)  
  2. {  
  3.     __e820_add_region(&e820, start, size, type);  
  4. }  

 

e820為e820map結構

[cpp]  view plain copy print ?
 
  1. struct e820map {  
  2.     __u32 nr_map;  
  3.     struct e820entry map[E820_X_MAX];  
  4. };  

其中E820_X_MAX大小為128.

[cpp]  view plain copy print ?
 
  1. tatic void __init __e820_add_region(struct e820map *e820x, u64 start, u64 size,  
  2.                      int type)  
  3. {  
  4.     int x = e820x->nr_map;  
  5.   
  6.     if (x >= ARRAY_SIZE(e820x->map)) {  
  7.         printk(KERN_ERR "Ooops! Too many entries in the memory map!\n");  
  8.         return;  
  9.     }  

到這里,物理內存就已經從BIOS中讀出來存放到全局變量e820中,e820是linux內核中用於建立內存管理框架的基礎。在后面我們會看到,建立初始化節點、管理區會用到他。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM