Linux 内存管理 重要结构体 (转)

简介: 虚拟内存地址与实际内存地址之间的关系,是如此转换的,逻辑地址-->线性地址-->物理地址。也是从分段单元到分页单元的转换。在 linux中,用户程序所使用的地址与硬件使用的物理地址是不等同的。

虚拟内存地址与实际内存地址之间的关系,是如此转换的,逻辑地址-->线性地址-->物理地址。也是从分段单元到分页单元的转换。在 linux中,用户程序所使用的地址与硬件使用的物理地址是不等同的。虚拟内存引入一个间接层,它使得许多操作成为可能。在引入虚拟内存这个概念和方法后,在系统中运行的程序可以分配比物理内存更多的内存。而linux的地址有分下面几个类型:

用户虚拟地址:用户空间所能看到的常规地址
物理地址:在处理器和系统内存之间使用
总线地址:在外围总线和内存之间使用
内核逻辑地址:组成内核的常规地址空间,该地址映射了部分或者全部内存,并经常被视为物理地址。
内核虚拟地址:其与物理地址的映射不必是线性的和一对一的,所有的逻辑地址都是内核虚拟地址,但是有些内核地址不是逻辑地址。

 

Linux 2.6支持非一致性内存访问(Non-Uniform Memory AccessNUMA)模型,在这种模型中,给定CPU对不同内存单元的访问时间不一样。系统的物理内存被划分为几个节点(node)。在一个单独的节点中,任一给定CPU访问页面所需的时间都是相同的,然而对于不同的CPU,这个时间就不同。对于每个CPU而言,内核都试图把耗时节点的访问次数减少到最少,这就要小心的选择CPU最常引用的内核数据结构的存放位置。


UMA,一致内存访问体系结构,此结构通常用pg_data_t来引用,系统中每个节点链接到一个以NULL结尾的 pgdat_list链表中,而其中的每个节点利用pg_data_tnode_next字段链接到下一个节点。

