Linux内核物理内存初始化

Linux内核物理内存初始化,在内核启动时,内核知道物理内存DDR的大小并且计算出高端内存的起始地址和内核空间的内存布局后,物理内存页面page就要加入到伙伴系统中,那么物理内存页面如何添加到伙伴系统中呢?

伙伴系统(Buddy System)是操作系统中最常用的一种动态存储管理方法,在用户提出申请时,分配一块大小合适的内存块给用户,反之在用户释放内存块时回收。在伙伴系统中,内存块是2的order次幂。

Linux内核中order的最大值用MAX_ORDER来表示,通常是11,也就是把所有的空闲页面分组成11个内存块链表,每个内存块链表分别包括1、2、4、8、16、32、…、1024个连续的页面。1024个页面对应着4MB大小的连续物理内存。

物理内存在Linux内核中分出几个zone来管理,zone根据内核的配置来划分,例如在ARM Vexpress平台中,zone分为ZONE_NORMAL和ZONE_HIGHMEM。

伙伴系统的空闲页块的管理如下图所示,zone数据结构中有一个free_area数组,数组的大小是MAX_ORDER。free_area数据结构中包含了MIGRATE_TYPES个链表,这里相当于zone中根据order的大小有0到MAX_ORDER-1个free_area,每个free_area根据MIGRATE_TYPES类型有几个相应的链表。

伙伴系统的空闲页块管理

Linux内核物理内存初始化

      [include/linux/mmzone.h]
      struct zone {

            ...
            /* free areas of different sizes */
            struct free_area     free_area[MAX_ORDER];
            ...
      };

      struct free_area {
            struct list_head     free_list[MIGRATE_TYPES];
            unsigned long          nr_free;
      };
       MIGRATE_TYPES类型的定义也在mmzone.h文件中。

      [include/linux/mmzone.h]

      enum {
            MIGRATE_UNMOVABLE,
            MIGRATE_RECLAIMABLE,
            MIGRATE_MOVABLE,
            MIGRATE_PCPTYPES,    /* the number of types on the pcp lists */
            MIGRATE_RESERVE = MIGRATE_PCPTYPES,
            MIGRATE_TYPES
      };

MIGRATE_TYPES类型包含MIGRATE_UNMOVABLE、MIGRATE_RECLAIMABLE、MIGRATE_MOVABLE以及MIGRATE_RESERVE等几种类型。当前页面分配的状态可以从/proc/pagetypeinfo中获取得到。

如下图所示,从pagetypeinfo可以看出两个特点:

ARM Vexpress平台pagetypeinfo信息
Linux内核物理内存初始化

  • 大部分物理内存页面都存放在MIGRATE_MOVABLE链表中。
  • 大部分物理内存页面初始化时存放在2的10次幂的链表中。

我们思考一个问题,Linux内核初始化时究竟有多少页面是MIGRATE_MOVABLE?

内存管理中有一个pageblock的概念,一个pageblock的大小通常是(MAX_ORDER-1)个页面。如果体系结构中提供了HUGETLB_PAGE特性,那么pageblock_order定义为HUGETLB_PAGE_ORDER。

      #ifdef CONFIG_HUGETLB_PAGE
      #define pageblock_order          HUGETLB_PAGE_ORDER
      #else
      #define pageblock_order          (MAX_ORDER-1)
      #endif

