第八章 内存管理

        内存中,某些部分被永久的分配给内核,用来存放内核代码和静态内核数据结构。其余的部分叫做动态内存。本章描述了内核如何给自己分配动态内存。一般情况下,我们使用kmalloc分配小块内存,使用vmalloc分配大块内存。

1:页框管理

        下面的图就显示了内存中,动态内存或者被保留的内存的分布情况。

1.1:页描述符

        页框,也就是物理内存上的4K或者其它大小的物理页。内核必须记录每个页框的当前状态。这些状态信息保存在一个page结构体中,这个结构体叫做页描述符。

struct page {
    unsigned long flags;        /* Atomic flags, some possibly
                     * updated asynchronously */
    /*
     * Five words (20/40 bytes) are available in this union.
     * WARNING: bit 0 of the first word is used for PageTail(). That
     * means the other users of this union MUST NOT use the bit to
     * avoid collision and false-positive PageTail().
     */
    union {     //联合体。page可能有多种用处,每种用处只会使用一种类型的数据结构
        struct {    /* Page cache and anonymous pages *///当作用页高速缓存和匿名页的时候
            /**
             * @lru: Pageout list, eg. active_list protected by
             * pgdat->lru_lock.  Sometimes used as a generic list
             * by the page owner.
             */
            struct list_head lru;
            /* See page-flags.h for PAGE_MAPPING_FLAGS *///从page-flags.h的注释中可以看到,当page是用作页缓存的时候,mapping指向的是address_space。当page是映射到用户态地址空间的匿名页的时候,mapping指向的是anon_vma
            struct address_space *mapping;
            pgoff_t index;        /* Our offset within mapping. */
            /**
             * @private: Mapping-private opaque data.
             * Usually used for buffer_heads if PagePrivate.
             * Used for swp_entry_t if PageSwapCache.
             * Indicates order in the buddy system if PageBuddy.
             *///指向缓冲头部,或者swp_entry_t,或者表示伙伴系统中的order
            unsigned long private;
        };
        struct {    /* page_pool used by netstack *///用作netstack的页池,不关注
           ……
        };
        struct {    /* slab, slob and slub *///用于slub等
            union {
                struct list_head slab_list;
                struct {    /* Partial pages */
                    struct page *next;
#ifdef CONFIG_64BIT
                    int pages;    /* Nr of pages left */
                    int pobjects;    /* Approximate count */
#else
                    short int pages;
                    short int pobjects;
#endif
                };
            };
            struct kmem_cache *slab_cache; /* not slob */
            /* Double-word boundary */
            void *freelist;        /* first free object */
            union {
                void *s_mem;    /* slab: first object */
                unsigned long counters;        /* SLUB */
                struct {            /* SLUB */
                    unsigned inuse:16;
                    unsigned objects:15;
                    unsigned frozen:1;
                };
            };
        };
        struct {    /* Tail pages of compound page */
            ……
        };
        struct {    /* Second tail page of compound page */
            ……
        };
        struct {    /* Page table pages */
            unsigned long _pt_pad_1;    /* compound_head */
            pgtable_t pmd_huge_pte; /* protected by page->ptl */
            unsigned long _pt_pad_2;    /* mapping */
            union {
                struct mm_struct *pt_mm; /* x86 pgds only */
                atomic_t pt_frag_refcount; /* powerpc */
            };
#if ALLOC_SPLIT_PTLOCKS
            spinlock_t *ptl;
#else
            spinlock_t ptl;
#endif
        };
        struct {    /* ZONE_DEVICE pages */
            ……
        };

        /** @rcu_head: You can use this to free a page by RCU. */
        struct rcu_head rcu_head;
    };

    union {        /* This union is 4 bytes in size. */
        /*
         * If the page can be mapped to userspace, encodes the number
         * of times this page is referenced by a page table.
         */
        atomic_t _mapcount;

        /*
         * If the page is neither PageSlab nor mappable to userspace,
         * the value stored here may help determine what this page
         * is used for.  See page-flags.h for a list of page types
         * which are currently stored here.
         */
        unsigned int page_type;

        unsigned int active;        /* SLAB */
        int units;            /* SLOB */
    };

    /* Usage count. *DO NOT USE DIRECTLY*. See page_ref.h */
    atomic_t _refcount;           //当这个值为0的时候,表示这个page没有被使用
} _struct_page_alignment;
 

        所有的页描述符存放在mem_map数组中。

struct page *mem_map;

        page结构体的lru成员有以下用途:

        1:当page是空闲的时候,lru链接在伙伴系统上:

zone -> free_area[order] ->free_list[migratetype] = page -> lru

1.2:非一致性内存访问

        非一致性内存访问模型(NUMA模型):在这种模型下,给定CPU对不同的内存单元的访问用时可能不同。系统的物理内存被划分成几个节点。同一CPU访问不同节点中的内存需要的时间不同;同样,不同CPU访问同一节点需要的时间也不一样。

        不过只考虑X86系统,他是一致访问内存模型。因此,i386只有一个节点。这个节点使用全局变量描述:

struct pglist_data contig_page_data = { .bdata = &contig_bootmem_data };

        所有的节点描述符会使用链表串联起来。当然,i386只有一个节点,因此:

struct pglist_data *pgdat_list;     // pgdat_list=& contig_page_data

        节点的数据结构如下:

/*
 * On NUMA machines, each NUMA node would have a pg_data_t to describe
 * it's memory layout. On UMA machines there is a single pglist_data which
 * describes the whole memory.
 *
 * Memory statistics and page replacement data structures are maintained on a
 * per-zone basis.
 */
typedef struct pglist_data {
    /*
     * node_zones contains just the zones for THIS node. Not all of the
     * zones may be populated, but it is the full list. It is referenced by
     * this node's node_zonelists as well as other node's node_zonelists.
     *///节点中管理区描述符数组。一个节点可能有多个管理区
    struct zone node_zones[MAX_NR_ZONES];

    /*
     * node_zonelists contains references to all zones in all nodes.
     * Generally the first zones will be references to this node's
     * node_zones.
     *///包含了所有节点的所有内存区。这里的zonerefs是从高级别zone往低级别排的
    struct zonelist node_zonelists[MAX_ZONELISTS];

    int nr_zones; /* number of populated zones in this node */
    unsigned long node_start_pfn;
    unsigned long node_present_pages; /* total number of physical pages */
    unsigned long node_spanned_pages; /* total size of physical page
                         range, including holes */
    int node_id;
    wait_queue_head_t kswapd_wait;
    wait_queue_head_t pfmemalloc_wait;
    struct task_struct *kswapd;    /* Protected by
                       mem_hotplug_begin/end() */
    int kswapd_order;
    enum zone_type kswapd_highest_zoneidx;

    int kswapd_failures;        /* Number of 'reclaimed == 0' runs */
    /*
     * This is a per-node reserve of pages that are not available
     * to userspace allocations.
     */
    unsigned long        totalreserve_pages;

    /* Write-intensive fields used by page reclaim */
    ZONE_PADDING(_pad1_)
    spinlock_t        lru_lock;

    /* Fields commonly accessed by the page reclaim scanner */

    /*
     * NOTE: THIS IS UNUSED IF MEMCG IS ENABLED.
     *
     * Use mem_cgroup_lruvec() to look up lruvecs.
     */
    struct lruvec        __lruvec;

    unsigned long        flags;

    ZONE_PADDING(_pad2_)

    /* Per-node vmstats */
    struct per_cpu_nodestat __percpu *per_cpu_nodestats;
    atomic_long_t        vm_stat[NR_VM_NODE_STAT_ITEMS];
} pg_data_t;
 

1.3:内存管理区

        Linux系统中包含的内存管理区可以查看/proc/zoneinfo。
        每个内存管理区都有自己的描述符,见结构体zone:


struct zone {
    /* Read-mostly fields */

    /* zone watermarks, access with *_wmark_pages(zone) macros */
    unsigned long _watermark[NR_WMARK];    //记录了内存区的水位
    unsigned long watermark_boost;

    unsigned long nr_reserved_highatomic;

    /*
     * We don't know if the memory that we're going to allocate will be
     * freeable or/and it will be released eventually, so to avoid totally
     * wasting several GB of ram we must reserve some of the lower zone
     * memory (otherwise we risk to run OOM on the lower zones despite
     * there being tons of freeable ram on the higher zones).  This array is
     * recalculated at runtime if the sysctl_lowmem_reserve_ratio sysctl
     * changes.
     */
    long lowmem_reserve[MAX_NR_ZONES];   //保留的内存。避免从低级别内存区中分配
    struct pglist_data    *zone_pgdat;
    struct per_cpu_pageset __percpu *pageset;

    /* zone_start_pfn == zone_start_paddr >> PAGE_SHIFT */
    unsigned long        zone_start_pfn;

    /*
     * spanned_pages is the total pages spanned by the zone, including
     * holes, which is calculated as:
     *     spanned_pages = zone_end_pfn - zone_start_pfn;
     *
     * present_pages is physical pages existing within the zone, which
     * is calculated as:
     *    present_pages = spanned_pages - absent_pages(pages in holes);
     *
     * managed_pages is present pages managed by the buddy system, which
     * is calculated as (reserved_pages includes pages allocated by the
     * bootmem allocator):
     *    managed_pages = present_pages - reserved_pages;
     *
     * So present_pages may be used by memory hotplug or memory power
     * management logic to figure out unmanaged pages by checking
     * (present_pages - managed_pages). And managed_pages should be used
     * by page allocator and vm scanner to calculate all kinds of watermarks
     * and thresholds.
     *
     * Locking rules:
     *
     * zone_start_pfn and spanned_pages are protected by span_seqlock.
     * It is a seqlock because it has to be read outside of zone->lock,
     * and it is done in the main allocator path.  But, it is written
     * quite infrequently.
     *
     * The span_seq lock is declared along with zone->lock because it is
     * frequently read in proximity to zone->lock.  It's good to
     * give them a chance of being in the same cacheline.
     *
     * Write access to present_pages at runtime should be protected by
     * mem_hotplug_begin/end(). Any reader who can't tolerant drift of
     * present_pages should get_online_mems() to get a stable value.
     */
    atomic_long_t        managed_pages;
    unsigned long        spanned_pages;
    unsigned long        present_pages;//这三个值之间的差异看注释

    const char        *name;

    int initialized;

    /* Write-intensive fields used from the page allocator */
    ZONE_PADDING(_pad1_)

    /* free areas of different sizes */
    struct free_area    free_area[MAX_ORDER];    //每个元素都是一个链表头,链表链接了不同大小的空闲页框组成的块。第n个元素,就是大小为2^n的空闲页框块链表的链表头

    /* zone flags, see below */
    unsigned long        flags;

    /* Primarily protects free_area */
    spinlock_t        lock;

    /* Write-intensive fields used by compaction and vmstats. */
    ZONE_PADDING(_pad2_)

    /*
     * When free pages are below this point, additional steps are taken
     * when reading the number of free pages to avoid per-cpu counter
     * drift allowing watermarks to be breached
     */
    unsigned long percpu_drift_mark;

    bool            contiguous;

    ZONE_PADDING(_pad3_)
    /* Zone statistics */
    atomic_long_t        vm_stat[NR_VM_ZONE_STAT_ITEMS];
    atomic_long_t        vm_numa_stat[NR_VM_NUMA_STAT_ITEMS];
} ____cacheline_internodealigned_in_smp;
 

        现在我们有了页框,内存节点,内存管理区的概念,还需要将页框和他们联系起来。使用函数

static inline struct zone *page_zone(const struct page *page)

{

    return &NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)];

}

/* Page flags: | [SECTION] | [NODE] | ZONE | [LAST_CPUPID] | ... | FLAGS | */

        可以从页框描述符获得他所在的内存管理区的描述符。

1.4:保留的页框池

        在请求内存的时候,如果有足够的空闲内存,请求就会立即满足;否则的话,将发出请求的内核控制路径阻塞,直到有内存被释放。

        但是在某些请求中,无法阻塞内核控制路径(例如在中断或者在临界区代码中)。这种内存分配叫做原子内存分配请求。这时候页框分配会失败并且返回。但是,我们希望这种情况尽量少发生,尽量保证有内存可以分配。因此,我们引入保留的页框池,只有在内存不足的时候才能使用。

        保留的内存数量使用参数min_free_kbytes表示,可以通过文件/proc/sys/vm/min_free_kbytes查看

        每个内存区都将他们的一部分页框用于保留的页框池,在管理区描述符的_watermark[WMARK_MIN]字段,就表示了管理区内保留页框的数目。


/*
 * Initialise min_free_kbytes.
 *
 * For small machines we want it small (128k min).  For large machines
 * we want it large (256MB max).  But it is not linear, because network
 * bandwidth does not increase linearly with machine size.  We use
 *
 *    min_free_kbytes = 4 * sqrt(lowmem_kbytes), for better accuracy:
 *    min_free_kbytes = sqrt(lowmem_kbytes * 16)
 *
 * which yields
 *
 * 16MB:    512k
 * 32MB:    724k
 * 64MB:    1024k
 * 128MB:    1448k
 * 256MB:    2048k
 * 512MB:    2896k
 * 1024MB:    4096k
 * 2048MB:    5792k
 * 4096MB:    8192k
 * 8192MB:    11584k
 * 16384MB:    16384k
 */
int __meminit init_per_zone_wmark_min(void)
{
    unsigned long lowmem_kbytes;
    int new_min_free_kbytes;

    lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10);
    new_min_free_kbytes = int_sqrt(lowmem_kbytes * 16);

    if (new_min_free_kbytes > user_min_free_kbytes) {
        min_free_kbytes = new_min_free_kbytes;
        if (min_free_kbytes < 128)
            min_free_kbytes = 128;
        if (min_free_kbytes > 262144)
            min_free_kbytes = 262144;
    } else {
        pr_warn("min_free_kbytes is not updated to %d because user defined value %d is preferred\n",
                new_min_free_kbytes, user_min_free_kbytes);
    }

//根据系统中总的动态内存(也就是不包含保留内存)计算得到min_free_kbytes。这个值可以通过/proc/sys/vm/min_free_kbytes查看
    setup_per_zone_wmarks();
    refresh_zone_stat_thresholds();
    setup_per_zone_lowmem_reserve();

#ifdef CONFIG_NUMA
    setup_min_unmapped_ratio();
    setup_min_slab_ratio();
#endif

    khugepaged_min_free_kbytes_update();

    return 0;
}
 

        每个内存管理区中,还有另一个参数值得关注,就是zone->lowmem_reserve数组

/*
 * setup_per_zone_lowmem_reserve - called whenever
 *    sysctl_lowmem_reserve_ratio changes.  Ensures that each zone
 *    has a correct pages reserved value, so an adequate number of
 *    pages are left in the zone after a successful __alloc_pages().
 */
static void setup_per_zone_lowmem_reserve(void)
{
    struct pglist_data *pgdat;
    enum zone_type i, j;

    for_each_online_pgdat(pgdat) {
        for (i = 0; i < MAX_NR_ZONES - 1; i++) {
            struct zone *zone = &pgdat->node_zones[i];
            int ratio = sysctl_lowmem_reserve_ratio[i];
            bool clear = !ratio || !zone_managed_pages(zone);
            unsigned long managed_pages = 0;

            for (j = i + 1; j < MAX_NR_ZONES; j++) {
                struct zone *upper_zone = &pgdat->node_zones[j];

                managed_pages += zone_managed_pages(upper_zone);

                if (clear)
                    zone->lowmem_reserve[j] = 0;
                else
                    zone->lowmem_reserve[j] = managed_pages / ratio; //这个数组的值可以通过/proc/zoneinfo中的protection字段看到   
            }
        }
    }

    /* update totalreserve_pages */
    calculate_totalreserve_pages();
}
 

        通过上面的函数,可以知道,zone->lowmem_reserve是一个数组。假设我们的系统中有三个内存区,那么每个内存区的lowmem_reserve数组的内容如下所示(32和256的值来源于数组int sysctl_lowmem_reserve_ratio[MAX_NR_ZONES-1] = { 256, 32 };):

0

1

2

DMA->lowmem_reserve

0

NORMAL->managed_pages/256

NORMAL->managed_pages/256+HIGHMEM->managed_pages/32

NORMAL->lowmem_reserve

0

0

HIGHMEM->managed_pages/32

HIGHMEM->lowmem_reserve

0

0

0

  •         可以认为,这个数组中的内容是,当我们指定从NORMAL内存区中分配内存的时候,如果NORMAL中空闲内存不够,从DMA中去分配空闲内存的时候,不仅要满足一般的水线要求,还要求空闲内存要大于lowmem_reserve指定的内存。也就是说,当我们分配内存,我们尽量避免从低内存区借用。

1.5:分区页框分配器

        被称为分区页框分配器的内核子系统用处理连续页框分配请求。

        在请求分配的时候,分配器搜索一个包含能够满足要求的连续页框的管理区。在每个管理区中,使用伙伴系统处理页框。还有一小部分页框保留在高速缓存中,用于快速满足对单个页框的分配请求(也就是每cpu页框高速缓存)。

1.6:高端内存页框的内核映射(不适用于x86_64arm64)

        全局变量high_memory用于表示高端内存的物理地址(896M)。高端内存的页框并不会直接映射在内核线性地址空间中(虚拟地址3~4G,实际为3~3G+896M)。因此,内核不能直接访问高端内存。

        32X86系统中,为了能够使用高端内存,需要做以下处理:

        1:高端内存页框通过alloc_pages()或者alloc_page()分配。他们不返回页框的线性地址。因为高端内存的页框的线性地址不存在(不能确定,需要在映射的时候才能确定高端内存对应的线性地址;低端内存对应的线性地址一定是3G+physical_addr)。而是返回分配的页框的页描述符的线性地址。

        2:因为高端内存的页框没有线性地址,不能被内核访问。所以,内核线性地址空间的最后128M中的一部分专门用于映射高端内存页框。不同的高端内存都会使用这128M的线性地址。

        内核使用三种不同的机制将页框映射到高端内存,分别是永久内核映射,临时内核映射,非连续内存分配。

        建立永久内核映射可能会阻塞当前进程,此时空闲页表项不存在。因此,不能在中断处理程序和可延迟函数中建立永久内核映射。

        建立临时内核映射一定不会阻塞当前进程,并且他要保证当前没有其他的内核控制路径使用这个用于建立临时内核映射的页表项,以避免其他内核控制路径使用这个页表项映射其他的高端内存页。


         上图也可很清晰的说明,内核的线性地址空间的使用情况。

1.6.1:永久内核映射

        永久内核映射用于建立高端页框到内核地址空间的长期映射。建立永久内核映射的过程可能发生阻塞(空闲页表项不存在)。因此,不能在中断或者临界区中建立永久内核映射。

        注意,i386中存在永久内核映射,但是其他架构中不存在这个东西。不要以为则会个特性是所有架构通用的。

        永久内核映射建立的页表地址存放在全局变量pkmap_page_table

pte_t *pkmap_page_table;           //总共是LAST_PKMAP512或者1024)个页表项

void __init permanent_kmaps_init(pgd_t *pgd_base)

//--------------------------------------------------------------------------------

首先,要说明这个函数的定义在文件arch/i386/mm/init.c中。因此,只有少部分架构中有高端内存的永久映射,在很多架构中,没有这个特性。并且,我们这里讨论的,是i3863级页表的情况

pgd_base就是全局变量swapper_pg_dir的值。这个值在文件entry.S中定义

ENTRY(swapper_pg_dir)

.fill 1024,4,0

//--------------------------------------------------------------------------------

{

pgd_t *pgd;

pud_t *pud;

pmd_t *pmd;

pte_t *pte;

unsigned long vaddr;

vaddr = PKMAP_BASE;         //前面已经说过,PKMAP_BASE的值是用于高端内存永久映射的线性地址(也就是虚拟地址)的起点

page_table_range_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base); //在上面的函数中,如果对应的虚拟地址范围的pmd(页中级目录)pte(页表)还没有被分配出来,那么就分配页表。这时候,*pte中的值是空的。我们后续建立高端内存的映射的时候,就是在这里面填入高端内存的物理地址信息

