您的位置首页>企业动态>

你知道linux kernel内存映射?

导读 大家好,我是极客范的本期栏目编辑小友,现在为大家讲解你知道linux kernel内存映射?问题。目前安卓智能手机市场异常火爆,硬件升级非常

大家好,我是极客范的本期栏目编辑小友,现在为大家讲解你知道linux kernel内存映射?问题。

目前安卓智能手机市场异常火爆,硬件升级非常迅速,而arm cortex A9 1GB DDR似乎已经跟不上主流配置。虽然硬件为王,但我们不禁要问,如此强大的硬件配置是否得到了充分利用?因此,我将在未来分析ARM平台内核的内容。

主体

在linux内存管理中,两个资源非常重要,一个是虚拟地址,另一个是物理地址。听起来像废话。其实内存管理主要围绕这两个概念。如果对如何管理linux内核的虚拟地址和物理地址没有概念,建议浏览文献[2],这是一本很棒的书,简洁明了。文献[1]将讲述更多的实现细节。

本文的主要目的是对内核1GB虚拟地址空间映射有一个大致的了解,包括:

1.1gb内核虚拟地址空间具体用在哪里?

2.它与实际物理地址的映射关系。

3.一些板级相关的宏观定义,我已经整理好了,以供以后参考。根据这些宏定义,还可以很容易地绘制出你的平台的内核虚拟地址空间的映射关系。

首先,示例中的映射计划不一定是最优的,但它是一个实际示例。其实我个人觉得还有很多问题需要讨论。

从下图中,我们可以看到粉色部分0xbf80 0000 ~0xc000 0000是模块和kpmap。从下面的板级宏定义中,我们可以看到模块被放在这个位置,因为它需要与内核代码段在32MB的寻址空间中。不知道KP为什么会放这个空间。这在kpmap HIGHMEM中使用。

橙色0xc000 0000 ~0xe000 0000映射lowmem(低端内存,即区域[正常])。这个映射是一对一的平面映射,这意味着内核初始化这个映射后,页表不会改变。这样就可以避免不断修改页表和刷新TLB(TLB可以看作是页表的硬件缓存。如果被访问的虚拟地址的页表在这个缓存中,那么CPU就不需要访问DDR寻址的页表,可以提高IO效率)。显然,这个地址空间非常珍贵,因为这种映射是高效的。从图中我们可以看到,在512MB的映射空间中,128MB预留给PMEM(安卓专用的连续物理内存管理机制),16MB预留给CP(调制解调器运行空间)。实际可用的低内存仅为360MB左右。

蓝色0xe000 0000 ~0xf000 0000银蛇Hihmem(高端内存,即分区[Hihmem])的一部分。因为示例是1GB DDR,所以需要高端内存映射的部分物理地址空间。

绿色部分0xf000 0000 ~0xffc0 0000是IO映射区。我们知道在内核空间,比如写驱动的时候,需要访问芯片的寄存器(IO空间)。有些IO空间映射通过ioremap动态应用于VMALLOC区域的映射,有些则在系统初始化时通过iotable_init静态映射。在图中,我们可以看到IO静态映射区大约有200MB的未使用空间。这是不是太浪费了?

紫色部分没有花头,这是ARM默认的定义。

下图显示了内核虚拟地址空间和实际物理地址之间的映射关系。

让我们充满激情地玩,看看这个映射有什么问题。

其实我在这个平台上遇到了一个bug,就是用monkey test做压力测试时,系统长时间运行后vmalloc失败。OMG,调用vmalloc会失败,这个时候还有足够的物理内存,是不是很神奇?

[错误日志]系统的图形模块无法用vmalloc申请1MB内存。

[分析]

1.此时首先检查基本的内存信息。从/proc/meminfo可以看出,实际可用物理内存为156MB,此时内存并未耗尽。vmalloc使用的VMALL

OC虚拟地址还剩余22MB,也是够用的。根据vmalloc实现原理,它会通过调用alloc_page()去buddy系统中取一个个孤立的page(即在2^0链表上取page)。page此时是足够多的,为什么会申请失败呢?vmalloc要求虚拟地址是连续的,难道是VMALLOC中没有连续的1MB虚拟地址了?

2. 带着这个问题,我们继续分析/proc/vmallocinfo.

从/proc/vmallocinfo的信息看到,VMALLOC已经用到0xefeff00了,那么最大可用连续空间为0xf0000000 - 0xefeff000 = 0x101000. 还记得我们要申请的内存空间大小吗?没错,是0x1a0000。哇,第一次发现kernel虚拟地址也能耗尽。那为什么从meminfo信息来看还有22MB VMALLOC虚拟地址呢?显然这段虚拟地址空间也产生了大量碎片。

