啟動過程內存check和vmalloc大小設置

ARM 533瀏覽

Linux內核啟動過程中,內核根據系統配置來設置虛擬地址空間的布局,如PAGE_OFFSET的起始地址,PHYS_OFFSET等。對于宏PHYS_OFFSET來說,其描述的是物理內存的起始地址,一般由硬件給出。如下面一些設置:

ARM:

arch/arm/include/asm/memory.h

#define PLAT_PHYS_OFFSET?? ?UL(CONFIG_PHYS_OFFSET)

#if defined(__virt_to_phys)
#define PHYS_OFFSET?? ?PLAT_PHYS_OFFSET
#define PHYS_PFN_OFFSET?? ?((unsigned long)(PHYS_OFFSET >> PAGE_SHIFT))

#define virt_to_pfn(kaddr) (__pa(kaddr) >> PAGE_SHIFT)

#elif defined(CONFIG_ARM_PATCH_PHYS_VIRT)

/*
?* Constants used to force the right instruction encodings and shifts
?* so that all we need to do is modify the 8-bit constant field.
?*/
#define __PV_BITS_31_24?? ?0x81000000
#define __PV_BITS_7_0?? ?0x81

extern unsigned long __pv_phys_pfn_offset;
extern u64 __pv_offset;
extern void fixup_pv_table(const void *, unsigned long);
extern const void *__pv_table_begin, *__pv_table_end;

#define PHYS_OFFSET?? ?((phys_addr_t)__pv_phys_pfn_offset << PAGE_SHIFT)
#define PHYS_PFN_OFFSET?? ?(__pv_phys_pfn_offset)

#define virt_to_pfn(kaddr) 
?? ?((((unsigned long)(kaddr) - PAGE_OFFSET) >> PAGE_SHIFT) + 
?? ? PHYS_PFN_OFFSET)

#define __pv_stub(from,to,instr,type)?? ??? ??? ?
?? ?__asm__("@ __pv_stubn"?? ??? ??? ??? ?
?? ?"1:?? ?" instr "?? ?%0, %1, %2n"?? ??? ?
?? ?"?? ?.pushsection .pv_table,"a"n"?? ??? ?
?? ?"?? ?.long?? ?1bn"?? ??? ??? ??? ?
?? ?"?? ?.popsectionn"?? ??? ??? ??? ?
?? ?: "=r" (to)?? ??? ??? ??? ??? ?
?? ?: "r" (from), "I" (type))

#define __pv_stub_mov_hi(t)?? ??? ??? ??? ?
?? ?__asm__ volatile("@ __pv_stub_movn"?? ??? ?
?? ?"1:?? ?mov?? ?%R0, %1n"?? ??? ??? ?
?? ?"?? ?.pushsection .pv_table,"a"n"?? ??? ?
?? ?"?? ?.long?? ?1bn"?? ??? ??? ??? ?
?? ?"?? ?.popsectionn"?? ??? ??? ??? ?
?? ?: "=r" (t)?? ??? ??? ??? ??? ?
?? ?: "I" (__PV_BITS_7_0))

#define __pv_add_carry_stub(x, y)?? ??? ??? ?
?? ?__asm__ volatile("@ __pv_add_carry_stubn"?? ?
?? ?"1:?? ?adds?? ?%Q0, %1, %2n"?? ??? ??? ?
?? ?"?? ?adc?? ?%R0, %R0, #0n"?? ??? ??? ?
?? ?"?? ?.pushsection .pv_table,"a"n"?? ??? ?
?? ?"?? ?.long?? ?1bn"?? ??? ??? ??? ?
?? ?"?? ?.popsectionn"?? ??? ??? ??? ?
?? ?: "+r" (y)?? ??? ??? ??? ??? ?
?? ?: "r" (x), "I" (__PV_BITS_31_24)?? ??? ?
?? ?: "cc")

static inline phys_addr_t __virt_to_phys(unsigned long x)
{
?? ?phys_addr_t t;

?? ?if (sizeof(phys_addr_t) == 4) {
?? ??? ?__pv_stub(x, t, "add", __PV_BITS_31_24);
?? ?} else {
?? ??? ?__pv_stub_mov_hi(t);
?? ??? ?__pv_add_carry_stub(x, t);
?? ?}
?? ?return t;
}

