Linux内核zone初始化

Linux内核zone初始化,对页表的初始化完成之后,内核就可以对内存进行管理了,但是内核并不是统一对待这些页面,而是采用区块zone的方式来管理。struct zone数据结构的主要成员如下:

      [include/linux/mmzone.h]

      struct zone {
          /* Read-mostly fields */
          unsigned long watermark[NR_WMARK];
          long lowmem_reserve[MAX_NR_ZONES];
          struct pglist_data    *zone_pgdat;
          struct per_cpu_pageset __percpu *pageset;
          unsigned long          zone_start_pfn;
          unsigned long          managed_pages;
          unsigned long          spanned_pages;
          unsigned long          present_pages;
          const char        *name;

          ZONE_PADDING(_pad1_)
          struct free_area     free_area[MAX_ORDER];
          unsigned long          flags;
          spinlock_t          lock;

          ZONE_PADDING(_pad2_)
          spinlock_t          lru_lock;
          struct lruvec          lruvec;

          ZONE_PADDING(_pad3_)
            atomic_long_t          vm_stat[NR_VM_ZONE_STAT_ITEMS];
        }      cacheline internodealigned in smp;

首先struct zone是经常会被访问到的,因此这个数据结构要求以L1 Cache对齐。另外,这里的ZONE_PADDING()是让zone->lockzone->lru_lock这两个很热门的锁可以分布在不同的cache line中。一个内存节点最多也就几个zone,因此zone数据结构不需要像struct page一样关注数据结构的大小,因此这里ZONE_PADDING()可以为了性能而浪费空间。

在内存管理开发过程中,内核开发者逐步发现有一些自旋锁会竞争得非常厉害,很难获取。像zone->lockzone->lru_lock这两个锁有时需要同时获取锁,因此保证它们使用不同的cache line是内核常用的一种优化技巧。

  • watermark:每个zone在系统启动时会计算出3个水位值,分别是WMARK_MIN、WMARK_LOW和WMARK_HIGH水位,这在页面分配器和kswapd页面回收中会用到。
  • lowmem_reserve:zone中预留的内存。
  • zone_pgdat:指向内存节点。
  • pageset:用于维护Per-CPU上的一系列页面,以减少自旋锁的争用。
  • zone_start_pfn:zone中开始页面的页帧号。
  • managed_pages:zone中被伙伴系统管理的页面数量。
  • spanned_pages:zone包含的页面数量。
  • present_pages:zone里实际管理的页面数量。对一些体系结构来说,其值和spanned_pages相等。
  • free_area:管理空闲区域的数组,包含管理链表等。
  • lock:并行访问时用于对zone保护的自旋锁。
  • lru_lock:用于对zone中LRU链表并行访问时进行保护的自旋锁。
  • lruvec:LRU链表集合。
  • vm_stat:zone计数。

通常情况下,内核的zone分为ZONE_DMA、ZONE_DMA32、ZONE_NORMAL和ZONE_HIGHMEM。在ARM Vexpress平台中,没有定义CONFIG_ZONE_DMA和CONFIG_ZONE_DMA32,所以只有ZONE_NORMAL和ZONE_HIGHMEM两种。zone类型的定义在include/linux/mmzone.h文件中。

      enum zone_type {
          ZONE_NORMAL,
      #ifdef CONFIG_HIGHMEM
          ZONE_HIGHMEM,
      #endif
          ZONE_MOVABLE,
          __MAX_NR_ZONES
      };

zone的初始化函数集中在bootmem_init()中完成,所以需要确定每个zone的范围。在find_limits()函数中会计算出min_low_pfn、max_low_pfn和max_pfn这3个值。其中,min_low_pfn是内存块的开始地址的页帧号(0x60000), max_low_pfn(0x8f800)表示normal区域的结束页帧号,它由arm_lowmem_limit这个变量得来,max_pfn(0xa0000)是内存块的结束地址的页帧号。

下面是ARM Vexpress平台运行之后打印出来的zone的信息。

        Normal zone: 1520 pages used for memmap
        Normal zone: 0 pages reserved
        Normal zone: 194560 pages, LIFO batch:31  //ZONE_NORMAL
        HighMem zone: 67584 pages, LIFO batch:15 //ZONE_HIGHMEM

        Virtual kernel memory layout:
            vector  : 0xffff0000 - 0xffff1000   (   4 KB)
            fixmap  : 0xffc00000 - 0xfff00000   (3072 KB)
            vmalloc : 0xf0000000 - 0xff000000   ( 240 MB)
            lowmem  : 0xc0000000 - 0xef800000   ( 760 MB)
            pkmap   : 0xbfe00000 - 0xc0000000   (   2 MB)
            modules : 0xbf000000 - 0xbfe00000   (  14 MB)
              .text : 0xc0008000 - 0xc0676768   (6586 KB)
              .init : 0xc0677000 - 0xc07a0000   (1188 KB)
              .data : 0xc07a0000 - 0xc07cf938   ( 191 KB)
              .bss : 0xc07cf938 - 0xc07f9378   ( 167 KB)

可以看出ARM Vexpress平台分为两个zone, ZONE_NORMAL和ZONE_HIGHMEM。其中ZONE_NORMAL是从0xc0000000到0xef800000,这个地址空间有多少个页面呢?

      (0xef800000 - 0xc0000000)/ 4096 = 194560

所以ZONE_NORMAL有194560个页面。