pgd = swapper_pg_dir + pgd_index(vaddr);

pud = pud_offset(pgd, vaddr);

pmd = pmd_offset(pud, vaddr);

pte = pte_offset_kernel(pmd, vaddr);

pkmap_page_table = pte;              //这里,将全局变量pkmap_page_table的值赋为PKMAP_BASE对应的页表项。之后对pkmap_page_table的操作,就是对适当的页表项的操作

//------------------------------------------------------------------------

在这里还想讨论的是,由于在i386中,如果没有设置CONFIG_X86_PAE。这时候一个页表项pte_t的大小是4字节。因此,如果在分配页表内存的时候,1页可以容纳1024pte。因此,pkmap_page_table指向的页刚好可以容纳1024(也就是LAST_PKMAP)个页表项

//------------------------------------------------------------------------

}

        此外,还使用数组

static int pkmap_count[LAST_PKMAP];  //pkmap_page_table中的成员是一一对应关系,数值用于描述pkmap_page_table每个pte的状态。0表示这个pte是空闲的,没有映射高端内存;1表示这个pte没有映射高端内存,但是他是不能使用的,因为其对应的TLB还没有刷新;n表示这个pte映射了高端内存,并且有n-1个内核路径在使用这个pte

来描述永久内核映射页表项是否有效。

        可以看到,永久内核映射对应的线性地址为PKMAP_BASE~FIXADDR_START,大小为4M     

        通过页表,我们可以使用虚拟地址(线性地址),很方便的找到物理地址(也就是对应的页框)。但是,如果我们知道物理地址的话,如何去获得这个物理地址对应的线性地址呢?我们使用全局数组page_address_htable来辅助获得一个高端内存页框对应的线性地址。

static struct page_address_slot {

    struct list_head lh;            /* List of page_address_maps */

    spinlock_t lock;            /* Protect this bucket's list */

} ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];   //128

        这是一个有128个成员的数组。每个数组成员都有一个自旋锁,并且还是一个链表的头结点。这个链表链接的是具有相同哈希值的struct page_address_map结构体。这个结构体中,描述了物理页框(物理地址,由struct page *page描述)到线性地址(void *virtual描述)的转换关系。

static struct page_address_map page_address_maps[LAST_PKMAP];

struct page_address_map {

struct page *page;

void *virtual;

struct list_head list;

};

使用。

在系统中,这两个数据结构的组织结构如下图所示:

        介绍一下,Linux是如何将一个映射过后的高端内存页框添加到这个哈希表中的。使用函数set_page_address

void set_page_address(struct page *page, void *virtual)

//page是高端内存中的页框的描述符;virtual是分配给这个页框的虚拟地址

{

    unsigned long flags;

    struct page_address_slot *pas;

    struct page_address_map *pam;

    BUG_ON(!PageHighMem(page));        //要求添加的必须是高端页框

    pas = page_slot(page);                        //通过哈希算法,获得pagepage_address_htable中应该对应的page_address_slot

    if (virtual) {        /* Add */   //增加映射关系

        spin_lock_irqsave(&pool_lock, flags);

        pam = list_entry(page_address_pool.next,    struct page_address_map, list);

        list_del(&pam->list);     //pam_list链接的是空闲的pam。这时候将他从空闲链表中移出来,增加到page_address_map的链表中

        spin_unlock_irqrestore(&pool_lock, flags);

        pam->page = page;

        pam->virtual = virtual;    //填充物理地址和线性地址信息

        spin_lock_irqsave(&pas->lock, flags);

        list_add_tail(&pam->list, &pas->lh);

        spin_unlock_irqrestore(&pas->lock, flags);

    } else {        /* Remove */

        spin_lock_irqsave(&pas->lock, flags);

        list_for_each_entry(pam, &pas->lh, list) {

            if (pam->page == page) {

                list_del(&pam->list);

                spin_unlock_irqrestore(&pas->lock, flags);

                spin_lock_irqsave(&pool_lock, flags);

                list_add_tail(&pam->list, &page_address_pool);      //page_address_pool没有讲。不过很容易看出来,作为一个内存池,他存放的是空闲的struct page_address_map结构体

                spin_unlock_irqrestore(&pool_lock, flags);

                goto done;

            }

        }

        spin_unlock_irqrestore(&pas->lock, flags);

    }

done:

    return;

}

        至此,我们明白了这两个数据结构的使用方法。

        page_address()函数用于返回页框对应的线性地址。如果页框属于高端内存,并且没有被映射,就返回NULL

void *page_address(struct page *page)

{

    unsigned long flags;

    void *ret;

    struct page_address_slot *pas;

    if (!PageHighMem(page))      //test_bit(PG_highmem, &(page)->flags)。设置了这一位页就是高端内存

        return lowmem_page_address(page);     //__va(page_to_pfn(page) << PAGE_SHIFT)。非高端内存的线性地址是固定的

    pas = page_slot(page);         //这个页框是高端内存。获得这个page对应的struct page_address_slot。他的lh成员链接了哈希值相同的,所有映射了的高端内存

    ret = NULL;

    spin_lock_irqsave(&pas->lock, flags);

    if (!list_empty(&pas->lh)) {

        struct page_address_map *pam;

        list_for_each_entry(pam, &pas->lh, list) {    //如果寻找的页框是高端内存,并且没有被映射,那么这个页框不会被加到全局变量page_address_htable中,也就不会在链表操作中被找到,就会返回NULL。如果找到,就说明这个高端内存页框已经被映射,那么就返回这个高端页框映射的虚拟地址

            if (pam->page == page) {

                ret = pam->virtual;                   //返回这儿高端内存的线性地址     

                goto done;

            }

        }

    }

done:

    spin_unlock_irqrestore(&pas->lock, flags);

    return ret;

}

    介绍一下函数lowmem_page_address

static inline void *lowmem_page_address(struct page *page)

{

    return __va(page_to_pfn(page) << PAGE_SHIFT);   //先获得page是第几个页框,再左移12位获得这个页框的物理地址,再将物理地址转化成线性地址(+PAGE_OFFSET)

}

#define page_to_pfn(page)    ((unsigned long)((page) - mem_map))   //两个指针相减,得到的结果是这两个指针之间数组元素的个数,也就是这个page是第几个页框。

        要建立高端内存的永久内核映射,需要使用kmap()函数。

void *kmap(struct page *page)

{

    might_sleep();            

    if (!PageHighMem(page))            //test_bit(PG_highmem, &(page)->flags)

        return page_address(page);    //如果这个页框不属于高端内存,那么就直接返回这个页框的虚拟地址

    return kmap_high(page);

}

void fastcall *kmap_high(struct page *page)

{

    unsigned long vaddr;

    spin_lock(&kmap_lock);       //这里要加锁,避免多个CPU都修改高端内存页框对应的页表。但是这里不需要禁止中断。因为中断中不能走到这儿

    vaddr = (unsigned long)page_address(page);

    if (!vaddr)                                             //判断这个页框是否已经被映射过了

        vaddr = map_new_virtual(page);     //页框没有被映射过

    pkmap_count[PKMAP_NR(vaddr)]++;

    spin_unlock(&kmap_lock);

    return (void*) vaddr;

}

    map_new_virtual此函数的主要操作是,检查pkmap_page_table中是否有空闲的页表项位置(此页表专门用于存储高端内存的映射关系)。如果有的话,那么就填入这个位置;如果没有的话,就将进程加入等待队列,直到另一个进程释放这个高端内存页表中的一个位置,然后唤醒等待队列中的进程。

static inline unsigned long map_new_virtual(struct page *page)

{

    unsigned long vaddr;

    int count;

start:

    count = LAST_PKMAP;          //1024

    /* Find an empty entry */

    for (;;) {

        last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;     //这个位操作的作用是,当last_pkmap_nr<512时,保持他的值;等于512时,变成0

        if (!last_pkmap_nr) {             //如果last_pkmap_nr的值变成0的时候,这时候应该将高端内存对应的页表项中,无效的项刷出去。

            flush_all_zero_pkmaps();     //寻找数组pkmap_count[n]中为1的项。这一项就代表这个永久内核映射页表已经不再使用,但是还没有刷新tlb。将这项清空,对应的页(这一项存放的就是这个页的物理地址,因此能够获得他是哪一页)在哈希表中的内容清空。最终,刷新高端内存映射虚拟地址的所有tlb

            count = LAST_PKMAP;

        }

        if (!pkmap_count[last_pkmap_nr])                  //找到了空闲位置。之前说过,pkmap_count数组中某一项为0表示高端内存页表中对应的页表项没有使用

            break;   

        if (--count)

            continue

        {    //如果没有找到空闲的页表项,那么将当前任务加入等待队列

            DECLARE_WAITQUEUE(wait, current);

            __set_current_state(TASK_UNINTERRUPTIBLE);

            add_wait_queue(&pkmap_map_wait, &wait);   //将当前任务挂在pkmap_map_wait等待队列上

            spin_unlock(&kmap_lock);

            schedule();

            remove_wait_queue(&pkmap_map_wait, &wait);   //当有任务释放了高端页表中的页表项的时候,会唤醒挂在这个等待队列上的任务

            spin_lock(&kmap_lock);

            /* Somebody else might have mapped it while we slept 可能有其他进程已经映射了*/

            if (page_address(page))

                return (unsigned long)page_address(page);

            /* Re-start */

            goto start;

        }

    }

//找到位置的情况,将其加入高端内存页表中,此时获得的vaddr是高端内存页表中,对应页表项的虚拟地址

    vaddr = PKMAP_ADDR(last_pkmap_nr);   //高端内存页表中的第last_pkmap_nr是空闲的。这里是找到序号为last_pkmap_nr的页表项对应的虚拟地址,为(PKMAP_BASE + ((nr) << PAGE_SHIFT)),其中PKMAP_BASE是高端内存页表开始映射的虚拟地址首地址。高端内存页表最多映射4M内存

    set_pte(&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));   // mk_pte(page, kmap_prot)的作用是作出高端页表项

    pkmap_count[last_pkmap_nr] = 1;

    set_page_address(page, (void *)vaddr);   //这个高端内存页框已经完成映射,将他加到哈希表中。

    return vaddr;

}

        通过这里建立页表项的过程,我们也能够得到高端页表的组织形式,如下图所示:

        也就是说,页表项中并没有线性地址。线性地址是根据pte是高端内存页表的第几项决定的,里面只有20位物理地址,以及12位访问权限。

        这里也是一个使用等待队列的典型例子。

DECLARE_WAITQUEUE(wait, current);

#define DECLARE_WAITQUEUE(name, tsk)                    \

    wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

#define __WAITQUEUE_INITIALIZER(name, tsk) {                \

    .task        = tsk,                        \

    .func        = default_wake_function,            \

    .task_list    = { NULL, NULL } }

        因此,展开后,就是

wait_queue_t wait =

      {.task = current,

       .func = default_wake_function,

       .task_list = {NULL, NULL} }

        同样,有建立虚拟地址和高端内存映射的接口,也有取消虚拟地址和高端内存映射的接口。通过函数kunmap可以实现这个功能。

void kunmap(struct page *page)

{

    if (in_interrupt())             //不能在中断中执行这个函数

        BUG();

    if (!PageHighMem(page))

        return;

    kunmap_high(page);    //必须是属于高端内存的页框才能进入这个函数

}

    如果确认是高端内存的页框,那么就会调用函数kunmap_high

void fastcall kunmap_high(struct page *page)

{

    unsigned long vaddr;

    unsigned long nr;

    int need_wakeup;

    spin_lock(&kmap_lock);

    vaddr = (unsigned long)page_address(page);

    nr = PKMAP_NR(vaddr);          //通过这个高端内存的页框的线性地址,得到他是对应高端内存页表中的第几个页表项。注意,映射高端内存页框的线性地址是连续的

    /*

     * A count must never go down to zero

     * without a TLB flush!  

     *///没有刷高端内存的页表之前,这个值不能变成0

    need_wakeup = 0;

    switch (--pkmap_count[nr]) {      //获得这个高端内存页框的页表项的使用个数

    case 0:         //再次声明,值为1的计数器表示页表中的页表项是空闲的,但是还不能使用,因为他可能在TLB中还留有备份

        BUG();

    case 1:

        need_wakeup = waitqueue_active(&pkmap_map_wait);

    }

    spin_unlock(&kmap_lock);

    /* do wake-up, if needed, race-free outside of the spin lock */

    if (need_wakeup)

        wake_up(&pkmap_map_wait);    //唤醒挂在这个等待队列上的进程

}

1.6.2:临时内核映射

        还是强调一下,永久内核映射和临时内核映射,还有后面的非连续内存映射,都是使用3G+896M~4G的线性地址空间来映射大于896M的物理地址空间。他们使用的线性地址空间如下所示:

        使用临时内核映射的时候,考虑一个问题,两个进程使用了同一个临时内核映射(同样的线性地址A,映射到不同的物理地址BC),这种情况会造成混乱。因此,要求使用临时内核映射的内核控制路径不能被阻塞抢占,不然就会有上面说说的那种混乱情况发生。

        通过临时内核映射来建立内核到高端内存的页表项。并且由于他们不会阻塞的特性,可以用在中断处理程序和可延迟函数中。

        临时内核映射使用的线性地址,是固定映射的线性地址的一部分。

enum fixed_addresses {

……

#ifdef CONFIG_HIGHMEM     //这里定义的线性地址就用于临时内核映射

    FIX_KMAP_BEGIN,    /* reserved pte's for temporary kernel mappings */

    FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,

#endif

……

};

    通过函数kmap_atomic建立临时内核映射。

void *kmap_atomic(struct page *page, enum km_type type)

{

    enum fixed_addresses idx;

    unsigned long vaddr;

    inc_preempt_count();

    if (!PageHighMem(page))

        return page_address(page);

    idx = type + KM_TYPE_NR*smp_processor_id();     //找到这个CPU,这个type对应的线性地址的idx

    vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);        //找到线性地址

    set_pte(kmap_pte-idx, mk_pte(page, kmap_prot));      //建立页表项

    __flush_tlb_one(vaddr);

    return (void*) vaddr;

}

        可见,如果之前已经建立了临时内核映射,这里会直接覆盖之前建立的映射。

1.7:伙伴系统算法

        上面讲的是,内核建立映射的时候,线性地址是怎么来的。他们之间保持了

物理地址+PAGE_OFFSET=线性地址

的关系。

        在后面,我们主要描述,如何给内核分配物理地址空间,也就是分配动态内存。

        内核通过伙伴系统,为自己分配页框。要注意,内核为自己分配页框的时候,不会延后;内核为用户进程分配页框的时候,总是会延后分配。

        页框的分配过程中,会出现外碎片的问题。外碎片就是,随着频繁的请求和释放一系列大小不同的页框,会导致在已分配的页框中分散了许多小块的空闲页框。因此,可能系统中剩余的空闲页框的大小大于要分配的页框大小,但是由于不连续,已经无法分配了。

        为了解决外碎片的问题,通过伙伴算法这一类算法,避免外碎片的大量产生。

        Linux使用伙伴系统算法来解决外碎片的问题。他将所有的空闲页框分组为11个链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续的页框,也就是4K~4M大小的连续页框。并且,如果一个页框是4M空闲页框的第一个页框,要求这个页框的物理地址必须按照4M对齐。

1.7.1:数据结构

        每个管理区使用不同的伙伴系统。

        伙伴系统需要用到下面的数据成员:

        1:管理区包含的页框。每个管理区包含的页框都是mem_map的子集。struct page *zone_mem_map:表示了这个管理区包含的第一个页框描述符;size:表示了这个管理区包含的页框的个数。

        2:管理区描述符中struct free_area free_area[MAX_ORDER]:每个元素对应的就是一种伙伴系统大小(order)。比如第k个元素,对应的就是包含1<<k个页框的空闲块。

struct free_area {
    struct list_head    free_list[MIGRATE_TYPES];          //构成了链表,每个链表成员是struct page->struct list_head lru
    unsigned long        nr_free;      //指定这个大小的空闲页框块的个数
};

        他的free_list指向空闲块的第一个页描述符的lru。第一个页描述符的lru的prev指向free_list,next指向第二个空闲块的起始页的页描述符的lru的prev。这样就是用一个双向链表将所有大小为2^k的空闲内存块的起始页的页描述符联系了起来。

        此外,一个2^k大小的空闲块的第一个页的页描述符,private字段存放了数值k。当这个内存块被释放的时候,就可以得到它的前后内存块的第一个页。根据他们是否空闲,就能实现伙伴系统的合并。

1.7.2:分配块

        使用函数

static struct page *__rmqueue(struct zone *zone, unsigned int order)

从管理区中分配一个空闲块。这个函数使用时,默认已经关中断,并且获得了内存管理区的锁。关中断是为了避免正常路径和中断路径上,分配内存的时候存在的可能的竞争条件。

/*
 * Do the hard work of removing an element from the buddy allocator.
 * Call me with the zone->lock already held.
 */
static __always_inline struct page *
__rmqueue(struct zone *zone, unsigned int order, int migratetype,
                        unsigned int alloc_flags)
{
    struct page *page;
retry:
    page = __rmqueue_smallest(zone, order, migratetype);       //从指定的migratetype空闲链表中分配
    if (unlikely(!page)) {
        if (alloc_flags & ALLOC_CMA)
            page = __rmqueue_cma_fallback(zone, order);

        if (!page && __rmqueue_fallback(zone, order, migratetype,        //如果分配不出来,就先从migratetype的后备类型链表中,将部分页转移到指定的migratetype链表,然后retry
                                alloc_flags))
            goto retry;
    }
out:
    if (page)
        trace_mm_page_alloc_zone_locked(page, order, migratetype);
    return page;
}

        我们首先看函数__rmqueue_smallest的实现

/*
 * Go through the free lists for the given migratetype and remove
 * the smallest available page from the freelists
 */
static __always_inline
struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
                        int migratetype)
{
    unsigned int current_order;
    struct free_area *area;
    struct page *page;

    /* Find a page of the appropriate size in the preferred list */
    for (current_order = order; current_order < MAX_ORDER; ++current_order) {
        area = &(zone->free_area[current_order]);
        page = get_page_from_free_area(area, migratetype);     //zone -> free_area[order] -> free_list[migratetype]中,找到空闲内存块。返回的page就是
        if (!page)
            continue;
        del_page_from_free_list(page, zone, current_order);        //page->lrufree_list中删除,将page->private设置为0。在伙伴系统中的空闲页,private字段记录了当前page为首的空闲内存块的order
        expand(zone, page, order, current_order, migratetype);   //如果是从高order分的部分空闲内存,还要将剩下的空闲内存再插入free_list链表中
        set_pcppage_migratetype(page, migratetype);         //page->index = migratetype
        return page;
    }

    return NULL;
}
 

        在这里分析一下函数expand。假设某一次high的值为8,low的值为2。也就是说,我们本来需要4个连续页框,但是没有这么小的连续页框了,只能从256个连续页框中分出4个页框出来。

/*
 * The order of subdivision here is critical for the IO subsystem.
 * Please do not alter this order without good reasons and regression
 * testing. Specifically, as large blocks of memory are subdivided,
 * the order in which smaller blocks are delivered depends on the order
 * they're subdivided in this function. This is the primary factor
 * influencing the order in which pages are delivered to the IO
 * subsystem according to empirical testing, and this is also justified
 * by considering the behavior of a buddy system containing a single
 * large block of memory acted on by a series of small allocations.
 * This behavior is a critical factor in sglist merging's success.
 *
 * -- nyc
 */