static inline unsigned long __phys_to_virt(phys_addr_t x)
{
?? ?unsigned long t;

?? ?/*
?? ? * 'unsigned long' cast discard upper word when
?? ? * phys_addr_t is 64 bit, and makes sure that inline
?? ? * assembler expression receives 32 bit argument
?? ? * in place where 'r' 32 bit operand is expected.
?? ? */
?? ?__pv_stub((unsigned long) x, t, "sub", __PV_BITS_31_24);
?? ?return t;
}

#else

#define PHYS_OFFSET?? ?PLAT_PHYS_OFFSET
#define PHYS_PFN_OFFSET?? ?((unsigned long)(PHYS_OFFSET >> PAGE_SHIFT))

static inline phys_addr_t __virt_to_phys(unsigned long x)
{
?? ?return (phys_addr_t)x - PAGE_OFFSET + PHYS_OFFSET;
}

static inline unsigned long __phys_to_virt(phys_addr_t x)
{
?? ?return x - PHYS_OFFSET + PAGE_OFFSET;
}

#define virt_to_pfn(kaddr) 
?? ?((((unsigned long)(kaddr) - PAGE_OFFSET) >> PAGE_SHIFT) + 
?? ? PHYS_PFN_OFFSET)

#endif

可以認為PHYS_OFFSET是由CONFIG_PHYS_OFFSET來設置的。對于上面不同的定義,其影響物理地址到虛擬地址的轉化,但并無本質的區別。

另外一個地址PAGE_OFFSET也是非常重要的,其也在arch/arm/include/asm/memory.h中給出,如下:

#define UL(x) _AC(x, UL)

/* PAGE_OFFSET - the virtual address of the start of the kernel image */
#define PAGE_OFFSET?? ??? ?UL(CONFIG_PAGE_OFFSET)

注意PAGE_OFFSET? 是內核虛擬地址開始的地方。通常來說,PAGE_OFFSET? 會被直接映射到PHYS_OFFSET處。

我們本篇主要是描述虛擬的布局,可以看到,PAGE_OFFSET之上是內核虛擬地址,而PAGE_OFFSET之下是進程的用戶空間地址。用戶空間較為重要一個宏TASK_SIZE,如下:

#define TASK_SIZE?? ??? ?(UL(CONFIG_PAGE_OFFSET) - UL(SZ_16M))
#define TASK_UNMAPPED_BASE?? ?ALIGN(TASK_SIZE / 3, SZ_16M)

也就是說,用戶空間到內核空間有個SZ_16M大小的洞。

需要注意的是TASK_UNMAPPED_BASE,其給出了mmap類函數對虛擬地址空間映射時使用的最小虛擬地址。如果CONFIG_PAGE_OFFSET為0xC00 00000(3G) ,那么TASK_SIZE為0XBF0 00000.

TASK_SIZE / 3 = 0XBF0 00000/3 =0x3FAAAAAA

再對SZ_16M對齊,所以TASK_UNMAPPED_BASE為0x3FAAAAB0。

所以,使用mmap類函數,虛擬地址最小為3FAAAAB0,最大為TASK_SIZE。

通常PAGE_OFFSET被映射到物理地址起始處,即PHYS_OFFSET給出的地址。這樣,以PHYS_OFFSET為開始地址,至于結束地址則不同的平臺定義并不相同,但是有一點是相同的,即有塊連續的物理內存被直接映射到虛擬地址空間,也就是我們說的低端內存直接映射。至于這塊低端內存linux內核是如何計算的,我們下文會詳細的論述,這里與低端內存對應的是high_memory,即高端內存,從PHYS_OFFSET開始,到高端內存,我們用LOW_BOUNCE_HIGH做邊界。max_low_pfn是給出的低端內存的最大頁幀號,而min_low_pfn則是最小的頁幀編號,由于我們從PHYS_OFFSET開始,其一般為0,所以min_low_pfn一般為0.如果我們把低端內存限制在512M,則max_low_pfn為512M/4k
= 0x20000 = 131072,。內核還有一個max_pfn,其對應整個內存對應的最大的頁幀號,如果內存為

