谷动谷力

 找回密码
 立即注册
查看: 2208|回复: 0
打印 上一主题 下一主题
收起左侧

linux内核那些事之struct page(长文)

[复制链接]
跳转到指定楼层
楼主
发表于 2022-10-14 19:50:12 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
linux内核那些事之struct page(长文)
struct page
page(页)是linux内核管理物理内存的最小单位,内核将整个物理内存按照页对齐方式划分成千上万个页进行管理,核为了管理这些页将每个页抽象成struct page结构管理每个页状态及其他属性,针对一个4GB内存,那么将会存在上百万个struct page结构。而struct page结构本身就占有一定内存,如果struct page结构设计过大,那么本身就会占用较多内存,而给系统或者用户可用的内存就较少,所以对strcut page结构大小非常敏感,即使增加一个字节 对系统影响也会非常大,故社区对struct page的结构做了严格设计,不会轻易增加字段:

One of these structures exists for every physical page in the system; on a 4GB system, there will be one million page structures. Given that every byte added to struct page is amplified a million times, it is not surprising that there is a strong motivation to avoid growing this structure at any cost. So struct page contains no less than three unions and is surrounded by complicated rules describing which fields are valid at which times. Changes to how this structure is accessed must be made with great care.

为了减少struct page占用空间大小,设计之初使用了很多技巧,其中一直就是使用union结构,在5.8.10版本中整个struct page使用了两个较大union结构以节省内存,page结构划分如下几块:

可以看到在一个64位系统中,struct page主要包含两个union结构,大小分别位40个字节和4个字节,这样设计的目的主要是减少占用空间 。

除了使用union技术减少占用空间之外,还使用了其他两个技术其中一个就是对flags标志的使用:

Unions are not the only technique used to shoehorn as much information as possible into this small structure. Non-uniform memory access (NUMA) systems need to track information on which node each page belongs to, and which zone within the node as well. Rather than add fields to struct page

在NUMA系统中为了节省占用空间,将flags页标志位中 划分出一部分给node id 和zone使用,如下:

还有另外一个比较重要的技术就是复用,最典型的一个应用就是list_head lru链表,在page不同的时期及不同的用途,会指向不同的链表,以节省空间。

struct page 结构定义位于include\linux\mm_types.h文件中,5.8.10版本定义如下:

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 {
        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 */
            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.
             */

            unsigned long private;
        };
        struct {    /* page_pool used by netstack */
            /**
             * @dma_addr: might require a 64-bit value even on
             * 32-bit architectures.
             */

            dma_addr_t dma_addr;
        };
        struct {    /* slab, slob and 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 */
            unsigned long compound_head;    /* Bit zero is set */

            /* First tail page only */
            unsigned char compound_dtor;
            unsigned char compound_order;
            atomic_t compound_mapcount;
        };
        struct {    /* Second tail page of compound page */
            unsigned long _compound_pad_1;  /* compound_head */
            atomic_t hpage_pinned_refcount;
            /* For both global and memcg */
            struct list_head deferred_list;
        };
        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 */
            /** @pgmap: Points to the hosting device page map. */
            struct dev_pagemap *pgmap;
            void *zone_device_data;
            /*
             * ZONE_DEVICE private pages are counted as being
             * mapped so the next 3 words hold the mapping, index,
             * and private fields from the source anonymous or
             * page cache page while the page is migrated to device
             * private memory.
             * ZONE_DEVICE MEMORY_DEVICE_FS_DAX pages also
             * use the mapping, index, and private fields when
             * pmem backed DAX files are mapped.
             */

        };

        /** @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;

#ifdef CONFIG_MEMCG
    struct mem_cgroup *mem_cgroup;
#endif

    /*
     * On machines where all RAM is mapped into kernel address space,
     * we can simply calculate the virtual address. On machines with
     * highmem some memory is mapped into kernel virtual memory
     * dynamically, so we need a place to store that address.
     * Note that this field could be 16 bits on x86 ... ;)
     *
     * Architectures with slow multiplication can define
     * WANT_PAGE_VIRTUAL in asm/page.h
     */

#if defined(WANT_PAGE_VIRTUAL)
    void *virtual;          /* Kernel virtual address (NULL if
                       not kmapped, ie. highmem) */

#endif /* WANT_PAGE_VIRTUAL */

#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
    int _last_cpupid;
#endif
} _struct_page_alignment;

该结构较大,需要按照上述分四块进行分析。

page flag

page flags标志位主要采用bit 位方式,来描述一个物理页的状态信息:

Page flags" are simple bit flags describing the state of a page of physical memory. hey are defined in <linux/page-flags.h>. Flags exist to mark "reserved" pages (kernel memory, I/O memory, or simply nonexistent), locked pages, those under writeback I/O, those which are part of a compound page, pages managed by the slab allocator, and more. Depending on the target architecture and kernel configuration options selected, there can be as many as 24 individual flags defined.