static inline void expand(struct zone *zone, struct page *page,
    int low, int high, int migratetype)
{

//----------------------------------------------------------------------------------

low:2

high:8

area:对应了8的struct free_area结构体。也就是说,这个结构体链接的都是大小为1<<8个页框的空闲页框块

page:我们这次找到的,包含1<<8个页框的空闲页框块的第一个页框描述符

//----------------------------------------------------------------------------------

    unsigned long size = 1 << high;

    while (high > low) {
        high--;
        size >>= 1;
        VM_BUG_ON_PAGE(bad_range(zone, &page[size]), &page[size]);

        /*
         * Mark as guard pages (or page), that will allow to
         * merge back to allocator when buddy will be freed.
         * Corresponding page table entries will not be touched,
         * pages will stay not present in virtual address space
         */
        if (set_page_guard(zone, &page[size], high, migratetype))
            continue;

        add_to_free_list(&page[size], zone, high, migratetype);     //第一次循环的时候,我们将order=8的内存块分成了两个order=7的内存块,page[size]就是对应第二个内存块。将这个内存块放入free_area[high]->free_list[migratetype]链表中
        set_buddy_order(&page[size], high);    //将空闲内存块的第一个page->private成员设置为order
    }
}

        上面描述的是当前migratetype中有满足要求的内存块的情况。当不满足时,可能从后备migratetype,也就是fallback的free_list中分配内存。fallbacks描述了每种migratetype的后备类型migrate

/*
 * This array describes the order lists are fallen back to when
 * the free lists for the desirable migrate type are depleted
 */
static int fallbacks[MIGRATE_TYPES][3] = {
    [MIGRATE_UNMOVABLE]   = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_TYPES },        //也就是说,当MIGRATE_UNMOVABLE类型分配不出page的时候,可以去MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE,   MIGRATE_TYPES这些类型的空闲链表中找page
    [MIGRATE_MOVABLE]     = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_TYPES },
    [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE,   MIGRATE_MOVABLE,   MIGRATE_TYPES },
};

         函数__rmqueue_fallback就是将后备type中的空闲内存块移动到需要的migratetype上。

/*
 * Try finding a free buddy page on the fallback list and put it on the free
 * list of requested migratetype, possibly along with other pages from the same
 * block, depending on fragmentation avoidance heuristics. Returns true if
 * fallback was found so that __rmqueue_smallest() can grab it.
 *
 * The use of signed ints for order and current_order is a deliberate
 * deviation from the rest of this file, to make the for loop
 * condition simpler.
 */
static __always_inline bool
__rmqueue_fallback(struct zone *zone, int order, int start_migratetype,
                        unsigned int alloc_flags)
{
    struct free_area *area;
    int current_order;
    int min_order = order;
    struct page *page;
    int fallback_mt;
    bool can_steal;

    /*
     * Do not steal pages from freelists belonging to other pageblocks
     * i.e. orders < pageblock_order. If there are no local zones free,
     * the zonelists will be reiterated without ALLOC_NOFRAGMENT.
     */
    if (alloc_flags & ALLOC_NOFRAGMENT)     //FRAGMENT:分段,碎片
        min_order = pageblock_order;

    /*
     * Find the largest available free page in the other list. This roughly
     * approximates finding the pageblock with the most free pages, which
     * would be too costly to do exactly.
     */
    for (current_order = MAX_ORDER - 1; current_order >= min_order;
                --current_order) {
        area = &(zone->free_area[current_order]);
        fallback_mt = find_suitable_fallback(area, current_order,      //从后备migratefree_list中找能够偷取的内存块
                start_migratetype, false, &can_steal);
        if (fallback_mt == -1)
            continue;

        /*
         * We cannot steal all free pages from the pageblock and the
         * requested migratetype is movable. In that case it's better to
         * steal and split the smallest available page instead of the
         * largest available page, because even if the next movable
         * allocation falls back into a different pageblock than this
         * one, it won't cause permanent fragmentation.
         */
        if (!can_steal && start_migratetype == MIGRATE_MOVABLE
                    && current_order > order)
            goto find_smallest;

        goto do_steal;
    }

    return false;

find_smallest:
    for (current_order = order; current_order < MAX_ORDER;
                            current_order++) {
        area = &(zone->free_area[current_order]);
        fallback_mt = find_suitable_fallback(area, current_order,
                start_migratetype, false, &can_steal);
        if (fallback_mt != -1)
            break;
    }

    /*
     * This should not happen - we already found a suitable fallback
     * when looking for the largest page.
     */
    VM_BUG_ON(current_order == MAX_ORDER);

do_steal:      //如果找到了可以偷取的内存块,就将整个内存块移动到需要的migrate类型的free_list链表上
    page = get_page_from_free_area(area, fallback_mt);

    steal_suitable_fallback(zone, page, alloc_flags, start_migratetype,
                                can_steal);

    trace_mm_page_alloc_extfrag(page, order, current_order,
        start_migratetype, fallback_mt);

    return true;

}
 

1.7.3:释放块

         使用函数__free_pages_ok释放块。

         同样,假设我们要释放的是内存管理区ISA DMA(0~16M)中的页框块,是从8~10M的大小为2M的页框块,并且假设其他页框块都是空闲的。

static void __free_pages_ok(struct page *page, unsigned int order,
                fpi_t fpi_flags)
{
    unsigned long flags;
    int migratetype;
    unsigned long pfn = page_to_pfn(page);

    if (!free_pages_prepare(page, order, true))           //这个函数中并没有做重要操作
        return;

    migratetype = get_pfnblock_migratetype(page, pfn);
    local_irq_save(flags);
    __count_vm_events(PGFREE, 1 << order);
    free_one_page(page_zone(page), page, pfn, order, migratetype,
              fpi_flags);
    local_irq_restore(flags);
}

static void free_one_page(struct zone *zone,
                struct page *page, unsigned long pfn,
                unsigned int order,
                int migratetype, fpi_t fpi_flags)
{
    spin_lock(&zone->lock);
    if (unlikely(has_isolate_pageblock(zone) ||
        is_migrate_isolate(migratetype))) {
        migratetype = get_pfnblock_migratetype(page, pfn);
    }
    __free_one_page(page, pfn, zone, order, migratetype, fpi_flags);
    spin_unlock(&zone->lock);
}

/*
 * Freeing function for a buddy system allocator.
 *
 * The concept of a buddy system is to maintain direct-mapped table
 * (containing bit values) for memory blocks of various "orders".
 * The bottom level table contains the map for the smallest allocatable
 * units of memory (here, pages), and each level above it describes
 * pairs of units from the levels below, hence, "buddies".
 * At a high level, all that happens here is marking the table entry
 * at the bottom level available, and propagating the changes upward
 * as necessary, plus some accounting needed to play nicely with other
 * parts of the VM system.
 * At each level, we keep a list of pages, which are heads of continuous
 * free pages of length of (1 << order) and marked with PageBuddy.
 * Page's order is recorded in page_private(page) field.
 * So when we are allocating or freeing one, we can derive the state of the
 * other.  That is, if we allocate a small block, and both were
 * free, the remainder of the region must be split into blocks.
 * If a block is freed, and its buddy is also free, then this
 * triggers coalescing into a block of larger size.
 *
 * -- nyc
 */

static inline void __free_one_page(struct page *page,
        unsigned long pfn,
        struct zone *zone, unsigned int order,
        int migratetype, fpi_t fpi_flags)
{
    struct capture_control *capc = task_capc(zone);
    unsigned long buddy_pfn;
    unsigned long combined_pfn;
    unsigned int max_order;
    struct page *buddy;
    bool to_tail;

    max_order = min_t(unsigned int, MAX_ORDER - 1, pageblock_order);

    VM_BUG_ON(!zone_is_initialized(zone));
    VM_BUG_ON_PAGE(page->flags & PAGE_FLAGS_CHECK_AT_PREP, page);

    VM_BUG_ON(migratetype == -1);
    if (likely(!is_migrate_isolate(migratetype)))
        __mod_zone_freepage_state(zone, 1 << order, migratetype);   //修改zone中,表示free page数目的字段

    VM_BUG_ON_PAGE(pfn & ((1 << order) - 1), page);
    VM_BUG_ON_PAGE(bad_range(zone, page), page);

continue_merging:
    while (order < max_order) {
        if (compaction_capture(capc, page, order, migratetype)) {
            __mod_zone_freepage_state(zone, -(1 << order),
                                migratetype);
            return;
        }
        buddy_pfn = __find_buddy_pfn(pfn, order);
        buddy = page + (buddy_pfn - pfn);        //找到pagebuddy

        if (!pfn_valid_within(buddy_pfn))
            goto done_merging;
        if (!page_is_buddy(page, buddy, order))
            goto done_merging;           //当发现page没有buddy,或者已经找到了buddy,并且将他们组合之后,就会走到这里。这时候,page就是order大小的空闲内存块
        /*
         * Our buddy is free or it is CONFIG_DEBUG_PAGEALLOC guard page,
         * merge with it and move up one order.
         */
        if (page_is_guard(buddy))
            clear_page_guard(zone, buddy, order, migratetype);
        else
            del_page_from_free_list(buddy, zone, order);  //将找到的buddy从free_list中删除
        combined_pfn = buddy_pfn & pfn;
        page = page + (combined_pfn - pfn);
        pfn = combined_pfn;
        order++;
    }
    if (order < MAX_ORDER - 1) {
        /* If we are here, it means order is >= pageblock_order.
         * We want to prevent merge between freepages on isolate
         * pageblock and normal pageblock. Without this, pageblock
         * isolation could cause incorrect freepage or CMA accounting.
         *
         * We don't want to hit this code for the more frequent
         * low-order merging.
         */
        ......

        max_order = order + 1;
        goto continue_merging;
    }

//我们将首页为page,大小为1<<order的内存块释放到伙伴系统中

done_merging:
    set_buddy_order(page, order);

    if (fpi_flags & FPI_TO_TAIL)
        to_tail = true;
    else if (is_shuffle_order(order))
        to_tail = shuffle_pick_tail();
    else
        to_tail = buddy_merge_likely(pfn, buddy_pfn, page, order);

    if (to_tail)
        add_to_free_list_tail(page, zone, order, migratetype);    //page释放到free_list
    else
        add_to_free_list(page, zone, order, migratetype);

    /* Notify page reporting subsystem of freed page */
    if (!(fpi_flags & FPI_SKIP_REPORT_NOTIFY))
        page_reporting_notify_free(order);
}
 

1.8:每CPU页框高速缓存

        内核经常请求或者释放单个页框。为此,每个内存管理区都定义了一个每CPU页框高速缓存。所有的每CPU高速缓存都包含一些预先分配的页框,用于处理CPU发出的单一内存请求。这样做的好处是,避免了从伙伴系统中分配的时候,需要多cpu之间进行同步的问题。也就是说,这和slub相似,每cpu页框缓存是伙伴系统的缓存。

        还是要注意,每CPU高速缓存的页框是从伙伴系统中分出来的。

struct zone {

    ……

    struct per_cpu_pageset __percpu *pageset;    //每个cpu都有一个pageset结构体

……

}

struct per_cpu_pageset {
    struct per_cpu_pages pcp;

    ……

#ifdef CONFIG_SMP
    s8 stat_threshold;
    s8 vm_stat_diff[NR_VM_ZONE_STAT_ITEMS];
#endif
};

struct per_cpu_pages {
    int count;        /* number of pages in the list *//* 缓存中页框个数 */
    int high;        /* high watermark, emptying needed *//* 上界,表示高速缓存需要释放*/
    int batch;        /* chunk size for buddy add/remove *//*每次添加或者删除页框的数目*/

    /* Lists of pages, one per migrate type stored on the pcp-lists */
    struct list_head lists[MIGRATE_PCPTYPES];     //链表头,链表链接的是所有属于此高速缓存的页的页描述符,依然是连接到页描述符的lru
};

        总之,在系统运行过程中,每当分配1个page的时候,都会从每cpu页框缓存中分配。类似于slub,如果每cpu页框缓存是空的话,那么就先从伙伴系统中分batch个page给每cpu页框缓存。如果每cpu页框缓存多于high值,那么就会释放batch个页框到伙伴系统中。总的来说,每cpu页框缓存的思路和slub是一致的。

1.8.1:通过每CPU页框缓存分配页框

        使用函数rmqueue_pcplist从每cpu页框缓存中分配页框。

/* Lock and remove page from the per-cpu list */
static struct page *rmqueue_pcplist(struct zone *preferred_zone,
            struct zone *zone, gfp_t gfp_flags,
            int migratetype, unsigned int alloc_flags)
{
    struct per_cpu_pages *pcp;
    struct list_head *list;
    struct page *page;
    unsigned long flags;

    local_irq_save(flags);
    pcp = &this_cpu_ptr(zone->pageset)->pcp;//this_cpu_ptr(zone->pageset)拿到了属于当前cpu的每cpu变量地址。struct per_cpu_pages中存放了每cpu缓存页框的list链表
    list = &pcp->lists[migratetype];          //拿到存放空闲页面的链表头
    page = __rmqueue_pcplist(zone,  migratetype, alloc_flags, pcp, list);
    if (page) {
        __count_zid_vm_events(PGALLOC, page_zonenum(page), 1);
        zone_statistics(preferred_zone, zone);
    }
    local_irq_restore(flags);
    return page;
}

/* Remove page from the per-cpu list, caller must protect the list */
static struct page *__rmqueue_pcplist(struct zone *zone, int migratetype,
            unsigned int alloc_flags,
            struct per_cpu_pages *pcp,
            struct list_head *list)
{
    struct page *page;

    do {
        if (list_empty(list)) {
            pcp->count += rmqueue_bulk(zone, 0,         //如果每cpu缓存页框中没有空闲页,那么还需要先扩充每cpu缓存页框。和slub的增长类似
                    pcp->batch, list,
                    migratetype, alloc_flags);
            if (unlikely(list_empty(list)))
                return NULL;
        }

        page = list_first_entry(list, struct page, lru);    //有的话,我们就直接分配空闲页框出去
        list_del(&page->lru);
        pcp->count--;
    } while (check_new_pcp(page));

    return page;
}
 

        分析内部函数rmqueue_bulk,我们观察它的参数,可知,他从内存管理区zone中,分batch次order=0的单个页框,将这些页框添加给每CPU高速缓存。这些页框在伙伴系统中看来,不是空闲的。但是在每cpu缓存中看来,他们是空闲的。

/*
 * Obtain a specified number of elements from the buddy allocator, all under
 * a single hold of the lock, for efficiency.  Add them to the supplied list.
 * Returns the number of new pages which were placed at *list.
 */
static int rmqueue_bulk(struct zone *zone, unsigned int order,
            unsigned long count, struct list_head *list,
            int migratetype, unsigned int alloc_flags)
{
    int i, alloced = 0;

    spin_lock(&zone->lock);
    for (i = 0; i < count; ++i) {
        struct page *page = __rmqueue(zone, order, migratetype,      //这个函数的实现已经在伙伴系统中描述过了,就是从伙伴系统中分配页框
                                alloc_flags);
        if (unlikely(page == NULL))
            break;

        if (unlikely(check_pcp_refill(page)))
            continue;

        /*
         * Split buddy pages returned by expand() are received here in
         * physical page order. The page is added to the tail of
         * caller's list. From the callers perspective, the linked list
         * is ordered by page number under some conditions. This is
         * useful for IO devices that can forward direction from the
         * head, thus also in the physical page order. This is useful
         * for IO devices that can merge IO requests if the physical
         * pages are ordered properly.
         */
        list_add_tail(&page->lru, list);   //将这些page添加到每cpu页框缓存的pcp->lists[migratetype]链表中
        alloced++;
        if (is_migrate_cma(get_pcppage_migratetype(page)))
            __mod_zone_page_state(zone, NR_FREE_CMA_PAGES,
                          -(1 << order));
    }

    /*
     * i pages were removed from the buddy list even if some leak due
     * to check_pcp_refill failing so adjust NR_FREE_PAGES based
     * on i. Do not confuse with 'alloced' which is the number of
     * pages added to the pcp list.
     */
    __mod_zone_page_state(zone, NR_FREE_PAGES, -(i << order));      //这些页在伙伴系统中看,是使用中的页
    spin_unlock(&zone->lock);
    return alloced;
}

1.8.2:释放页框到每CPU页框高速缓存

        函数free_unref_page用于将order=0的内存块释放到每cpu页框缓存。调用链为__free_pages->free_the_page->free_unref_page

/*
 * Free a 0-order page
 */
void free_unref_page(struct page *page)
{
    unsigned long flags;
    unsigned long pfn = page_to_pfn(page);

    if (!free_unref_page_prepare(page, pfn))       //这里同样不做关键操作
        return;

    local_irq_save(flags);     //关中断。同步是为了保护数据结构。中断和普通路径都可能走到这里,要避免他们之间产生竞争
    free_unref_page_commit(page, pfn);
    local_irq_restore(flags);
}

static void free_unref_page_commit(struct page *page, unsigned long pfn)
{
    struct zone *zone = page_zone(page);
    struct per_cpu_pages *pcp;
    int migratetype;

    migratetype = get_pcppage_migratetype(page);
    __count_vm_event(PGFREE);

    /*
     * We only track unmovable, reclaimable and movable on pcp lists.
     * Free ISOLATE pages back to the allocator because they are being
     * offlined but treat HIGHATOMIC as movable pages so we can get those
     * areas back if necessary. Otherwise, we may have to free
     * excessively into the page allocator
     */
    if (migratetype >= MIGRATE_PCPTYPES) {
        if (unlikely(is_migrate_isolate(migratetype))) {
            free_one_page(zone, page, pfn, 0, migratetype,
                      FPI_NONE);
            return;
        }
        migratetype = MIGRATE_MOVABLE;
    }

    pcp = &this_cpu_ptr(zone->pageset)->pcp;
    list_add(&page->lru, &pcp->lists[migratetype]);   //page添加到pcp->lists
    pcp->count++;
    if (pcp->count >= pcp->high) {     //如果每cpu页框缓存过大,将batch个页释放到伙伴系统中
        unsigned long batch = READ_ONCE(pcp->batch);
        free_pcppages_bulk(zone, batch, pcp);
    }
}

        分析函数free_pcppages_bulk

/*

 * Frees a number of pages from the PCP lists

 * Assumes all pages on list are in same zone, and of same order.

 * count is the number of pages to free.

 *

 * If the zone was previously in an "all pages pinned" state then look to

 * see if this freeing clears that state.

 *

 * And clear the zone's pages_scanned counter, to hold off the "all pages are

 * pinned" detection logic.

 */

static void free_pcppages_bulk(struct zone *zone, int count,

struct per_cpu_pages *pcp)

{

int migratetype = 0;

int batch_free = 0;

int prefetch_nr = 0;

bool isolated_pageblocks;

struct page *page, *tmp;

LIST_HEAD(head);

/*

 * Ensure proper count is passed which otherwise would stuck in the

 * below while (list_empty(list)) loop.

 */

count = min(pcp->count, count);

while (count) {

struct list_head *list;

/*

 * Remove pages from lists in a round-robin fashion. A

 * batch_free count is maintained that is incremented when an

 * empty list is encountered.  This is so more pages are freed

 * off fuller lists instead of spinning excessively around empty

 * lists

 */

do {

batch_free++;

if (++migratetype == MIGRATE_PCPTYPES)

migratetype = 0;

list = &pcp->lists[migratetype];           //这个循环再加上外面的大循环,能保证从不同的migratetype链表中都找到page释放

} while (list_empty(list));

/* This is the only non-empty list. Free them all. */

if (batch_free == MIGRATE_PCPTYPES)

batch_free = count;

do {

page = list_last_entry(list, struct page, lru);

/* must delete to avoid corrupting pcp list */

list_del(&page->lru);

pcp->count--;

if (bulkfree_pcp_prepare(page))         //此函数没有做有意义的操作

continue;

list_add_tail(&page->lru, &head);     //要释放的页链接在head链表上

/*

 * We are going to put the page back to the global

 * pool, prefetch its buddy to speed up later access

 * under zone->lock. It is believed the overhead of

 * an additional test and calculating buddy_pfn here

 * can be offset by reduced memory latency later. To

 * avoid excessive prefetching due to large count, only

 * prefetch buddy for the first pcp->batch nr of pages.

 */

if (prefetch_nr++ < pcp->batch)

prefetch_buddy(page);

} while (--count && --batch_free && !list_empty(list));

}

spin_lock(&zone->lock);

isolated_pageblocks = has_isolate_pageblock(zone);

/*

 * Use safe version since after __free_one_page(),

 * page->lru.next will not point to original list.

 */

list_for_each_entry_safe(page, tmp, &head, lru) {

int mt = get_pcppage_migratetype(page);

……

__free_one_page(page, page_to_pfn(page), zone, 0, mt, FPI_NONE);       //调用__free_one_page,将page释放到伙伴系统中,这个函数已经在1.7.3中描述过了

trace_mm_page_pcpu_drain(page, 0, mt);

}

spin_unlock(&zone->lock);

}