mem= 1G, max_pfn= 2^18 =262144

mem= 2G, max_pfn=2^19 =524288

mem= 3G, max_pfn=0xC0000 = 786432

mem= 4G, max_pfn=2^20 =1048576

mem= 8G, max_pfn=2^21 =2097152

mem=16G, max_pfn=2^22= 4194304

對于低端內存區大小和高端內存區設置由平臺來配置,對于high_memory端內存,其之上對應vmalloc區域,Linux中主要配置如下:

?arch/arm/include/asm/pgtable.h

#define VMALLOC_OFFSET?? ??? ?(8*1024*1024)
#define VMALLOC_START?? ??? ?(((unsigned long)high_memory + VMALLOC_OFFSET) & ~(VMALLOC_OFFSET-1))
#define VMALLOC_END?? ??? ?0xff000000UL // 4G - 16M == 4080M

可以看到,對于VMALLOC區域,其從高端內存high_memory附近開始,這里有8M的空洞。然后到VMALLOC_END處,可以看到,VMALLOC_END到0xFFFFFFFF還有16M的空間,這些一般被特殊的設備使用。

一般來說,VMALLOC區大小可以由內核的命令行參數來指定,如下函數分析命令行參數,設置VMALLOC區大小:

arch/arm/mm/mmu.c

static void *? vmalloc_min =
?? ?(void *)(VMALLOC_END - (240 << 20) - VMALLOC_OFFSET);

/*
?* vmalloc=size forces the vmalloc area to be exactly 'size'
?* bytes. This can be used to increase (or decrease) the vmalloc
?* area - the default is 240m. vmalloc
?*/
static int __init early_vmalloc(char *arg)
{
?? ?unsigned long vmalloc_reserve = memparse(arg, NULL);

?? ?if (vmalloc_reserve < SZ_16M) {
?? ??? ?vmalloc_reserve = SZ_16M;
?? ??? ?pr_warn("vmalloc area too small, limiting to %luMBn",
?? ??? ??? ?vmalloc_reserve >> 20);
?? ?}

?? ?if (vmalloc_reserve > VMALLOC_END - (PAGE_OFFSET + SZ_32M)) {?
?? ??? ?vmalloc_reserve = VMALLOC_END - (PAGE_OFFSET + SZ_32M);?
?? ??? ?pr_warn("vmalloc area is too big, limiting to %luMBn",
?? ??? ??? ?vmalloc_reserve >> 20);
?? ?}

?? ?vmalloc_min = (void *)(VMALLOC_END - vmalloc_reserve);?
?? ?return 0;
}
early_param("vmalloc", early_vmalloc);

這段代碼接受VMALLOC大小在16M和976M之間。

如果命令行提供vmalloc大小,假若為240M,則vmalloc_min為0xF00 00000

如果命令行沒有提供,內核采用默認配置,則vmalloc_min為0xEF8 00000

在了解上面內存布局主要參數定義后,我們再來詳細的看看內核啟動過程中主要函數處理,函數sanity_check_meminfo()對主要的的內存區檢查。函數實現如下:

