1.用户内存空间和内核内存空间:
32位的CPU可访问的物理地址空间为4GB(2^32).LINUX内核中把这4G典型的划分是:前3GB用于用户空间,最后1GB用于内核空间.如下图所示:
其中,我们比较关注的内核空间部分又被划分为三个区:
1).ZONE_DMA(0-16 MB):包含 ISA/PCI设备需要的低端物理内存区域中的内存范围;
2).ZONE_NORMAL(16-896 MB)内核大部分关于内存的操作都位于此区域,它里面记录的是逻辑地址,其地址与实质的物理地址是线性关系.所谓的低端内存;
3).ZONE_HIGHMEM(896 MB 以及更高的内存):是内核遍历所有物理内存的一种"可能手段",它的存在是补偿ZONE_NORMAL线性关系导致只能部分内存空间访问的不足.因此,它是非线性的.见[附1:]
2. 地址:
关于内存所涉及的"地址"主要有三种:物理地址、逻辑地址、虚拟地址(线性地址).
物理地址:
物理内存块(如RAM、DDR2)对应的起始位置及区域,以及CPU内部控制器寄存器的值.它是最终地址的结果.
比如说,S3C2440外挂了一个64M的SDRAM,其起始地址是:0x3000 0000 ~ 0x34000000.这里的0x3000 0000 和0x3400 0000及其间的区域的值就是物理地址.其对应的空间就是物理内存空间.
逻辑地址:
是指由程序产生的地址数值.例如,在C语言指针编程中,可以读取指针变量本身值(&操作),实际上这个值就是逻辑地址,它是相对于你当前进程数据段的地址,不和绝对物理地址相干.这也是我们程序中涉及最多的一种地址.它和物理地址只是相差了一个偏移量.见[附1:]低端内存部分内容.
在反汇编中,我们最经常接触到逻辑地址.比如说,下面的程序:
#include <stdlib.h>
#include <stdio.h>
void func(void)
{
;
}
int main(int argc,char **argv)
{
func();
printf("Hello World!\n");
return 0;
}
反汇编出来的部分代码:
其中,第一列是代码的运行地址,也就是逻辑地址;第二列是机器码.计算机本身也只关注这两个东东.它们一个是决定了内存的位置,一个是决定此位置上内存的内容.也就是说,在内存位置00008384这个位置上存放的内容是e52db004.后面的汇编只是给人类看的,计算机不懂.
虚拟地址(线性地址):
表征一个CPU实际寻址的能力范围,比如32bit的CPU,它的虚拟地址空间为4G.物理地址空间只是虚拟地址空间的一个子集.就算我们在支持虚拟内存的系统里面看到某个地址值落在物理地址空间范围内,我们也是不能简单地理解成是物理地址--你看到的,不一定是真的.
[附1:]
低端内存:
在分配给内核的1G空间里面,其低于896M以下的就是低端内存. 也就是我们实际编译中接触最多的一个内存区间.它包括前面三个ZONE(ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM)中的ZONE_DMA、ZONE_NORMAL.在启用MMU的情况下,此区间里面的地址就是逻辑地址--和物理地址保持着永久性的线性映射关系,即它和物理地址之间只相差了一个固定的偏移量--|PAGE_OFFSET - PHYS_OFFSET|.如果PHYS_OFFSET为0,即这个固定的偏移量为PAGE_OFFSET.
比如说,S3C2440的SDRAM是接在BANK6上,其PHYS_OFFSET为0x3000 0000.如下:
/* arch/arm/mach-s3c2410/include/mach/memory.h
* from arch/arm/mach-rpc/include/mach/memory.h
*
* Copyright (C) 1996,1997,1998 Russell King.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#ifndef __ASM_ARCH_MEMORY_H
#define __ASM_ARCH_MEMORY_H
#define PHYS_OFFSET UL(0x30000000)
#endif
ARM平台的PAGE_OFFSET的定义则位于相应的平台头文件,如下:
/*
* arch/arm/include/asm/memory.h
*
* Copyright (C) 2000-2002 Russell King
* modification for nommu, Hyok S. Choi, 2004
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* Note: this file should not be included by non-asm/.h files
*/
#ifndef __ASM_ARM_MEMORY_H
#define __ASM_ARM_MEMORY_H
#include <linux/compiler.h>
#include <linux/const.h>
#include <mach/memory.h>
#include <asm/sizes.h>
/*
* Allow for constants defined here to be used from assembly code
* by prepending the UL suffix only with actual C code compilation.
*/
#define UL(x) _AC(x, UL)
#ifdef CONFIG_MMU
/*
* PAGE_OFFSET - the virtual address of the start of the kernel image
* TASK_SIZE - the maximum size of a user space task.
* TASK_UNMAPPED_BASE - the lower boundary of the mmap VM area
*/
#define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)
CONFIG_PAGE_OFFSET即是在配置文件里面指定的.如mini2440平台的配置文件arch/arm/configs/mini2440_defconfig见到下面字码:
#
# Kernel Features
#
CONFIG_VMSPLIT_3G=y
# CONFIG_VMSPLIT_2G is not set
# CONFIG_VMSPLIT_1G is not set
CONFIG_PAGE_OFFSET=0xC0000000
# CONFIG_PREEMPT is not set
CONFIG_HZ=200
CONFIG_AEABI=y
因此,对于S3C2440平台,其偏移量为|PAGE_OFFSET - PHYS_OFFSET| = 0x9000 0000.
下面用一个简单的示例来演示一个低端内存区域的逻辑地址值对应的物理地址值是多少?
首先,我们要知道内核映象是如何被排布建立起来的?以ARM平台为例,见arch/arm/kernel/vmlinux.lds下面字样:
OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
. = 0xC0000000 + 0x00008000;
.init : { /* Init code and data
因此,我们便确定vmlinux便是以地址0xC0008000为基地址往上布局的.反汇编vmlinux可以看到:
vmlinux: file format elf32-littlearm
Disassembly of section .init:
c0008000 <stext>:
c0008000: e321f0d3 msr CPSR_c, #211 ; 0xd3
c0008004: ee109f10 mrc 15, 0, r9, cr0, cr0, {0}
c0008008: eb0000ba bl c00082f8 <__lookup_processor_type>
也就是说,我们在内核使用的所有的地址,必须是大于等于0xc0008000.比如我们要操作低端内存区域的某地址0xc0008004,这个地址值对应的物理地址值为:
0xc000 8004 - PAGE_OFFSET + PHYS_OFFSET = 0xc000 8004 - 0xc000 0000 + 0x3000 0000 = 0x3000 8004
也就是说,物理内存[0x3000 0000,0x3000,8000]这段区间是预留出来的,内核运行后是没办法再去访问的.
内核为我们实现虚拟地址和物理地址的相互转换也提供了相应的API:
#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)
高端内存:
在分配给内核的1G空间里面,其高于896M以上的内存就是高端内存.使CPU能访问所有内存空间提供一种可能的手段,它相当于一个”中介”,属于”二级运算”.比如说内核试图访问高于896MB的内存空间时,会在高端地址空间范围内找一段相应大小空闲的逻辑地址空间,作为桥梁借用.借用这段逻辑地址空间,建立映射到想访问的那段物理内存,用完后归还.这样别人也可以再次借用这段高端地址空间访问其他物理内存,因此,很明显,这是非线性映射的.这样一来,虽然空间上得到了扩充,但是时间效率上就受到了一定的制约.因为它的内部工作比较烦琐,至少多了下面的步骤:
申请高端内存的地址空间-->建立申请到的高端内存地址空间到物理地址的映射-->释放申请的高端内存地址空间.
示意图如下:
红色部分位于高端内存空间,它的可以指向物理空间的橙黄部分,也可以指向物理空间紫色部分.应该指向物理空间哪一部分呢?这就是通过内存管理算法建立页表的过程.