内存管理[1]

 

   内核本身不能像用户空间那样奢侈地使用内存,故在内核里分配内存不像在其他地方那么容易。

  内核把物理页作为内存管理的基本单位。尽管处理器的最小可寻址单位通常为字(甚至是字节),但是,内存管理单元(MMU,管理内存并把虚地址转换为物理地址的硬件)通常以页为单位进行处理。MMU也页page为单位来管理系统中的页表,从虚拟内存的角度来看,页就是最小单位。

  体系结构不同支持的页大小也不尽相同。大多数32位体系结构支持4KB的页,而64位体系结构一般会支持8KB的页。

   内核用struct page结构表示系统中的每个物理页:

 

  1. 在<Mm_type.h(inluce/linux)>中
  2. struct address_space;
  3. /*
  4.  * Each physical page in the system has a struct page associated with
  5.  * it to keep track of whatever it is we are using the page for at the
  6.  * moment. Note that we have no way to track which tasks are using
  7.  * a page, though if it is a pagecache page, rmap structures can tell us
  8.  * who is mapping it.
  9.  */
  10. struct page {
  11.     unsigned long flags;        /* Atomic flags, some possibly
  12.                      * updated asynchronously */
  13.     atomic_t _count;        /* Usage count, see below. */  引用计数,当值为0是,当前内核没有引用这一页,在新的分配中可以使用它;内核代码不应直接引用该域,而是通过page_count()函数进行检查。
  14.     atomic_t _mapcount;     /* Count of ptes mapped in mms,
  15.                      * to show when page is mapped
  16.                      * & limit reverse map searches.
  17.                      */
  18.     union {
  19.         struct {
  20.         unsigned long private;      /* Mapping-private opaque data:
  21.                          * usually used for buffer_heads
  22.                          * if PagePrivate set; used for
  23.                          * swp_entry_t if PageSwapCache;
  24.                          * indicates order in the buddy
  25.                          * system if PG_buddy is set.
  26.                          */
  27.         struct address_space *mapping;  /* If low bit clear, points to       一个页可以由页缓存使用,这时mapping指向与                        * inode address_space, or NULL.                                   这个 页关联的 addreaa_space 对象
  28.                          * If page mapped as anonymous
  29.                          * memory, low bit is set, and
  30.                          * it points to anon_vma object:
  31.                          * see PAGE_MAPPING_ANON below.
  32.                          */
  33.         };
  34. #if NR_CPUS >= CONFIG_SPLIT_PTLOCK_CPUS
  35.         spinlock_t ptl;
  36. #endif
  37.     };
  38.     pgoff_t index;          /* Our offset within mapping. */
  39.     struct list_head lru;       /* Pageout list, eg. active_list
  40.                      * protected by zone->lru_lock !
  41.                      */
  42.     /*
  43.      * On machines where all RAM is mapped into kernel address space,
  44.      * we can simply calculate the virtual address. On machines with
  45.      * highmem some memory is mapped into kernel virtual memory
  46.      * dynamically, so we need a place to store that address.
  47.      * Note that this field could be 16 bits on x86 ... ;)
  48.      *
  49.      * Architectures with slow multiplication can define
  50.      * WANT_PAGE_VIRTUAL in asm/page.h
  51.      */
  52. #if defined(WANT_PAGE_VIRTUAL)
  53. /*页的虚拟地址。有些内存(如高端内存)并不永久的映射到内核地址空间上。这时这个值为NULL,需要时在动态地映射这些页*/
  54.     void *virtual;          /* Kernel virtual address (NULL if
  55.                        not kmapped, ie. highmem) */
  56. #endif /* WANT_PAGE_VIRTUAL */
  57. };

  

  1. 在<Mm.h(include/linux)>中
  2. static inline int page_count(struct page *page)
  3. {
  4.     if (unlikely(PageCompound(page)))
  5.         page = (struct page *)page_private(page);
  6.     return atomic_read(&page->_count);
  7. }
  8. #define page_private(page)      ((page)->private)

   一个页可以有页缓存使用(这时,mapping域指向和这个页关联的address_space对象),或者作为私有数据(由private指向),或者作为进程页表中的映射。

   page结构与物理页相关,而并非与虚拟页相关。因此,该结构对页的描述只是短暂的。即使页中所包含的数据继续存在,但是由于交换等原因,它们可能并不再和同一个page相关联。内核仅仅用这个数据结构来描述当前时刻在相关的物理页中存放的的哦你哦更新。这种数据结构的目的在于描述物理内存本身,而不是描述包含在其中的数据

   内核用这一结构来管理系统中的所有页,因为内存需要知道一个页是否空闲(也就是没有被分配)。如果页已经分配,内核还uxy知道谁拥有这个页。拥有者可能是用户空间进程、动态分配的内核数据、静态内核代码、或页高速缓存等。

   系统中的每个物理页都要分配一个这样的结构体。

  由于硬件限制,有些页位于内存中特定的物理地址上,不能将其用于一些特定的任务。 Linux必须处理如下两种由于硬件存在的缺陷引起的内存寻址问题:

一些硬件只能用特定的内存来执行DMA

一些体系结构其内存的物理寻址范围比虚拟寻址范围大得多。这样,就有一些内存不能永久的映射到内核空间上

  因此,内核把页分为不同的区(zone)。内核使用区对具有相似特性的页进行分组。Linux使用了三种区:

ZONE_DMA,这个区包含的页能用来执行DMA操作

ZONE_NORMAL,这个包含的是能正常映射的页

ZONE_HIGHMEM,这个区包含“高端内存”,其中的页并不能永久地映射到内核地址空间

 

 

  区的实际使用和分布是与体系结构相关的。

  某些体系结构在内存的任何地址上执行DMA都没问题,在这些体系结构中,ZONE_DMA为空,ZONE_NORMAL就可以直接用于分配。

   x86体系结构与此相反,ISA设备不能在整个32位的地址空间中执行DMA,因为ISA设备只能访问物理内存的前16MB。因此,ZONE_DMA在x86上包含的页都在0~16MB的内存范围里。

   ZONE_HIGHMEM的工作方式页差不多。能否直接映射取决于体系结构。x86上,ZONE_HIGHMEM为高于896MB的所有物理内存。在其他体系结构上,由于所有的内存都被直接映射,所以ZONE_HIGHMEM为空。

   ZONE_HIGHMEM所在的内存就是所谓的高端内存,系统其余部分就是所谓的低端内存。

   ZONE_HIGHMEM和ZONE_DMA各取所需之后剩下的就是ZONE_NORMAL区独享了。在x86上,ZONE_NORMAL区是从16MB到896MB之间的所有物理内存。其他体系结构上,ZONE_NORMAL是所有可用物理内存。

 

  1. 在<Mmzone.h(include/linux)>中
  2. enum zone_type {
  3. #ifdef CONFIG_ZONE_DMA
  4.     /*
  5.      * ZONE_DMA is used when there are devices that are not able
  6.      * to do DMA to all of addressable memory (ZONE_NORMAL). Then we
  7.      * carve out the portion of memory that is needed for these devices.
  8.      * The range is arch specific.
  9.      *
  10.      * Some examples
  11.      *
  12.      * Architecture     Limit
  13.      * ---------------------------
  14.      * parisc, ia64, sparc  <4G
  15.      * s390         <2G
  16.      * arm26        <48M
  17.      * arm          Various
  18.      * alpha        Unlimited or 0-16MB.
  19.      *
  20.      * i386, x86_64 and multiple other arches
  21.      *          <16M.
  22.      */
  23.     ZONE_DMA,
  24. #endif
  25. #ifdef CONFIG_ZONE_DMA32
  26.     /*
  27.      * x86_64 needs two ZONE_DMAs because it supports devices that are
  28.      * only able to do DMA to the lower 16M but also 32 bit devices that
  29.      * can only do DMA areas below 4G.
  30.      */
  31.     ZONE_DMA32,
  32. #endif
  33.     /*
  34.      * Normal addressable memory is in ZONE_NORMAL. DMA operations can be
  35.      * performed on pages in ZONE_NORMAL if the DMA devices support
  36.      * transfers to all addressable memory.
  37.      */
  38.     ZONE_NORMAL,
  39. #ifdef CONFIG_HIGHMEM
  40.     /*
  41.      * A memory area that is only addressable by the kernel through
  42.      * mapping portions into its own address space. This is for example
  43.      * used by i386 to allow the kernel to address the memory beyond
  44.      * 900MB. The kernel will set up special mappings (page
  45.      * table entries on i386) for each page that the kernel needs to
  46.      * access.
  47.      */
  48.     ZONE_HIGHMEM,
  49. #endif
  50.     MAX_NR_ZONES
  51. };

x86上的区

 

描述物理内存
 ZONE_DMADMA使用的页<16MB
