内存管理一之基础

   本文主要以32位机器为准。

1.0 虚拟地址、物理地址、逻辑地址、线性地址

虚拟地址又叫线性地址。linux没有采用分段机制,所以逻辑地址和虚拟地址(线性地址)(在用户态,内核态逻辑地址专指下文说的线性偏移前的地址)是一个概念。物理地址则是内存的实际地址。

内核的虚拟地址和物理地址,大部分只差一个线性偏移量。用户空间的虚拟地址和物理地址则采用了多级页表进行映射,但仍称之为线性地址。

1.1 虚拟内存的作用

1.扩展实际有限的物理内存,当然这种扩展是虚拟的,比如物理内存512M,对于一个需要1G空间的进程来说,照样可以运行。 

2. 使得进程中的数据空间增大,增大到多少与硬件有关,对于一个32位的芯片,进程中的数据空间可以为4G[2^32],对于64位的芯片则支持2^64大小 的空间。这一点使得进程自身可操作的空间大大增加。

通俗来讲,虚拟内存的管理的核心是解决如何在小的物理内存中运行更大程序的问题。

在Linux中,解决这个问题的关键是一个叫做pagetable[PT页面转换表]的结构。Linux把物理内存分为了固定统一大小的块,称为page[页],一般为4KB,并且每个页都有一个编号 [page frame number]。这样一个512M大小的内存将包括128K个页。这种方式称为paging,使得操作系统对内存的管理更方便。pagetable的作用就是将进程操作的地址[虚拟地址]转换成物理地址

其原理很简单,如下:

用一个32位芯片的系统为例[64位同理],运行的每个进程的可操作数据空间为2^32,即2^20个页,设其物理内存为512M,则物理页有2^17个,现在就说明如何将2^20个页放入2^17个页中运行。我们把进程操作的地址分为两部分,第一部分为地址的高20位,第二部分为后12位,这样很容易将第一部分理解为虚拟页标号,第二部分理解为在页中的offset。那么现在我们只需将虚拟页标号对应到物理页号即可,这个对应就是page table的工作,在这个例子中pagetable包括了2^20个记录,每个记录有两部分组成:20位的虚拟标号和17位的物理标号,这样CPU用进程地址的第一部分作为索引找到对应的17位物理标号,与地址的第二部分一起便组成一个29位的地址,这个地址就是要找的物理地址。因为物理页少于虚拟页,所以pagetable中的有些记录的后17位是空的或无效的。

利用这个方法,使得运行的进程无需知道自己操作的地址是虚拟的,和运行在一个真实的大物理内存中效果是一样的。

可以看出,在进程的运行过程中,page table必须一直保存在内存中,在上面的例子中,我们把虚拟地址分了2层,pagetable有2^20个记录,需要1M左右的空间,为了节省空间我们可以将地址分为3层,第一层10位,需要1K左右的空间,第二层10位,需要1K左右的空间,第三层12位,这样在一段时间内只需要2K的空间保存pagetable。实际上,Alpha的芯片采用的就是这种3层的分法,Intel的芯片采用的2层的分法。


1.2 地址空间分布由于linux下不主张将程序分段,而是分页,所以即使是在80x86的体系结构下,段的基地址也是0.因此逻辑地址、线性地址、虚拟地址在linux中都是相同的。所以对于linux下的elf文件来说,代码段的起始地址0x08048000即使逻辑地址也是虚拟地址。

1、X86物理地址布局

[ 4G |--| 3G |--| 896M |-------------| 16M |---| 1M |---|  0M ]

|<ZONE_HIGHMEM>|<ZONE_NORMAL>|<ZONE_DMA>|

linux系统在初始化时,会根据实际的物理内存的大小,为每一个物理页面创建一个page对象,所有的page对象构成一个mem_map数组。

ZONE_DMA的范围是0-16M,该区域的物理页面专门供I/O设备的DMA使用。因为DMA使用物理地址访问内存,不经过MMU,并且需要连续的缓冲区,所以为了能够提供物理上连续的缓冲区,必须从物理地址空间专门划分一段区域用于DMA。

ZONE_NORMAL 的范围为16M-896M,该区域的物理页面时内核可以以直接使用的,一一映射,有个偏移地址。