/*
 * Frees a number of pages from the PCP lists
 * Assumes all pages on list are in same zone, and of same order.
 * count is the number of pages to free.
 *
 * If the zone was previously in an "all pages pinned" state then look to
 * see if this freeing clears that state.
 *
 * And clear the zone's pages_scanned counter, to hold off the "all pages are
 * pinned" detection logic.
 */
static void free_pcppages_bulk(struct zone *zone, int count,
                    struct per_cpu_pages *pcp)
{
    int migratetype = 0;
    int batch_free = 0;
    int prefetch_nr = 0;
    bool isolated_pageblocks;
    struct page *page, *tmp;
    LIST_HEAD(head);

    /*
     * Ensure proper count is passed which otherwise would stuck in the
     * below while (list_empty(list)) loop.
     */
    count = min(pcp->count, count);
    while (count) {
        struct list_head *list;

        /*
         * Remove pages from lists in a round-robin fashion. A
         * batch_free count is maintained that is incremented when an
         * empty list is encountered.  This is so more pages are freed
         * off fuller lists instead of spinning excessively around empty
         * lists
         */
        do {
            batch_free++;
            if (++migratetype == MIGRATE_PCPTYPES)
                migratetype = 0;
            list = &pcp->lists[migratetype];           //这个循环再加上外面的大循环,能保证从不同的migratetype链表中都找到page释放
        } while (list_empty(list));

        /* This is the only non-empty list. Free them all. */
        if (batch_free == MIGRATE_PCPTYPES)
            batch_free = count;

        do {
            page = list_last_entry(list, struct page, lru);
            /* must delete to avoid corrupting pcp list */
            list_del(&page->lru);
            pcp->count--;

            if (bulkfree_pcp_prepare(page))         //此函数没有做有意义的操作
                continue;

            list_add_tail(&page->lru, &head);    //要释放的页链接在head链表上

            /*
             * We are going to put the page back to the global
             * pool, prefetch its buddy to speed up later access
             * under zone->lock. It is believed the overhead of
             * an additional test and calculating buddy_pfn here
             * can be offset by reduced memory latency later. To
             * avoid excessive prefetching due to large count, only
             * prefetch buddy for the first pcp->batch nr of pages.
             */
            if (prefetch_nr++ < pcp->batch)
                prefetch_buddy(page);
        } while (--count && --batch_free && !list_empty(list));
    }

    spin_lock(&zone->lock);
    isolated_pageblocks = has_isolate_pageblock(zone);

    /*
     * Use safe version since after __free_one_page(),
     * page->lru.next will not point to original list.
     */
    list_for_each_entry_safe(page, tmp, &head, lru) {
        int mt = get_pcppage_migratetype(page);
       ……

        __free_one_page(page, page_to_pfn(page), zone, 0, mt, FPI_NONE);       //调用__free_one_page,将page释放到伙伴系统中,这个函数已经在1.7.3中描述过了
        trace_mm_page_pcpu_drain(page, 0, mt);
    }
    spin_unlock(&zone->lock);
}

1.9:管理区分配器

        Linux5-10.110版本请参考第8章补第2节。

        在上面的页框分配中,我们已经很好的分配了页框。但是,还有一个前提条件,就是,我们还需要找到一个合适的内存管理区,然后在这个内存管理区中分配页框。因此,这一部分就是如果找到合适的内存管理区。

        管理区分配器要满足几个目标:1:他应当保护保留的页框池。2:当内存不足,并且允许阻塞当前进程的时候,他应该触发页框回收算法。一旦某些页框被释放,管理区分配器将再次尝试分配。3:如果可能,他应该保存珍贵的ZONE_DMA内存管理区。

        对一组连续页框的每次请求实质上是通过执行alloc_pages宏来处理的。这个宏实际上调用了函数:

#define alloc_pages(gfp_mask, order) \

        alloc_pages_node(numa_node_id(), gfp_mask, order)

static inline struct page *alloc_pages_node(int nid, unsigned int gfp_mask,

                        unsigned int order)

{

    return __alloc_pages(gfp_mask, order,

        NODE_DATA(nid)->node_zonelists + (gfp_mask & GFP_ZONEMASK));

}

struct page * fastcall __alloc_pages(unsigned int gfp_mask, unsigned int order, struct zonelist *zonelist)

gfp_mask:内存分配的标志。这个标志能够表示从哪些内存管理区中分配,在分配过程中是否可以阻塞,分配不成功时的处理方式等

order:表示要分配2^order个页框

zonelist:指向zonelist的数据结构,该数据结构按照优先次序描述了适用于内存分配的内存管理区,他的数据结构如下

struct zone *zones[MAX_NUMNODES * MAX_NR_ZONES + 1];

本次分配可以使用哪些内存管理区由分配参数gfp_mask决定

需要注意的是参数struct zonelist *zonelist,他表示了用于内存分配的内存管理区的优先次序。

        首先要理解函数zone_watermark_ok。他的含义是,从zone中分配order幂的空闲页后,zone的空闲页依然高于水位要求。

bool zone_watermark_ok(struct zone *z, unsigned int order, unsigned long mark,
              int highest_zoneidx, unsigned int alloc_flags)
{
    return __zone_watermark_ok(z, order, mark, highest_zoneidx, alloc_flags,
                    zone_page_state(z, NR_FREE_PAGES));           //传入的最后一个参数是这个内存区中的free_pages
}

/*
 * Return true if free base pages are above 'mark'. For high-order checks it
 * will return true of the order-0 watermark is reached and there is at least
 * one free page of a suitable size. Checking now avoids taking the zone lock
 * to check in the allocation paths if no pages are free.
 */
bool __zone_watermark_ok(struct zone *z, unsigned int order, unsigned long mark,
             int highest_zoneidx, unsigned int alloc_flags,
             long free_pages)
{
    long min = mark;
    int o;
    const bool alloc_harder = (alloc_flags & (ALLOC_HARDER|ALLOC_OOM));        //这种情况下,alloc_harder

    /* free_pages may go negative - that's OK */
    free_pages -= __zone_watermark_unusable_free(z, order, alloc_flags);    //看分配了order幂的页后,zone还剩的空闲页

    if (alloc_flags & ALLOC_HIGH)
        min -= min / 2;

    if (unlikely(alloc_harder)) {
        /*
         * OOM victims can try even harder than normal ALLOC_HARDER
         * users on the grounds that it's definitely going to be in
         * the exit path shortly and free memory. Any allocation it
         * makes during the free path will be small and short-lived.
         */
        if (alloc_flags & ALLOC_OOM)
            min -= min / 2;
        else
            min -= min / 4;
    }

//上面共同确定min的值

    /*
     * Check watermarks for an order-0 allocation request. If these
     * are not met, then a high-order request also cannot go ahead
     * even if a suitable page happened to be free.
     */
    if (free_pages <= min + z->lowmem_reserve[highest_zoneidx])
        return false;

// 要求管理区剩下的页框数目必须大于min + z->lowmem_reserve[classzone_idx]。也就是虽然我们分配内存的时候,尽量避免从低级别的zone(也就是DMA/DMA32)中分配

    /* If this is an order-0 request then the watermark is fine */
    if (!order)
        return true;

//并且要有一个连续内存区,能够满足分配需要。也就是z->free_area[o]不为空

    /* For a high-order request, check at least one suitable page is free */
    for (o = order; o < MAX_ORDER; o++) {
        struct free_area *area = &z->free_area[o];
        int mt;

        if (!area->nr_free)
            continue;

//每个area的free_list包含MIGRATE_TYPES中链表。只有在alloc_harder情况下,我们才能使用area->free_list[MIGRATE_HIGHATOMIC]

        for (mt = 0; mt < MIGRATE_PCPTYPES; mt++) {
            if (!free_area_empty(area, mt))
                return true;
        }

        ......
        if (alloc_harder && !free_area_empty(area, MIGRATE_HIGHATOMIC))
            return true;
    }
    return false;
}

        通过下面的函数__alloc_pages可以看到,只有当函数zone_watermark_ok返回1的时候,才能够分配页框。zone_watermark_ok会依据不同的参数确定一个最小值min。只有在以下情况,函数会返回1:

        1:除了要分配的页框外,在内存管理区中至少还有min个空闲页框,并且这min个空闲页框不能包含为内存不足保留的页框(lowmem_reserve字段描述)。

        2:必须是一段连续的空闲内存满足要求。

        可以看到,上面的函数也是按照这样的要求编写的。

        再看函数__alloc_pages,这个函数的逻辑就是,找到一个合适的内存管理区,然后在这个内存管理区中分配需要的页框。

struct page * fastcall __alloc_pages(unsigned int gfp_mask, unsigned int order,

        struct zonelist *zonelist)

{

    const int wait = gfp_mask & __GFP_WAIT;     // __GFP_WAIT:当前进程可以阻塞在这个函数中

    struct zone **zones, *z;

    struct page *page;

    struct reclaim_state reclaim_state;

    struct task_struct *p = current;

    int i;

    int classzone_idx;

    int do_retry;

    int can_try_harder;

    int did_some_progress;

    might_sleep_if(wait);

    can_try_harder = (unlikely(rt_task(p)) && !in_interrupt()) || !wait;

    zones = zonelist->zones;        //可以分配的内存管理区构成的数组的首地址

    classzone_idx = zone_idx(zones[0]);    //最希望使用classzone_idx这一管理区分配

 restart:

    /* Go through the zonelist once, looking for a zone with enough free */

    for (i = 0; (z = zones[i]) != NULL; i++) {      //遍历zones内存管理区数组中,所有的内存管理区。这是这次分配可以使用的管理区

        if (!zone_watermark_ok(z, order, z->pages_low,  

                       classzone_idx, 0, 0))        // pages_low回收页框使用的上界。这时第一次扫描,这时候can_try_harder和gfp_high被设置为0.

            continue;

        page = buffered_rmqueue(z, order, gfp_mask);    //找到了合适的内存管理区,在这个管理区中,通过伙伴系统分配页框

        if (page)

            goto got_pg;

    }

    for (i = 0; (z = zones[i]) != NULL; i++)         //上面的操作没有分配出来页框,那么唤醒内核线程kswapd。这部分内容参见第十七章

        wakeup_kswapd(z, order);

    for (i = 0; (z = zones[i]) != NULL; i++) {

        if (!zone_watermark_ok(z, order, z->pages_min,  // pages_min保留页框的个数

                       classzone_idx, can_try_harder,

                       gfp_mask & __GFP_HIGH))   //这时候使用了较低的阈值,最终反映到zone中要求保留的页框变少

            continue;

        page = buffered_rmqueue(z, order, gfp_mask);

        if (page)

            goto got_pg;

    }

    //如果此次内核控制路径不是中断处理程序也不是可延迟函数,并且这个进程试图回收页框,那么这次会忽略内存不足的阈值,使用为内存不足预留的页框来进行分配

    if (((p->flags & PF_MEMALLOC) || unlikely(test_thread_flag(TIF_MEMDIE))) && !in_interrupt()) {

        for (i = 0; (z = zones[i]) != NULL; i++) {

            page = buffered_rmqueue(z, order, gfp_mask);      //直接分配页框,不需要为管理区保留。只有在这种情况下,才会使用保留的页框

            if (page)

                goto got_pg;

        }

        goto nopage;

    }

    /* Atomic allocations - we can't balance anything */

    if (!wait)     //如果这个进程不能在这个函数中阻塞,那么打印错误信息后,返回空

        goto nopage;

rebalance:    

    cond_resched();     //进程可以在这个函数中阻塞。如果没有分配到页框,那么这里检查是否需要调度。可能TIF_NEED_RESCHED已经被设置了,但是还没有到调度点,没有得到调度

    p->flags |= PF_MEMALLOC;    //设置PF_MEMALLOC,表示进程要进行内存回收

    reclaim_state.reclaimed_slab = 0;

    p->reclaim_state = &reclaim_state;

    did_some_progress = try_to_free_pages(zones, gfp_mask, order);   //紧急回收内存,具体内容参考第17章。注意,之前说过,设置了PF_MEMALLOC的进程,在请求页框的时候,会将内存保护区中的页框分配给这个进程。因此,这里是先设置进程的PF_MEMALLOC,然后让他回收页框

    p->reclaim_state = NULL;

    p->flags &= ~PF_MEMALLOC;

    cond_resched();

    if (likely(did_some_progress)) {     //如果紧急回收页框回收到了一些页框,那么再尝试一次页框分配

        for (i = 0; (z = zones[i]) != NULL; i++) {

            if (!zone_watermark_ok(z, order, z->pages_min,

                           classzone_idx, can_try_harder,

                           gfp_mask & __GFP_HIGH))

                continue;

            page = buffered_rmqueue(z, order, gfp_mask);

            if (page)

                goto got_pg;

        }

    } else if ((gfp_mask & __GFP_FS) && !(gfp_mask & __GFP_NORETRY)) {    //紧急回收页框不成功。再试一下

        for (i = 0; (z = zones[i]) != NULL; i++) {

            if (!zone_watermark_ok(z, order, z->pages_high,

                           classzone_idx, 0, 0))

                continue;

            page = buffered_rmqueue(z, order, gfp_mask);

            if (page)

                goto got_pg;

        }

        out_of_memory(gfp_mask);     //分不出来,进入OOM。杀死一个进程并释放内存,参考17章

        goto restart;

    }

    do_retry = 0;

    if (!(gfp_mask & __GFP_NORETRY)) {

        if ((order <= 3) || (gfp_mask & __GFP_REPEAT))

            do_retry = 1;

        if (gfp_mask & __GFP_NOFAIL)

            do_retry = 1;

    }

    if (do_retry) {

        blk_congestion_wait(WRITE, HZ/50);

        goto rebalance;

    }

nopage:

    if (!(gfp_mask & __GFP_NOWARN) && printk_ratelimit()) {

        printk(KERN_WARNING "%s: page allocation failure."

            " order:%d, mode:0x%x\n",

            p->comm, order, gfp_mask);

        dump_stack();

    }

    return NULL;

got_pg:

    return page;

}

        因此,做个总结。当使用函数alloc_pages的时候,首先去选择一个有足够空闲页框的内存管理区。然后在这个管理区中使用伙伴系统或者每CPU高速缓存(只需要一个页框的时候),分出需要的页框。

1.9.1:释放一组页框

        释放页框的函数如下所示:

void free_pages(unsigned long addr, unsigned int order)

{

    if (addr != 0) {

    VM_BUG_ON(!virt_addr_valid((void *)addr));

    __free_pages(virt_to_page((void *)addr), order);

    }

}

void __free_pages(struct page *page, unsigned int order)

{

    if (put_page_testzero(page))        //页框没有进程使用

        free_the_page(page, order);

    else if (!PageHead(page))

        while (order-- > 0)

            free_the_page(page + (1 << order), order);

}

static inline void free_the_page(struct page *page, unsigned int order)

{

    if (order == 0)                /* Via pcp? */

        free_unref_page(page);           //释放到每cpu缓存中

    else

        __free_pages_ok(page, order, FPI_NONE);         //释放到伙伴系统中

}

        释放到每cpu缓存和释放到伙伴系统中的函数我们都已经描述过了。

2:内存区管理

        上面关注的都是按页框进行分配。但是,当我们请求小内存区的时候,如果按照页框进行分配,就会造成大量的浪费。

        kmem_cache是Linux内核中用于分配小内存块的高速缓存机制。kmem_cache高速缓存通过预先分配一些内存块,并将其存储在一个特定大小的内存池中。这些与分配的内存块被组织成一个链表。当应用程序需要分配内存的时候,内核可以直接从这个链表中分配,而不是从通用的内存分配器中分配。同样,当内存块不再需要的时候,他们也不会立即释放,而是被放回高速缓存中备用。

        kmem_cache的主要优点如下:

        1:减少内存分配开销:直接从预分配的内存块中获取内存可以避免再每次分配的时候调用通用内存分配器,减少了分配操作的开销。

        2:降低内存碎片:由于内存块被预先分配,并保持在池中,因此内存碎片变少了。

        3:提高性能:避免了内存的频繁分配和释放。

2.1:slab分配器

        slab分配器把对象,也就是一种数据结构,放入高速缓存kmem_cache。这个高速缓存就可以看做是这一类对象的专用储备。每个高速缓存都被划分成多个slab。每个slab由一个或者多个连续的页框组成,其中包含了已分配的对象(数据结构),也包含空闲的对象(数据结构)。

        从上图中能够看出来,一个kmem_cache包含多个slab,一个slab包含多个页框,里面存放了分配的对象。

2.2:高速缓存描述符

        每个高速缓存都通过kmem_cache_s结构体描述。所有的高速缓存通过双链表连接在一起,生成了高速缓存链表cache_chain。高速缓存描述符kmem_cache_t的内容如下:

struct kmem_cache_s {

/* 1) per-cpu data, touched during every alloc/free */

    struct array_cache    *array[NR_CPUS];

    unsigned int        batchcount;

    unsigned int        limit;

/* 2) touched by every alloc & free from the backend */

    struct kmem_list3    lists;      //用于连接这个高速缓存中的slab描述符

    unsigned int        objsize;     //高速缓存中每个对象的大小

    unsigned int         flags;    /* constant flags */

    unsigned int        num;         //一个slab中的对象的数目

    unsigned int        free_limit; /* upper limit of objects in the lists */

    spinlock_t        spinlock;

/* 3) cache_grow/shrink */

    /* order of pgs per slab (2^n) */

    unsigned int        gfporder;     //一个单独的slab中包含的连续页框数目的对数

    /* force GFP flags, e.g. GFP_DMA */

    unsigned int        gfpflags;

    size_t            colour;        /* cache colouring range */

    unsigned int        colour_off;         //高速缓存中,slab结构体在slab页框中的偏移

    unsigned int        colour_next;    /* cache colouring */

    kmem_cache_t        *slabp_cache;     //一个kmem_cache指针。当slab结构体从外部分配的时候,slab结构体从这个kmem_cache中分配。当然,如果slab结构体从内部分配,这个值为NULL

    unsigned int        slab_size;

    unsigned int        dflags;        /* dynamic flags */

/* 4) cache creation/removal */

    const char        *name;    //高速缓存名字

    struct list_head    next;    //用于连接下一个高速缓存描述符

};

        其中,lists成员较为关键,他指示了高速缓存中已经用完的自己所有空闲对象的slab链表slabs_full,用了部分,还剩有部分空闲对象的slab链表slabs_partial,完全没使用的slab链表slabs_free。他的成员如下:

struct kmem_list3 {

    struct list_head    slabs_partial;    //指向部分使用的slab描述符

    struct list_head    slabs_full;         //指向完全使用的slab描述符

    struct list_head    slabs_free;        //指向完全没使用的 slab描述符

    unsigned long    free_objects;

    int        free_touched;

    unsigned long    next_reap;

    struct array_cache    *shared;

};

2.3:slab描述符

        高速缓存中的每个slab都有自己的描述符

struct slab {

    struct list_head    list;         //用于连接高速缓存描述符的kmem_list3中的链表字段

    unsigned long        colouroff;     //slab中第一个对象的偏移