每个pageblock有一个相应的MIGRATE_TYPES类型。zone数据结构中有一个成员指针pageblock_flags,它指向用于存放每个pageblock的MIGRATE_TYPES类型的内存空间。pageblock_flags指向的内存空间的大小通过usemap_size()函数来计算,每个pageblock用4个比特位来存放MIGRATE_TYPES类型。
zone的初始化函数free_area_init_core()会调用setup_usemap()函数来计算和分配pageblock_flags所需要的大小,并且分配相应的内存。

      [free_area_init_core->setup_usemap-> usemap_size]

      static unsigned long __init usemap_size(unsigned long zone_start_pfn, unsigned
      long zonesize)
      {
            unsigned long usemapsize;

            zonesize += zone_start_pfn & (pageblock_nr_pages-1);
            usemapsize = roundup(zonesize, pageblock_nr_pages);
            usemapsize = usemapsize >> pageblock_order;
            usemapsize *= NR_PAGEBLOCK_BITS;
            usemapsize = roundup(usemapsize, 8 * sizeof(unsigned long));
            return usemapsize / 8;
      }

usemap_size()函数首先计算zone有多少个pageblock,每个pageblock需要4bit来存放MIGRATE_TYPES类型,最后可以计算出需要多少Byte。然后通过memblock_virt_alloc_try_nid_nopanic()来分配内存,并且zone->pageblock_flags成员指向这段内存。

例如在ARM Vexpress平台,ZONE_NORMAL的大小是760MB,每个pageblock大小是4MB,那么就有190个pageblock,每个pageblock的MIGRATE_TYPES类型需要4bit,所以管理这些pageblock,需要96Byte。

内核有两个函数来管理这些迁移类型:get_pageblock_migratetype()和set_pageblock_migratetype()。内核初始化时所有的页面最初都标记为MIGRATE_MOVABLE类型,见free_area_init_core()->memmap_init()函数。

      [start kernel()->setup arch()->paging init()->bootmem init()->zone sizes
      init()->free area init node()->free area init core()->memmap init()]

      void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
              unsigned long start_pfn, enum memmap_context context)
      {
            struct page *page;
            unsigned long end_pfn = start_pfn + size;
            unsigned long pfn;
            struct zone *z;

            z = &NODE_DATA(nid)->node_zones[zone];
            for (pfn = start_pfn; pfn < end_pfn; pfn++) {
                page = pfn_to_page(pfn);
                init_page_count(page);
                page_mapcount_reset(page);
                page_cpupid_reset_last(page);
                SetPageReserved(page);

                if ((z->zone_start_pfn <= pfn)
                      && (pfn < zone_end_pfn(z))
                      && ! (pfn & (pageblock_nr_pages - 1)))
                      set_pageblock_migratetype(page, MIGRATE_MOVABLE);

                INIT_LIST_HEAD(&page->lru);
            }
      }

set_pageblock_migratetype()用于设置指定pageblock的MIGRATE_TYPES类型,最后调用set_pfnblock_flags_mask()来设置pagelock的迁移类型。

      void set_pfnblock_flags_mask(struct page *page, unsigned long flags,
                              unsigned long pfn,
                              unsigned long end_bitidx,
                              unsigned long mask)
      {
            struct zone *zone;
            unsigned long *bitmap;
            unsigned long bitidx, word_bitidx;
            unsigned long old_word, word;

            BUILD_BUG_ON(NR_PAGEBLOCK_BITS ! = 4);

            zone = page_zone(page);
            bitmap = get_pageblock_bitmap(zone, pfn);
            bitidx = pfn_to_bitidx(zone, pfn);
            word_bitidx = bitidx / BITS_PER_LONG;
            bitidx &= (BITS_PER_LONG-1);

            VM_BUG_ON_PAGE(! zone_spans_pfn(zone, pfn), page);

            bitidx += end_bitidx;
            mask <<= (BITS_PER_LONG - bitidx - 1);
            flags <<= (BITS_PER_LONG - bitidx - 1);

            word = ACCESS_ONCE(bitmap[word_bitidx]);
            for (; ; ) {
                  old_word = cmpxchg(&bitmap[word_bitidx], word, (word & ~mask) | flags);
                  if (word == old_word)
                      break;
                  word = old_word;
            }
      }

下面我们来思考,物理页面是如何加入到伙伴系统中的?是一页一页地添加,还是以2的几次幂来加入吗?