ZONE_HIGHMEM 的范围是896-结束,改区域为高端内存,内核不能直接使用,多通过多级页表进行映射。


2、linux虚拟地址内核空间分布

[ 4G | fix|kmap|vmalloc | 3G +896M |--| mem_map | kernel Image | 3G+16M |---| 3G |------------| 0M ]

|<---------high_memory=128M---->|

|<----------------------kernel space -------------------------------------------------->|<---user space---->|

       在kernel image下面有16M的内核空间用于DMA操作。位于内核空间高端的128M地址主要由3部分组成,分别为vmalloc area,持久化内核映射区,临时内核映射区。

由于ZONE_NORMAL和内核线性空间存在直接映射关系,所以内核会将频繁使用的数据如kernel代码、GDT、mem_map舒卓等放在这个区域。二将用户数据、页表(PT)等不常用数据放在ZONE_HIGHMEM里,只在要访问这些数据时才建立映射关系(kmap)。比如,但内核要访问I/O设备存储空间时,就使用ioreamap将位于物理地址高端的mmio区内存映射到内核空间的vmalloc area中,在使用完之后便断开映射关系。


3、虚拟地址用户空间分布

[ 4G | 3G+896M | 3G | env/param|stack|--|---------|mmap |---| heap | bss | data | text | 0x08048000 | --|0M]
|<--kernel space---->|<-------------------------------------user space-------------------------------------------->|

      用户进程的代码区一般从虚拟地址空间的0x08048000开始,这是为了便于检查空指针。代码区之上便是数据区,未初始化数据区,堆区,栈区,以及参数、全局环境变量。

一段代码实例,说明用户空间分布:

b.c 代码如下:

#include<stdio.h>

int global_init_var= 84;

intglobal_uninit_var;

void func(int i)

{

        printf("zll-----n=%d\n",i);

}

int main(void)

{

        static int static_var = 85;

        static int static_var2;

        int a = 1;

        int b;

        func(static_var + static_var2 + a + b);

       printf("global_init_var=%p,global_uninit_var=%p",&global_init_var,&global_uninit_var);

       printf("static_var=%p,static_var2=%p",&static_var,&static_var2);

       printf("a=%p,b=%p",&a,&b);

        return 0;

}

 

执行命令:

(1) gcc-o b.out b.c

(2)./b.out

输出:global_init_var=0x601040,global_uninit_var=0x601050static_var=0x601044,static_var2=0x60104ca=0x7ffff0602578,b=0x7ffff060257c

(3)readefs-S b.out

输出如下(得到段的地址):

 

