page结构体关键成员
内核中使用page结构体来描述物理内存,每个物理页就对应一个page结构体来描述,所以page结构体占用的内存大小是与系统物理内存大小成正比的。物理内存越大,用于描述物理页的page结构体就越多,占用的内存也就越大,因此为了减少内存的占用,page结构体大量使用了union联合体结构。如下我们只列举了本文下面要讲述的一些page成员。
include/linux/mm_types.h:
struct page {
unsigned long flags; /* Atomic flags, some possibly
* updated asynchronously */
union {
struct address_space *mapping; /* If low bit clear, points to
* inode address_space, or NULL.
* If page mapped as anonymous
* memory, low bit is set, and
* it points to anon_vma object:
* see PAGE_MAPPING_ANON below.
*/
void *s_mem; /* slab first object */
atomic_t compound_mapcount; /* first tail page */
/* page_deferred_list().next -- second tail page */
};
...
atomic_t _refcount;
...
/*
* 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
*/
void *virtual; /* Kernel virtual address (NULL if
not kmapped, ie. highmem) */
...
}
/*
* The struct page can be forced to be double word aligned so that atomic ops
* on double words work. The SLUB allocator can make use of such a feature.
*/
#ifdef CONFIG_HAVE_ALIGNED_STRUCT_PAGE
__aligned(2 * sizeof(unsigned long))
#endif
;
- flags
描述页的状态 - _refcount
描述页被引用计数 - mapping
这个结构体很关键,如果低位为0,它用于描述该页被映射的地址空间结构体,指向一个文件系统页缓存结构体struct address_space;如果低位为1,那么它指向一个匿名映射结构体struct anon_vma。
因此这个mapping可能是一个(struct address_space *)类型的指针,也可能是一个(struct anon_vma *)类型的指针。 - virtual
注意这个virtual表示的是Kernel virtual address,内核虚拟地址而不是用户空间虚拟地址,表示物理页对应的内核虚拟地址,如果是高端内存,可能并没有kmap到内核虚拟地址,此时为NULL。
一个物理页可能同时被映射到内核虚拟地址空间和用户虚拟地址空间,并不冲突,因为页表是通过虚拟地址转换为物理地址的,所以可能存在多对一的关系。
mapping怎么区分匿名页映射的
内核中的映射分为两种,一种为匿名映射,一种为文件映射,匿名映射对应的结构体为struct anon_vma
;文件映射会对应页缓存,结构体为struct address_space
。由于地址对齐的关系,每个 struct address_space
和 struct anon_vma
结构体都不会存放在奇数地址上,所以假如不做任何处理,mapping正常情况下的最低位肯定是0,那么由于page结构体在内存中会大量存在,为了充分利用每个bit的空间,这里使用了如下操作:
- 保存
struct anon_vma
地址到mapping
page->mapping = (void *)&anon_vma + 1;
- 从mapping获取
struct anon_vma
地址
struct anon_vma * anon_vma = (struct anon_vma *)(page->mapping - 1);
通过保存struct anon_vma
结构体地址时加1操作,提取struct anon_vma
结构体地址时减1操作,从而可以利用在mapping中的最低位来区分当前page是否为匿名映射:
#define PAGE_MAPPING_ANON 0x1
static __always_inline int PageMappingFlags(struct page *page)
{
return ((unsigned long)page->mapping & PAGE_MAPPING_FLAGS) != 0;
}
static __always_inline int PageAnon(struct page *page)
{
page = compound_head(page);
return ((unsigned long)page->mapping & PAGE_MAPPING_ANON) != 0;
}
这里可以看到通过判断mapping成员的最低位来区分是否为匿名映射。
Linux 4.14 source code