对大型机来说,内存会分成很多簇,每个簇都被认为是一个节点。struct pg_data_t体现此概念,系统中的每个节点链接到一个以NULL结尾的pgdat_list脸表中,而其中的每个节点利用 pg_data_tnode_next连接到下一个节点。内存中,每个节点被分成很多的成为管理区的块,而用于表示内存中的某个范围,一个管理区由 sruct zone_struct描述,并被定义为zone_t,且每个管理区的类型都是ZONE_DMA(低端范围的物理内存,内存首部--16MB,ZONE_NORMAL(由内核直接映射到线性地址空间的较高部分,16MB--896MB)或者ZONE_HIGHMEM(系统中预留的可用内存空间,不被内核直接映射,896MB--末尾)中的一种。而三者中,ZONE_NORMAL是影响系统性能最重要的管理区。linux2.6.32中引入了ZONE_MOVABLE类型,用于适应大块连续内存的分配。
系统的内存划分成大小确定的许多块,这些块用struct page结构体来描述,所有的结构都存储在一个全局mem_map数组中,通常存放在ZONE_NORMAL的首部,或者就在小内存系统中装入内核映像而预留的区域之后。就因为ZONE_NORMAL大小有限,所以linux才会提出高端内存这个概念。
linux
中,运用page结构体来保存内核需要知道的所有物理内存信息,对系统中每个物理页,都有一个page结构体相对应。

内存中的每个节点都由pg_data_t描述,而此则由struct pglist_data定义而来,在分配一个页面时,linux 采用节点局部分配的策略,从最靠近运行中的cpu的节点分配内存,由于进程往往是在同一个cpu上运行,因此从当前节点得到的内存很有可能被用到,每个管理区由一个struct zone描述,此结构体用于跟踪页面使用情况统计数,空闲区域信息和锁信息等,在文件中声明.

以下这三个结构体互相结合,彼此指向形成了一个关于内存分配的树型结构。

  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;  

所有的节点都有一个pgdat_list的链表维护,这些节点都放在该链表中。

 


管理区

每个管理区由一个zone结构体描述,对于管理区的类型描述如下

  1. enum zone_type {  
  2. #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         * s390                * arm          Various       * alpha        Unlimited or 0-16MB.       *       * i386, x86_64 and multiple other arches       *                 */       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   };  


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

  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  
  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 采用了一种与具体体系结构无关代码的三层页表机制来完成内存管理,即使底层的体系结构并不支持这个概念。每一个进程都有一个指向其自己的 PGD 指针( mm_struct->pgd ),这就是一个物理页面号,其中包含了一个 pgd_t 类型的数组,进程页表的载入是通过把这个结构体复制到 cr3 寄存器完成。 PGD 表中每个有效的项都指向一个页面号,此页面号包含一个 pmd_t 类型的 PMD 项数组,每一个 pmd_t 又指向另外的页面号,这些页面号由很多个 pte_t 类型的 PTE 构成,而 pte_t 最终指向包含真正用户数组的页面。

整体结构是这样的: PGD 进程内偏移量 PMD 页面号内偏移量 PTE 页面号内偏移量数据号内偏移量。这些结构各自拥有自己的偏移量( offset )在寻址过程中,不断的通过基地址和偏移量来找到下一个相关结构体最后寻到带有用户数据的页面号。

由于所有在 vmlinuz 中的普通内核代码都编译成以 PAGE_OFFSET+1MB 为起始地址,实际上系统将内核装载到以第一个 1MB 0x00100000 )为起始地址,实际上系统内核装载到以第一个 1MB 为起始地址的物理空间中,第一个 1MB 的地址常在以些设备用作和 BISO 进行通讯的地方自行跳过。该文件中的引导初始化代码总是把虚拟地址减去 __PAGE_OFFSET, 从而获得以 1M 为起始地址的物理地址。在开启换页单元以前,必须首先建立相应的页表映射,从而将 8MB 的物理空间转换为虚拟地址 PAGE_OFFSET.

而物理空间和 struct page 之间的映射概念也很重要,系统将内核映像装载到 1MB 物理地址起始位置,这个物理地址就是虚拟地址 PAGE_OFFSET+0x00100000. 物理内存为内核映像预留了 8MB 的虚拟空间,耗个空间可以被两个 PGD 所访问到, linux ZONE_DMA 预留了 16MB 的内存空间,所以真正被内核分配使用的内存起始位置应在 0xc1000000, 这个位置久违全局量 mem_map 所在的位置。通过把物理地址作为 mem_map 里的一个下标,从而将其转换成对应的 struct page 。通过把物理地址位右移 PAGE_SHIFT 位,从而将右移后的物理地址作为物理地址 0 开始的页面号 PFN ,同样也是 mem_map 数组的一个下标。  
linux 中,为了可以更快的运行程序,从内存中装入数据,还设置了高速缓存管理。其实在,基本上都存在一级缓存和二级缓存,后者更大一点,但是速度要慢的一些。而地址映射高级缓存行的方式因为体系结构的不同可能会有差别,但是基本会是如下三个方法:
1
直接映射,每个内存块只与唯一一个可能的高速缓存相映射
2
关联映射,任意的内存块可以与任意的高速缓存行相对应
3
关联集映射,任意的内存块可以与任意的高速缓存行相映射,但是只能在一个可用行的子集里面映射

虚拟内存的好处之一就是可以让进程都有属于自己的虚拟地址空间,这种虚拟地址空间可以通过操作系统映射到物理内存。对于进程,它通过一个页表项指针指向一个只读的全局全零页面,以实现在进程的线性地址空间中保留空间。一旦进程对该页面进行写操作,就会发生缺页中断,这时系统会分配一个新的全零页面,并由一个页表指定,且标记为可写,新的全零页面看起来和原来的全局全零页面完全一样。

地址空间由 sruct mm_struct 结构体来管理,而它到 struct page 之间,还需要有几个步骤,它们之间的关系是通过如下结构体相连,并且这些结构体构成了一个关于文件的各个方面的描述:

struct mm_struct,struct vm_area_struct,struct _file,struct vm_operations_struct,struct dentry,struct inode,struct address_space,struct address_space_operations,struct page.

描述进程地址空间,一个进程只有一个 mm_struct 结构,且该结构在进程用户空间中由多个线程共享,线程正是通过任务链表里的任务是否指向同一个 mm_struct 来判定的。主要字段:
struct mm_struct{
    struct vm_area_struct * mmap;
地址空间中所有 VMA 的链表首部
    struct vm_area_struct * mmap_avl;
    rb_root_t mm_rb;
    struct vm_area_struct * mmap_cache;
最后一次通过 find_vma() 找到的 VMA 存放处
    pgd_t * pgd;
全局目录表的起始地址
    atomic_t mm_users;
访问用户空间部分的用户计数值
    atomic_t mm_count;
匿名用户计数值
    int map_count;
正在被使用中的 vma 数量
    struct semaphore mmap_sem;
读写保护锁,长期有效
    spinlock_t page_table_lock;
用于保护 mm_struct 中大部分字段
    struct list_head mmlist;
所有的 mm_struct 结构通过它链接在一起
    unsigned long start_code, end_code, start_data, end_data
代码段和数据段的起始地址和中止地址。
    unsigned long start_brk, brk, start_stack;
堆的起始地址和结束地址,栈的起始地址和结束地址
    unsigned long arg_start, arg_end, env_start, env_end;
命令行参数的起始地址和结束地址,环境变量区域的起始地址和结束地址。
    unsigned long rss, total_vm,locked_vm; (resident set -->rss
某一时刻,一般一个进程虚存空间不会完全在内存中,一般驻留在内存中的为其虚存空间的子集, rss 描述有多少页驻留内存中 )
驻留集的大小是该进程常驻内存的页面数,不包括全局零页面,进程中所有 vma 区域的内存空间总和,内存中被锁住的常驻页面数。
    unsigned long def_flags;VM_LOCKED
用于指定在默认情况下将来所有的映射是上锁还是未锁。
    unsigned long cpu_vm_mask;
    unsigned long swap_cnt;
    unsigned long swap_address;
当换出整个进程时,页换出进程记录最后一次被换出的地址
    mm_context_t context;
}

与内存区域描述器相关的函数: mm_init(),allocate_mm(),mm_alloc(),exit_mmap(),copy_mm(),free_mm().

系统中第一个 mm_struct 通过 init_mm() 初始化,以后的 mm_struct 都会通过复制它来进行设置,所以第一个要手动静态设置,这是一个模板:
mm_rb:RB_ROOT,
pgd:swapper_pg_dir,
mm_users:ATOMIC_INIT(2),
mm_count:ATOMIC_INIT(1),
mmap_sem:__RWSEM_INITIALLZER(name,mmap_sem),
page_table_lock:SPIN_LOCK_UNLOCKED,
mmlist:LIST_HEAD_INIT(name.mmlist),

而系统用于分配 mm_struct 结构的函数有两个: Allocate_mm() 是一个预处理宏,从 slab allocator 中分配 mm, mm_alloc() slab 中分配,然后 init.

源代码主要语句:

static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
函数为给定的进程复制一份 mm, 仅在创建一个新进程后且需要它自己的 mm 时由 do_fork 调用。
{
    struct mm_struct * mm, *oldmm;
    int retval;
    tsk->min_flt = tsk->maj_flt = 0;
初始化与内存管理相关的 task_struct 字段 ,tsk 是一个进程控制块。
    tsk->nvcsw = tsk->nivcsw = 0;
    tsk->mm = NULL;
    tsk->active_mm = NULL;
    oldmm = current->mm;
借用当前运行进程的 mm 来复制。
    if (clone_flags & CLONE_VM) {
如果设置 clone_vm ,则子进程将与父进程共享 mm
        atomic_inc(&oldmm->mm_users);
用户数量加 1 ,以便于不会过早销毁
        mm = oldmm;
        goto good_mm;
    }
    mm = dup_mm(tsk);//
链接过程,以及文件信息的设置。
good_mm:
    mm->token_priority = 0;
    mm->last_interval = 0;
    tsk->mm = mm;
    tsk->active_mm = mm;
    return 0;
}

static struct mm_struct * mm_init(struct mm_struct * mm, struct task_struct *p)
{
    atomic_set(&mm->mm_users, 1);
用户数为 1
    atomic_set(&mm->mm_count, 1);mm
引用数为 1
    init_rwsem(&mm->mmap_sem);
初始化保护 vma 链表的信号量
    INIT_LIST_HEAD(&mm->mmlist);
初始化 mm 链表
    mm->flags = (current->mm) ? current->mm->flags : MMF_DUMP_FILTER_DEFAULT;
设置标识位
    mm->core_waiters = 0;
    mm->nr_ptes = 0;
    set_mm_counter(mm, file_rss, 0);
    set_mm_counter(mm, anon_rss, 0);
    spin_lock_init(&mm->page_table_lock);
    rwlock_init(&mm->ioctx_list_lock);
    mm->ioctx_list = NULL;
    mm->free_area_cache = TASK_UNMAPPED_BASE;
    mm->cached_hole_size = ~0UL;
    mm_init_owner(mm, p);//void mm_init_owner(struct mm_struct *mm, struct task_struct *p){mm->owner = p;}
    if (likely(!mm_alloc_pgd(mm))) {
        mm->def_flags = 0;
        return mm;
    }
    free_mm(mm);
    return NULL;

#define allocate_mm()    (kmem_cache_alloc(mm_cachep, GFP_KERNEL))
#define free_mm(mm)    (kmem_cache_free(mm_cachep, (mm)))

struct mm_struct * mm_alloc(void)
{
    struct mm_struct * mm;
    mm = allocate_mm();//kmem_cache_alloc(mm_cachep, GFP_KERNEL)
slab 分配器分配一个 mm_struct
    if (mm) {
        memset(mm, 0, sizeof(*mm));
字段归零
        mm = mm_init(mm, current);//
初始化
    }
    return mm;
}

进程的地址空间很少用满,而一般仅仅用到其中一些分离的区域,这个区域由结构体 vm_area_struct 来表示,区域之间是不会交叉的,各自代表一个有着相同属性和用途的地址集合。一个进程所有被映射的区域都可以在 /proc /PID/maps 里面看到。

当一个文件被映射到内存,则可以通过 vm_file 字段得到 struct_file. 而这个字段又指向 struct inode, 索引节点用于找到 struct address_space, 而在后者中,包含与文件有关的所有信息,包括一系列指向与文件系统相关操作函数的指针。

struct vm_area_struct {
    struct mm_struct * vm_mm;
所述的 mm_struct
    unsigned long  vm_start;
这个区域的起始地址
    unsigned long vm_end;
这个区域的结束地址
    struct vm_area_struct * vm_next;
一个地址空间中的所有 vma 都按地址空间次序通过该字段简单的链接在一起。
    pgrot t_vm_page_prot;
对应的每个 pte 里的保护标志位
    unsigned long vm_flags;
这个 vma 的保护标志位和属性标志位
    short vm_avl_height;
    rb_node_ vm_rb;
所有的 vma 都存储在一个红黑树上以加快查找速度
    struct vm_area_struct * vm_avl_left;
    struct vm_area_struct * vm_avl_rigth; 
    struct vm_area_struct * vm_next_share;
把文件映射而来的 vma 共享区域链接在一起
    struct vm_area_struct ** vm_pprev_share;vm_next_share
的辅助指针
    struct vm_operations_struct * vm_ops;
包含指向与磁盘同步操作时所需要函数的指针。此字段包含有指向 open(),close(),nopage() 的函数指针
    unsigned long vm_pgoff;
在已被映射文件里对齐页面的偏移
    struct file * vm_file;
指向被映射的文件的指针
    unsigned long vm_raend;
预读窗口的结束地址,在发生错误时,一些额外的页面将被收回,这个值决定了这些额外页面的个数。
    void * vm_private_data;
一些设备驱动私有数据的存储,与内存管理无关。
}

pgrot t_vm_page_prot;
对应的每个 pte 里的保护标志位:
_PAGE_PRESENT
页面常驻内存,不进行换出操作
_PAGE_PROTNONE
页面常驻内存,但不可访问
_PAGE_RW
页面可能被写入时设置该位
_PAGE_USER
页面可以被用户空间访问时设置该位
_PAGE_DIRTY
页面被写入时设置该位
_PAGE_ACCESSED
页面被访问时设置该位

unsigned long vm_flags;
这个 vma 的保护标志位和属性标志位

保护标志位
VM_READ
页面可能被读取
VM_WRITE
页面可能被写入
VM_EXEC
页面可能被执行
VM_SHARED
页面可能被共享
VM_DONTCOPY vma
不能在 fork 时被复制
VM_DONTEXPAND
防止一个区域被重新设置大小。标志位没有被使用过

struct vm_operations_struct {
    void (*open) (struct vm_area_struct * area);
    void (*close) (struct vm_area_struct * area);
    struct page * (*nopage)(struct vm_area_struct *area, unsigned long address, int write_access);
}

struct address_space {
定义的文件信息
    struct inode        *host;
    struct radix_tree_root    page_tree;
    rwlock_t        tree_lock;
    unsigned int        i_mmap_writable;
    struct prio_tree_root    i_mmap;
    struct list_head    i_mmap_nonlinear;
    spinlock_t        i_mmap_lock;
    unsigned int        truncate_count;
    unsigned long        nrpages;
在地址空间中正被使用且常驻内存的页面数
    pgoff_t            writeback_index;
    const struct address_space_operations *a_ops;
操纵文件系统的函数结构。每一个文件系统都提供其自身的 operations.
    unsigned long        flags;
    struct backing_dev_info *backing_dev_info;
    spinlock_t        private_lock;
    struct list_head    private_list;
    struct address_space    *assoc_mapping;   
}

struct address_space_operations {
定义的函数方法
    int (*writepage)(struct page *page, struct writeback_control *wbc);
    int (*readpage)(struct file *, struct page *);
    void (*sync_page)(struct page *);
    int (*writepages)(struct address_space *, struct writeback_control *);
    int (*set_page_dirty)(struct page *page);
    int (*readpages)(struct file *filp, struct address_space *mapping,struct list_head *pages, unsigned nr_pages);
    int (*prepare_write)(struct file *, struct page *, unsigned, unsigned);
    int (*commit_write)(struct file *, struct page *, unsigned, unsigned);
    int (*write_begin)(struct file *, struct address_space *mapping,loff_t pos, unsigned len, unsigned flags,struct page **pagep, void **fsdata);
    int (*write_end)(struct file *, struct address_space *mapping,loff_t pos, unsigned len, unsigned copied,struct page *page, void *fsdata);
    sector_t (*bmap)(struct address_space *, sector_t);
    void (*invalidatepage) (struct page *, unsigned long);
    int (*releasepage) (struct page *, gfp_t);
    ssize_t (*direct_IO)(int, struct kiocb *, const struct iovec *iov,loff_t offset, unsigned long nr_segs);
    int (*get_xip_mem)(struct address_space *, pgoff_t, int,void **, unsigned long *);
    int (*migratepage) (struct address_space *,struct page *, struct page *);
    int (*launder_page) (struct page *);
};

 

目录
相关文章
|
24天前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
153 6
|
23天前
|
缓存 Java Linux
如何解决 Linux 系统中内存使用量耗尽的问题?
如何解决 Linux 系统中内存使用量耗尽的问题?
109 48
|
7天前
|
算法 Linux
深入探索Linux内核的内存管理机制
本文旨在为读者提供对Linux操作系统内核中内存管理机制的深入理解。通过探讨Linux内核如何高效地分配、回收和优化内存资源,我们揭示了这一复杂系统背后的原理及其对系统性能的影响。不同于常规的摘要,本文将直接进入主题,不包含背景信息或研究目的等标准部分,而是专注于技术细节和实际操作。
|
21天前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
81 12
|
21天前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
47 11
|
19天前
|
缓存 Ubuntu Linux
Linux环境下测试服务器的DDR5内存性能
通过使用 `memtester`和 `sysbench`等工具,可以有效地测试Linux环境下服务器的DDR5内存性能。这些工具不仅可以评估内存的读写速度,还可以检测内存中的潜在问题,帮助确保系统的稳定性和性能。通过合理配置和使用这些工具,系统管理员可以深入了解服务器内存的性能状况,为系统优化提供数据支持。
27 4
|
21天前
|
编译器 Go
探索 Go 语言中的内存对齐:为什么结构体大小会有所不同?
在 Go 语言中,内存对齐是优化内存访问速度的重要概念。通过调整数据在内存中的位置,编译器确保不同类型的数据能够高效访问。本文通过示例代码展示了两个结构体 `A` 和 `B`,尽管字段相同但排列不同,导致内存占用分别为 40 字节和 48 字节。通过分析内存布局,解释了内存对齐的原因,并提供了优化结构体字段顺序的方法,以减少内存填充,提高性能。
37 3
|
23天前
|
Linux
如何在 Linux 系统中查看进程占用的内存?
如何在 Linux 系统中查看进程占用的内存?
|
23天前
|
缓存 Linux
如何检查 Linux 内存使用量是否耗尽?
何检查 Linux 内存使用量是否耗尽?
|
1月前
|
算法 Linux 开发者
深入探究Linux内核中的内存管理机制
本文旨在对Linux操作系统的内存管理机制进行深入分析,探讨其如何通过高效的内存分配和回收策略来优化系统性能。文章将详细介绍Linux内核中内存管理的关键技术点,包括物理内存与虚拟内存的映射、页面置换算法、以及内存碎片的处理方法等。通过对这些技术点的解析,本文旨在为读者提供一个清晰的Linux内存管理框架,帮助理解其在现代计算环境中的重要性和应用。