    void            *s_mem;          //slab中的第一个对象

    unsigned int        inuse;        /* num of objs active in slab */

    kmem_bufctl_t        free;    //slab中下一个空闲对象的下标

};

        每个高速缓存的第一个slab的list的prev指向高速缓存描述符,next指向下一个与之相似的slab的slab的list.prev。slab描述符可以存在slab外部或者内部。

2.4:普通和专用高速缓存

       kmem高速缓存分成两类,普通高速缓存和专用高速缓存。普通高速缓存是由slab分配器用于自己目的的高速缓存;内核其他组件使用的高速缓存是专用高速缓存;例如task结构体构成的高速缓存。普通高速缓存包含以下两种

        1:cache_cache:高速缓存结构体kmem_cache自己作为一个对象,也需要高速缓存以及slab系统来存储他。存储存储高速缓存描述符的高速缓存是cache_cache。

static kmem_cache_t cache_cache = {

    .lists        = LIST3_INIT(cache_cache.lists),    //三个slab双向链表

    .batchcount    = 1,

    .limit        = BOOT_CPUCACHE_ENTRIES,

    .objsize    = sizeof(kmem_cache_t),              //高速缓存存储的每个成员的大小(这里也可以看到,这个高速缓存是存储高速缓存对象的高速缓存)

    .flags        = SLAB_NO_REAP,

    .spinlock    = SPIN_LOCK_UNLOCKED,

    .name        = "kmem_cache",

};

        由于他是存储高速缓存描述符的高速缓存,可想而知,他也是系统中第一个高速缓存。因此,在高速缓存描述符构成的链表cache_chain中,他也是第一个元素。

static struct list_head cache_chain;

void __init kmem_cache_init(void)

{

……

    list_add(&cache_cache.next, &cache_chain);   //第一个被加入链表的元素

……

}

        2:另外还有一些高速缓存,这种高速缓存包含的内存大小分别是32字节,64字节……2M。由于每种大小的高速缓存都有两个,分别适用于ISA DMA分配和常规分配。所以总共有26个高速缓存,因此也对应了26个高速缓存描述符。

        专用高速缓存是由kmem_cache_create创建的。在创建一个新的高速缓存的时候,会将新创建的高速缓存描述符加入到kmem_cache。并且还需要将高速缓存描述符加入cache_chain链表中。

        接口kmem_cache_destory用于删除一个高速缓存,并且将高速缓存描述符从cache_chain链表上删除。要注意的是,必须在删除高速缓存之前,删除他所有的slab。

        我们以文件系统中使用到的一个例子,来研究kmem_cache_create的行为。

示例:
radix_tree_node_cachep = kmem_cache_create("radix_tree_node",

sizeof(struct radix_tree_node), 0,

SLAB_PANIC, radix_tree_node_ctor, NULL);

/**

 * kmem_cache_create - Create a cache.

 * @name: A string which is used in /proc/slabinfo to identify this cache.

//name为radix_tree_node。我们可以通过/proc/slabinfo来观察这个kmem_cache

 * @size: The size of objects to be created in this cache.

//kmem_cache中存储的是struct radix_tree_node结构体

 * @align: The required alignment for the objects.

 * @flags: SLAB flags

 * @ctor: A constructor for the objects.

//给slab分配新的页的时候,需要做的操作

 * @dtor: A destructor for the objects.

 *

//不能在中断中使用这个函数

 * Returns a ptr to the cache on success, NULL on failure.

 * Cannot be called within a int, but can be interrupted.

 * The @ctor is run when new pages are allocated by the cache

 * and the @dtor is run before the pages are handed back.

 *

 * @name must be valid until the cache is destroyed. This implies that

 * the module calling this has to destroy the cache before getting

 * unloaded.

 *

 * The flags are

 *

 * %SLAB_POISON - Poison the slab with a known test pattern (a5a5a5a5)

 * to catch references to uninitialised memory.

 *

 * %SLAB_RED_ZONE - Insert `Red' zones around the allocated memory to check

 * for buffer overruns.

 *

 * %SLAB_NO_REAP - Don't automatically reap this cache when we're under

 * memory pressure.

 *

 * %SLAB_HWCACHE_ALIGN - Align the objects in this cache to a hardware

 * cacheline.  This can be beneficial if you're counting cycles as closely

 * as davem.

 */

kmem_cache_t *

kmem_cache_create (const char *name, size_t size, size_t align,

unsigned long flags, void (*ctor)(void*, kmem_cache_t *, unsigned long),

void (*dtor)(void*, kmem_cache_t *, unsigned long))

{

size_t left_over, slab_size, ralign;

kmem_cache_t *cachep = NULL;

/* Check that size is in terms of words.  This is needed to avoid

 * unaligned accesses for some archs when redzoning is used, and makes

 * sure any on-slab bufctl's are also correctly aligned.

 */

//----------------------------------------------------------------------------

如果我们有一个值size,还有一个对齐值align。我们可以按照下面的方法,让size按照align向上或者向下对齐

向上对齐:size = (size + align - 1) & ~(align - 1);

向下对齐:size = size & ~(align - 1);

//----------------------------------------------------------------------------

if (size & (BYTES_PER_WORD-1)) {

size += (BYTES_PER_WORD-1);

size &= ~(BYTES_PER_WORD-1);

}

/* calculate out the final buffer alignment: */

/* 1) arch recommendation: can be overridden for debug */

if (flags & SLAB_HWCACHE_ALIGN) {      //SLAB_HWCACHE_ALIGN表示是否需要硬件缓存对齐

/* Default alignment: as specified by the arch code.

 * Except if an object is really small, then squeeze multiple

 * objects into one cacheline.

 */

ralign = cache_line_size();

while (size <= ralign/2)

ralign /= 2;

} else {

ralign = BYTES_PER_WORD;

}

/* 2) arch mandated alignment: disables debug if necessary */

if (ralign < ARCH_SLAB_MINALIGN) {      //ARCH_SLAB_MINALIGN表示架构要求的最小对齐值

ralign = ARCH_SLAB_MINALIGN;

//---------------------------------------------------------------------------------

如果ralign的值大于一个字,那么,这时候需要删除SLAB_RED_ZONE和SLAB_STORE_USER。

SLAB_RED_ZONE:这个标志用于在分配的内存块两侧插入Red区域,用于检测内存泄漏。

SLAB_STORE_USER:这个标志用于在缓存的对象中存储一些用户特定的信息

//---------------------------------------------------------------------------------

if (ralign > BYTES_PER_WORD)

flags &= ~(SLAB_RED_ZONE|SLAB_STORE_USER);

}

/* 3) caller mandated alignment: disables debug if necessary */

if (ralign < align) {

ralign = align;

if (ralign > BYTES_PER_WORD)

flags &= ~(SLAB_RED_ZONE|SLAB_STORE_USER);

}

/* 4) Store it. Note that the debug code below can reduce

*    the alignment to BYTES_PER_WORD.

*/

align = ralign;

/* Get cache's description obj. */

//在这里创建了新的kmem_cache结构体。kmem_cache也是通过cache_cache这个高速缓存分配出来的

cachep = (kmem_cache_t *) kmem_cache_alloc(&cache_cache, SLAB_KERNEL);

if (!cachep)

goto opps;

memset(cachep, 0, sizeof(kmem_cache_t));

/* Determine if the slab management is 'on' or 'off' slab. */

if (size >= (PAGE_SIZE>>3))

/*

 * Size is large, assume best to place the slab management obj

 * off-slab (should allow better packing of objs).

 */

flags |= CFLGS_OFF_SLAB;                //CFLGS_OFF_SLAB:将slab结构体放在分配给slab存放对象的页框外部

size = ALIGN(size, align);

if ((flags & SLAB_RECLAIM_ACCOUNT) && size <= PAGE_SIZE) {

//--------------------------------------------------------------------------------------

SLAB_RECLAIM_ACCOUNT:表示分配给这个kmem_cache的页是可回收的。在内核内存紧张的时候,可以回收这个页面。

在这种时候,会将cachep->gfporder设置为0。这个值代表了,给这个kmem_cache的slab,从伙伴系统中分配页面的时候,每次分配1页

//--------------------------------------------------------------------------------------

/*

 * A VFS-reclaimable slab tends to have most allocations

 * as GFP_NOFS and we really don't want to have to be allocating

 * higher-order pages when we are unable to shrink dcache.

 */

cachep->gfporder = 0;

cache_estimate(cachep->gfporder, size, align, flags,         

&left_over, &cachep->num);            

//--------------------------------------------------------------------------------------

//cache_estimate是一个相当重要的函数。他的参数解释如下:

cachep->gfporder:入参,指定了一个slab占用了多少页

size:入参,指定了slab中每个对象的字节大小

align:入参,指定了每个对象应该按照什么样的方式对齐

flags:入参,控制了slab的分配方式

left_over:出参,表示了一个slab占有的页中,分配了对象之后,还剩下的内存字节数

cachep->num:出参,表示了一个slab占有的页可以分配的对象的数目

//--------------------------------------------------------------------------------------

} else {

/*

 * Calculate size (in pages) of slabs, and the num of objs per

 * slab.  This could be made much more intelligent.  For now,

 * try to avoid using high page-orders for slabs.  When the

 * gfp() funcs are more friendly towards high-order requests,

 * this should be changed.

 */

do {

unsigned int break_flag = 0;

cal_wastage:

cache_estimate(cachep->gfporder, size, align, flags,

&left_over, &cachep->num);

if (break_flag)

break;

if (cachep->gfporder >= MAX_GFP_ORDER)

break;

if (!cachep->num)

goto next;

if (flags & CFLGS_OFF_SLAB && cachep->num > offslab_limit) {

/* This num of objs will cause problems. */

cachep->gfporder--;

break_flag++;

goto cal_wastage;

}

/*

 * Large num of objs is good, but v. large slabs are

 * currently bad for the gfp()s.

 */

if (cachep->gfporder >= slab_break_gfp_order)

break;

if ((left_over*8) <= (PAGE_SIZE<<cachep->gfporder))

break;        /* Acceptable internal fragmentation. */

next:

cachep->gfporder++;

} while (1);

}

if (!cachep->num) {            //这个值就是通过cache_estimate函数计算出来的,一个slab中,能够存储对象的个数

printk("kmem_cache_create: couldn't create cache %s.\n", name);

kmem_cache_free(&cache_cache, cachep);

cachep = NULL;

goto opps;

}

//slab_size就是slab结构体需要的字节大小。这里注意,不仅需要sizeof(struct slab),还需要cachep->num*sizeof(kmem_bufctl_t)。后者会在slab分配的时候使用

slab_size = ALIGN(cachep->num*sizeof(kmem_bufctl_t)

+ sizeof(struct slab), align);

/*

 * If the slab has been placed off-slab, and we have enough space then

 * move it on-slab. This is at the expense of any extra colouring.

 */

//如果我们指定了CFLGS_OFF_SLAB,但是给slab分配的页中,有足够的空间来存放slab结构体。这时候,我们清楚CFLGS_OFF_SLAB标志。也就是说,这时候我们将slab结构体放在slab页中

if (flags & CFLGS_OFF_SLAB && left_over >= slab_size) {

flags &= ~CFLGS_OFF_SLAB;

left_over -= slab_size;

}

if (flags & CFLGS_OFF_SLAB) {

/* really off slab. No need for manual alignment *///真的将slab放在外面。这时候不需要手动对齐

slab_size = cachep->num*sizeof(kmem_bufctl_t)+sizeof(struct slab);

}

cachep->colour_off = cache_line_size();

/* Offset must be a multiple of the alignment. */

if (cachep->colour_off < align)

cachep->colour_off = align;           //cachep->colour_off 用于着色。描述了每个着色的字节偏移

cachep->colour = left_over/cachep->colour_off;      //left_over是所有剩下的内存,cachep->colour_off是每个着色的字节偏移。因此,cachep->colour描述了总共有多少个颜色

cachep->slab_size = slab_size;     //每个slab结构体的大小。之后,创建新的slab的时候,不需要再通过函数cache_estimate进行计算

cachep->flags = flags;              //描述了slab结构体在页面内还是页面外等信息

cachep->gfpflags = 0;

if (flags & SLAB_CACHE_DMA)

cachep->gfpflags |= GFP_DMA;

spin_lock_init(&cachep->spinlock);

cachep->objsize = size;                   //cachep->objsize的值就是slab中存放的每个对象的字节大小

/* NUMA */

INIT_LIST_HEAD(&cachep->lists.slabs_full);

INIT_LIST_HEAD(&cachep->lists.slabs_partial);

INIT_LIST_HEAD(&cachep->lists.slabs_free);

if (flags & CFLGS_OFF_SLAB)

cachep->slabp_cache = kmem_find_general_cachep(slab_size,0);   

//---------------------------------------------------

还记得之前我们说的吗,通用高速缓存由slab系统内部使用。这里,根据slab_size去malloc_sizes这一全局数组中,寻找一个合适的通用高速缓存。

struct cache_sizes malloc_sizes[] = {

#define CACHE(x) { .cs_size = (x) },

#include <linux/kmalloc_sizes.h>

{ 0, }

#undef CACHE

};

这种代码已经在其他地方看过了。是一种比较方便的定义全局数组的方式。

函数kmem_find_general_cachep使用的时候,根据slab_size找到一个合适的struct cache_sizes结构体。然后,根据(gfpflags & GFP_DMA) ? csizep->cs_dmacachep : csizep->cs_cachep,选择一个合适的高速缓存

//---------------------------------------------------

cachep->ctor = ctor;

cachep->dtor = dtor;

cachep->name = name;

if (g_cpucache_up == FULL) {

enable_cpucache(cachep);                //这部分内容,参考2.9节:空闲slab对象的本地高速缓存

} else {

……                      

}

cachep->lists.next_reap = jiffies + REAPTIMEOUT_LIST3 +

((unsigned long)cachep)%REAPTIMEOUT_LIST3;

/* Need the semaphore to access the chain. */

//--------------------------------------------------------------

在上面的过程中,我们已经完成了kmem_cache的创建。现在,我们要将这个新的高速缓存插入高速缓存链表中

//--------------------------------------------------------------

down(&cache_chain_sem);

{

struct list_head *p;

mm_segment_t old_fs;

old_fs = get_fs();

set_fs(KERNEL_DS);

list_for_each(p, &cache_chain) {

kmem_cache_t *pc = list_entry(p, kmem_cache_t, next);

char tmp;

/* This happens when the module gets unloaded and doesn't

   destroy its slab cache and noone else reuses the vmalloc

   area of the module. Print a warning. *///不懂

if (__get_user(tmp,pc->name)) {

printk("SLAB: cache with size %d has lost its name\n",

pc->objsize);

continue;

}         

if (!strcmp(pc->name,name)) {

printk("kmem_cache_create: duplicate cache %s\n",name);

up(&cache_chain_sem);

unlock_cpu_hotplug();

BUG();

}        

}

set_fs(old_fs);

}

/* cache setup completed, link it into the list */

list_add(&cachep->next, &cache_chain);

up(&cache_chain_sem);

opps:

if (!cachep && (flags & SLAB_PANIC))     //SLAB_PANIC:没完成创建就panic

panic("kmem_cache_create(): failed to create slab `%s'\n",

name);

return cachep;

}

        我们观察函数cache_estimate的实现:

/* Cal the num objs, wastage, and bytes left over for a given slab size. */

static void cache_estimate (unsigned long gfporder, size_t size, size_t align,

 int flags, size_t *left_over, unsigned int *num)

{

int i;

size_t wastage = PAGE_SIZE<<gfporder;

size_t extra = 0;

size_t base = 0;

if (!(flags & CFLGS_OFF_SLAB)) {

base = sizeof(struct slab);

extra = sizeof(kmem_bufctl_t);

}

i = 0;

while (i*size + ALIGN(base+i*extra, align) <= wastage)

i++;

if (i > 0)

i--;

if (i > SLAB_LIMIT)

i = SLAB_LIMIT;

*num = i;

wastage -= i*size;

wastage -= ALIGN(base+i*extra, align);

*left_over = wastage;

}

        上面的逻辑很清晰,就是获得了,指定了slab的页面数量后,一个slab中,能够容纳的对象的数量。

2.5:给高速缓存分配slab,以及删除一个slab

        在上面的步骤中,我们创建了一个新的高速缓存kmem_cache。不过,这时候,我们还没有给这个kmem_cache创建slab。

       当我们向一个高速缓存发出一个分配新对象的请求,并且高速缓存不包含任何空闲对象的时候,才会给高速缓存分配slab。通过下面的函数给一个kmem_cache创建一个新的slab。

static int cache_grow (kmem_cache_t * cachep, int flags, int nodeid)

//对于一致性内存模型,nodeid的值为-1

//flags是分配对象时指定的flags

{

    struct slab    *slabp;

    void        *objp;

    size_t         offset;

    int         local_flags;

    unsigned long     ctor_flags;

    if (flags & SLAB_NO_GROW)

        return 0;

    ctor_flags = SLAB_CTOR_CONSTRUCTOR;

    local_flags = (flags & SLAB_LEVEL_MASK);

    if (!(local_flags & __GFP_WAIT))

        ctor_flags |= SLAB_CTOR_ATOMIC;

    /* About to mess with non-constant members - lock. */

    check_irq_off();

    spin_lock(&cachep->spinlock);

    /* Get colour for the slab, and cal the next value. */

    offset = cachep->colour_next;                //这次创建的slab使用的颜色

    cachep->colour_next++;

    if (cachep->colour_next >= cachep->colour)

        cachep->colour_next = 0;

    offset *= cachep->colour_off;                //colour_off是每个颜色使用的字节大小

    spin_unlock(&cachep->spinlock);

    if (local_flags & __GFP_WAIT)

        local_irq_enable();

    /* Get mem for the objs. */

    if (!(objp = kmem_getpages(cachep, flags, nodeid)))      //此处通过伙伴系统完成页框分配,返回分配的页框块的第一个页框的线性地址。具体分析见下一小节

        goto failed;

    /* Get slab management. */

    if (!(slabp = alloc_slabmgmt(cachep, objp, offset, local_flags)))    //在动态内存中分配slabp结构体。这里会根据cachep的属性,判断是在另一个专门存放slab描述符的高速缓存中分配,还是就在slabp的第一个页框中分配。

        goto opps1;

    set_slab_attr(cachep, slabp, objp);                              

//------------------------------------------------------------------------------

刚刚分配了以objp为首的1<<order个页框。这个函数将这些页框的页框描述符中的lru字段置位

page->lru.next = cachep

page->lru.prev = slabp

这样的目的是,通过页框描述符,可以快速找到它属于的高速缓存和slab。

当一个页框是空闲的时候,page->lru用于链接伙伴系统中,具有相同order的页框块

//------------------------------------------------------------------------------

    cache_init_objs(cachep, slabp, ctor_flags);       //我们已经说过,slab结构体后面,紧挨着cachep->num个kmem_bufctl_t数据。这个函数的作用就是,将这个数组中的元素,分别赋值为1,2……,BUFCTL_END。然后将slabp->free设置为0。 kmem_bufctl_t用于表示下一个空闲的对象块

    if (local_flags & __GFP_WAIT)

        local_irq_disable();

    check_irq_off();

    spin_lock(&cachep->spinlock);

    /* Make slab active. */

    list_add_tail(&slabp->list, &(list3_data(cachep)->slabs_free));   //将新分配的slab加入到高速缓存cachep的slabs_free链表中

    list3_data(cachep)->free_objects += cachep->num;

    spin_unlock(&cachep->spinlock);

    return 1;

opps1:

    kmem_freepages(cachep, objp);

failed:

    if (local_flags & __GFP_WAIT)

        local_irq_disable();

    return 0;

}

        里面的函数alloc_slabmgmt需要分析。可以告诉我们什么叫做将slab结构体放在slab拥有的页框中,还是放在专门用于存放slab的高速缓存中。

static struct slab* alloc_slabmgmt (kmem_cache_t *cachep,

            void *objp, int colour_off, int local_flags)