在free_low_memory_core_early()函数中,通过for_each_free_mem_range()函数来遍历所有的memblock内存块,找出内存块的起始地址和结束地址。

      [start_kernel-> mm_init-> mem_init-> free_all_bootmem-> free_low_memory_core_early]

      static unsigned long __init free_low_memory_core_early(void)
      {
            unsigned long count = 0;
            phys_addr_t start, end;
            u64 i;

            memblock_clear_hotplug(0, -1);

            for_each_free_mem_range(i, NUMA_NO_NODE, &start, &end, NULL)
                count += __free_memory_core(start, end);
            return count;
      }

把内存块传递到__free_pages_memory()函数中,该函数定义如下:

      static inline unsigned long __ffs(unsigned long x)
      {
            return ffs(x) - 1;
      }

      static void __init __free_pages_memory(unsigned long start, unsigned long end)
      {
            int order;

            while (start < end) {
                  order = min(MAX_ORDER - 1UL, __ffs(start));

                  while (start + (1UL << order) > end)
                      order--;

                  __free_pages_bootmem(pfn_to_page(start), order);
                  start += (1UL << order);
            }
      }

注意这里参数start和end指页帧号,while循环一直从起始页帧号start遍历到end,循环的步长和order有关。首先计算order的大小,取MAX_ORDER-1和__ffs(start)的最小值。ffs(start)函数计算start中第一个bit为1的位置,注意__ffs() = ffs() -1。因为伙伴系统的链表都是2的n次幂,最大的链表是2的10次方,也就是1024,即0x400。所以,通过ffs()函数可以很方便地计算出地址的对齐边界。例如start等于0x63300,那么__ffs(0x63300)等于8,那么这里order选用8。

得到order值后,我们就可以把这块内存通过__free_pages_bootmem()函数添加到伙伴系统了。

      void __init __free_pages_bootmem(struct page *page, unsigned int order)
      {
            unsigned int nr_pages = 1 << order;
            struct page *p = page;

            page_zone(page)->managed_pages += nr_pages;
            set_page_refcounted(page);
            __free_pages(page, order);
      }

__free_pages()函数是伙伴系统的核心函数,这里按照order的方式添加到伙伴系统中。

下面是向系统中添加一段内存的情况,页帧号范围为[0x8800e, 0xaecea],以start为起始来计算其order,一开始order的数值还比较凌乱,等到start和0x400对齐,以后基本上order都取值为10了,也就是都挂入order为10的free_list链表中。

      __free_pages_memory: start=0x8800e, end=0xaecea

      __free_pages_memory: start=0x8800e, order=1, __ffs()=1, ffs()=2
      __free_pages_memory: start=0x88010, order=4, __ffs()=4, ffs()=5
      __free_pages_memory: start=0x88020, order=5, __ffs()=5, ffs()=6
      __free_pages_memory: start=0x88040, order=6, __ffs()=6, ffs()=7
      __free_pages_memory: start=0x88080, order=7, __ffs()=7, ffs()=8
      __free_pages_memory: start=0x88100, order=8, __ffs()=8, ffs()=9
      __free_pages_memory: start=0x88200, order=9, __ffs()=9, ffs()=10
      __free_pages_memory: start=0x88400, order=10, __ffs()=10, ffs()=11
      __free_pages_memory: start=0x88800, order=10, __ffs()=11, ffs()=12
      __free_pages_memory: start=0x88c00, order=10, __ffs()=10, ffs()=11
      __free_pages_memory: start=0x89000, order=10, __ffs()=12, ffs()=13
      __free_pages_memory: start=0x89400, order=10, __ffs()=10, ffs()=11
      __free_pages_memory: start=0x89800, order=10, __ffs()=11, ffs()=12
      __free_pages_memory: start=0x89c00, order=10, __ffs()=10, ffs()=11
      …

酷客网相关文章:

赞(0)

评论 抢沙发

评论前必须登录!