page flags 不仅仅划分分成标志位使用,还划分给了section、node id、zone使用,其划分的形式和内存模型以及内核配置有关,在include\linux\page-flags-layout.h文件中描述了其主要5种划分形式:

第一种形式为非sparse内存模式或者sparse vmemmap内存模式如下:

上述形式是常见的page flags形式,其中从0到63位最高位依次位FLAGS位(真正的页状态标志位)、中间剩余保留,以及ZONE和NODE部分,其中zone代表着该page归属于的zone区域,而NODE在NUMA系统种代表着该page所属于的node 节点id,如果是非NUMA系统则为0。中间剩余的部分为保留位。

而在上述第一种形式中如果开启了last_cpupid,则会开启LAST_CPUPID字段,形式如下:

如果是开启可非vmemmap的sparse内存模式,则需要增加section字段表示page所处于的mem_section:

当然上述形式如果开启了last_cpupid,则划分如下:

除了上述四种形式,sparse还支持没有node id形式来支持非NUMA系统:

上述几种形式字段的大小以及偏移每个架构都有不同,内核种对每个字段都提供了PGOFF宏,方便统一计算,宏定义位于(include\linux\mm.h)文件中 :

/* Page flags: | [SECTION] | [NODE] | ZONE | [LAST_CPUPID] | ... | FLAGS | */
#define SECTIONS_PGOFF        ((sizeof(unsigned long)*8) - SECTIONS_WIDTH)
#define NODES_PGOFF        (SECTIONS_PGOFF - NODES_WIDTH)
#define ZONES_PGOFF        (NODES_PGOFF - ZONES_WIDTH)
#define LAST_CPUPID_PGOFF    (ZONES_PGOFF - LAST_CPUPID_WIDTH)
#define KASAN_TAG_PGOFF        (LAST_CPUPID_PGOFF - KASAN_TAG_WIDTH)
除了PGOFF还提供了 shitfs定义,如果一个字段位0则PGSHIFT则为0:
#define SECTIONS_PGSHIFT    (SECTIONS_PGOFF * (SECTIONS_WIDTH != 0))
#define NODES_PGSHIFT        (NODES_PGOFF * (NODES_WIDTH != 0))
#define ZONES_PGSHIFT        (ZONES_PGOFF * (ZONES_WIDTH != 0))
#define LAST_CPUPID_PGSHIFT    (LAST_CPUPID_PGOFF * (LAST_CPUPID_WIDTH != 0))
#define KASAN_TAG_PGSHIFT    (KASAN_TAG_PGOFF * (KASAN_TAG_WIDTH != 0))

各个字段的MASK定义如下:

    #define ZONEID_PGSHIFT        (ZONEID_PGOFF * (ZONEID_SHIFT != 0))

    #define ZONES_MASK        ((1UL << ZONES_WIDTH) - 1)
    #define NODES_MASK        ((1UL << NODES_WIDTH) - 1)
    #define SECTIONS_MASK        ((1UL << SECTIONS_WIDTH) - 1)
    #define LAST_CPUPID_MASK    ((1UL << LAST_CPUPID_SHIFT) - 1)
    #define KASAN_TAG_MASK        ((1UL << KASAN_TAG_WIDTH) - 1)
    #define ZONEID_MASK        ((1UL << ZONEID_SHIFT) - 1)

可以看到上述几个宏最终都依赖于各个地段的WIDTH宏代表每个字段占有多少位,该如果某个字段不存在则将该字段的WIDTH为0:

SECTIONS字段操作

section字段宽度SECTIONS_WIDTH定义如下:

#if defined(CONFIG_SPARSEMEM) && !defined(CONFIG_SPARSEMEM_VMEMMAP)
#define SECTIONS_WIDTH        SECTIONS_SHIFT
#else
#define SECTIONS_WIDTH        0
#endif

只有在配置内存模型为sparse且不支持vememap时,SECTION_WIDTH才为非零,此时取决于SECTIONS_SHIFT:

#define SECTIONS_SHIFT    (MAX_PHYSMEM_BITS - SECTION_SIZE_BITS)

MAX_PHYSMEM_BITS和SECTION_SIZE_BITS与具体芯片架构有关

内核还将获取或者设置page section封装成函数,设置page section函数为:

   static inline void set_page_section(struct page *page, unsigned long section)
   
{
        page->flags &= ~(SECTIONS_MASK << SECTIONS_PGSHIFT);
        page->flags |= (section & SECTIONS_MASK) << SECTIONS_PGSHIFT;
    }

ZONES字段

ZONES_WIDTH定义如下:

#define ZONES_WIDTH        ZONES_SHIFT

ZONES_SHIFT定义与具体的zone 最大MAX_NR_ZONE有关:

#if MAX_NR_ZONES < 2
#define ZONES_SHIFT 0
#elif MAX_NR_ZONES <= 2
#define ZONES_SHIFT 1
#elif MAX_NR_ZONES <= 4
#define ZONES_SHIFT 2
#elif MAX_NR_ZONES <= 8
#define ZONES_SHIFT 3
#else
#error ZONES_SHIFT -- too many zones configured adjust calculation
#endif
设置page中zone 操作函数如下:
    static inline void set_page_zone(struct page *page, enum zone_type zone)
   
{
        page->flags &= ~(ZONES_MASK << ZONES_PGSHIFT);
        page->flags |= (zone & ZONES_MASK) << ZONES_PGSHIFT;
    }

以及获取到zone id接口:

    static inline struct zone *page_zone(const struct page *page)
   
{
        return &NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)];
    }
NODES字段操作

node节点宽度NODES_WIDTH定义如下:

#if SECTIONS_WIDTH+ZONES_WIDTH+NODES_SHIFT <= BITS_PER_LONG - NR_PAGEFLAGS
#define NODES_WIDTH        NODES_SHIFT
#else
#ifdef CONFIG_SPARSEMEM_VMEMMAP
#error "Vmemmap: No space for nodes field in page flags"
#endif
#define NODES_WIDTH        0
#endif

这里做了检查防止使用的bit位综合超过unsigned long的BITS_PER_LONG 大小,如果没有超过则使用NODE_SHIFT配置:

#ifdef CONFIG_NODES_SHIFT
#define NODES_SHIFT     CONFIG_NODES_SHIFT
#else
#define NODES_SHIFT     0
#endif
NODE_SHIFT的大小可以通过内核 CONFIG_NODES_SHIFT 来配置。

获取page中的node字段操作如下:

static inline int page_to_nid(const struct page *page)
{
    struct page *p = (struct page *)page;

    return (PF_POISONED_CHECK(p)->flags >> NODES_PGSHIFT) & NODES_MASK;
}

LAST__CPU_SHIFT

last cpu pid没有专门的width宏 只有LAST_CPUPID_SHIFT,定义如下:

#ifdef CONFIG_NUMA_BALANCING
#define LAST__PID_SHIFT 8
#define LAST__PID_MASK  ((1 << LAST__PID_SHIFT)-1)

#define LAST__CPU_SHIFT NR_CPUS_BITS
#define LAST__CPU_MASK  ((1 << LAST__CPU_SHIFT)-1)

#define LAST_CPUPID_SHIFT (LAST__PID_SHIFT+LAST__CPU_SHIFT)
#else
#define LAST_CPUPID_SHIFT 0
#endif

需要开启CONFIG_NUMA_BALANCING宏,才支持。且LAST_CPUID_SHIFT取决于NR_CPUS_BITS。

获取last cpu pid:

static inline int page_cpupid_last(struct page *page)
{
    return (page->flags >> LAST_CPUPID_PGSHIFT) & LAST_CPUPID_MASK;
}
reset last cpu pid:
static inline void page_cpupid_reset_last(struct page *page)
{
    page->flags |= LAST_CPUPID_MASK << LAST_CPUPID_PGSHIFT;
}
struct page成员详细描述

管理struct page各个字段详细描述:

flags字段

flags字段基本都是固定的,每个flags占一个bit位,专门位于include\linux\page-flags.h对标志位进行统一管理,在该头文件中有一个针对page flags有一个详细的描述,其中有一段:

* The page flags field is split into two parts, the main flags area
* which extends from the low bits upwards, and the fields area which
* extends from the high bits downwards.
*
*  | FIELD | ... | FLAGS |
*  N-1           ^       0
*               (NR_PAGEFLAGS)
*
page flags主要划分为两端,其中以NR_PAGEFLAGS为分水线,NR_PAGEFLAGS以上的称之为可扩展部分:
page 标志
说明