{

    struct slab *slabp;

   

    if (OFF_SLAB(cachep)) {           //外部slab描述符

        /* Slab management obj is off-slab. */

        slabp = kmem_cache_alloc(cachep->slabp_cache, local_flags);    //如果slab在外部,那么,从cachep->slabp_cache这一通用高速缓存中分出slabp描述符

        if (!slabp)

            return NULL;

    } else {                                       //内部slab描述符 

        slabp = objp+colour_off;     // objp是分给这个slab的连续页框的首地址,offset是slab描述符的偏移(用于slab着色)

        colour_off += cachep->slab_size;

    }

    slabp->inuse = 0;

    slabp->colouroff = colour_off;

    slabp->s_mem = objp+colour_off;       //s_mem是slab中存放的第一个对象的线性地址

    return slabp;

}

        再次强调,当我们通过kmem_cache分配一个新的结构体,然后这个kmem_cache没有空闲对象的位置的时候,才会给这个kmem_cache创建新的slab。也就是说,函数cache_grow的调用关系如下:

void * kmem_cache_alloc (kmem_cache_t *cachep, int flags):适用于专用高速缓存
static inline void *kmalloc(size_t size, int flags):适用于通用高速缓存

static inline void * __cache_alloc (kmem_cache_t *cachep, int flags)

static void* cache_alloc_refill(kmem_cache_t* cachep, int flags)

static int cache_grow (kmem_cache_t * cachep, int flags, int nodeid)

        使用函数slab_destroy删除一个slab。

2.6:slab分配器与分区页框分配器的接口

        刚刚已经介绍了,如何给一个高速缓存中创建新的slab。这里介绍如何创建一个新的slab。创建新的slab的时候,我们首先创建了slab结构体,此外,还需要从伙伴系统中,分出一部分页框,供给这个slab使用。

        必须使用页框分配器(也就是之前所说的,先通过内存区管理区确定内存管理区,再在此管理区中分配页框的函数)来获得一组连续的空闲页框。使用下面的函数完成新创建的slab的页框分配。

static void *kmem_getpages(kmem_cache_t *cachep, int flags, int nodeid)

{

    struct page *page;

    void *addr;

    int i;

    flags |= cachep->gfpflags;

    if (likely(nodeid == -1)) {

        page = alloc_pages(flags, cachep->gfporder);    //这里就是前面讲的页框分配器,通过伙伴系统,获得空闲页框,gfporder决定了一个slab包含的页框的数目

    } else {

        page = alloc_pages_node(nodeid, flags, cachep->gfporder);

    }

    if (!page)

        return NULL;

    addr = page_address(page);         //如果不是高端内存,那么就返回页框对应的虚拟地址;如果是高端内存,就返回页框的页框描述符的虚拟地址

    i = (1 << cachep->gfporder);

    if (cachep->flags & SLAB_RECLAIM_ACCOUNT)

        atomic_add(i, &slab_reclaim_pages);     //slab_reclaim_pages表示,当内核遇到很大的内存压力的情况下,可以释放的页的数目

    add_page_state(nr_slab, i);   

    while (i--) {         

        SetPageSlab(page);            //set_bit(PG_slab, &(page)->flags)。表示这个页用于slab

        page++;

    }

    return addr;

}

完成页框分配。它会返回分配的页框的线性地址。

        使用函数kmem_freepages,将slab的页框返还给伙伴系统

static void kmem_freepages(kmem_cache_t *cachep, void *addr)   //参数是高速缓存描述符而不是slab描述符。这是因为slab占有多少个页框是在高速缓存描述符中确定的

{

    unsigned long i = (1<<cachep->gfporder);

    struct page *page = virt_to_page(addr);

    const unsigned long nr_freed = i;

    while (i--) {

        if (!TestClearPageSlab(page))              //要求&(page)->flags的PG_slab必须被设置,也就是这个页属于slab

            BUG();

        page++;

    }

    sub_page_state(nr_slab, nr_freed);

    if (current->reclaim_state)

        current->reclaim_state->reclaimed_slab += nr_freed;

    free_pages((unsigned long)addr, cachep->gfporder);    //将页框释放到伙伴系统中

    if (cachep->flags & SLAB_RECLAIM_ACCOUNT)

        atomic_sub(1<<cachep->gfporder, &slab_reclaim_pages);

}

完成slab的页框释放。

        可见,伙伴系统实际上是页框分配的基础。不论是伙伴系统,每CPU高速缓存,还是kmem_cache和slab,最终都是建立在伙伴系统之上的。

2.7:对象描述符

        存放在slab中的每个对象都有一个对象描述符

typedef unsigned short kmem_bufctl_t;

        从创建一个高速缓存的时候也知道,每个slab描述符后面就是跟着num个kmem_bufctl_t,num就是一个slab中包含的对象的个数。因此,kmem_bufctl_t和结构体是一一对应的关系。kmem_bufctl_t包含的是下一个空闲对象在slab中的下标。

        从这个图中,我们可以看到这个字段是如何使用的。slab结构体中的free字段,表示了这个slab中第一个空闲的对象。同样,这个空闲对象对应了一个kmem_bufctl_t描述符,这个描述符中,存放的是下一个空闲的对象的下标。

2.8:slab着色

        着色主要用于处理下面的问题:由于不同的内存块可能映射到相同的缓存行之上,在同一个kmem_cache的不同slab内,有相同偏移量的对象很可能映射到同一高速缓存行中。这样就可能发生cache的抖动。

        之前讲过,一个高速缓存中一般有没有用完的字节。使用这些字节,让同一cache中不同的slab的对象结构体存放首地址不同。

        例如,从下图中看,就是不同的颜色col,导致每个对象的位置不同。aln是每个颜色的偏移。

2.9:空闲slab对象的本地高速缓存

        为了减少处理器对自旋锁的竞争,并且更好的利用硬件高速缓存,kmem_cache结构体中包含了一个被称作slab本地高速缓存的每CPU数据结构。

struct kmem_cache_s {

/* 1) per-cpu data, touched during every alloc/free */

struct array_cache        *array[NR_CPUS];

unsigned int                batchcount;    //本地高速缓存中,slab对象过多或者过少的时候,从kmem_cache的slab中拿走或者放回的slab对象的个数

unsigned int                limit;                //本地高速缓存中slab对象的最大数目

……

};

        要理解这个成员的作用,就要清晰的了解我们从slab中分配对象或者释放对象到slab的过程。如上面所说,为了减少处理器对自旋锁的竞争,我们不是每次都从kmem_cache包含的所有slab中,分配一个对象或者释放一个对象。我们的操作是,先将一些slab中的对象分给一个本地高速缓存,然后,在分配或者释放对象的时候,从属于这个CPU的对象此中分配。

        每个struct array_cache成员如下所示:

struct array_cache {

    unsigned int avail;

    unsigned int limit;

    unsigned int batchcount;

    unsigned int touched;

};

这里有部分内容没有暴露出来。实际上,struct array_cache *array[NR_CPUS]并不是NR_CPUS个struct array_cache指针,它还包含了limit个void *指针,每个指针就是一个属于这个本地高速缓存的slab中的对象的地址。

kmem_cache_t *

kmem_cache_create (const char *name, size_t size, size_t align,

unsigned long flags, void (*ctor)(void*, kmem_cache_t *, unsigned long),

void (*dtor)(void*, kmem_cache_t *, unsigned long))

{

……

if (g_cpucache_up == FULL) {

enable_cpucache(cachep);        //通过这个函数使能cpucache

}

……

}

static void enable_cpucache (kmem_cache_t *cachep)

{

int err;

int limit, shared;

/* The head array serves three purposes:

 * - create a LIFO ordering, i.e. return objects that are cache-warm

 * - reduce the number of spinlock operations.

 * - reduce the number of linked list operations on the slab and

 *   bufctl chains: array operations are cheaper.

 * The numbers are guessed, we should auto-tune as described by

 * Bonwick.

 */

if (cachep->objsize > 131072)

limit = 1;

else if (cachep->objsize > PAGE_SIZE)

limit = 8;

else if (cachep->objsize > 1024)

limit = 24;

else if (cachep->objsize > 256)

limit = 54;

else

limit = 120;

/* Cpu bound tasks (e.g. network routing) can exhibit cpu bound

 * allocation behaviour: Most allocs on one cpu, most free operations

 * on another cpu. For these cases, an efficient object passing between

 * cpus is necessary. This is provided by a shared array. The array

 * replaces Bonwick's magazine layer.

 * On uniprocessor, it's functionally equivalent (but less efficient)

 * to a larger limit. Thus disabled by default.

 */

shared = 0;

#ifdef CONFIG_SMP

if (cachep->objsize <= PAGE_SIZE)

shared = 8;

#endif

err = do_tune_cpucache(cachep, limit, (limit+1)/2, shared);      //我们会使用这里的limit的值,设置array_cache中的limit的值

if (err)

printk(KERN_ERR "enable_cpucache failed for %s, error %d.\n",

cachep->name, -err);

}

static int do_tune_cpucache (kmem_cache_t* cachep, int limit, int batchcount, int shared)

{

struct ccupdate_struct new;

struct array_cache *new_shared;

int i;

memset(&new.new,0,sizeof(new.new));

for (i = 0; i < NR_CPUS; i++) {

if (cpu_online(i)) {

//----------------------------------------------------------  

//new.new[i]就是struct array_cache *new[i]。这里就是给这个指针分配内存。在这个函数中我们可以看到,分配的内存大小是sizeof(void*)*limit+sizeof(struct array_cache)

//---------------------------------------------------------- 

new.new[i] = alloc_arraycache(i, limit, batchcount);

if (!new.new[i]) {

for (i--; i >= 0; i--) kfree(new.new[i]);

return -ENOMEM;

}

} else {

new.new[i] = NULL;

}

}

new.cachep = cachep;

//-------------------------------------------

这个函数先暂时不详细说明。反正就是,对所有的CPU都执行do_ccupdate_local函数。可以认为,它实现了下面的效果:

将新创建的struct array_cache *的值赋给struct kmem_cache_s->struct array_cache *array[NR_CPUS]。然后将struct kmem_cache_s->struct array_cache *的原有值赋给new.new。

//-------------------------------------------

smp_call_function_all_cpus(do_ccupdate_local, (void *)&new);

check_irq_on();

spin_lock_irq(&cachep->spinlock);

cachep->batchcount = batchcount;

cachep->limit = limit;

cachep->free_limit = (1+num_online_cpus())*cachep->batchcount + cachep->num;

spin_unlock_irq(&cachep->spinlock);

for (i = 0; i < NR_CPUS; i++) {

struct array_cache *ccold = new.new[i];

if (!ccold)

continue;

spin_lock_irq(&cachep->spinlock);

//-------------------------------------------

在这里, ccold->avail的值为0,所以,这里不会释放任何块

//-------------------------------------------

free_block(cachep, ac_entry(ccold), ccold->avail);

spin_unlock_irq(&cachep->spinlock);

kfree(ccold);

}

new_shared = alloc_arraycache(-1, batchcount*shared, 0xbaadf00d);

if (new_shared) {

struct array_cache *old;

spin_lock_irq(&cachep->spinlock);

old = cachep->lists.shared;

cachep->lists.shared = new_shared;

if (old)

free_block(cachep, ac_entry(old), old->avail);

spin_unlock_irq(&cachep->spinlock);

kfree(old);

}

return 0;

}

        因此,通过上面的函数,我们可以知道,他在struct kmem_cache_s中,建立了一个指针数组,每个指针都指向了上面的图所示的一段内存。不过,这时候这段内存中,void *区域还没有初始化,也就是说,这时候的cpucache中,还没有分配具体的slab对象。我们会在后面,将对象分配给一个CPU,然后,将这个对象的地址填入void *区域中。

2.10:分配slab对象

        我们已经在上面描述了很多内容。实际上,他们都是被分配slab对象的函数调用的。例如,分配slab对象的时候,从cpucache中分配。这时候,就会给上面刚创建的,还是空状态的void *指针赋值,也就是将slab对象分配给cpucache。又或者,slab中已经没有空闲的对象。这时候,我们就要通过cache_grow,创建新的slab对象。我们将在这一节,串联起之前描述的所有内容。

        通过函数kmem_cache_alloc()获得新对象,代码如下所示:

void * kmem_cache_alloc (kmem_cache_t *cachep, int flags)

{

    return __cache_alloc(cachep, flags);

}

static inline void * __cache_alloc (kmem_cache_t *cachep, int flags)

{

    unsigned long save_flags;

    void* objp;

    struct array_cache *ac;

    local_irq_save(save_flags);

//---------------------------------------------------------------------------------

我们已经说过,在分配slab对象的时候,为了降低处理器对自旋锁的竞争,以及更好的利用高速缓存,我们并不是从一个slab的所有对象池中分配的。我们是先将一些slab中的对象分配给cpucache。然后,每次从这个cpu的cpucache中分配对象

//---------------------------------------------------------------------------------

    ac = ac_data(cachep);    //获取属于此cpu的struct array_cache。里面包含了limit个void *指针,每个指针都指向了一个slab对象

    if (likely(ac->avail)) {    

        ac->touched = 1;

        objp = ac_entry(ac)[--ac->avail];               //从后往前分配

    } else {

        objp = cache_alloc_refill(cachep, flags);    //第一次在这个cpu上获取这种slab对象的时候,会走到这里。函数do_tune_cpucache在初始化array_cache的时候,将ac->avail设置为0

    }

    local_irq_restore(save_flags);

    return objp;

}

        因此,我们主要关注,在第一次在此cpu上,malloc这种类型的slab对象的时候,我们做了什么。按照之前的分析,我们应该将slab对象分配给这个cpu,也就是说,填充这个cpu的array_cache

static void* cache_alloc_refill(kmem_cache_t* cachep, int flags)

{

    int batchcount;

    struct kmem_list3 *l3;

    struct array_cache *ac;

    check_irq_off();

    ac = ac_data(cachep);                   //在这里,获取了此CPU使用的array_cache

retry:

    batchcount = ac->batchcount;    // batchcount:每次分配给此cpu的array_cache的slab对象的数目

    if (!ac->touched && batchcount > BATCHREFILL_LIMIT) {   //如果这个高速缓存被使用过,那么touched标志被设置为1

        batchcount = BATCHREFILL_LIMIT;

    }

    l3 = list3_data(cachep);

    spin_lock(&cachep->spinlock);

    if (l3->shared) {                          

        struct array_cache *shared_array = l3->shared;

        if (shared_array->avail) {

            if (batchcount > shared_array->avail)

                batchcount = shared_array->avail;

            shared_array->avail -= batchcount;

            ac->avail = batchcount;

            memcpy(ac_entry(ac), &ac_entry(shared_array)[shared_array->avail],

                    sizeof(void*)*batchcount);

            shared_array->touched = 1;

            goto alloc_done;

        }

    }

    while (batchcount > 0) {

        struct list_head *entry;

        struct slab *slabp;

        entry = l3->slabs_partial.next;        //slabs_partial链接的是,部分对象被使用的slab

        if (entry == &l3->slabs_partial) {    //考虑到链表结构,这个条件为真的话,说明slabs_partial中已经slab结构体了。也就是说,现在没有slab,他的对象是部分使用的

            l3->free_touched = 1;

            entry = l3->slabs_free.next;        //因此,我们到slabs_free链表中,找完全未使用的slab结构体

            if (entry == &l3->slabs_free)

                goto must_grow;                    //slabs_free链表为空。也就是说,这时候也没有一个slab结构体,它的对象一个未使用。这时候,我们要创建新的slab

        }

        slabp = list_entry(entry, struct slab, list);    //这里找到了一个slab结构体,他又未使用的对象

        check_slabp(cachep, slabp);

        check_spinlock_acquired(cachep);

        while (slabp->inuse < cachep->num && batchcount--) {

            kmem_bufctl_t next;

            ac_entry(ac)[ac->avail++]=slabp->s_mem+ slabp->free * cachep->objsize;   

// s_mem:slab中第一个对象的地址。free:slab中空闲对象的下标。因此,ac_entry(ac)[ac->avail++]就是一个空闲对象的地址

            slabp->inuse++;

            next = slab_bufctl(slabp)[slabp->free];  //这里访问了kmem_bufctl_t数组。回忆一下,slab中有多少个对象,kmem_bufctl_t数组就有多少个成员。每个成员都是一个下标,表示了这个成员的下一个空闲成员的下标。具体可以看2.7节对象描述符

              

slabp->free = next;  

        }

        check_slabp(cachep, slabp);

        /* move slabp to correct slabp list: */

        list_del(&slabp->list);

        if (slabp->free == BUFCTL_END)             //分配完成后,还需要决定slab的去向。如果这个slab没有空闲对象了,就将他添加到slabs_full链表中。如果还有空闲对象,就加到slabs_partial链表中

            list_add(&slabp->list, &l3->slabs_full);

        else

            list_add(&slabp->list, &l3->slabs_partial);

    }

must_grow:

    l3->free_objects -= ac->avail;

alloc_done:

    spin_unlock(&cachep->spinlock);

    if (unlikely(!ac->avail)) {

        int x;

        x = cache_grow(cachep, flags, -1);

       

        // cache_grow can reenable interrupts, then ac could change.

        ac = ac_data(cachep);

        if (!x && ac->avail == 0)    // no objects in sight? abort

            return NULL;

        if (!ac->avail)        // objects refilled by interrupt?

            goto retry;

    }

    ac->touched = 1;

    return ac_entry(ac)[--ac->avail];

}

        因此,我们看到,通过函数cache_alloc_refill的作用,我们给array_cache分配了空闲的slab对象。这时候,array_cache->avail表示了array_cache中,void *数组的下标。我们是先分配array_cache中后面的空闲slab对象。

2.11:释放slab对象

        上面讲述了如何分配slab对象的。现在我们描述如何释放slab对象。释放slab对象的代码如下:

void kmem_cache_free (kmem_cache_t *cachep, void *objp)

{

    unsigned long flags;

    local_irq_save(flags);

    __cache_free(cachep, objp);

    local_irq_restore(flags);

}

static inline void __cache_free (kmem_cache_t *cachep, void* objp)

{

    struct array_cache *ac = ac_data(cachep);

    check_irq_off();

    if (likely(ac->avail < ac->limit)) {

        ac_entry(ac)[ac->avail++] = objp;

        return;

    } else {

        cache_flusharray(cachep, ac);

        ac_entry(ac)[ac->avail++] = objp;

    }

}

        因此,我们这里要画图,说明分配和释放的过程。假设,我们给array_cache中,分配了8个slab对象。在某一次分配的时候,avail的值为3,也就是说,这时候,此cpu的array_cache中,还有3个空闲的对象。需要注意的是,这里存放的都是对象的指针:

        然后,假设某一次释放的时候,我们先释放了*object5描述的对象。这时候,按照释放的逻辑,它会变成下面这样:

        也就是说,这时候avail对应的是object5的指针。也不需要担心我们失去了object3的指针信息。它保存在我们分配的一个对象中。

        如果本地高速缓存中,没有位置来存放空闲对象的指针,就进入函数

static void cache_flusharray (kmem_cache_t* cachep, struct array_cache *ac)

{

    int batchcount;

    batchcount = ac->batchcount;

    check_irq_off();

    spin_lock(&cachep->spinlock);

    if (cachep->lists.shared) {        //先不考虑这种情况

        struct array_cache *shared_array = cachep->lists.shared;

        int max = shared_array->limit-shared_array->avail;

        if (max) {

            if (batchcount > max)

                batchcount = max;

            memcpy(&ac_entry(shared_array)[shared_array->avail],

                    &ac_entry(ac)[0],

                    sizeof(void*)*batchcount);

            shared_array->avail += batchcount;

            goto free_done;

        }

    }

    free_block(cachep, &ac_entry(ac)[0], batchcount);

free_done:

    spin_unlock(&cachep->spinlock);

    ac->avail -= batchcount;

    memmove(&ac_entry(ac)[0], &ac_entry(ac)[batchcount],

            sizeof(void*)*ac->avail);

}

        上面函数的关键操作都在free_block中完成;