Section Headers:

  [Nr] Name              Type             Address           Offset       Size              EntSize          Flags Link  Info  Align

  [ 0]                   NULL             0000000000000000  00000000      0000000000000000 0000000000000000           0     0    0

  [ 1] .interp           PROGBITS         0000000000400238  00000238      000000000000001c 0000000000000000   A       0    0     1

  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254      0000000000000020 0000000000000000   A       0    0     4

  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274      0000000000000024 0000000000000000   A       0    0     4

  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298      000000000000001c 0000000000000000   A       5    0     8

  [ 5] .dynsym           DYNSYM           00000000004002b8  000002b8      0000000000000060 0000000000000018   A       6    1     8

  [ 6] .dynstr           STRTAB           0000000000400318  00000318      000000000000003f 0000000000000000   A       0    0     1

  [ 7] .gnu.version      VERSYM           0000000000400358  00000358      0000000000000008 0000000000000002   A       5    0     2

  [ 8] .gnu.version_r    VERNEED          0000000000400360  00000360      0000000000000020 0000000000000000   A       6    1     8

  [ 9] .rela.dyn         RELA             0000000000400380  00000380      0000000000000018 0000000000000018   A       5    0     8

  [10] .rela.plt         RELA             0000000000400398  00000398      0000000000000048 0000000000000018   A       5   12     8

  [11] .init             PROGBITS         00000000004003e0  000003e0      000000000000001a 0000000000000000  AX       0    0     4

  [12] .plt              PROGBITS         0000000000400400  00000400      0000000000000040 0000000000000010  AX       0    0     16

  [13] .text            PROGBITS        0000000000400440  00000440       00000000000001c2  0000000000000000  AX      0     0     16

  [14] .fini             PROGBITS         0000000000400604  00000604      0000000000000009 0000000000000000  AX       0    0     4

  [15] .rodata           PROGBITS         0000000000400610  00000610      0000000000000012 0000000000000000   A       0    0     4

  [16] .eh_frame_hdr     PROGBITS         0000000000400624  00000624      000000000000003c 0000000000000000   A       0    0     4

  [17] .eh_frame         PROGBITS         0000000000400660  00000660      0000000000000114 0000000000000000   A       0    0     8

  [18] .init_array       INIT_ARRAY       0000000000600e10  00000e10      0000000000000008 0000000000000000  WA       0    0     8

  [19] .fini_array       FINI_ARRAY       0000000000600e18  00000e18      0000000000000008 0000000000000000  WA       0    0     8

  [20] .jcr              PROGBITS         0000000000600e20  00000e20      0000000000000008 0000000000000000  WA       0    0     8

  [21] .dynamic          DYNAMIC          0000000000600e28  00000e28      00000000000001d0 0000000000000010  WA       6    0     8

  [22] .got              PROGBITS         0000000000600ff8  00000ff8      0000000000000008 0000000000000008  WA       0    0     8

  [23] .got.plt          PROGBITS         0000000000601000  00001000      0000000000000030 0000000000000008  WA       0    0     8

  [24] .data             PROGBITS         0000000000601030  00001030      0000000000000018 0000000000000000  WA       0    0     8

 [25] .bss              NOBITS           0000000000601048  00001048      0000000000000010 0000000000000000  WA       0    0     4

  [26] .comment          PROGBITS         0000000000000000  00001048      0000000000000024 0000000000000001  MS       0    0     1

  [27] .shstrtab         STRTAB           0000000000000000  0000106c      0000000000000108 0000000000000000           0     0    1

  [28] .symtab           SYMTAB           0000000000000000  000018f8      0000000000000690 0000000000000018          29    47    8

  [29] .strtab           STRTAB           0000000000000000  00001f88      000000000000027d 0000000000000000           0     0    1

Key to Flags:

  W (write), A (alloc), X (execute), M (merge),S (strings), l (large)

  I (info), L (link order), G (group), T (TLS),E (exclude), x (unknown)

  O (extra OS processing required) o (OSspecific), p (processor specific)

 

对比输出结果:

global_init_var=0x601040--------------------全局赋值变量位于data区

global_uninit_var=0x601050-------------------全局不赋值变量位于bss区

static_var=0x601044-------------------------------局部静态赋值变量位于data区

static_var2=0x60104c------------------------------局部静态不赋值变量位于bss区

a=0x7ffff0602578------------------------------------局部赋值变量位于栈区

b=0x7ffff060257c------------------------------------局部不赋值变量位于栈区


1.3 总结

1、32位地址空间,通过2^32算得有4G空间,其中0~3G为用户空间,3G~4G为内核空间。

2、物理地址,为实际内存的地址空间,如1G、2G、3G、4G等,所以分配为0~16M为DMA区,16~896M为NORMAL区,896~最大为HIGH区

3、对于线性地址,[0-3G)为用户空间,其对物理地址的映射随进程不同而变化;  用户空间常见的内存分配方式为malloc函数; 

4、对线性地址[3G-4G)内核空间,其对物理地址的映射关系是由内核负责的,不会随进程改变,是固定的  内核空间内存分配方式           有kmallocvmallockmap 

5、内核空间地址分为直接内存映射区、动态内存映射区、永久内存映射区、固定内存映射区。直接映射区映射的物理地址是低端内存(小于896M的范围)。动态映射区可映射为物理地址的低端内存或高端内存,永久映射区映射物理地址的高端内存。(物理地址大于896M的内存称为高端内存。)固定映射区的每个地址都用于特殊的用途。 

6、之所以引入内核空间对高端内存的特定的一些映射方式,是使内核可以实现对物理内存的全部访问。直接映射建立了对896M以下物理内存的映射。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值