大内高手—内存模型

转载 2006年06月24日 18:32:00

大内高手内存模型

 

了解linux的内存模型,或许不能让你大幅度提高编程能力,但是作为一个基本知识点应该熟悉。坐火车外出旅行时,即时你对沿途的地方一无所知,仍然可以到达目标地。但是你对整个路途都很比较清楚的话,每到一个站都知道自己在哪里,知道当地的风土人情,对比一下所见所想,旅程可能更有趣一些。

 

类似的,了解linux的内存模型,你知道每块内存,每个变量,在系统中处于什么样的位置。这同样会让你心情愉快,知道这些,有时还会让你的生活轻更松些。看看变量的地址,你可以大致断定这是否是一个有效的地址。一个变量被破坏了,你可以大致推断谁是犯罪嫌疑人。

 

Linux的内存模型,一般为:

地址

作用

说明

>=0xc000 0000

内核虚拟存储器

用户代码不可见区域

<0xc000 0000

Stack(用户栈)

ESP指向栈顶

 

 

 

空闲内存

>=0x4000 0000

文件映射区

 

<0x4000 0000

 

 

 

空闲内存

 

 

Heap(运行时堆)

通过brk/sbrk系统调用扩大堆,向上增长。

 

.data.bss(读写段)

从可执行文件中加载

>=0x0804 8000

.init.text.rodata(只读段)

从可执行文件中加载

<0x0804 8000

保留区域

 

 

很多书上都有类似的描述,本图取自于《深入理解计算机系统》p603,略做修改。本图比较清析,很容易理解,但仍然有两点不足。下面补充说明一下:

 

1.         第一点是关于运行时堆的

为说明这个问题,我们先运行一个测试程序,并观察其结果:

#include <stdio.h>

 

int main(int argc, char* argv[])

{

    int  first = 0;

    int* p0 = malloc(1024);

    int* p1 = malloc(1024 * 1024);

    int* p2 = malloc(512 * 1024 * 1024 );

    int* p3 = malloc(1024 * 1024 * 1024 );

    printf("main=%p print=%p/n", main, printf);

    printf("first=%p/n", &first);

    printf("p0=%p p1=%p p2=%p p3=%p/n", p0, p1, p2, p3);

 

    getchar();

 

    return 0;

}

 

运行后,输出结果为:

main=0x8048404 print=0x8048324

first=0xbfcd1264

p0=0x9253008 p1=0xb7ec0008 p2=0x97ebf008 p3=0x57ebe008

 

l         mainprint两个函数是代码段(.text)的,其地址符合表一的描述。

l         first是第一个临时变量,由于在first之前还有一些环境变量,它的值并非0xbfffffff,而是0xbfcd1264,这是正常的。

l         p0是在堆中分配的,其地址小于0x4000 0000,这也是正常的。

l         p1p2也是在堆中分配的,而其地址竟大于0x4000 0000,与表一描述不符。

 

原因在于:运行时堆的位置与内存管理算法相关,也就是与malloc的实现相关。关于内存管理算法的问题,我们在后继文章中有详细描述,这里只作简要说明。在glibc实现的内存管理算法中,Malloc小块内存是在小于0x4000 0000的内存中分配的,通过brk/sbrk不断向上扩展,而分配大块内存,malloc直接通过系统调用mmap实现,分配得到的地址在文件映射区,所以其地址大于0x4000 0000

 

maps文件中可以清楚的看到一点:

00514000-00515000 r-xp 00514000 00:00 0

00624000-0063e000 r-xp 00000000 03:01 718192     /lib/ld-2.3.5.so

0063e000-0063f000 r-xp 00019000 03:01 718192     /lib/ld-2.3.5.so

0063f000-00640000 rwxp 0001a000 03:01 718192     /lib/ld-2.3.5.so

00642000-00766000 r-xp 00000000 03:01 718193     /lib/libc-2.3.5.so

00766000-00768000 r-xp 00124000 03:01 718193     /lib/libc-2.3.5.so

00768000-0076a000 rwxp 00126000 03:01 718193     /lib/libc-2.3.5.so

0076a000-0076c000 rwxp 0076a000 00:00 0

08048000-08049000 r-xp 00000000 03:01 1307138    /root/test/mem/t.exe

08049000-0804a000 rw-p 00000000 03:01 1307138    /root/test/mem/t.exe

09f5d000-09f7e000 rw-p 09f5d000 00:00 0          [heap]

57e2f000-b7f35000 rw-p 57e2f000 00:00 0

b7f44000-b7f45000 rw-p b7f44000 00:00 0

bfb2f000-bfb45000 rw-p bfb2f000 00:00 0          [stack]

 

2.         第二是关于多线程的。

现在的应用程序,多线程的居多。表一所描述的模型无法适用于多线程环境。按表一所述,程序最多拥有上G的栈空间,事实上,在多线程情况下,能用的栈空间是非常有限的。为了说明这个问题,我们再看另外一个测试:

#include <stdio.h>

#include <pthread.h>

 

 

void* thread_proc(void* param)

{

    int  first = 0;

    int* p0 = malloc(1024);

    int* p1 = malloc(1024 * 1024);

 

    printf("(0x%x): first=%p/n",    pthread_self(), &first);

    printf("(0x%x): p0=%p p1=%p /n", pthread_self(), p0, p1);

 

    return 0;

}

 