void __init sanity_check_meminfo(void)
{
?? ?phys_addr_t memblock_limit = 0;
?? ?int highmem = 0;
?? ?phys_addr_t vmalloc_limit = __pa(vmalloc_min - 1) + 1;
?? ?struct memblock_region *reg;
?? ?bool should_use_highmem = false;
//了解內存區情況
?? ?for_each_memblock(memory, reg) {
?? ??? ?phys_addr_t block_start = reg->base;
?? ??? ?phys_addr_t block_end = reg->base + reg->size;
?? ??? ?phys_addr_t size_limit = reg->size;

?? ??? ?if (reg->base >= vmalloc_limit)
?? ??? ??? ?highmem = 1;
?? ??? ?else
?? ??? ??? ?size_limit = vmalloc_limit - reg->base;

?? ??? ?if (!IS_ENABLED(CONFIG_HIGHMEM) || cache_is_vipt_aliasing()) {

?? ??? ??? ?if (highmem) {
?? ??? ??? ??? ?pr_notice("Ignoring RAM at %pa-%pa (!CONFIG_HIGHMEM)n",
?? ??? ??? ??? ??? ?? &block_start, &block_end);
?? ??? ??? ??? ?memblock_remove(reg->base, reg->size);
?? ??? ??? ??? ?should_use_highmem = true;
?? ??? ??? ??? ?continue;
?? ??? ??? ?}

?? ??? ??? ?if (reg->size > size_limit) {
?? ??? ??? ??? ?phys_addr_t overlap_size = reg->size - size_limit;

?? ??? ??? ??? ?pr_notice("Truncating RAM at %pa-%pa to -%pa",
?? ??? ??? ??? ??? ?? &block_start, &block_end, &vmalloc_limit);
?? ??? ??? ??? ?memblock_remove(vmalloc_limit, overlap_size);
?? ??? ??? ??? ?block_end = vmalloc_limit;
?? ??? ??? ??? ?should_use_highmem = true;
?? ??? ??? ?}
?? ??? ?}

?? ??? ?if (!highmem) {
?? ??? ??? ?if (block_end > arm_lowmem_limit) {
?? ??? ??? ??? ?if (reg->size > size_limit)
?? ??? ??? ??? ??? ?arm_lowmem_limit = vmalloc_limit;
?? ??? ??? ??? ?else
?? ??? ??? ??? ??? ?arm_lowmem_limit = block_end;
?? ??? ??? ?}

?? ??? ??? ?/*
?? ??? ??? ? * Find the first non-pmd-aligned page, and point
?? ??? ??? ? * memblock_limit at it. This relies on rounding the
?? ??? ??? ? * limit down to be pmd-aligned, which happens at the
?? ??? ??? ? * end of this function.
?? ??? ??? ? *
?? ??? ??? ? * With this algorithm, the start or end of almost any
?? ??? ??? ? * bank can be non-pmd-aligned. The only exception is
?? ??? ??? ? * that the start of the bank 0 must be section-
?? ??? ??? ? * aligned, since otherwise memory would need to be
?? ??? ??? ? * allocated when mapping the start of bank 0, which
?? ??? ??? ? * occurs before any free memory is mapped.
?? ??? ??? ? */
?? ??? ??? ?if (!memblock_limit) {
?? ??? ??? ??? ?if (!IS_ALIGNED(block_start, PMD_SIZE))
?? ??? ??? ??? ??? ?memblock_limit = block_start;
?? ??? ??? ??? ?else if (!IS_ALIGNED(block_end, PMD_SIZE))
?? ??? ??? ??? ??? ?memblock_limit = arm_lowmem_limit;
?? ??? ??? ?}

?? ??? ?}
?? ?}

?? ?if (should_use_highmem)
?? ??? ?pr_notice("Consider using a HIGHMEM enabled kernel.n");

?? ?high_memory = __va(arm_lowmem_limit - 1) + 1;

?? ?/*
?? ? * Round the memblock limit down to a pmd size.? This
?? ? * helps to ensure that we will allocate memory from the
?? ? * last full pmd, which should be mapped.
?? ? */
?? ?if (memblock_limit)
?? ??? ?memblock_limit = round_down(memblock_limit, PMD_SIZE);
?? ?if (!memblock_limit)
?? ??? ?memblock_limit = arm_lowmem_limit;

?? ?memblock_set_current_limit(memblock_limit);
}

在了解此函數之前,我們熟悉下面幾個函數

int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)

int __init_memblock memblock_add_node(phys_addr_t base, phys_addr_t size, int nid)

在系統啟動過程中,內核會通過上面函數添加有效內存。內核有幾處添加處理:

一. early_init_dt_add_memory_arch() --memblock_add()

二. arm_add_memory() --> memblock_add()

三. 體系決定增加,直接調用函數memblock_add()

函數sanity_check_meminfo()作用很簡單,對內存大小分析后設置high_memory和arm_lowmem_limit。

轉載至:http://blog.csdn.net/sunlei0625/article/details/50465713

七星彩走势图2元网官网