zoukankan      html  css  js  c++  java
  • 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 plaincopy
     
    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 plaincopy
     
    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上运行,因此从当前节点得到的内存很可能被用到。

    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结构体描述,对于管理区的类型描述如下

    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. };  

    里面的英文注释已经写的很详细了。

    管理区用于跟踪诸如页面使用情况统计数,空闲区域信息和锁信息等。

    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用于记录该页面的状态。

    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中执行下面汇编代码:

    1. start_of_setup:  
    2.        .....  
    3. # Jump to C code (should not return)  
    4.     calll   main  
    5.        .....  

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

    1. void main(void)  
    2. {  
    3.         ......  
    4.     /* Detect memory layout */  
    5.     detect_memory();/*内存探测函数*/  
    6.     ......  
    7. }  
    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()的代码,其它代码基本一样。

    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. }  

    其中存放中断返回值得结构如下

    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()

    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: ");  
    12.     /*打印输出*/  
    13.     e820_print_map(who);  
    14. }  

    在x86_init.c中定义了x86下的memory_setup函数

    1. struct x86_init_ops x86_init __initdata = {  
    2.   
    3.     .resources = {  
    4.         ……  
    5.         .memory_setup       = default_machine_specific_memory_setup,  
    6.     },  
    7.         ……  
    8. };  
    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实现

    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. }  
    1. void __init e820_add_region(u64 start, u64 size, int type)  
    2. {  
    3.     __e820_add_region(&e820, start, size, type);  
    4. }  

    e820为e820map结构

    1. struct e820map {  
    2.     __u32 nr_map;  
    3.     struct e820entry map[E820_X_MAX];  
    4. };  

    其中E820_X_MAX大小为128.

    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! ");  
    8.         return;  
    9.     }  

    到这里,物理内存就已经从BIOS中读出来存放到全局变量e820中,e820是linux内核中用于建立内存管理框架的基础。在后面我们会看到,建立初始化节点、管理区会用到他。

     

  • 相关阅读:
    BZOJ 3236 AHOI 2013 作业 莫队算法
    使用再哈希算法查找元素
    冰雪奇缘--用爱酿就一部经典
    Scrapy系列教程(2)------Item(结构化数据存储结构)
    html学习笔记二
    知方可补不足~sqlserver中触发器的使用
    我心中的核心组件(可插拔的AOP)~调度组件quartz.net
    EF架构~为BulkInsert引入SET IDENTITY_INSERT ON功能
    知方可补不足~sqlserver中使用sp_who查看sql的进程
    知方可补不足~为千万级数据表加索引
  • 原文地址:https://www.cnblogs.com/virusolf/p/4951122.html
Copyright © 2011-2022 走看看