static void free_block(kmem_cache_t *cachep, void **objpp, int nr_objects)

{

    int i;

    check_spinlock_acquired(cachep);

    cachep->lists.free_objects += nr_objects;

    for (i = 0; i < nr_objects; i++) {

        void *objp = objpp[i];

        struct slab *slabp;

        unsigned int objnr;

        slabp = GET_PAGE_SLAB(virt_to_page(objp));    //struct page->lru.prev指向了这个页属于的slab结构体

        list_del(&slabp->list);

        objnr = (objp - slabp->s_mem) / cachep->objsize;   //这里计算出,这次释放的对象是这个slab中的第几个对象

        check_slabp(cachep, slabp);

        slab_bufctl(slabp)[objnr] = slabp->free; 

 //--------------------------------------------------------------------

我们说过,free字段存放的是,这个slab中,第一个空闲对象的下标。这里我们设置了(kmem_bufctl_t *)(slabp+1)[objnr]的值为free

slabp+1就是地址增加了sizeof(struct slab)。因此,我们拿到了kmem_bufctl_t数组的首地址。如前面所说,这个数组成员和这个slab包含的对象一一对应,表示下一个空闲对象的下标。这时候,我们设置了kmem_bufctl_t[objnr]=free,也就是说,设置了之前的第一个空闲对象,作为我们这次释放对象的下一个空闲对象

 //--------------------------------------------------------------------

        slabp->free = objnr;        //现在slab中的第一个空闲对象,就是我们释放的对象

        slabp->inuse--;

        check_slabp(cachep, slabp);

        /* fixup slab chains */

        if (slabp->inuse == 0) {                      //如果slab没有一个对象被使用

            if (cachep->lists.free_objects > cachep->free_limit) {

                cachep->lists.free_objects -= cachep->num;

                slab_destroy(cachep, slabp);      //这种情况下删除slab

            } else {

                list_add(&slabp->list,&list3_data_ptr(cachep, objp)->slabs_free);

            }

        } else {

            list_add_tail(&slabp->list,&list3_data_ptr(cachep, objp)->slabs_partial); 

 //将slab加入部分使用,部分为使用的链表中

        }

    }

}

2.12:通用对象

        如果是分配一些普通的结构体,就使用普通高速缓存来处理。

 static inline void *kmalloc(size_t size, int flags)

{

    return __kmalloc(size, flags);

}

void * __kmalloc (size_t size, int flags)

{

    struct cache_sizes *csizep = malloc_sizes;

 //------------------------------------------------------------------------

malloc_sizes是一个全局数组,struct cache_sizes malloc_sizes的每个成员如下所示:

struct cache_sizes {

size_t                 cs_size;    //对应不同的大小。每次从普通kmem_cache中分配一个对象的时候,要先找到满足条件的struct cache_sizes

kmem_cache_t        *cs_cachep;   

kmem_cache_t        *cs_dmacachep;

};

 //------------------------------------------------------------------------

    for (; csizep->cs_size; csizep++) {

        if (size > csizep->cs_size)         //要满足的条件就是,分配的size<=csizep->cs_size

            continue;

        return __cache_alloc(flags & GFP_DMA ?

             csizep->cs_dmacachep : csizep->cs_cachep, flags);        //还是调用函数__cache_alloc进行内存分配,但是不同的是,这时候指定的高速缓存是cs_dmacachep或者cs_cachep

    }

    return NULL;

}

2.13:内存池

        内存池和kmem_cache具有相似之处,都是专门申请的,用于某种功能的内存。它主要用于在内存不足的情况下,给某种类型的内存分配提供一个预留分配内存。描述内存池的结构体如下所示:

typedef struct mempool_s {
    spinlock_t lock;
    int min_nr;        /* nr of elements at *elements *///mempool内存池中包含的对象的数目
    int curr_nr;        /* Current nr of elements at *elements *///当前空闲对象在elements数组中的索引
    void **elements;          //指针数组。每个成员是mempool中含有的每个对象的地址

    void *pool_data;
    mempool_alloc_t *alloc;       //当分配的时候,首先使用alloc函数做分配。分不出来再分配mempool中的成员
    mempool_free_t *free;
    wait_queue_head_t wait;
} mempool_t;

通过函数mempool_create创建一个内存池。

/**
 * mempool_create - create a memory pool
 * @min_nr:    the minimum number of elements guaranteed to be
 *             allocated for this pool.
 * @alloc_fn:  user-defined element-allocation function.
 * @free_fn:   user-defined element-freeing function.
 * @pool_data: optional private data available to the user-defined functions.
 *
 * this function creates and allocates a guaranteed size, preallocated
 * memory pool. The pool can be used from the mempool_alloc() and mempool_free()
 * functions. This function might sleep. Both the alloc_fn() and the free_fn()
 * functions might sleep - as long as the mempool_alloc() function is not called
 * from IRQ contexts.
 *
 * Return: pointer to the created memory pool object or %NULL on error.
 */
mempool_t *mempool_create(int min_nr, mempool_alloc_t *alloc_fn,
                mempool_free_t *free_fn, void *pool_data)
{
    return mempool_create_node(min_nr,alloc_fn,free_fn, pool_data,
                   GFP_KERNEL, NUMA_NO_NODE);
}

mempool_t *mempool_create_node(int min_nr, mempool_alloc_t *alloc_fn,
                   mempool_free_t *free_fn, void *pool_data,
                   gfp_t gfp_mask, int node_id)
{
    mempool_t *pool;

    pool = kzalloc_node(sizeof(*pool), gfp_mask, node_id);      //分配mempool_t结构体
    if (!pool)
        return NULL;

    if (mempool_init_node(pool, min_nr, alloc_fn, free_fn, pool_data,
                  gfp_mask, node_id)) {
        kfree(pool);
        return NULL;
    }

    return pool;
}
 

int mempool_init_node(mempool_t *pool, int min_nr, mempool_alloc_t *alloc_fn,
              mempool_free_t *free_fn, void *pool_data,
              gfp_t gfp_mask, int node_id)
{
    spin_lock_init(&pool->lock);
    pool->min_nr    = min_nr;
    pool->pool_data = pool_data;
    pool->alloc    = alloc_fn;
    pool->free    = free_fn;
    init_waitqueue_head(&pool->wait);

    pool->elements = kmalloc_array_node(min_nr, sizeof(void *),    //elements指针数组包含了mempool中所有的element地址
                        gfp_mask, node_id);
    if (!pool->elements)
        return -ENOMEM;

    /*
     * First pre-allocate the guaranteed number of buffers.
     */
    while (pool->curr_nr < pool->min_nr) {
        void *element;

        element = pool->alloc(gfp_mask, pool->pool_data);      //其实就是kmallockmem_cache_allocalloc_pages这三个函数,将内存预分配出来
        if (unlikely(!element)) {
            mempool_exit(pool);
            return -ENOMEM;
        }
        add_element(pool, element);    //将分配出来的element地址添加到elements数组中
    }

    return 0;
}
 

        当要从mempool中分配内存的时候,使用接口mempool_alloc。这个接口会首先利用普通方式分配内存。如果分不出来,再使用mempool中预留的内存。

/**
 * mempool_alloc - allocate an element from a specific memory pool
 * @pool:      pointer to the memory pool which was allocated via
 *             mempool_create().
 * @gfp_mask:  the usual allocation bitmask.
 *
 * this function only sleeps if the alloc_fn() function sleeps or
 * returns NULL. Note that due to preallocation, this function
 * *never* fails when called from process contexts. (it might
 * fail if called from an IRQ context.)
 * Note: using __GFP_ZERO is not supported.
 *
 * Return: pointer to the allocated element or %NULL on error.
 */
void *mempool_alloc(mempool_t *pool, gfp_t gfp_mask)
{
    void *element;
    unsigned long flags;
    wait_queue_entry_t wait;
    gfp_t gfp_temp;

    VM_WARN_ON_ONCE(gfp_mask & __GFP_ZERO);
    might_sleep_if(gfp_mask & __GFP_DIRECT_RECLAIM);

    gfp_mask |= __GFP_NOMEMALLOC;    /* don't allocate emergency reserves */
    gfp_mask |= __GFP_NORETRY;    /* don't loop in __alloc_pages */
    gfp_mask |= __GFP_NOWARN;    /* failures are OK */

    gfp_temp = gfp_mask & ~(__GFP_DIRECT_RECLAIM|__GFP_IO);

repeat_alloc:

    element = pool->alloc(gfp_temp, pool->pool_data);         //通过普通的内存分配方式获取内存
    if (likely(element != NULL))
        return element;

    spin_lock_irqsave(&pool->lock, flags);
    if (likely(pool->curr_nr)) {
        element = remove_element(pool);       //如果没有分配到的话,就从mempool中分配内存
        spin_unlock_irqrestore(&pool->lock, flags);
        /* paired with rmb in mempool_free(), read comment there */
        smp_wmb();
        /*
         * Update the allocation stack trace as this is more useful
         * for debugging.
         */
        kmemleak_update_trace(element);
        return element;
    }

    /*
     * We use gfp mask w/o direct reclaim or IO for the first round.  If
     * alloc failed with that and @pool was empty, retry immediately.
     */
    if (gfp_temp != gfp_mask) {
        spin_unlock_irqrestore(&pool->lock, flags);
        gfp_temp = gfp_mask;
        goto repeat_alloc;
    }

    /* We must not sleep if !__GFP_DIRECT_RECLAIM */
    if (!(gfp_mask & __GFP_DIRECT_RECLAIM)) {
        spin_unlock_irqrestore(&pool->lock, flags);
        return NULL;
    }

    /* Let's wait for someone else to return an element to @pool */
    init_wait(&wait);
    prepare_to_wait(&pool->wait, &wait, TASK_UNINTERRUPTIBLE);

    spin_unlock_irqrestore(&pool->lock, flags);

    /*
     * FIXME: this should be io_schedule().  The timeout is there as a
     * workaround for some DM problems in 2.6.18.
     */
    io_schedule_timeout(5*HZ);

    finish_wait(&pool->wait, &wait);
    goto repeat_alloc;
}

3:非连续内存区管理

        将内存区映射到一组连续的页框是最好的选择,这样能够充分利用高速缓存并获得较低的访问时间。但是,如果对内存区的请求不是很频繁。那么,通过连续的线性地址来访问非连续的页框也会很有意义。这样的主要优点是避免外碎片,缺点是要打乱内核页表。

3.1:非连续内存区的线性地址

        这张图可以看到内核的线性地址布局。物理内存映射部分是对0~896M内存进行映射的线性地址(也就是说线性地址是3G~3G+896M)。FIXADDR_START~4G是包含固定映射的线性地址(固定映射的线性地址),PKMAP_BASE~FIXADDR_START用于高端内存页框的永久内核映射(高端内存页框的内核映射)。

        最终,剩下的,从VMALLOC_START开始,到VMALLOC_END结束的线性地址用于非连续内存区映射(也就是vmalloc使用)。这个区域的大小可以参考linux/Documentation/x86/x86_64/mm.rst。

3.2:非连续内存区的描述符

        每个非连续内存区都对应着一个类型为vm_struct的描述符。

struct vm_struct {

    void            *addr;          //这个内存区的虚拟地址首地址

    unsigned long        size;     //内存区大小+4096(安全区大小)

    unsigned long        flags;         //表示了非连续区映射的内存的类型,VM_ALLOC表示使用vmalloc得到的线性区;VM_MAP表示使用vmap映射已经分配的页框;VM_IOREMAP表示使用ioremap映射的硬件设备的板上内存

    struct page        **pages;        //指向了这个内存区对应的所有的物理页页框组成的数组

    unsigned int        nr_pages;

    unsigned long        phys_addr;

    struct vm_struct    *next;

};

struct vm_struct *vmlist;

        不同的vm_struct结构体通过next连接成一个链表。第一个vm_struct结构体地址存放在全局变量vmlist中。

3.2.1:Linux-2.6.11

        通过函数get_vm_area,查找一个空闲的线性地址(当然,线性地址是位于VMALLOC_START和VMALLOC_END之间的)。一定要注意的是,struct vm_struct是用于表示内核的非连续内存映射,struct vm_area_struct是用于表示进程的线性区。

/**

 *        get_vm_area  -  reserve a contingous kernel virtual area

 *

 *        @size:                size of the area

 *        @flags:                %VM_IOREMAP for I/O mappings or VM_ALLOC

 *

 *        Search an area of @size in the kernel virtual mapping area,

 *        and reserved it for out purposes.  Returns the area descriptor

 *        on success or %NULL on failure.

 */
struct vm_struct *get_vm_area(unsigned long size, unsigned long flags)

{

    return __get_vm_area(size, flags, VMALLOC_START, VMALLOC_END);

}

struct vm_struct *__get_vm_area(unsigned long size, unsigned long flags,

                unsigned long start, unsigned long end)

{

    struct vm_struct **p, *tmp, *area;

    unsigned long align = 1;

    unsigned long addr;

    if (flags & VM_IOREMAP) {             // VM_IOREMAP:使用ioremap()映射的硬件设备的板上内存。VM_ALLOC:使用vmalloc()得到的页。VM_MAP:表示使用vmap()映射的已经被分配的页。

        int bit = fls(size);

        if (bit > IOREMAP_MAX_ORDER)

            bit = IOREMAP_MAX_ORDER;

        else if (bit < PAGE_SHIFT)

            bit = PAGE_SHIFT;

        align = 1ul << bit;

    }

    addr = ALIGN(start, align);                        

    area = kmalloc(sizeof(*area), GFP_KERNEL);      //使用slab,从内核内存区域中malloc出来一个结构体

    if (unlikely(!area))

        return NULL;

    /*

     * We always allocate a guard page.

     */

    size += PAGE_SIZE;                            //在分配的时候,额外多分配4K作为保护页

    write_lock(&vmlist_lock);

    for (p = &vmlist; (tmp = *p) != NULL ;p = &tmp->next) {

        if ((unsigned long)tmp->addr < addr) {

            if((unsigned long)tmp->addr + tmp->size >= addr)

                addr = ALIGN(tmp->size +

                         (unsigned long)tmp->addr, align);

            continue;

        }

        if ((size + addr) < addr)

            goto out;

        if (size + addr <= (unsigned long)tmp->addr)

            goto found;

        addr = ALIGN(tmp->size + (unsigned long)tmp->addr, align);

        if (addr > end - size)

            goto out;

    }                    //这段逻辑可以在纸上画图得到,反正最终结果,就是获得了一个满足我们大小要求的,位于VMALLOC_START~VMALLOC_END之间的线性区

found:

    area->next = *p;

    *p = area;                               //这两句组成将一个元素插入链表的操作

    area->flags = flags;

    area->addr = (void *)addr;    //addr是这个物理非连续内存区的虚拟地址首地址

    area->size = size;

    area->pages = NULL;

    area->nr_pages = 0;

    area->phys_addr = 0;

    write_unlock(&vmlist_lock);

    return area;

out:

    write_unlock(&vmlist_lock);

    kfree(area);

    if (printk_ratelimit())

        printk(KERN_WARNING "allocation failed: out of vmalloc space - use vmalloc=<size> to increase size.\n");

    return NULL;

}

        现在,我们有了一段足够长的空闲的线性地址了,接下来,我们还要找一段足够长的,空闲的物理页框(不连续的),将他们和这段线性地址对应起来。

3.2.2:Linux-5.10.110

        使用函数__get_vm_area_node来找到一段合适的内核虚拟地址。

//startend还是使用宏VMALLOC_STARTVMALLOC_END来表示
static struct vm_struct *__get_vm_area_node(unsigned long size,
        unsigned long align, unsigned long flags, unsigned long start,
        unsigned long end, int node, gfp_t gfp_mask, const void *caller)
{
    struct vmap_area *va;
    struct vm_struct *area;
    unsigned long requested_size = size;

    BUG_ON(in_interrupt());      //不能在中断环境中使用vmalloc
    size = PAGE_ALIGN(size);
    if (unlikely(!size))
        return NULL;

    if (flags & VM_IOREMAP)
        align = 1ul << clamp_t(int, get_count_order_long(size),
                       PAGE_SHIFT, IOREMAP_MAX_ORDER);

    area = kzalloc_node(sizeof(*area), gfp_mask & GFP_RECLAIM_MASK, node);     //分配一个struct vm_struct结构体
    if (unlikely(!area))
        return NULL;

    if (!(flags & VM_NO_GUARD))
        size += PAGE_SIZE;

    va = alloc_vmap_area(size, align, start, end, node, gfp_mask);   //分配一个struct vmap_struct结构体。在给进程分配线性区的时候,我们使用了一个红黑树来表示进程拥有的线性区。这里也是一样,通过一个红黑树,描述内核使用的非连续线性区。这个函数找到一段足够的虚拟内存
    if (IS_ERR(va)) {
        kfree(area);
        return NULL;
    }

    kasan_unpoison_vmalloc((void *)va->va_start, requested_size);

    setup_vmalloc_vm(area, va, flags, caller);

    return area;
}
 

        因此,我们分析函数alloc_vmap_area

/*
 * Allocate a region of KVA of the specified size and alignment, within the
 * vstart and vend.
 */
static struct vmap_area *alloc_vmap_area(unsigned long size,
                unsigned long align,
                unsigned long vstart, unsigned long vend,
                int node, gfp_t gfp_mask)
{
    struct vmap_area *va, *pva;
    unsigned long addr;
    int purged = 0;
    int ret;

    BUG_ON(!size);
    BUG_ON(offset_in_page(size));
    BUG_ON(!is_power_of_2(align));

    if (unlikely(!vmap_initialized))
        return ERR_PTR(-EBUSY);

    might_sleep();
    gfp_mask = gfp_mask & GFP_RECLAIM_MASK;

    va = kmem_cache_alloc_node(vmap_area_cachep, gfp_mask, node);//从kmem_cache中分配struct vmap_area结构体内存
    if (unlikely(!va))
        return ERR_PTR(-ENOMEM);

    /*
     * Only scan the relevant parts containing pointers to other objects
     * to avoid false negatives.
     */
    kmemleak_scan_area(&va->rb_node, SIZE_MAX, gfp_mask);

retry:
    /*
     * Preload this CPU with one extra vmap_area object. It is used
     * when fit type of free area is NE_FIT_TYPE. Please note, it
     * does not guarantee that an allocation occurs on a CPU that
     * is preloaded, instead we minimize the case when it is not.
     * It can happen because of cpu migration, because there is a
     * race until the below spinlock is taken.
     *
     * The preload is done in non-atomic context, thus it allows us
     * to use more permissive allocation masks to be more stable under
     * low memory condition and high memory pressure. In rare case,
     * if not preloaded, GFP_NOWAIT is used.
     *
     * Set "pva" to NULL here, because of "retry" path.
     */
    pva = NULL;

    if (!this_cpu_read(ne_fit_preload_node))
        /*
         * Even if it fails we do not really care about that.
         * Just proceed as it is. If needed "overflow" path
         * will refill the cache we allocate from.
         */
        pva = kmem_cache_alloc_node(vmap_area_cachep, gfp_mask, node);

    spin_lock(&free_vmap_area_lock);

    if (pva && __this_cpu_cmpxchg(ne_fit_preload_node, NULL, pva))
        kmem_cache_free(vmap_area_cachep, pva);

//如果this_cpu_read(ne_fit_preload_node)为空,那么就额外分配一个struct vmap_area,存放在这个每cpu变量中

    /*
     * If an allocation fails, the "vend" address is
     * returned. Therefore trigger the overflow path.
     */
    addr = __alloc_vmap_area(size, align, vstart, vend);   //我们使用这个函数,找到一段合适的虚拟内存空间
    spin_unlock(&free_vmap_area_lock);

    if (unlikely(addr == vend))
        goto overflow;

    va->va_start = addr;     //va_start设置为我们找到的虚拟地址
    va->va_end = addr + size;
    va->vm = NULL;


    spin_lock(&vmap_area_lock);
    insert_vmap_area(va, &vmap_area_root, &vmap_area_list);          //struct vmap_area插入到红黑树vmap_area_root中。这个红黑树中是所有用于非连续内存映射的虚拟地址空间
    spin_unlock(&vmap_area_lock);

    BUG_ON(!IS_ALIGNED(va->va_start, align));
    BUG_ON(va->va_start < vstart);
    BUG_ON(va->va_end > vend);

    ret = kasan_populate_vmalloc(addr, size);
    if (ret) {
        free_vmap_area(va);
        return ERR_PTR(ret);
    }

    return va;

overflow:
    if (!purged) {
        purge_vmap_area_lazy();
        purged = 1;
        goto retry;
    }

    if (gfpflags_allow_blocking(gfp_mask)) {
        unsigned long freed = 0;
        blocking_notifier_call_chain(&vmap_notify_list, 0, &freed);
        if (freed > 0) {
            purged = 0;
            goto retry;
        }
    }

    if (!(gfp_mask & __GFP_NOWARN) && printk_ratelimit())
        pr_warn("vmap allocation for size %lu failed: use vmalloc=<size> to increase size\n",
            size);

    kmem_cache_free(vmap_area_cachep, va);
    return ERR_PTR(-EBUSY);
}
 

        函数 __alloc_vmap_area的作用是找到一段满足我们条件的虚拟地址空间。内核将从VMALLOC_START和VMALLOC_END的所有空闲虚拟地址段组织成一个红黑树free_vmap_area_root。因此,这个函数遍历红黑树free_vmap_area_root,找到一个合适的虚拟地址空间。并且,如果被分割的虚拟地址空间有空余的话,还要讲剩余的虚拟地址空间对应的struct vmap_area在插入红黑树中。