ZONE_NORMAL正常可寻址的页16-896MB
ZONE_HIGHMEM动态映射的页>896MB
  Linux把系统的页划分为区,形成不同的内存池,这样就可以根据用途进行分配了。注意,区的划分没有任何物理意义,只不过是内核为了管理页而采取的一种逻辑上的分组。

  并不是某些用途的内存一定要从对应的区获取。比如,一般用途的内存机能从ZONE_DMA分配,也能从ZONE_NORMAL分配。

  每个区都用sturct zone来表示:

 

  1. struct zone {
  2.     /* Fields commonly accessed by the page allocator */
  3.     unsigned long       pages_min, pages_low, pages_high;
  4.     /*
  5.      * We don't know if the memory that we're going to allocate will be freeable
  6.      * or/and it will be released eventually, so to avoid totally wasting several
  7.      * GB of ram we must reserve some of the lower zone memory (otherwise we risk
  8.      * to run OOM on the lower zones despite there's tons of freeable ram
  9.      * on the higher zones). This array is recalculated at runtime if the
  10.      * sysctl_lowmem_reserve_ratio sysctl changes.
  11.      */
  12.     unsigned long       lowmem_reserve[MAX_NR_ZONES];
  13. #ifdef CONFIG_NUMA
  14.     int node;
  15.     /*
  16.      * zone reclaim becomes active if more unmapped pages exist.
  17.      */
  18.     unsigned long       min_unmapped_pages;
  19.     unsigned long       min_slab_pages;
  20.     struct per_cpu_pageset  *pageset[NR_CPUS];
  21. #else
  22.     struct per_cpu_pageset  pageset[NR_CPUS];
  23. #endif
  24.     /*
  25.      * free areas of different sizes
  26.      */
  27.     spinlock_t      lock;  /* 防止该结构被并发访问,注意该结构只保护结构而不保护驻留在这个区中的所有页*/
  28. #ifdef CONFIG_MEMORY_HOTPLUG
  29.     /* see spanned/present_pages for more description */
  30.     seqlock_t       span_seqlock; 
  31. #endif
  32.     struct free_area    free_area[MAX_ORDER];
  33.     ZONE_PADDING(_pad1_)
  34.     /* Fields commonly accessed by the page reclaim scanner */
  35.     spinlock_t      lru_lock;   
  36.     struct list_head    active_list;
  37.     struct list_head    inactive_list;
  38.     unsigned long       nr_scan_active;
  39.     unsigned long       nr_scan_inactive;
  40.     unsigned long       pages_scanned;     /* since last reclaim */
  41.     int         all_unreclaimable; /* All pages pinned */
  42.     /* A count of how many reclaimers are scanning this zone */
  43.     atomic_t        reclaim_in_progress;
  44.     /* Zone statistics */
  45.     atomic_long_t       vm_stat[NR_VM_ZONE_STAT_ITEMS];
  46.     /*
  47.      * prev_priority holds the scanning priority for this zone.  It is
  48.      * defined as the scanning priority at which we achieved our reclaim
  49.      * target at the previous try_to_free_pages() or balance_pgdat()
  50.      * invokation.
  51.      *
  52.      * We use prev_priority as a measure of how much stress page reclaim is
  53.      * under - it drives the swappiness decision: whether to unmap mapped
  54.      * pages.
  55.      *
  56.      * Access to both this field is quite racy even on uniprocessor.  But
  57.      * it is expected to average out OK.
  58.      */
  59.     int prev_priority;
  60.     ZONE_PADDING(_pad2_)
  61.     /* Rarely used or read-mostly fields */
  62.     /*
  63.      * wait_table       -- the array holding the hash table
  64.      * wait_table_hash_nr_entries   -- the size of the hash table array
  65.      * wait_table_bits  -- wait_table_size == (1 << wait_table_bits)
  66.      *
  67.      * The purpose of all these is to keep track of the people
  68.      * waiting for a page to become available and make them
  69.      * runnable again when possible. The trouble is that this
  70.      * consumes a lot of space, especially when so few things
  71.      * wait on pages at a given time. So instead of using
  72.      * per-page waitqueues, we use a waitqueue hash table.
  73.      *
  74.      * The bucket discipline is to sleep on the same queue when
  75.      * colliding and wake all in that wait queue when removing.
  76.      * When something wakes, it must check to be sure its page is
  77.      * truly available, a la thundering herd. The cost of a
  78.      * collision is great, but given the expected load of the
  79.      * table, they should be so rare as to be outweighed by the
  80.      * benefits from the saved space.
  81.      *
  82.      * __wait_on_page_locked() and unlock_page() in mm/filemap.c, are the
  83.      * primary users of these fields, and in mm/page_alloc.c
  84.      * free_area_init_core() performs the initialization of them.
  85.      */
  86.     wait_queue_head_t   * wait_table;
  87.     unsigned long       wait_table_hash_nr_entries;
  88.     unsigned long       wait_table_bits;
  89.     /*
  90.      * Discontig memory support fields.
  91.      */
  92.     struct pglist_data  *zone_pgdat;
  93.     /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
  94.     unsigned long       zone_start_pfn;
  95.     /*
  96.      * zone_start_pfn, spanned_pages and present_pages are all
  97.      * protected by span_seqlock.  It is a seqlock because it has
  98.      * to be read outside of zone->lock, and it is done in the main
  99.      * allocator path.  But, it is written quite infrequently.
  100.      *
  101.      * The lock is declared along with zone->lock because it is
  102.      * frequently read in proximity to zone->lock.  It's good to
  103.      * give them a chance of being in the same cacheline.
  104.      */
  105.     unsigned long       spanned_pages;  /* total size, including holes */
  106.     unsigned long       present_pages;  /* amount of memory (excluding holes) */
  107.     /*
  108.      * rarely used fields:
  109.      */
  110.     const char      *name;   /* 以NULL结束的字符串,表示这个区的名字。内核启动期间初始化这个值,其代码位于mm/page_alloc.c中*/
  111. } ____cacheline_internodealigned_in_smp;

  系统中只有三个区,因此页只有三个这样的结构。

 

  • 获得页

  内核提供了一种请求内存的底层机制,并提供了对它进行访问的几个接口。所有这些接口都以页为单位分配内存,定义于<linux/gfp.h>中,最核心的函数是alloc_pages(),该函数分配2^order个连续的物理页,并返回一个指针:

 

  1. #ifdef CONFIG_NUMA
  2. extern struct page *alloc_pages_current(gfp_t gfp_mask, unsigned order);
  3. /*分配2^order页,返回一个指向第一页页结构的指针*/
  4. static inline struct page *
  5. alloc_pages(gfp_t gfp_mask, unsigned int order)
  6. {
  7.     if (unlikely(order >= MAX_ORDER))
  8.         return NULL;
  9.     return alloc_pages_current(gfp_mask, order);
  10. }
  11. extern struct page *alloc_page_vma(gfp_t gfp_mask,
  12.             struct vm_area_struct *vma, unsigned long addr);
  13. #else
  14. #define alloc_pages(gfp_mask, order) /
  15.         alloc_pages_node(numa_node_id(), gfp_mask, order)
  16. #define alloc_page_vma(gfp_mask, vma, addr) alloc_pages(gfp_mask, 0)
  17. #endif
  18. typedef unsigned __bitwise__ gfp_t;
  1. 在<Mempolice.c(mm)>中
  2. /**
  3.  *  alloc_pages_current - Allocate pages.
  4.  *
  5.  *  @gfp:
  6.  *      %GFP_USER   user allocation,
  7.  *          %GFP_KERNEL kernel allocation,
  8.  *          %GFP_HIGHMEM highmem allocation,
  9.  *          %GFP_FS     don't call back into a file system.
  10.  *          %GFP_ATOMIC don't sleep.
  11.  *  @order: Power of two of allocation size in pages. 0 is a single page.
  12.  *
  13.  *  Allocate a page from the kernel page pool.  When not in
  14.  *  interrupt context and apply the current process NUMA policy.
  15.  *  Returns NULL when no page can be allocated.
  16.  *
  17.  *  Don't call cpuset_update_task_memory_state() unless
  18.  *  1) it's ok to take cpuset_sem (can WAIT), and
  19.  *  2) allocating for current task (not interrupt).
  20.  */
  21. struct page *alloc_pages_current(gfp_t gfp, unsigned order)
  22. {
  23.     struct mempolicy *pol = current->mempolicy;
  24.     if ((gfp & __GFP_WAIT) && !in_interrupt())
  25.         cpuset_update_task_memory_state();
  26.     if (!pol || in_interrupt() || (gfp & __GFP_THISNODE))
  27.         pol = &default_policy;
  28.     if (pol->policy == MPOL_INTERLEAVE)
  29.         return alloc_page_interleave(gfp, order, interleave_nodes(pol));
  30.     return __alloc_pages(gfp, order, zonelist_policy(gfp, pol));
  31. }

 

   可以使用page_address()函数把给定的页转换成它的逻辑地址:

  1. 在<Mm.h(include/linux)>中
  2. #if defined(WANT_PAGE_VIRTUAL)
  3. #define page_address(page) ((page)->virtual)
  4. #define set_page_address(page, address)         /
  5.     do {                        /
  6.         (page)->virtual = (address);        /
  7.     } while(0)
  8. #define page_address_init()  do { } while(0)
  9. #endif
  10. #if defined(HASHED_PAGE_VIRTUAL)
  11. void *page_address(struct page *page);
  12. void set_page_address(struct page *page, void *virtual);
  13. void page_address_init(void);
  14. #endif
  15. #if !defined(HASHED_PAGE_VIRTUAL) && !defined(WANT_PAGE_VIRTUAL)
  16. #define page_address(page) lowmem_page_address(page)
  17. #define set_page_address(page, address)  do { } while(0)
  18. #define page_address_init()  do { } while(0)
  19. #endif
  20. 在<Highmem.c(mm)>中
  21. void *page_address(struct page *page)
  22. {
  23.     unsigned long flags;
  24.     void *ret;
  25.     struct page_address_slot *pas;
  26.     if (!PageHighMem(page))
  27.         return lowmem_page_address(page);
  28.     pas = page_slot(page);
  29.     ret = NULL;
  30.     spin_lock_irqsave(&pas->lock, flags);
  31.     if (!list_empty(&pas->lh)) {
  32.         struct page_address_map *pam;
  33.         list_for_each_entry(pam, &pas->lh, list) {
  34.             if (pam->page == page) {
  35.                 ret = pam->virtual;
  36.                 goto done;
  37.             }
  38.         }
  39.     }
  40. done:
  41.     spin_unlock_irqrestore(&pas->lock, flags);
  42.     return ret;
  43. }
  44. /*
  45.  * Hash table bucket
  46.  */
  47. static struct page_address_slot {
  48.     struct list_head lh;            /* List of page_address_maps */
  49.     spinlock_t lock;            /* Protect this bucket's list */
  50. } ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];
  51. static struct page_address_slot *page_slot(struct page *page)
  52. {
  53.     return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
  54. }

   如果不需要用的struct page,可以调用__get_free_pages()函数直接返回所请求的第一个页的逻辑地址。因为页是连续的,因此其他的页也会紧随其后:

 

  1. 在<Page_alloc.c(mm)>中
  2. /*
  3.  * Common helper functions.
  4.  */
  5. /*分配2^order页,返回一个指向第一页逻辑地址的指针*/
  6. fastcall unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
  7. {
  8.     struct page * page;
  9.     page = alloc_pages(gfp_mask, order);
  10.     if (!page)
  11.         return 0;
  12.     return (unsigned long) page_address(page);
  13. }