PG_locked该页面释放已经上锁,如果已经上锁则置1,其他内核模块不能再访问
PG_referenced如果该页面最近是否被访问,如果被访问过则置位。用于LRU算法
PG_uptodate该页面已经从硬盘中成功读取
PG_dirty该页面是一个脏页,需要将该页面的数据刷新到硬盘中。当页面数据被修改时并不会立即刷新到硬盘中,而是暂时先保证到内存中,等待后面刷新到硬盘中。设置该页为脏页意味着再该页被置换出去之前必须要保证该页不能被-释放
PG_lru表示该page 位于某个LRU链表中(active、inactive、unevictable LRU中)。
PG_active表示该页处于活跃状态
PG_workingset设置该页为某个进程的woring set,关于working set 可以看下面文章:How To Measure the Working Set Size on Linux
PG_waiters有进程在等待这个页面
PG_error该页面在操作IO过程中出现错误
PG_slab该页被slab所使用
PGownerpriv_1被页面的所有者使用,如果是作为pagecache页面,则文件系统有可能使用
PGarch1与体系结构相关的一个状态位
PG_reserved该页被保留,不能够被swap out出去。在系统中kernek image(包括vDSO)以及 BIOS,initrd、hw table 以及vememap等待在系统系统初始化阶段就需要做保留以及DAM等常见需要做保留的页 都需要将页状态设置位保留
PG_private如果page中的private成员非空,则需要设置该标志, 用于I/O的页可使用该字段将页细分为多核缓冲区
PGprivate2在PG_private基础上的扩充,经常用于aux data
PG_writeback页面的内存正在向磁盘写
PG_head该页是一个head page页。在内核中有时需要将多个页组成一个compound pages,而设置该状态时表明该页是compound pages的第一个页
PG_mappedtodisk该页被映射到硬盘中
PG_reclaim该页可被回收
PG_swapbacked该page的后备存储器是swap/ram,一般匿名页才可以回写swap分
PG_unevictable该page被锁住,不能回收,并会出现在LRU_UNEVICTABLE链表中
PG_mlocked该页对应的vma被锁住,一般是通过系统调用mlock()锁定了一段内
PG_uncached该页被设置为不可缓存,需要配置CONFIGARCHUSESPGUNCACHED
PG_hwpoisonhardware poisoned page. Don't touch,需要配置CONFIGMEMORYFAILURE
PG_young需要CONFIGIDLEPAGETRACKING和CONFIG64BIT才支持
PG_idle需要CONFIGIDLEPAGETRACKING和CONFIG64BIT才支持

为了方便进行标位位置位,清零以及查看是否置位等操作,内核做了系列的宏定义,看起来比较复杂:

/*
* Macros to create function definitions for page flags
*/