#define N 5

int main(int argc, char* argv[])

{

    int first = 0;

    int i= 0;

    void* ret = NULL;

    pthread_t tid[N] = {0};

 

    printf("first=%p/n", &first);

    for(i = 0; i < N; i++)

    {

        pthread_create(tid+i, NULL, thread_proc, NULL);

    }

 

    for(i = 0; i < N; i++)

    {

        pthread_join(tid[i], &ret);

    }

 

    return 0;

}

 

运行后,输出结果为:

first=0xbfd3d35c

(0xb7f2cbb0): first=0xb7f2c454

(0xb7f2cbb0): p0=0x84d52d8 p1=0xb4c27008

(0xb752bbb0): first=0xb752b454

(0xb752bbb0): p0=0x84d56e0 p1=0xb4b26008

(0xb6b2abb0): first=0xb6b2a454

(0xb6b2abb0): p0=0x84d5ae8 p1=0xb4a25008

(0xb6129bb0): first=0xb6129454

(0xb6129bb0): p0=0x84d5ef0 p1=0xb4924008

(0xb5728bb0): first=0xb5728454

(0xb5728bb0): p0=0x84d62f8 p1=0xb7e2c008

 

我们看一下:

主线程与第一个线程的堆之间的距离:0xbfd3d35c - 0xb7f2c454=0x7e10f08=126M

第一个线程与第二个线程的堆之间的距离:0xb7f2c454 - 0xb752b454=0xa01000=10M

其它几个线程间的距离均为10M

也就是说,主线程的堆空间最大为126M,而普通线程的栈空间仅为10M,超这个范围就会造成栈溢出。

 

栈溢出的后果是比较严重的,或者出现Segmentation fault错误,或者出现莫名其妙的错误。

大内高手—栈/堆

大内高手—栈/堆 l         栈 栈作为一种基本数据结构,我并不感到惊讶,用来实现函数调用,这也司空见惯的作法。直到我试图找到另外一种方式实现递归操作时,我才感叹于它的巧妙。要实现...
  • xjbclz
  • xjbclz
  • 2016年06月29日 22:47
  • 98

大内高手--调试手段及原理

大内高手--调试手段及原理     知其然也知其所以然,是我们《大内高手》系列一贯做法,本文亦是如此。这里我不打算讲解如何使用boundschecker、purify、valgrind或者gdb,...
  • sunnylgz
  • sunnylgz
  • 2013年02月25日 11:07
  • 803

求解旋转图像的最大内接矩形

在机器学习和深度学习中,通常使用为了增加数据(Data Augmentation)可以对数据进行例如一系列的旋转(rotate)、镜像(flip)等操作,本文将讲解如何求取旋转图像的最大内接矩形问题。...
  • YhL_Leo
  • YhL_Leo
  • 2016年05月27日 12:45
  • 4323

教你怎样成为C++高手

1.把C++当成一门新的语言学习(和C没啥关系!真的。); 2.看《Thinking In C++》,不要看《C++变成死相》(C++编程思想,翻译的非常差); 3.看《The C++ Program...
  • hellokandy
  • hellokandy
  • 2016年10月24日 11:07
  • 492

MATLAB:定位手掌最大内切圆

最近要做的一件事就是:对于一份给定的手掌图,要定位可寻的最大内切圆。做了近12个小时,才终于解决了这个问题,下面记录一下过程。 1、图片在MATLAB里是个矩阵,如何在矩阵作圆: fu...
  • liaocs2008
  • liaocs2008
  • 2013年03月06日 11:20
  • 3568

opencv:求区域的内接矩形

实验室项目中,希望求取一个近似圆形区域的质心,原本使用最小外接圆的质心来等效为该区域质心。但是由于部分区域的形状过于不规则导致发生质心偏移现象。如下图:     蓝色为实际要求质心。红色为等效圆质心 ...
  • cfqcfqcfqcfqcfq
  • cfqcfqcfqcfqcfq
  • 2016年11月08日 17:09
  • 2916

成为java高手的八大条件

成为java高手的八大条件
  • javaniuniu
  • javaniuniu
  • 2016年07月16日 13:56
  • 1075

全面理解Java内存模型

Java内存模型即Java Memory Model,简称JMM。JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。如果...
  • suifeng3051
  • suifeng3051
  • 2016年09月21日 18:39
  • 49401

什么是Java内存模型

概述:本文向您介绍Java内存模型的概念,在C或C++中, 利用不同操作平台下的内存模型来编写并发程序;Java利用了自身虚拟机的优势, 使内存模型不束缚于具体的处理器架构,真正实现了跨平台。 ...
  • guomei
  • guomei
  • 2015年04月21日 00:51
  • 1876

成为高手的必经之路——学会调试代码(菜鸟必读)

任何一个编程者都少不了要去调试代码,不管你是高手还是菜鸟,调试程序都是一项必不可少的工作。一般来说调试程序是在编写代码之后或测试期修改Bug 时进行的,往往在调试代码期间更加能够体现出编程者的水平高低...
  • yhzhang1016
  • yhzhang1016
  • 2014年01月15日 11:10
  • 820
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:大内高手—内存模型
举报原因:
原因补充:

(最多只允许输入30个字)