如果只须一页,可以使用下面两个函数:

  1. 在<Gfp.h(include/linux)>中
  2. /*分配1页,返回一个指向页结构的指针*/
  3. #define alloc_page(gfp_mask) alloc_pages(gfp_mask, 0)
  4. /*分配1页,返回一个指向其逻辑地址的指针*/
  5. #define __get_free_page(gfp_mask) /
  6.         __get_free_pages((gfp_mask),0)

获得填充为0的页:

  1. 在<Page_alloc.c(mm)>中
  2. /*分配1页,让其内容填充为0,返回一个指向其逻辑地址的指针*/
  3. fastcall unsigned long get_zeroed_page(gfp_t gfp_mask)
  4. {
  5.     struct page * page;
  6.     /*
  7.      * get_zeroed_page() returns a 32-bit address, which cannot represent
  8.      * a highmem page
  9.      */
  10.     VM_BUG_ON((gfp_mask & __GFP_HIGHMEM) != 0);
  11.     page = alloc_pages(gfp_mask | __GFP_ZERO, 0);
  12.     if (page)
  13.         return (unsigned long) page_address(page);
  14.     return 0;
  15. }
  • 释放页

当不再需要页时可以使用下面的函数进行释放:

 

  1. 在<Page_alloc.c(mm)>中
  2. fastcall void free_pages(unsigned long addr, unsigned int order)
  3. {
  4.     if (addr != 0) {
  5.         VM_BUG_ON(!virt_addr_valid((void *)addr));
  6.         __free_pages(virt_to_page((void *)addr), order);
  7.     }
  8. }
  9. fastcall void __free_pages(struct page *page, unsigned int order)
  10. {
  11.     if (put_page_testzero(page)) {
  12.         if (order == 0)
  13.             free_hot_page(page);
  14.         else
  15.             __free_pages_ok(page, order);
  16.     }
  17. }
  18. 在<Gfp.h(include/linux)>中
  19. #define __free_page(page) __free_pages((page), 0)
  20. #define free_page(addr) free_pages((addr),0)

  释放页时,要谨慎。只能释放属于你的页。传递了错误的struct page或地址,用错误的order值,都可能导致系统崩溃。

  当你需要以页为单位的一簇连续物理页时,尤其是只需要一两页时,这些低级页函数很有用。对于常用的以字节为单位的分配来说,内核提供的函数是Kmalloc()。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值