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. 第一點是關於運行時堆的。
為說明這個問題,我們先運行一個測試程序,並觀察其結果:
- #include <stdio.h>
- intmain(intargc, char* argv[])
- {
- int first = 0;
- int* p0 = malloc(1024);
- int* p1 = malloc(1024 * 1024);
- int* p2 = malloc(512 * 1024 * 1024 );
- int* p3 = malloc(1024 * 1024 * 1024 );
- printf("main=%p print=%p/n", main, printf);
- printf("first=%p/n", &first);
- printf("p0=%p p1=%p p2=%p p3=%p/n", p0, p1, p2, p3);
- getchar();
- return 0;
- }
運行后,輸出結果為:
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的棧空間,事實上,在多線程情況下,能用的棧空間是非常有限的。為了說明這個問題,我們再看另外一個測試:
- #include <stdio.h>
- #include <pthread.h>
- void* thread_proc(void* param)
- {
- int first = 0;
- int* p0 = malloc(1024);
- int* p1 = malloc(1024 * 1024);
- printf("(0x%x): first=%p/n", pthread_self(), &first);
- printf("(0x%x): p0=%p p1=%p /n", pthread_self(), p0, p1);
- return 0;
- }
- #define N 5
- intmain(intargc, char* argv[])
- {
- intfirst = 0;
- inti= 0;
- void* ret = NULL;
- pthread_t tid[N] = {0};
- printf("first=%p/n", &first);
- for(i = 0; i < N; i++)
- {
- pthread_create(tid+i, NULL, thread_proc, NULL);
- }
- for(i = 0; i < N; i++)
- {
- pthread_join(tid[i], &ret);
- }
- return 0;
- }
運行后,輸出結果為:
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錯誤,或者出現莫名其妙的錯誤。
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上運行,因此從當前節點得到的內存很可能被用到。
- /*
- * The pg_data_t structure is used in machines with CONFIG_DISCONTIGMEM
- * (mostly NUMA machines?) to denote a higher-level memory zone than the
- * zone denotes.
- *
- * On NUMA machines, each NUMA node would have a pg_data_t to describe
- * it's memory layout.
- *
- * Memory statistics and page replacement data structures are maintained on a
- * per-zone basis.
- */
- struct bootmem_data;
- typedef struct pglist_data {
- /*該節點內的內存區。可能的區域類型用zone_type表示。 */
- struct zone node_zones[MAX_NR_ZONES];
- /* 該節點的備用內存區。當節點沒有可用內存時,就從備用區中分配內存。*/
- struct zonelist node_zonelists[MAX_ZONELISTS];
- /*可用內存區數目,即node_zones數據中保存的最后一個有效區域的索引*/
- int nr_zones;
- #ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
- /* 在平坦型的內存模型中,它指向本節點第一個頁面的描述符。 */
- struct page *node_mem_map;
- #ifdef CONFIG_CGROUP_MEM_RES_CTLR
- /*cgroup相關*/
- struct page_cgroup *node_page_cgroup;
- #endif
- #endif
- /**
- * 在內存子系統初始化以前,即boot階段也需要進行內存管理。
- * 此結構用於這個階段的內存管理。
- */
- struct bootmem_data *bdata;
- #ifdef CONFIG_MEMORY_HOTPLUG
- /*
- * Must be held any time you expect node_start_pfn, node_present_pages
- * or node_spanned_pages stay constant. Holding this will also
- * guarantee that any pfn_valid() stays that way.
- *
- * Nests above zone->lock and zone->size_seqlock.
- */
- /*當系統支持內存熱插撥時,用於保護本結構中的與節點大小相關的字段。
- 哪調用node_start_pfn,node_present_pages,node_spanned_pages相關的代碼時,需要使用該鎖。
- */
- spinlock_t node_size_lock;
- #endif
- /*起始頁面幀號,指出該節點在全局mem_map中
- 的偏移*/
- 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;
管理區
每個管理區由一個zone結構體描述,對於管理區的類型描述如下
- enum zone_type {
- #ifdef CONFIG_ZONE_DMA
- /*
- * ZONE_DMA is used when there are devices that are not able
- * to do DMA to all of addressable memory (ZONE_NORMAL). Then we
- * carve out the portion of memory that is needed for these devices.
- * The range is arch specific.
- *
- * Some examples
- *
- * Architecture Limit
- * ---------------------------
- * parisc, ia64, sparc <4G
- * s390 <2G
- * arm Various
- * alpha Unlimited or 0-16MB.
- *
- * i386, x86_64 and multiple other arches
- * <16M.
- */
- ZONE_DMA,
- #endif
- #ifdef CONFIG_ZONE_DMA32
- /*
- * x86_64 needs two ZONE_DMAs because it supports devices that are
- * only able to do DMA to the lower 16M but also 32 bit devices that
- * can only do DMA areas below 4G.
- */
- ZONE_DMA32,
- #endif
- /*
- * Normal addressable memory is in ZONE_NORMAL. DMA operations can be
- * performed on pages in ZONE_NORMAL if the DMA devices support
- * transfers to all addressable memory.
- */
- ZONE_NORMAL,
- #ifdef CONFIG_HIGHMEM
- /*
- * A memory area that is only addressable by the kernel through
- * mapping portions into its own address space. This is for example
- * used by i386 to allow the kernel to address the memory beyond
- * 900MB. The kernel will set up special mappings (page
- * table entries on i386) for each page that the kernel needs to
- * access.
- */
- ZONE_HIGHMEM,
- #endif
- /*
- 這是一個偽內存段。為了防止形成物理內存碎片,
- 可以將虛擬地址對應的物理地址進行遷移。
- */
- ZONE_MOVABLE,
- __MAX_NR_ZONES
- };
里面的英文注釋已經寫的很詳細了。
管理區用於跟蹤諸如頁面使用情況統計數,空閑區域信息和鎖信息等。
- struct zone {
- /* Fields commonly accessed by the page allocator */
- /* zone watermarks, access with *_wmark_pages(zone) macros */
- /*本管理區的三個水線值:高水線(比較充足)、低水線、MIN水線。*/
- unsigned long watermark[NR_WMARK];
- /*
- * We don't know if the memory that we're going to allocate will be freeable
- * or/and it will be released eventually, so to avoid totally wasting several
- * GB of ram we must reserve some of the lower zone memory (otherwise we risk
- * to run OOM on the lower zones despite there's tons of freeable ram
- * on the higher zones). This array is recalculated at runtime if the
- * sysctl_lowmem_reserve_ratio sysctl changes.
- */
- /**
- * 當高端內存、normal內存區域中無法分配到內存時,需要從normal、DMA區域中分配內存。
- * 為了避免DMA區域被消耗光,需要額外保留一些內存供驅動使用。
- * 該字段就是指從上級內存區退到回內存區時,需要額外保留的內存數量。
- */
- unsigned long lowmem_reserve[MAX_NR_ZONES];
- #ifdef CONFIG_NUMA
- /*所屬的NUMA節點。*/
- int node;
- /*
- * zone reclaim becomes active if more unmapped pages exist.
- */
- /*當可回收的頁超過此值時,將進行頁面回收。*/
- unsigned long min_unmapped_pages;
- /*當管理區中,用於slab的可回收頁大於此值時,將回收slab中的緩存頁。*/
- unsigned long min_slab_pages;
- /*
- * 每CPU的頁面緩存。
- * 當分配單個頁面時,首先從該緩存中分配頁面。這樣可以:
- *避免使用全局的鎖
- * 避免同一個頁面反復被不同的CPU分配,引起緩存行的失效。
- * 避免將管理區中的大塊分割成碎片。
- */
- struct per_cpu_pageset *pageset[NR_CPUS];
- #else
- struct per_cpu_pageset pageset[NR_CPUS];
- #endif
- /*
- * free areas of different sizes
- */
- /*該鎖用於保護伙伴系統數據結構。即保護free_area相關數據。*/
- spinlock_t lock;
- #ifdef CONFIG_MEMORY_HOTPLUG
- /* see spanned/present_pages for more description */
- /*用於保護spanned/present_pages等變量。這些變量幾乎不會發生變化,除非發生了內存熱插撥操作。
- 這幾個變量並不被lock字段保護。並且主要用於讀,因此使用讀寫鎖。*/
- seqlock_t span_seqlock;
- #endif
- /*伙伴系統的主要變量。這個數組定義了11個隊列,每個隊列中的元素都是大小為2^n的頁面*/
- struct free_area free_area[MAX_ORDER];
- #ifndef CONFIG_SPARSEMEM
- /*
- * Flags for a pageblock_nr_pages block. See pageblock-flags.h.
- * In SPARSEMEM, this map is stored in struct mem_section
- */
- /*本管理區里的頁面標志數組*/
- unsigned long *pageblock_flags;
- #endif /* CONFIG_SPARSEMEM */
- /*填充的未用字段,確保后面的字段是緩存行對齊的*/
- ZONE_PADDING(_pad1_)
- /* Fields commonly accessed by the page reclaim scanner */
- /*
- * lru相關的字段用於內存回收。這個字段用於保護這幾個回收相關的字段。
- * lru用於確定哪些字段是活躍的,哪些不是活躍的,並據此確定應當被寫回到磁盤以釋放內存。
- */
- spinlock_t lru_lock;
- /* 匿名活動頁、匿名不活動頁、文件活動頁、文件不活動頁鏈表頭*/
- struct zone_lru {
- struct list_head list;
- } lru[NR_LRU_LISTS];
- /*頁面回收狀態*/
- struct zone_reclaim_stat reclaim_stat;
- /*自從最后一次回收頁面以來,掃過的頁面數*/
- unsigned long pages_scanned; /* since last reclaim */
- unsigned long flags; /* zone flags, see below */
- /* Zone statistics */
- atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
- /*
- * prev_priority holds the scanning priority for this zone. It is
- * defined as the scanning priority at which we achieved our reclaim
- * target at the previous try_to_free_pages() or balance_pgdat()
- * invokation.
- *
- * We use prev_priority as a measure of how much stress page reclaim is
- * under - it drives the swappiness decision: whether to unmap mapped
- * pages.
- *
- * Access to both this field is quite racy even on uniprocessor. But
- * it is expected to average out OK.
- */
- int prev_priority;
- /*
- * The target ratio of ACTIVE_ANON to INACTIVE_ANON pages on
- * this zone's LRU. Maintained by the pageout code.
- */
- unsigned int inactive_ratio;
- /*為cache對齊*/
- ZONE_PADDING(_pad2_)
- /* Rarely used or read-mostly fields */
- /*
- * wait_table -- the array holding the hash table
- * wait_table_hash_nr_entries -- the size of the hash table array
- * wait_table_bits -- wait_table_size == (1 << wait_table_bits)
- *
- * The purpose of all these is to keep track of the people
- * waiting for a page to become available and make them
- * runnable again when possible. The trouble is that this
- * consumes a lot of space, especially when so few things
- * wait on pages at a given time. So instead of using
- * per-page waitqueues, we use a waitqueue hash table.
- *
- * The bucket discipline is to sleep on the same queue when
- * colliding and wake all in that wait queue when removing.
- * When something wakes, it must check to be sure its page is
- * truly available, a la thundering herd. The cost of a
- * collision is great, but given the expected load of the
- * table, they should be so rare as to be outweighed by the
- * benefits from the saved space.
- *
- * __wait_on_page_locked() and unlock_page() in mm/filemap.c, are the
- * primary users of these fields, and in mm/page_alloc.c
- * free_area_init_core() performs the initialization of them.
- */
- wait_queue_head_t * wait_table;
- unsigned long wait_table_hash_nr_entries;
- unsigned long wait_table_bits;
- /*
- * Discontig memory support fields.
- */
- /*管理區屬於的節點*/
- struct pglist_data *zone_pgdat;
- /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
- /*管理區的頁面在mem_map中的偏移*/
- unsigned long zone_start_pfn;
- /*
- * zone_start_pfn, spanned_pages and present_pages are all
- * protected by span_seqlock. It is a seqlock because it has
- * to be read outside of zone->lock, and it is done in the main
- * allocator path. But, it is written quite infrequently.
- *
- * The lock is declared along with zone->lock because it is
- * frequently read in proximity to zone->lock. It's good to
- * give them a chance of being in the same cacheline.
- */
- unsigned long spanned_pages; /* total size, including holes */
- unsigned long present_pages; /* amount of memory (excluding holes) */
- /*
- * rarely used fields:
- */
- const char *name;
- } ____cacheline_internodealigned_in_smp;
沒有說明的地方,內核中的英文注釋已經寫得很清楚了。
頁面
系統中每個物理頁面都有一個相關聯的page用於記錄該頁面的狀態。
- /*
- * Each physical page in the system has a struct page associated with
- * it to keep track of whatever it is we are using the page for at the
- * moment. Note that we have no way to track which tasks are using
- * a page, though if it is a pagecache page, rmap structures can tell us
- * who is mapping it.
- */
- struct page {
- unsigned long flags; /* Atomic flags, some possibly
- * updated asynchronously */
- atomic_t _count; /* Usage count, see below. */
- union {
- atomic_t _mapcount; /* Count of ptes mapped in mms,
- * to show when page is mapped
- * & limit reverse map searches.
- */
- struct { /* SLUB */
- u16 inuse;
- u16 objects;
- };
- };
- union {
- struct {
- unsigned long private; /* Mapping-private opaque data:
- * usually used for buffer_heads
- * if PagePrivate set; used for
- * swp_entry_t if PageSwapCache;
- * indicates order in the buddy
- * system if PG_buddy is set.
- */
- struct address_space *mapping; /* If low bit clear, points to
- * inode address_space, or NULL.
- * If page mapped as anonymous
- * memory, low bit is set, and
- * it points to anon_vma object:
- * see PAGE_MAPPING_ANON below.
- */
- };
- #if USE_SPLIT_PTLOCKS
- spinlock_t ptl;
- #endif
- struct kmem_cache *slab; /* SLUB: Pointer to slab */
- /* 如果屬於伙伴系統,並且不是伙伴系統中的第一個頁
- 則指向第一個頁*/
- struct page *first_page; /* Compound tail pages */
- };
- union {/*如果是文件映射,那么表示本頁面在文件中的位置(偏移)*/
- pgoff_t index; /* Our offset within mapping. */
- void *freelist; /* SLUB: freelist req. slab lock */
- };
- struct list_head lru; /* Pageout list, eg. active_list
- * protected by zone->lru_lock !
- */
- /*
- * On machines where all RAM is mapped into kernel address space,
- * we can simply calculate the virtual address. On machines with
- * highmem some memory is mapped into kernel virtual memory
- * dynamically, so we need a place to store that address.
- * Note that this field could be 16 bits on x86 ... ;)
- *
- * Architectures with slow multiplication can define
- * WANT_PAGE_VIRTUAL in asm/page.h
- */
- #if defined(WANT_PAGE_VIRTUAL)
- void *virtual; /* Kernel virtual address (NULL if
- not kmapped, ie. highmem) */
- #endif /* WANT_PAGE_VIRTUAL */
- #ifdef CONFIG_WANT_PAGE_DEBUG_FLAGS
- unsigned long debug_flags; /* Use atomic bitops on this */
- #endif
- #ifdef CONFIG_KMEMCHECK
- /*
- * kmemcheck wants to track the status of each byte in a page; this
- * is a pointer to such a status block. NULL if not tracked.
- */
- void *shadow;
- #endif
- };
linux中主要的結構描述體現了linux物理內存管理的設計。后面會介紹linux內存管理的各個細節。
版權聲明:本文為博主原創文章,未經博主允許不得轉載。
linux內存管理建立在基本的分頁機制基礎上,在linux內核中RAM的某些部分將會永久的分配給內核,並用來存放內核代碼以及靜態內核數據結構。RAM的其余部分稱為動態內存,這不僅是進程所需的寶貴資源,也是內核本身所需的寶貴資源。實際上,整個系統的性能取決於如何有效地管理動態內存。因此,現在所有多任務操作系統都在經歷優化對動態內存的使用,也就是說,盡可能做到當要時分配,不需要時釋放。
內存管理是os中最復雜的管理機制之一。linux中采用了很多有效的管理方法,包括頁表管理、高端內存(臨時映射區、固定映射區、永久映射區、非連續內存區)管理、為減小外部碎片的伙伴系統、為減小內部碎片的slab機制、伙伴系統未建立之前的頁面分配制度以及緊急內存管理等等。這些在后面的具體部分會詳細進行分析總結。
本來想自己畫張圖,但當我看到這張圖,我決定不畫了。這張圖來自http://bbs.chinaunix.net/thread-2018659-2-1.html,畫的很好,基本上說明了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中執行下面匯編代碼:
- start_of_setup:
- .....
- # Jump to C code (should not return)
- calll main
- .....
跳到boot目錄下的main.c文件中
- void main(void)
- {
- ......
- /* Detect memory layout */
- detect_memory();/*內存探測函數*/
- ......
- }
- int detect_memory(void)
- {
- int err = -1;
- if (detect_memory_e820() > 0)
- err = 0;
- if (!detect_memory_e801())
- err = 0;
- if (!detect_memory_88())
- err = 0;
- return err;
- }
由上面的代碼可知,linux內核會分別嘗試調用detect_memory_e820()、detcct_memory_e801()、detect_memory_88()獲得系統物理內存布局,這3個函數內部其實都會以內聯匯編的形式調用bios中斷以取得內存信息,該中斷調用形式為int 0x15,同時調用前分別把AX寄存器設置為0xe820h、0xe801h、0x88h,關於0x15號中斷有興趣的可以去查詢相關手冊。下面分析detect_memory_e820()的代碼,其它代碼基本一樣。
- #define SMAP 0x534d4150 /* ASCII "SMAP" */
- /*由於歷史原因,一些i/o設備也會占據一部分內存
- 物理地址空間,因此系統可以使用的物理內存空
- 間是不連續的,系統內存被分成了很多段,每個段
- 的屬性也是不一樣的。int 0x15 查詢物理內存時每次
- 返回一個內存段的信息,因此要想返回系統中所有
- 的物理內存,我們必須以迭代的方式去查詢。
- detect_memory_e820()函數把int 0x15放到一個do-while循環里,
- 每次得到的一個內存段放到struct e820entry里,而
- struct e820entry的結構正是e820返回結果的結構!而像
- 其它啟動時獲得的結果一樣,最終都會被放到
- boot_params里,e820被放到了 boot_params.e820_map。
- */
- static int detect_memory_e820(void)
- {
- int count = 0;/*用於記錄已檢測到的物理內存數目*/
- struct biosregs ireg, oreg;
- struct e820entry *desc = boot_params.e820_map;
- static struct e820entry buf; /* static so it is zeroed */
- initregs(&ireg);/*初始化ireg中的相關寄存器*/
- ireg.ax = 0xe820;
- ireg.cx = sizeof buf;/*e820entry數據結構大小*/
- ireg.edx = SMAP;/*標識*/
- ireg.di = (size_t)&buf;/*int15返回值的存放處*/
- /*
- * Note: at least one BIOS is known which assumes that the
- * buffer pointed to by one e820 call is the same one as
- * the previous call, and only changes modified fields. Therefore,
- * we use a temporary buffer and copy the results entry by entry.
- *
- * This routine deliberately does not try to account for
- * ACPI 3+ extended attributes. This is because there are
- * BIOSes in the field which report zero for the valid bit for
- * all ranges, and we don't currently make any use of the
- * other attribute bits. Revisit this if we see the extended
- * attribute bits deployed in a meaningful way in the future.
- */
- do {
- /*在執行這條內聯匯編語句時輸入的參數有:
- eax寄存器=0xe820
- dx寄存器=’SMAP’
- edi寄存器=desc
- ebx寄存器=next
- ecx寄存器=size
- 返回給c語言代碼的參數有:
- id=eax寄存器
- rr=edx寄存器
- ext=ebx寄存器
- size=ecx寄存器
- desc指向的內存地址在執行0x15中斷調用時被設置
- */
- intcall(0x15, &ireg, &oreg);
- /*選擇下一個*/
- ireg.ebx = oreg.ebx; /* for next iteration... */
- /* BIOSes which terminate the chain with CF = 1 as opposed
- to %ebx = 0 don't always report the SMAP signature on
- the final, failing, probe. */
- if (oreg.eflags & X86_EFLAGS_CF)
- break;
- /* Some BIOSes stop returning SMAP in the middle of
- the search loop. We don't know exactly how the BIOS
- screwed up the map at that point, we might have a
- partial map, the full map, or complete garbage, so
- just return failure. */
- if (oreg.eax != SMAP) {
- count = 0;
- break;
- }
- *desc++ = buf;/*將buf賦值給desc*/
- count++;/*探測數加一*/
- }
- while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map));
- /*將內存塊數保持到變量中*/
- return boot_params.e820_entries = count;
- }
其中存放中斷返回值得結構如下
- struct e820entry {
- __u64 addr; /* start of memory segment */
- __u64 size; /* size of memory segment */
- __u32 type; /* type of memory segment */
- } __attribute__((packed));
在內核初始化跳入start_kernel函數后執行以下初始化
start_kernel()->setup_arch()->setup_memory_map()
- /*調用x86_init.resources.memory_setup()實現對e820內存圖的優化,
- 將e820中得值保存在e820_saved中,打印內存圖
- */
- void __init setup_memory_map(void)
- {
- char *who;
- /*調用x86體系下的memory_setup函數*/
- who = x86_init.resources.memory_setup();
- /*保存到e820_saved中*/
- memcpy(&e820_saved, &e820, sizeof(struct e820map));
- printk(KERN_INFO "BIOS-provided physical RAM map:\n");
- /*打印輸出*/
- e820_print_map(who);
- }
在x86_init.c中定義了x86下的memory_setup函數
- struct x86_init_ops x86_init __initdata = {
- .resources = {
- ……
- .memory_setup = default_machine_specific_memory_setup,
- },
- ……
- };
- char *__init default_machine_specific_memory_setup(void)
- {
- char *who = "BIOS-e820";
- u32 new_nr;
- /*
- * Try to copy the BIOS-supplied E820-map.
- *
- * Otherwise fake a memory map; one section from 0k->640k,
- * the next section from 1mb->appropriate_mem_k
- */
- new_nr = boot_params.e820_entries;
- /*將重疊的去除*/
- sanitize_e820_map(boot_params.e820_map,
- ARRAY_SIZE(boot_params.e820_map),
- &new_nr);
- /*去掉重疊的部分后得到的內存個數*/
- boot_params.e820_entries = new_nr;
- /*將其賦值到全局變量e820中,小於0時,為出錯處理*/
- if (append_e820_map(boot_params.e820_map, boot_params.e820_entries)
- < 0) {
- ……
- }
- /* In case someone cares... */
- return who;
- }
append_e820_map調用__append_e820_map實現
- static int __init __append_e820_map(struct e820entry *biosmap, int nr_map)
- {
- while (nr_map) {/*循環nr_map次調用,添加內存塊到e820*/
- u64 start = biosmap->addr;
- u64 size = biosmap->size;
- u64 end = start + size;
- u32 type = biosmap->type;
- /* Overflow in 64 bits? Ignore the memory map. */
- if (start > end)
- return -1;
- /*添加函數*/
- e820_add_region(start, size, type);
- biosmap++;
- nr_map--;
- }
- return 0;
- }
- void __init e820_add_region(u64 start, u64 size, int type)
- {
- __e820_add_region(&e820, start, size, type);
- }
e820為e820map結構
- struct e820map {
- __u32 nr_map;
- struct e820entry map[E820_X_MAX];
- };
其中E820_X_MAX大小為128.
- tatic void __init __e820_add_region(struct e820map *e820x, u64 start, u64 size,
- int type)
- {
- int x = e820x->nr_map;
- if (x >= ARRAY_SIZE(e820x->map)) {
- printk(KERN_ERR "Ooops! Too many entries in the memory map!\n");
- return;
- }
到這里,物理內存就已經從BIOS中讀出來存放到全局變量e820中,e820是linux內核中用於建立內存管理框架的基礎。在后面我們會看到,建立初始化節點、管理區會用到他。