目录
前面谈过一篇只谈page不谈ddr,是站在操作系统的角度来看待物理内存。
实际上page只是一个外衣,只是一段物理地址的代号,alloc_page出来的page也不可能往里写东西,因为page存在vmemmap中,和实际的线性映射区不搭。
下面举例一些page的使用方法
page_to_phys
#define page_to_phys(page) (__pfn_to_phys(page_to_pfn(page)))
#define __page_to_pfn(page) (unsigned long)((page) - vmemmap)
#define __pfn_to_phys(pfn) PFN_PHYS(pfn)
#define PFN_PHYS(x) ((phys_addr_t)(x) << PAGE_SHIFT)
首先根据page找到物理页帧号,然后将页帧号转为某个物理块的起始地址(该地址是4K对齐的)。可以用于获取某page对应的起始物理块。
page_address
对于没有CONFIG_HIGHMEM,page_address等同于page_to_virt(page)
#define page_to_virt(page) ({ \
unsigned long __addr = \
((__page_to_voff(page)) | PAGE_OFFSET); \
__addr = __tag_set(__addr, page_kasan_tag(page)); \
((void *)__addr); \
})
#define __page_to_voff(kaddr) (((u64)(kaddr) & ~VMEMMAP_START) * PAGE_SIZE / sizeof(struct page))
结果就是获取page对应的虚拟地址
__get_free_page
作用是将alloc的page 通过page_address(page)转为虚拟地址
unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)
{
struct page *page;
page = alloc_pages(gfp_mask & ~__GFP_HIGHMEM, order);
if (!page)
return 0;
return (unsigned long) page_address(page);
}
在建立页表项的时候会使用该函数建立缺失的表项,然后转为物理地址填到上一级表项中
mk_pte
#define mk_pte(page,prot) pfn_pte(page_to_pfn(page),prot)
#define __page_to_pfn(page) (unsigned long)((page) - vmemmap)
#define pfn_pte(pfn,prot) \
__pte(__phys_to_pte_val((phys_addr_t)(pfn) << PAGE_SHIFT) | pgprot_val(prot))
在L3表项的建立过程中会使用到。将目标page转为物理地址,加上L3表项属性填充到pte中。
slab
slab中获取的对象是page中空闲freelist的地址,看一下freelist
static struct page *allocate_slab(struct kmem_cache *s, gfp_t flags, int node)
{
struct page *page;
//alloc_page获取page
page = alloc_slab_page(s, alloc_gfp, node, oo);
//page_address获取page的虚拟地址
start = page_address(page);
//freelist指向首地址
page->freelist = start;
for (idx = 0, p = start; idx < page->objects - 1; idx++) {
//不断获取下一个freelist地址,并链接起来
next = p + s->size;
next = setup_object(s, page, next);
set_freepointer(s, p, next);
p = next;
}
set_freepointer(s, p, NULL);
}
在申请page的时候就填充好了该page的freelist的地址
举例:vmalloc表项的建立
static int vmap_pte_range(pmd_t *pmd, unsigned long addr,
unsigned long end, pgprot_t prot, struct page **pages, int *nr)
{
pte_t *pte;
//get_user_page建立L3表项
pte = pte_alloc_kernel(pmd, addr);
do {
struct page *page = pages[*nr];
//mk_pte,填充L3表项
set_pte_at(&init_mm, addr, pte, mk_pte(page, prot));
(*nr)++;
} while (pte++, addr += PAGE_SIZE, addr != end);
return 0;
}
mk_pte的page来自alloc_page
static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
pgprot_t prot, int node)
{
...
for (i = 0; i < area->nr_pages; i++) {
struct page *page;
page = alloc_pages_node(node, alloc_mask|highmem_mask, 0);
area->pages[i] = page;
}
...
}
举例:进程创建时分配内核栈
static unsigned long *alloc_thread_stack_node(struct task_struct *tsk, int node)
{
#ifdef CONFIG_VMAP_STACK
......
#else
struct page *page = alloc_pages_node(node, THREADINFO_GFP,
THREAD_SIZE_ORDER);
//返回的是page的地址
return page ? page_address(page) : NULL;
#endif
}
怎么说呢,page其实就是外衣,不能直接使用根据前期线性映射的关系可以获取到物理地址然后获得虚拟地址。打开MMU的机器只能使用虚拟地址,间接使用了物理地址。通常虚拟地址空间大于实际的物理地址空间,对于没有建立映射的虚拟地址,将触发异常然后修复。
路,边走边修。