好吧,虚拟地址资源耗尽,我们似乎也没办法了,穷途末路。不过本着研究的精神,我们还得怀疑为什么VMALLOC这段虚拟地址使用这么多,毕竟我们给这段空间规划了256MB。物理内存还有这么多,为什么不直接调用kmalloc或者get_free_pages呢?

3. 继续分析看下此时物理内存分布情况

/proc/buddyinfo可以看到buddy系统总得内存分配状态, 及更多关于碎片管理的信息。

大致了解下pagetypeinfo,kernel会将物理内存分为不同的zone, 在我的平台上上,有zone[Normal]及zone[HighMem]。migrate type是为避免内存碎片而设计的,不明的可以参考文献【1】。从/proc/pagetypeinfo看到我们可以得到的最大连续内存为2^7个page,即512KB。看来此时是满足不了graphic需求,进一步验证的graphic为什么会大量使用vmalloc.

/proc/buddyinfo信息。

4. 结论

根据上面分析,graphic通过get_free_pages()向kernel的buddy系统申请连续内存,经过一段时间,buddy系统产生了大量碎片,graphic无法获取连续的物理内存,因此通过vmalloc想从buddy系统申请不连续的内存,不幸的是VMALLOC的虚拟地址空间耗尽,尽管这是还有大量物理内存,vmalloc申请失败。

5. 从新审视内存映射

这里一个问题就是lowmem的规划空间太小了,vmalloc默认会从zone[HighMem]申请内存,这样很容易在highmem产生碎片。看到最开始我们kernel虚拟映射图了吗?我们不是有200MB的虚拟空间没有使用吗?如果把它mapping给lowmem多好啊。

下面我对这段映射做了修改。最大的变化就是lowmem从512MB增加到了720MB。200MB未使用的虚拟地址空间得到了充分利用。

修改后,我们再看看buddy信息吧,最大可申请的连续内存为2^15个page=128MB。这样的规划也增加内存利用效率。

下面列表是板级相关的一些宏定义,这些宏定义决定了如何规划内核虚拟地址。现在一般也没什么机会从零开始bringup一块新的芯片,因此这些定义大家可能不会关注。不过在研究内存规划时,这些定义还是非常重要的,我将它们整理出来也是为了日后方便查阅。大家也可以试着根据自己的板子填写这些宏定义,这样整个内核空间映射视图就会展现出来。

Board specific macro definition

Refer to [DocumentaTIon/arm/PorTIng]

Decompressor Symbols

Macro name

descripTIon

example

ZTEXTADDR

[arch/arm/boot/compressed/Makefile]

Start address of decompressor.  There's no point in talking about virtual or physical addresses here, since the MMU will be off at the TIme when you call the decompressor code.  You normally call the kernel at this address to start it booting.  This doesn't have to be located in RAM, it can be in flash or other read-only or read-write addressable medium.

0x0

ZTEXTADDR        := $(CONFIG_ZBOOT_ROM_TEXT)

ONFIG_ZBOOT_ROM_TEXT=0x0

ZBSSADDR

[arch/arm/boot/compressed/Makefile]

Start address of zero-initialised work area for the decompressor. This must be pointing at RAM.  The decompressor will zero initialize this for you.  Again, the MMU will be off.

0x0

ZBSSADDR   := $(CONFIG_ZBOOT_ROM_BSS)

CONFIG_ZBOOT_ROM_BSS=0x0

ZRELADDR

[arch/arm/boot/Makefile]

This is the address where the decompressed kernel will be written, and eventually executed.  The following constraint must be valid:

__virt_to_phys(TEXTADDR) == ZRELADDR

The initial part of the kernel is carefully coded to be position independent.

Note: the following conditions must always be true:

ZRELADDR == virt_to_phys(PAGE_OFFSET + TEXT_OFFSET)

0x81088000

ZRELADDR    := $(zreladdr-y)

zreladdr-y       := $(__ZRELADDR)

__ZRELADDR = TEXT_OFFSET + 0x80000000

[arch/arm/mach-pxa/Makefile.boot]

INITRD_PHYS

Physical address to place the initial RAM disk.  Only relevant if you are using the bootpImage stuff (which only works on the old struct param_struct).

INITRD_PHYS must be in RAM

Not defined

INITRD_VIRT

Virtual address of the initial RAM disk.  The following constraint must be valid:

__virt_to_phys(INITRD_VIRT) == INITRD_PHYS

Not defined

PARAMS_PHYS

Physical address of the struct param_struct or tag list, giving the kernel various parameters about its execution environment.

PARAMS_PHYS must be within 4MB of ZRELADDR

Not defined

Kernel Symbols