#define TESTPAGEFLAG(uname, lname, policy)                \
static __always_inline int Page##uname(struct page *page)        \
    { return test_bit(PG_##lname, &policy(page, 0)->flags); }

#define SETPAGEFLAG(uname, lname, policy)                \
static __always_inline void SetPage##uname(struct page *page)        \
    { set_bit(PG_##lname, &policy(page, 1)->flags); }

#define CLEARPAGEFLAG(uname, lname, policy)                \
static __always_inline void ClearPage##uname(struct page *page)        \
    { clear_bit(PG_##lname, &policy(page, 1)->flags); }

#define __SETPAGEFLAG(uname, lname, policy)                \
static __always_inline void __SetPage##uname(struct page *page)        \
    { __set_bit(PG_##lname, &policy(page, 1)->flags); }

#define __CLEARPAGEFLAG(uname, lname, policy)                \
static __always_inline void __ClearPage##uname(struct page *page)    \
    { __clear_bit(PG_##lname, &policy(page, 1)->flags); }

#define TESTSETFLAG(uname, lname, policy)                \
static __always_inline int TestSetPage##uname(struct page *page)    \
    { return test_and_set_bit(PG_##lname, &policy(page, 1)->flags); }

#define TESTCLEARFLAG(uname, lname, policy)                \
static __always_inline int TestClearPage##uname(struct page *page)    \
    { return test_and_clear_bit(PG_##lname, &policy(page, 1)->flags); }

#define PAGEFLAG(uname, lname, policy)                    \
    TESTPAGEFLAG(uname, lname, policy)              \
    SETPAGEFLAG(uname, lname, policy)               \
    CLEARPAGEFLAG(uname, lname, policy)

#define __PAGEFLAG(uname, lname, policy)                \
    TESTPAGEFLAG(uname, lname, policy)              \
    __SETPAGEFLAG(uname, lname, policy)             \
    __CLEARPAGEFLAG(uname, lname, policy)

#define TESTSCFLAG(uname, lname, policy)                \
    TESTSETFLAG(uname, lname, policy)               \
    TESTCLEARFLAG(uname, lname, policy)

#define TESTPAGEFLAG_FALSE(uname)                    \
static inline int Page##uname(const struct page *page) { return 0; }

#define SETPAGEFLAG_NOOP(uname)                        \
static inline void SetPage##uname(struct page *page) {  }

#define CLEARPAGEFLAG_NOOP(uname)                    \
static inline void ClearPage##uname(struct page *page) {  }

#define __CLEARPAGEFLAG_NOOP(uname)                    \
static inline void __ClearPage##uname(struct page *page) {  }

#define TESTSETFLAG_FALSE(uname)                    \
static inline int TestSetPage##uname(struct page *page) { return 0; }

#define TESTCLEARFLAG_FALSE(uname)                    \
static inline int TestClearPage##uname(struct page *page) { return 0; }

#define PAGEFLAG_FALSE(uname) TESTPAGEFLAG_FALSE(uname)            \
    SETPAGEFLAG_NOOP(uname) CLEARPAGEFLAG_NOOP(uname)

#define TESTSCFLAG_FALSE(uname)                        \
    TESTSETFLAG_FALSE(uname) TESTCLEARFLAG_FALSE(uname)

__PAGEFLAG(Locked, locked, PF_NO_TAIL)
PAGEFLAG(Waiters, waiters, PF_ONLY_HEAD) __CLEARPAGEFLAG(Waiters, waiters, PF_ONLY_HEAD)
PAGEFLAG(Error, error, PF_NO_TAIL) TESTCLEARFLAG(Error, error, PF_NO_TAIL)
PAGEFLAG(Referenced, referenced, PF_HEAD)
    TESTCLEARFLAG(Referenced, referenced, PF_HEAD)
    __SETPAGEFLAG(Referenced, referenced, PF_HEAD)
PAGEFLAG(Dirty, dirty, PF_HEAD) TESTSCFLAG(Dirty, dirty, PF_HEAD)
    __CLEARPAGEFLAG(Dirty, dirty, PF_HEAD)
PAGEFLAG(LRU, lru, PF_HEAD) __CLEARPAGEFLAG(LRU, lru, PF_HEAD)
PAGEFLAG(Active, active, PF_HEAD) __CLEARPAGEFLAG(Active, active, PF_HEAD)
    TESTCLEARFLAG(Active, active, PF_HEAD)
PAGEFLAG(Workingset, workingset, PF_HEAD)
    TESTCLEARFLAG(Workingset, workingset, PF_HEAD)
__PAGEFLAG(Slab, slab, PF_NO_TAIL)
__PAGEFLAG(SlobFree, slob_free, PF_NO_TAIL)
PAGEFLAG(Checked, checked, PF_NO_COMPOUND)       /* Used by some filesystems */

/* Xen */
PAGEFLAG(Pinned, pinned, PF_NO_COMPOUND)
    TESTSCFLAG(Pinned, pinned, PF_NO_COMPOUND)
PAGEFLAG(SavePinned, savepinned, PF_NO_COMPOUND);
PAGEFLAG(Foreign, foreign, PF_NO_COMPOUND);
PAGEFLAG(XenRemapped, xen_remapped, PF_NO_COMPOUND)
    TESTCLEARFLAG(XenRemapped, xen_remapped, PF_NO_COMPOUND)

PAGEFLAG(Reserved, reserved, PF_NO_COMPOUND)
    __CLEARPAGEFLAG(Reserved, reserved, PF_NO_COMPOUND)
    __SETPAGEFLAG(Reserved, reserved, PF_NO_COMPOUND)
PAGEFLAG(SwapBacked, swapbacked, PF_NO_TAIL)
    __CLEARPAGEFLAG(SwapBacked, swapbacked, PF_NO_TAIL)
    __SETPAGEFLAG(SwapBacked, swapbacked, PF_NO_TAIL)

/*
* Private page markings that may be used by the filesystem that owns the page
* for its own purposes.
* - PG_private and PG_private_2 cause releasepage() and co to be invoked
*/

PAGEFLAG(Private, private, PF_ANY) __SETPAGEFLAG(Private, private, PF_ANY)
    __CLEARPAGEFLAG(Private, private, PF_ANY)
PAGEFLAG(Private2, private_2, PF_ANY) TESTSCFLAG(Private2, private_2, PF_ANY)
PAGEFLAG(OwnerPriv1, owner_priv_1, PF_ANY)
    TESTCLEARFLAG(OwnerPriv1, owner_priv_1, PF_ANY)

/*
* Only test-and-set exist for PG_writeback.  The unconditional operators are
* risky: they bypass page accounting.
*/

TESTPAGEFLAG(Writeback, writeback, PF_NO_TAIL)
    TESTSCFLAG(Writeback, writeback, PF_NO_TAIL)
PAGEFLAG(MappedToDisk, mappedtodisk, PF_NO_TAIL)

/* PG_readahead is only used for reads; PG_reclaim is only for writes */
PAGEFLAG(Reclaim, reclaim, PF_NO_TAIL)
    TESTCLEARFLAG(Reclaim, reclaim, PF_NO_TAIL)
PAGEFLAG(Readahead, reclaim, PF_NO_COMPOUND)
    TESTCLEARFLAG(Readahead, reclaim, PF_NO_COMPOUND)
将上述一系列宏展开,主要有以下三种:
  • 将页面置位统一命名位SetPageXXX,其中XXX为标志位中的后面小写部分,例如SetPageLRU,设置的是PGlru标志位,SetPageDirty 设置的是PGdirty标志位
  • ClearPageXXX是将相应的标志位清空
  • PageXXX,用于检查页面是否设置了该标志位。

关于page flags争论

关于struct page flags一直都有一个巨大争论 就是flags是否足够?

由于flags除了划分真正的状态标志位还需要划分给node,section、zone使用,这样flags显得就很拥挤,关于这一讨论可以详细了解下:How many page flags do we really have?
第一个Union

struct page第一个union 在64位系统下是一个40字节的联合体,里面包含8个部分,是记录page的主要功能数据,每个部分都有个结构体进行说明

Page cache and anonymous pages

第一个结构体主要是对匿名页和page cache的主要功能数据,主要结构成员如下:

    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 */
        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.
         */

        unsigned long private;
    };

主要成员说明:

struct list_head lru:为LRU链表,该链表会根据页面不同的用途挂载到不同的链表, 如在空闲时刻,被buddy系统管理时,会挂接到buffy的free 链表中。如果页面被分配,则会根据页面的激活状态,挂接到active list链表中。
struct address_space *mapping:当页面被映射时 指向映射的地址空间。当为匿名映射时,mapping实际上指向的是struct anon_vma *结构。当为文件映射时,mapping指向的是struct address_space *结构。如果判断当前页面是匿名映射还是文件映射,使用PageAnon()函数判断,如果mmaping 的前两位PAGE_MAPPING_ANON为true 则为匿名映射。以下为mapping的几个关键函数:
static __always_inline int PageAnon(struct page *page)
{
    page = compound_head(page);
    return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
}

struct anon_vma *page_anon_vma(struct page *page)
{
    unsigned long mapping;

    page = compound_head(page);
    mapping = (unsigned long)page->mapping;
    if ((mapping & PAGE_MAPPING_FLAGS) != PAGE_MAPPING_ANON)
        return NULL;
    return __page_rmapping(page);
}

struct address_space *page_mapping(struct page *page)
{
    struct address_space *mapping;

    page = compound_head(page);

    /* This happens if someone calls flush_dcache_page on slab page */
    if (unlikely(PageSlab(page)))
        return NULL;

    if (unlikely(PageSwapCache(page))) {
        swp_entry_t entry;

        entry.val = page_private(page);
        return swap_address_space(entry);
    }

    mapping = page->mapping;
    if ((unsigned long)mapping & PAGE_MAPPING_ANON)
        return NULL;

    return (void *)((unsigned long)mapping & ~PAGE_MAPPING_FLAGS);
}
  • pgoff_t index:该字段是一个复用字段。当该页面被文件映射时,代表偏移量。为匿名映射时,保存的是页迁移类型migratetype(见set_pcppage_migratetype()函数)。
  • unsigned long private:私有数据

page_pool used by netstack

如果该页被用作DMA映射,dma_addr_t则代表的是映射的一个总线地址:

struct {    /* page_pool used by netstack */
        /**
         * @dma_addr: might require a 64-bit value even on
         * 32-bit architectures.
         */

        dma_addr_t dma_addr;
    };
slab, slob and slub

该页面被slab/slob/slub所管理分配,即已经被buffy分配出去,进一步做小内存分配管理:

struct {    /* slab, slob and 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 list_head slab_list:指向的是slab list链表
  • struct page *next:在slub中分配使用
  • struct kmem_cache *slab_cache:指向的是slab缓存描述符
  • void *freelist::指向的是第一个空间的kobject。当一个页被buddy分配出去由slab进行管理时,会将该内存划分成相应大小的等份数组 即object进行分配管理。freelist指向的是第一个空闲的位置
  • void *s_mem:指向第一个slab对象的起始地址
  • unsigned long counters:被slub用作计数

Tail pages of compound page

该结构表明为compound pages的最后一个页(特别注意虽然是tail pages,但是compound pages的head page 也使用该结构)至于compound pages的主要功能有一段描述:

A compound page is simply a grouping of two or more physically contiguous pages into a unit that can, in many ways, be treated as a single, larger page. They are most commonly used to create huge pages

compound page将多个连续的物理页组装联合在一起组成一个更大页,其最大的用途是可以创建一个huge 页,具体介绍可以参考:An introduction to compound pages [LWN.net]

该此时该结果主要描述的是compound page的tail page:

struct {    /* Tail pages of compound page */
    unsigned long compound_head;    /* Bit zero is set */

    /* First tail page only */
    unsigned char compound_dtor;
    unsigned char compound_order;
    atomic_t compound_mapcount;
};

主要结构成员:

  • unsigned long compound_head:指向compound pages的第一个 head页。除了head 页之外,其他页都是tail 页,如果compound_head被设置成head页,如果compound_head被设置成head页,则表明该页是compound pages的tail页,可以看PageTail函数。compound head页不设置compound_head为零,获取compound pageshead页接口为compound_head。
  • unsigned char compound_dtor:a destructor,当每个compound pages都保存对应的destructor,用于释放该页时通过compound_dtor 从compound_page_dtors中获取到对应的释放函数,可以参见destroy_compound_page()函数:
  • static inline void destroy_compound_page(struct page *page)
    {
        VM_BUG_ON_PAGE(page[1].compound_dtor >= NR_COMPOUND_DTORS, page);
        compound_page_dtors[page[1].compound_dtor](page);
    }
  • 对应的 compound_page_dtors 定义如下

compound_page_dtor * const compound_page_dtors[NR_COMPOUND_DTORS] = {
    [NULL_COMPOUND_DTOR] = NULL,
    [COMPOUND_PAGE_DTOR] = free_compound_page,
#ifdef CONFIG_HUGETLB_PAGE
    [HUGETLB_PAGE_DTOR] = free_huge_page,
#endif
#ifdef CONFIG_TRANSPARENT_HUGEPAGE
    [TRANSHUGE_PAGE_DTOR] = free_transhuge_page,
#endif
};
  • unsigned char compound_order:只有在head page设置,将compound pages整体页数作为order,只存在head
  • atomic_t compound_mapcount: compound page被多少个用户进程的page 指向该页。当page 属于compound pages时,获取映射此时不再从第二个union结构中的_mapcount获取,而是从该字段中获取。

Second tail page of compound page

为了节省整个struct page空间,除了上述定义compound taile结构之外,还定义了第二种compound tail page结构用于扩展:

    struct {    /* Second tail page of compound page */
            unsigned long _compound_pad_1;  /* compound_head */
            atomic_t hpage_pinned_refcount;
            /* For both global and memcg */
            struct list_head deferred_list;
        };
  Page table pages

该结构主要用于page table,结构成员如下:

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
ZONE_DEVICE pages

当该页面属于ZONE_DEVICE时:

   struct {    /* ZONE_DEVICE pages */
            /** @pgmap: Points to the hosting device page map. */
            struct dev_pagemap *pgmap;
            void *zone_device_data;
            /*
             * ZONE_DEVICE private pages are counted as being
             * mapped so the next 3 words hold the mapping, index,
             * and private fields from the source anonymous or
             * page cache page while the page is migrated to device
             * private memory.
             * ZONE_DEVICE MEMORY_DEVICE_FS_DAX pages also
             * use the mapping, index, and private fields when
             * pmem backed DAX files are mapped.
             */

        };
rcu_head

rcu_head主要被用作RCU锁

  • struct rcu_head rcu_head;

第二个Union

struct page的第二个union大小为4个字节,主要成员包括如下:

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 */
    };

atomic_t _mapcount:(_mapcount is the number of page-table entries pointing to the page)有多少个page table 映射指向该页面。每个用户进程都拥有各自独立的虚拟空间(64位系统用户空间一般有256TB)以及拥有独立的页表,所有有可能出现多个用户进程空间同时映射到一个物理页面情况。该计数代表被映射到多少个用户进程。_mapcount为-1,则代表没有被PTE映射。等于0时 表示只有一个父进程使用被映射,当大于0时代表除了父进程还有其他进程使用这个页面。与该计数有关的一个重要特性就是RMAP。获取mapcount 函数为page_mapped,特别要说明的是如果该页是compound page则从compound_mapcount中获取。

    bool page_mapped(struct page *page)
   
{
        int i;

        if (likely(!PageCompound(page)))
            return atomic_read(&page->_mapcount) >= 0;
        page = compound_head(page);
        if (atomic_read(compound_mapcount_ptr(page)) >= 0)
            return true;
        if (PageHuge(page))
            return false;
        for (i = 0; i < compound_nr(page); i++) {
            if (atomic_read(&page._mapcount) >= 0)
                return true;
        }
        return false;

    }
  • unsigned int page_type :如果该页面即不属于page slab也不属于user space,则该代表页面类型即使用用途
  • unsigned int active :表示slab中活跃对象
  • int units :被slob使用

page type

page type用于表示一个物理页page使用类型,只要支持的page type如下:

#define PG_buddy    0x00000080
#define PG_offline    0x00000100
#define PG_kmemcg    0x00000200
#define PG_table    0x00000400
#define PG_guard    0x00000800
采用bit map表示方法:
  • PG_buddy:page是否位于buddy
  • PG_offline: page是否处于上线状态
  • PG_kmemcg:page为kmemcg使用
  • PG_table:page作为page table使用
  • PG_guard:page作为guard使用。

定义位于include\linux\page-flags.h文件中,该文件还定义了一系列宏:

#define PageType(page, flag)                        \
    ((page->page_type & (PAGE_TYPE_BASE | flag)) == PAGE_TYPE_BASE)

static inline int page_has_type(struct page *page)
{
    return (int)page->page_type < PAGE_MAPCOUNT_RESERVE;
}

#define PAGE_TYPE_OPS(uname, lname)                    \
static __always_inline int Page##uname(struct page *page)        \
{                                    \
    return PageType(page, PG_##lname);              \
}                                    \
static __always_inline void __SetPage##uname(struct page *page)        \
{                                    \
    VM_BUG_ON_PAGE(!PageType(page, 0), page);           \
    page->page_type &= ~PG_##lname;                 \
}                                    \
static __always_inline void __ClearPage##uname(struct page *page)    \
{                                    \
    VM_BUG_ON_PAGE(!Page##uname(page), page);           \
    page->page_type |= PG_##lname;                  \
}

/*
* PageBuddy() indicates that the page is free and in the buddy system
* (see mm/page_alloc.c).
*/

PAGE_TYPE_OPS(Buddy, buddy)

PAGE_TYPE_OPS(Offline, offline)

PAGE_TYPE_OPS(Kmemcg, kmemcg)

PAGE_TYPE_OPS(Table, table)

PAGE_TYPE_OPS(Guard, guard)
例如以PG_buddy为用例:
  • PageBuddy:表明PG_buddy位是否设置,如果设置返回true,否则返回false。
  • SetPageBuddy: 设置PGbuddy标志位位1。
  • ClearPageBuddy:清除PGbuddy标记位。

_refcount

_refcount 被用作引用计数管理,用于跟踪内存使用状况。初始化为空闲状态时计数为0,当被分配引用时 计数会+1,如果该页面被其他引用时也会+1。

特别要注意:如果该页是一个compound page,则计数只会记录再在head pages中。内核中常用的计数函数:get_page()计数+1函数接口。put_page()计数-1接口:

static inline void get_page(struct page *page)
{
    page = compound_head(page);
    /*
     * Getting a normal page or the head of a compound page
     * requires to already have an elevated page->_refcount.
     */

    VM_BUG_ON_PAGE(page_ref_zero_or_close_to_overflow(page), page);
    page_ref_inc(page);
}

static inline void put_page(struct page *page)
{
    page = compound_head(page);

    /*
     * For devmap managed pages we need to catch refcount transition from
     * 2 to 1, when refcount reach one it means the page is free and we
     * need to inform the device driver through callback. See
     * include/linux/memremap.h and HMM for details.
     */

    if (page_is_devmap_managed(page)) {
        put_devmap_managed_page(page);
        return;
    }

    if (put_page_testzero(page))
        __put_page(page);
}
上述两个函数首先是调用compound_head() 函数,如果是compound page则获取首页,如果不是则返回该页:
static inline struct page *compound_head(struct page *page)
{
    unsigned long head = READ_ONCE(page->compound_head);

    if (unlikely(head & 1))
        return (struct page *) (head - 1);
    return page;
}
最终都会调用到以下几个函数:
static inline int page_count(struct page *page)
{
    return atomic_read(&compound_head(page)->_refcount);
}

static inline void set_page_count(struct page *page, int v)
{
    atomic_set(&page->_refcount, v);
    if (page_ref_tracepoint_active(__tracepoint_page_ref_set))
        __page_ref_set(page, v);
}

static inline void init_page_count(struct page *page)
{
    set_page_count(page, 1);
}

static inline void page_ref_add(struct page *page, int nr)
{
    atomic_add(nr, &page->_refcount);
    if (page_ref_tracepoint_active(__tracepoint_page_ref_mod))
        __page_ref_mod(page, nr);
}

....
static inline void page_ref_inc(struct page *page)
{
    atomic_inc(&page->_refcount);
    if (page_ref_tracepoint_active(__tracepoint_page_ref_mod))
        __page_ref_mod(page, 1);
}

static inline void page_ref_dec(struct page *page)
{
    atomic_dec(&page->_refcount);
    if (page_ref_tracepoint_active(__tracepoint_page_ref_mod))
        __page_ref_mod(page, -1);
}
...
关于page refcount引用计数最终的函数定义及实现在 include\linux\page_ref.h 文件中。
compound pages VS ordinary allocations

compound pages在申请内存时也可正常的内存申请一样,都是使用order申请一块连续的内存,在内核中大部分的情况下是用不到compound特性的。如果该pages作为compound pages,那么这些pages将被看作一个整体,如果有些模块想操作compoundpages中的一个页,相当于操作整个compound pages,不能被分裂。释放时也是同样如此,作为一个整体释放:

In most cases, compound pages are unnecessary and ordinary allocations can be used; calling code needs to remember how many pages it allocated, but otherwise the metadata that would be stored in a compound page is unneeded. A compound page is indicated, though, whenever it is important to treat the group of pages as a whole even if somebody references a single page within it. Transparent huge pages are a classic example; if user space attempts to change the protections on a portion of a huge page, the entire huge page will need to be located and broken up. Various drivers also use compound pages to ease the management of larger buffers.

struct page历史演进

struct pages作为一个内核中物理内存中一个重要数据结构,经历经历过多次整体技术演进,一个重要目的就是减小structpage结构的尺度,尽量在较小的尺寸内表达尽量多的信息,所以该结构中大量使用了复用技术,需要了解每个结构的具体演进,才能不至于看代码看的云里雾里,其中一个比较重要的整改说明文章可以参考:《Cramming more into struct page》Cramming more into struct page [LWN.net]

参考资料

Cramming more into struct page [LWN.net]

How many page flags do we really have? [LWN.net]

An introduction to compound pages [LWN.net]

Minimizing the use of tail pages [LWN.net]



+10
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|深圳市光明谷科技有限公司|光明谷商城|Sunshine Silicon Corpporation ( 粤ICP备14060730号|Sitemap

GMT+8, 2024-11-17 11:44 , Processed in 0.250649 second(s), 42 queries .

Powered by Discuz! X3.2 Licensed

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表