3.3:分配非连续内存区

        使用函数vmalloc函数,分配一个虚拟地址连续,但是虚拟地址对应的物理地址不连续的非连续内存区,这个函数最终返回连续的虚拟地址的首地址。提示,kmalloc的作用是在普通slab系统中分出一个结构体的内存。

3.3.1:Linux-2.6.11

        Linux-2.6.11中,vmalloc的实现如下:

void *vmalloc(unsigned long size)

{

       return __vmalloc(size, GFP_KERNEL | __GFP_HIGHMEM, PAGE_KERNEL);

}

void *__vmalloc(unsigned long size, int gfp_mask, pgprot_t prot)

{

    struct vm_struct *area;

    struct page **pages;

    unsigned int nr_pages, array_size, i;

    size = PAGE_ALIGN(size);

    if (!size || (size >> PAGE_SHIFT) > num_physpages)

        return NULL;

    area = get_vm_area(size, VM_ALLOC);            //如上所述,这里获得一个连续的线性地址空间的首地址,VM_ALLOC表示,是函数__vmalloc调用的get_vm_area

    if (!area)

        return NULL;

//-------------------------------------------------------------------------------

在这里有一个点需要注意。在get_vm_area中,我们分配的虚拟地址空间的size增加了一个page,但是我们在这里使用的size,是没有增加page的。因此,后面在建立页表的时候,虚拟地址空间的最后一个page实际上没有建立映射。因此,访问最后一个page会出错。也就是说,我们的保护页是通过这样的方式实现的

//-------------------------------------------------------------------------------

    nr_pages = size >> PAGE_SHIFT;

    array_size = (nr_pages * sizeof(struct page *));

    area->nr_pages = nr_pages;

    /* Please note that the recursion is strictly bounded. */

    if (array_size > PAGE_SIZE)

        pages = __vmalloc(array_size, gfp_mask, PAGE_KERNEL);

    else

        pages = kmalloc(array_size, (gfp_mask & ~__GFP_HIGHMEM));   //先默认函数是走下面的流程,这里通过kmalloc,从伙伴系统以及slab中获得pages数组。pages包含了nr_pages个page指针。最终,会将每个page指针赋值为,此次分配出来的空闲页框的页框描述符指针。

    area->pages = pages;                             //area->pages是一个成员类型为struct page *的数组。也就是说,这个数组中每个成员都是一个页描述符的地址。它描述了area对应的物理页框,也表示了area对应的物理地址

    if (!area->pages) {

        remove_vm_area(area->addr);

        kfree(area);

        return NULL;

    }

    memset(area->pages, 0, array_size);

    for (i = 0; i < area->nr_pages; i++) {

        area->pages[i] = alloc_page(gfp_mask);            //这里给area分配页框

        if (unlikely(!area->pages[i])) {

            /* Successfully allocated i pages, free them in __vunmap() */

            area->nr_pages = i;

            goto fail;

        }

    }

   

    if (map_vm_area(area, prot, &pages))     //在这里对分配的页框和分配的线性地址做映射

        goto fail;

    return area->addr;

fail:

    vfree(area->addr);

    return NULL;

}

int map_vm_area(struct vm_struct *area, pgprot_t prot, struct page ***pages)   //已经获得了连续的虚拟地址空间,以及非连续的物理页框(代表了物理地址),现在将他们映射起来

{

    unsigned long address = (unsigned long) area->addr;

    unsigned long end = address + (area->size-PAGE_SIZE);   //这里减去PAGE_SIZE的原因是,area->size中包含了一个保护页

    unsigned long next;

    pgd_t *pgd;

    int err = 0;

    int i;

//------------------------------------------------------------------------------ 

在这里补充i386的架构信息。在这种架构中,如果使用的是3级页表,那么虚拟地址总共分成4部分

[31~30]:一级页表指数:pgd_index。从4项一级页表中选择一项

[29~21]:二级页表指数:pmd_index。从512项二级页表中选择一项

[20~12]:三级页表指数:pte_index。从512项三级页表中选择一项

[11~0]:偏移量offset

//------------------------------------------------------------------------------ 

    pgd = pgd_offset_k(address);                    //内核页表是init_mm,这里就是获得((&init_mm)->pgd+pgd_index(address)),获得这个address对应的一级页表项。按照i386的架构信息,pgd_index就是拿到address的前两位

    spin_lock(&init_mm.page_table_lock);

    for (i = pgd_index(address); i <= pgd_index(end-1); i++) {

        pud_t *pud = pud_alloc(&init_mm, pgd, address);          //如果不是4级页表的话,我们没有pud(页上级目录),这时候,pud=pgd。

        if (!pud) {

            err = -ENOMEM;

            break;

        }

        next = (address + PGDIR_SIZE) & PGDIR_MASK;

        if (next < address || next > end)

            next = end;

        if (map_area_pud(pud, address, next, prot, pages)) {      //在二级页表中建立映射关系

            err = -ENOMEM;

            break;

        }

        address = next;

        pgd++;

    }

    spin_unlock(&init_mm.page_table_lock);

    flush_cache_vmap((unsigned long) area->addr, end);

    return err;

}

static int map_area_pud(pud_t *pud, unsigned long address,

       unsigned long end, pgprot_t prot,

       struct page ***pages)

{

do {

pmd_t *pmd = pmd_alloc(&init_mm, pud, address);      //这里实际上是返回((pmd_t *)(*(pud)) + pmd_index(address))/我们已经说过,pud=pgd,因此,*(pud)就是二级页表的首地址,pmd_index(address)实际上获得了address的[29~21]位。因此,这时候的pmd(页中级目录)就是address对应的二级页表项的地址

//-------------------------------------------------------------------

在这里再补充一个信息。对于i386而言,他的三级页表,也就是pte,总是4K对齐的(也就是说,每个三级页表都占据一页的内存空间)。因此,*pmd指向了三级页表的地址,只有前20位有效。因此,我们可以使用后12位存储额外的信息,例如,这个pmd二级页表项是否有效等。

//-------------------------------------------------------------------

if (!pmd)

return -ENOMEM;

if (map_area_pmd(pmd, address, end - address, prot, pages))      //在这个函数中,首先判断pmd有效,也就是它是否指向了一个三级页表(也就是这个三级页表以及被分配出来了)。如果有效的话,通过address找到对应的三级页表项,通过接口set_pte(pte, mk_pte(page, prot))设置这个三级页表项

return -ENOMEM;

address = (address + PUD_SIZE) & PUD_MASK;

pud++;

} while (address && address < end);

return 0;

}

static int map_area_pmd(pmd_t *pmd, unsigned long address,

       unsigned long size, pgprot_t prot,

       struct page ***pages)

{

unsigned long base, end;

base = address & PUD_MASK;              //PUD_MASK的值为2M

address &= ~PUD_MASK;

end = address + size;

if (end > PUD_SIZE)

end = PUD_SIZE;

do {

pte_t * pte = pte_alloc_kernel(&init_mm, pmd, base + address);        //这个函数的功能是,通过pmd的后12位包含的额外信息(_PAGE_PRESENT位是否被设置),判断这个pmd是否对应了一个有效的三级页表。如果没有有效的三级页表,需要从内存中分一个页用作三级页表。最终,返回base + address对应的三级页表项。我们需要在这个三级页表项中填入值,表达虚拟地址和物理地址之间的映射关系

if (!pte)

return -ENOMEM;

if (map_area_pte(pte, address, end - address, prot, pages))

return -ENOMEM;

address = (address + PMD_SIZE) & PMD_MASK;

pmd++;

} while (address < end);

return 0;

}

static int map_area_pte(pte_t *pte, unsigned long address,

       unsigned long size, pgprot_t prot,

       struct page ***pages)

//在这个函数中,pte表示了要更新的三级页表项的地址,address是要映射的虚拟地址,size是要映射的大小,prot是映射权限,page表示了要映射的物理地址

{

unsigned long end;

address &= ~PMD_MASK;

end = address + size;

if (end > PMD_SIZE)

end = PMD_SIZE;

do {

struct page *page = **pages;

WARN_ON(!pte_none(*pte));

if (!page)

return -ENOMEM;

set_pte(pte, mk_pte(page, prot));                     //设置三级页表项

address += PAGE_SIZE;

pte++;

(*pages)++;

} while (address < end);

return 0;

}

        注意,一个进程进入内核态后,或者内核线程,通过函数map_vm_area建立非连续内存区的映射的时候,并不会修改当前用户进程的页表,而是修改的主内核页表。当用户进程进入内核态的时候,可能访问属于内核态的线性地址空间。这时候,他才有可能访问到非连续映射的内存区。

        这时,缺页处理程序起作用了。他会检查这个缺页地址是否在内核页表中。如果他在,就把这个页表项拷贝到用户进程的页表项中,然后恢复执行。

        一定要知道,用户进程的页表,和内核进程的页表,在3~4G的线性地址空间上,使用的是同一份表。pgd都是指向同样的内存区域。

        一些情况下,我们已经有了包含空闲页框的页框描述符数组,这时候我们只需要给他分配连续的线性地址即可。这种情况下,使用函数vmap来映射页框。

3.3.2:Linux-5.10.110

        Linux-5.10.110中,vmalloc的实现如下:

/**
 * vmalloc - allocate virtually contiguous memory
 * @size:    allocation size
 *
 * Allocate enough pages to cover @size from the page level
 * allocator and map them into contiguous kernel virtual space.
 *
 * For tight control over page level allocator and protection flags
 * use __vmalloc() instead.
 *
 * Return: pointer to the allocated memory or %NULL on error
 */
void *vmalloc(unsigned long size)
{
    return __vmalloc_node(size, 1, GFP_KERNEL, NUMA_NO_NODE,
                __builtin_return_address(0));
}
/**
 * __vmalloc_node - allocate virtually contiguous memory
 * @size:        allocation size
 * @align:        desired alignment
 * @gfp_mask:        flags for the page level allocator
 * @node:        node to use for allocation or NUMA_NO_NODE
 * @caller:        caller's return address
 *
 * Allocate enough pages to cover @size from the page level allocator with
 * @gfp_mask flags.  Map them into contiguous kernel virtual space.
 *
 * Reclaim modifiers in @gfp_mask - __GFP_NORETRY, __GFP_RETRY_MAYFAIL
 * and __GFP_NOFAIL are not supported
 *
 * Any use of gfp flags outside of GFP_KERNEL should be consulted
 * with mm people.
 *
 * Return: pointer to the allocated memory or %NULL on error
 */
void *__vmalloc_node(unsigned long size, unsigned long align,
                gfp_t gfp_mask, int node, const void *caller)
{
    return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,
                gfp_mask, PAGE_KERNEL, 0, node, caller);
}


/**
 * __vmalloc_node_range - allocate virtually contiguous memory
 * @size:          allocation size
 * @align:          desired alignment
 * @start:          vm area range start
 * @end:          vm area range end
 * @gfp_mask:          flags for the page level allocator
 * @prot:          protection mask for the allocated pages
 * @vm_flags:          additional vm area flags (e.g. %VM_NO_GUARD)
 * @node:          node to use for allocation or NUMA_NO_NODE
 * @caller:          caller's return address
 *
 * Allocate enough pages to cover @size from the page level
 * allocator with @gfp_mask flags.  Map them into contiguous
 * kernel virtual space, using a pagetable protection of @prot.
 *
 * Return: the address of the area or %NULL on failure
 */
void *__vmalloc_node_range(unsigned long size, unsigned long align,
            unsigned long start, unsigned long end, gfp_t gfp_mask,
            pgprot_t prot, unsigned long vm_flags, int node,
            const void *caller)
{
    struct vm_struct *area;
    void *addr;
    unsigned long real_size = size;

    size = PAGE_ALIGN(size);
    if (!size || (size >> PAGE_SHIFT) > totalram_pages())
        goto fail;

    area = __get_vm_area_node(real_size, align, VM_ALLOC | VM_UNINITIALIZED |
                vm_flags, start, end, node, gfp_mask, caller);//这个函数的功能如上所述,在内核的地址空间中找到了一段空闲的内存区,并且创建以及初始化了vmap_area和vm_struct结构体
    if (!area)
        goto fail;

    addr = __vmalloc_area_node(area, gfp_mask, prot, node);        //在这里完成了物理页面的分配,已经映射关系的建立
    if (!addr)
        return NULL;

    /*
     * In this function, newly allocated vm_struct has VM_UNINITIALIZED
     * flag. It means that vm_struct is not fully initialized.
     * Now, it is fully initialized, so remove this flag here.
     */
    clear_vm_uninitialized_flag(area);

    kmemleak_vmalloc(area, size, gfp_mask);

    return addr;

fail:
    warn_alloc(gfp_mask, NULL,
              "vmalloc: allocation failure: %lu bytes", real_size);
    return NULL;
}
 


static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
                 pgprot_t prot, int node)
{
    const gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;
    unsigned int nr_pages = get_vm_area_size(area) >> PAGE_SHIFT;
    unsigned int array_size = nr_pages * sizeof(struct page *), i;
    struct page **pages;

    gfp_mask |= __GFP_NOWARN;
    if (!(gfp_mask & (GFP_DMA | GFP_DMA32)))
        gfp_mask |= __GFP_HIGHMEM;

    /* Please note that the recursion is strictly bounded. */
    if (array_size > PAGE_SIZE) {
        pages = __vmalloc_node(array_size, 1, nested_gfp, node,
                    area->caller);
    } else {
        pages = kmalloc_node(array_size, nested_gfp, node);
    }

    if (!pages) {
        remove_vm_area(area->addr);
        kfree(area);
        return NULL;
    }

    area->pages = pages;
    area->nr_pages = nr_pages;

    for (i = 0; i < area->nr_pages; i++) {
        struct page *page;

        if (node == NUMA_NO_NODE)
            page = alloc_page(gfp_mask);
        else
            page = alloc_pages_node(node, gfp_mask, 0);

//我们分配物理页,将所有分配的物理页指针放入数组page中
        area->pages[i] = page;
        if (gfpflags_allow_blocking(gfp_mask))
            cond_resched();
    }
    atomic_long_add(area->nr_pages, &nr_vmalloc_pages);

    if (map_kernel_range((unsigned long)area->addr, get_vm_area_size(area),       //我们在这个函数中,将连续的虚拟地址和非连续的物理地址之间做映射
            prot, pages) < 0)
        goto fail;

    return area->addr;

fail:
    warn_alloc(gfp_mask, NULL,
              "vmalloc: allocation failure, allocated %ld of %ld bytes",
              (area->nr_pages*PAGE_SIZE), area->size);
    __vfree(area->addr);
    return NULL;
}
 

3.4:释放非连续内存区

        使用函数vfree释放vmalloc()创建的非连续内存区。

void vfree(const void *addr)

{

    BUG_ON(in_nmi());

    kmemleak_free(addr);

might_sleep_if(!in_interrupt());

    if (!addr)

        return;

    __vfree(addr);

}

static void __vfree(const void *addr)

{

    if (unlikely(in_interrupt()))

        __vfree_deferred(addr);

    else

        __vunmap(addr, 1);

}

static void __vunmap(const void *addr, int deallocate_pages)
{
    struct vm_struct *area;

    if (!addr)
        return;

    if (WARN(!PAGE_ALIGNED(addr), "Trying to vfree() bad address (%p)\n",
            addr))
        return;

    area = find_vm_area(addr);               //在红黑树vmap_area_root中找addr对应的vmap_area,返回vmap_area->vm_struct

    vm_remove_mappings(area, deallocate_pages);         //调用了vm_remove_mappings删除了addr对应的线性区,并且清除了内核页表中,这个线性区对应的映射关系。这个函数在下面展开解析

    if (deallocate_pages) {         //这个变量为1,表示还需要释放物理内存到伙伴系统中
        int i;

        for (i = 0; i < area->nr_pages; i++) {
            struct page *page = area->pages[i];

            BUG_ON(!page);
            __free_pages(page, 0);
        }
        atomic_long_sub(area->nr_pages, &nr_vmalloc_pages);

        kvfree(area->pages);         //释放数组占用的页
    }

    kfree(area);           //释放线性区结构体占用的空间

//可以看到,这里并没有释放vmap_area结构体。这个结构体会在要释放的vmap_area足够多的时候,才会释放
    return;
}

/**
 * remove_vm_area - find and remove a continuous kernel virtual area
 * @addr:        base address
 *
 * Search for the kernel VM area starting at @addr, and remove it.
 * This function returns the found VM area, but using it is NOT safe
 * on SMP machines, except for its size or flags.
 *
 * Return: the area descriptor on success or %NULL on failure.
 */
struct vm_struct *remove_vm_area(const void *addr)
{
    struct vmap_area *va;

    might_sleep();

    spin_lock(&vmap_area_lock);
    va = __find_vmap_area((unsigned long)addr);    //还是通过addr在红黑树vmap_area_root中,找到对应的vmap_area结构体
    if (va && va->vm) {
        struct vm_struct *vm = va->vm;

        va->vm = NULL;
        spin_unlock(&vmap_area_lock);

        kasan_free_shadow(vm);
        free_unmap_vmap_area(va);       //解除页表中,对vmap_area表示的区域的映射。

        return vm;
    }

    spin_unlock(&vmap_area_lock);
    return NULL;
}
 

        函数free_unmap_vmap_area的实现如下所示

static void free_unmap_vmap_area(struct vmap_area *va)

{

    flush_cache_vunmap(va->va_start, va->va_end);

    unmap_kernel_range_noflush(va->va_start, va->va_end - va->va_start);        //在这个函数中,解除内核页表中相应地址的映射

    if (debug_pagealloc_enabled_static())

    flush_tlb_kernel_range(va->va_start, va->va_end);

    free_vmap_area_noflush(va);         //vmap_area从使用中的红黑树vmap_area_root中摘除,但是添加到vmap_purge_list链表中。只有vmap_purge_list链表中的vmap_area占据的虚拟地址空间足够大的时候,才会将这些虚拟地址空间释放到空闲的红黑树free_vmap_area_root

}

        这部分的内容和建立映射关系刚好是反过来的。通过address找到三级页表项pte,然后设置pte无效。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值