另外ZONE_NORMAL的虚拟地址的结束地址是0xef800000,减去PAGE_OFFSET (0xc0000000),再加上PHY_OFFSET(0x60000000),正好等于0x8f80_0000,这个值等于我们之前计算出的arm_lowmem_limit。

zone的初始化函数在free_area_init_core()中。

        [start_kernel->setup_arch->paging_init->bootmem_init->zone_sizes_init->fre
        e_area_init_node->free_area_init_core]
        static void __paginginit free_area_init_core(struct pglist_data *pgdat,
                  unsigned long node_start_pfn, unsigned long node_end_pfn,
                  unsigned long *zones_size, unsigned long *zholes_size)
        {
              enum zone_type j;
              int nid = pgdat->node_id;
              unsigned long zone_start_pfn = pgdat->node_start_pfn;
              int ret;

              pgdat_resize_init(pgdat);
              init_waitqueue_head(&pgdat->kswapd_wait);
              init_waitqueue_head(&pgdat->pfmemalloc_wait);
              pgdat_page_ext_init(pgdat);

              for (j = 0; j < MAX_NR_ZONES; j++) {
                  struct zone *zone = pgdat->node_zones + j;
                  unsigned long size, realsize, freesize, memmap_pages;

                  size = zone_spanned_pages_in_node(nid, j, node_start_pfn,
                                      node_end_pfn, zones_size);
                  realsize = freesize = size - zone_absent_pages_in_node(nid, j,
                                                  node_start_pfn,
                                                  node_end_pfn,
                                          zholes_size);
          /*
            * Adjust freesize so that it accounts for how much memory
            * is used by this zone for memmap. This affects the watermark
            * and per-cpu initialisations
            */
          memmap_pages = calc_memmap_size(size, realsize);
          if (! is_highmem_idx(j)) {
                if (freesize >= memmap_pages) {
                        freesize -= memmap_pages;
                if (memmap_pages)
                      printk(KERN_DEBUG
                              "  %s zone: %lu pages used for memmap\n",
                              zone_names[j], memmap_pages);
            } else
                printk(KERN_WARNING
                      "  %s zone: %lu pages exceeds freesize %lu\n",
                      zone_names[j], memmap_pages, freesize);
        }

        /* Account for reserved pages */
        if (j == 0 && freesize > dma_reserve) {
              freesize -= dma_reserve;
              printk(KERN_DEBUG "  %s zone: %lu pages reserved\n",
                        zone_names[0], dma_reserve);
        }

        if (! is_highmem_idx(j))
              nr_kernel_pages += freesize;
        /* Charge for highmem memmap if there are enough kernel pages */
        else if (nr_kernel_pages > memmap_pages * 2)
              nr_kernel_pages -= memmap_pages;
        nr_all_pages += freesize;
        zone->spanned_pages = size;
        zone->present_pages = realsize;
        /*
          * Set an approximate value for lowmem here, it will be adjusted
          * when the bootmem allocator frees pages into the buddy system.
          * And all highmem pages will be managed by the buddy system.
          */
        zone->managed_pages = is_highmem_idx(j) ? realsize : freesize;
        zone->name = zone_names[j];
        spin_lock_init(&zone->lock);
        spin_lock_init(&zone->lru_lock);
        zone_seqlock_init(zone);
        zone->zone_pgdat = pgdat;
        zone_pcp_init(zone);

        /* For bootup, initialized properly in watermark setup */
        mod_zone_page_state(zone, NR_ALLOC_BATCH, zone->managed_pages);

        lruvec_init(&zone->lruvec);
        if (! size)
              continue;
                set_pageblock_order();

                setup_usemap(pgdat, zone, zone_start_pfn, size);
                ret = init_currently_empty_zone(zone, zone_start_pfn,
                                    size, MEMMAP_EARLY);
                BUG_ON(ret);
                memmap_init(size, nid, j, zone_start_pfn);
                zone_start_pfn += size;
            }
        }

另外系统中会有一个zonelist的数据结构,伙伴系统分配器会从zonelist开始分配内存, zonelist有一个zoneref数组,数组里有一个成员会指向zone数据结构。zoneref数组的第一个成员指向的zone是页面分配器的第一个候选者,其他成员则是第一个候选者分配失败之后才考虑,优先级逐渐降低。zonelist的初始化路径如下:

      [start_kernel->build_all_zonelists->build_all_zonelists_init->__build_all_
      zonelists->build_zonelists->build_zonelists_node]

      static int build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist,
                          int nr_zones)
      {
            struct zone *zone;
            enum zone_type zone_type = MAX_NR_ZONES;

            do {
                zone_type--;
                zone = pgdat->node_zones + zone_type;
                if (populated_zone(zone)) {
                      zoneref_set_zone(zone,
                          &zonelist->_zonerefs[nr_zones++]);
                    check_highest_zone(zone_type);
                }
            } while (zone_type);
            return nr_zones;
      }

这里从最高MAX_NR_ZONES的zone开始,设置到_zonerefs[0]数组中。在ARM Vexpress平台中,该函数的运行结果如下:

      HighMem       zonerefs[0]->zone index=1
      Normal        zonerefs[1]->zone index=0

这个在页面分配器中发挥着重要作用。

另外,系统中还有一个非常重要的全局变量——mem_map,它是一个struct page的数组,可以实现快速地把虚拟地址映射到物理地址中,这里指内核空间的线性映射,它的初始化是在free_area_init_node()->alloc_node_mem_map()函数中。

酷客网相关文章:

赞(0)

评论 抢沙发

评论前必须登录!