PHYS_OFFSET

[arch/arm/include/asm/memory.h]

Physical start address of the first bank of RAM.

#define PHYS_OFFSET      PLAT_PHYS_OFFSET

#define PLAT_PHYS_OFFSET    UL(0x80000000)

[arch/arm/mach-pxa/include/mach/memory.h]

PAGE_OFFSET

[arch/arm/include/asm/memory.h]

Virtual start address of the first bank of RAM.  During the kernel boot phase, virtual address PAGE_OFFSET will be mapped to physical address PHYS_OFFSET, along with any other mappings you supply. This should be the same value as TASK_SIZE.

CONFIG_PAGE_OFFSET

=0xC0000000

TASK_SIZE

[arch/arm/include/asm/memory.h]

The maximum size of a user process in bytes.  Since user space always starts at zero, this is the maximum address that a user process can access+1.  The user space stack grows down from this address.

Any virtual address below TASK_SIZE is deemed to be user process area, and therefore managed dynamically on a process by process basis by the kernel.  I'll call this the user segment.

Anything above TASK_SIZE is common to all processes.  I'll call this the kernel segment.

(In other words, you can't put IO mappings below TASK_SIZE, and hence PAGE_OFFSET).

CONFIG_PAGE_OFFSET

-0x01000000

=0xBF000000

TASK_UNMAPPED_BASE

[arch/arm/include/asm/memory.h]

the lower boundary of the mmap VM area

CONFIG_PAGE_OFFSET/3

=0x40000000

MODULES_VADDR

[arch/arm/include/asm/memory.h]

The module space lives between the addresses given by TASK_SIZE and PAGE_OFFSET - it must be within 32MB of the kernel text.

TEXT_OFFSET does not allow to use 16MB modules area as ARM32 branches to kernel may go out of range taking into account the kernel .text size

PAGE_OFFSET

- 8*1024*1024

=0x0XBF800000

MODULES_END

[arch/arm/include/asm/memory.h]

The highmem pkmap virtual space shares the end of the module area.

0XBFE00000

#ifdef CONFIG_HIGHMEM

#define MODULES_END           (PAGE_OFFSET - PMD_SIZE)

#else

#define MODULES_END           (PAGE_OFFSET)

#endif

TEXTADDR

Virtual start address of kernel, normally PAGE_OFFSET + 0x8000.

This is where the kernel image ends up.  With the latest kernels, it must be located at 32768 bytes into a 128MB region.  Previous kernels placed a restriction of 256MB here.

DATAADDR

Virtual address for the kernel data segment.  Must not be defined when using the decompressor.

VMALLOC_START

VMALLOC_END

[arch/arm/mach-pxa/include/mach/vmalloc.h]

Virtual addresses bounding the vmalloc() area.  There must not be any static mappings in this area; vmalloc will overwrite them. The addresses must also be in the kernel segment (see above). Normally, the vmalloc() area starts VMALLOC_OFFSET bytes above the last virtual RAM address (found using variable high_memory).

#define VMALLOC_END       (0xf0000000UL)

The default vmalloc size is 128MB.

vmalloc_min = (VMALLOC_END - SZ_128M);

[defined in arch/arm/mm/mmu.c]

If vmalloc is configured passed by OSL, then it’s redefined.

early_param("vmalloc", early_vmalloc);

[defined in arch/arm/mm/mmu.c]

VMALLOC_OFFSET

[arch/arm/include/asm/pgtable.h]

Offset normally incorporated into VMALLOC_START to provide a hole between virtual RAM and the vmalloc area.  We do this to allow out of bounds memory accesses (eg, something writing off the end of the mapped memory map) to be caught.  Normally set to 8MB.

#define VMALLOC_OFFSET               (8*1024*1024)

CONSISTENT_DMA_SIZE

CONSISTENT_BASE

CONSISTENT_END

[arch/arm/include/asm/memory.h]

Size of DMA-consistent memory region.  Must be multiple of 2M, between 2MB and 14MB inclusive.

CONSISTENT_DMA_SIZE = 2MB

CONSISTENT_BASE = 0XFFC00000

CONSISTENT_END = 0XFFE00000

FIXADDR_START

FIXADDR_TOP

FIXADDR_SIZE

[arch/arm/include/asm/fixmap.h]

fixed virtual addresses

#define FIXADDR_START          0xfff00000UL

#define FIXADDR_TOP              0xfffe0000UL

#define FIXADDR_SIZE              (FIXADDR_TOP - FIXADDR_START)

PKMAP_BASE

[arch/arm/include/asm/highmen.h]

0XBFE00000

#define PKMAP_BASE               (PAGE_OFFSET - PMD_SIZE)

 

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。