关于Linux内存管理goodWord文档格式.docx
《关于Linux内存管理goodWord文档格式.docx》由会员分享,可在线阅读,更多相关《关于Linux内存管理goodWord文档格式.docx(22页珍藏版)》请在冰豆网上搜索。
char*p2;
//p2为局部变量,所以存储于栈区
char*p3="
123456"
//123456\0在常量区(已初始化数据区),p3在栈上。
staticintc=0;
//c存储于静态数据区(静态数据区和全局初始化区同在一个区域)
p1=(char*)malloc(10);
//系统动态分配得来的10和20字节的区域就在堆区
p2=(char*)malloc(20);
free(p1);
free(p2);
return0;
}
2、内存的分配(内存的申请)
1)申请方式
Stack(静态分配):
静态对象是有名字的变量,可以直接对其进行操作。
由系统自动分配内存。
例如,声明在函数中一个局部变量intb;
系统自动在栈中为b开辟空间。
Heap(动态分配):
动态对象是没有名字的变量,需要通过指针间接地对它进行操作。
需要程序员自己申请内存,并指明大小。
在C中malloc函数,如p1=(char*)malloc(10);
在C++中用new运算符,如p2=newchar[20];
//(char*)malloc(20);
分配堆空间之后,p1、p2得到所分配堆空间首地址,将会指向堆空间。
2)申请后系统的响应
栈:
只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出
堆:
首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
其次,大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。
此外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
3)申请大小的限制
栈:
在Windows下,栈是高地址向低地址扩展的数据结构,是一块连续的内存的区域。
这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的。
在WINDOWS下,栈的大小是2M(也有的说是1M,总之它是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。
因此,能从栈获得的空间较小。
堆:
堆是低地址向高地址扩展的数据结构,是不连续的内存区域。
这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。
堆的大小受限于计算机系统中有效的虚拟内存,由此可见,堆获得的空间很灵活。
但由程序员操作的过程中容易发生内存泄露,也极易产生内存空间的不连续,即内存碎片(频繁使用malloc和free(new和delete)的结果)。
4)申请效率的比较
栈由系统自动分配,速度较快,但程序员是无法控制的。
堆是由malloc或者new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
5)分配效率的比较
栈是机器提供的数据结构,机器会在底层对栈提供支持:
分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令。
堆则是C函数库提供的,它的机制很复杂。
可见,堆得存取效率和栈比较起来要低得多。
3、下面再给出一个数据存储区域的实例
#include<
stdio.h>
malloc.h>
unistd.h>
alloca.h>
externvoidafunc(void);
//声明afunc()函数
externetext,edata,end;
//声明三个外部变量
intbss_var;
//未初始化全局数据存储在BSS区
intdata_var=42;
//初始化全局数据存储在数据区
//定义了一个宏,用于打印地址
#defineSHW_ADR(ID,I)printf("
the%8s\tisatadr:
%8x\n"
ID,&
I);
intmain(intargc,char*argv[])
char*p,*b,*nb;
printf("
Adretext:
%8x\tAdredata%8x\tAdrend%8x\t\n"
&
etext,&
edata,&
end);
\ntextLocation:
\n"
);
SHW_ADR("
main"
main);
//查看代码段main函数位置
afunc"
afunc);
//查看代码段afunc函数位置
\NBSSLocation:
bss_var"
bss_var);
//查看BSS段变量位置
\NDATALocation:
data_var"
data_var);
//查看数据段变量位置
\nSTACKLocation:
afunc();
p=(char*)alloca(32);
//从栈中分配空间
if(p!
=NULL){
start"
p);
//打印栈空间的起始位置
end"
p+31);
//打印栈空间的结束位置
}
b=(char*)malloc(32*sizeof(char));
//从堆中分配空间
nb=(char*)malloc(16*sizeof(char));
//从堆中分配空间
\NHEAPLocation:
theheapstart:
%p\n"
b);
//打印堆起始位置
theheapend:
(nb+16*sizeof(char)));
//打印堆结束位置
\nbandnbinStack\n"
b"
//打印栈中字符指针变量b的存储位置
nb"
nb);
//打印栈中字符指针变量nb的存储位置
free(b);
//释放申请的堆空间
free(nb);
voidafunc(void)
staticintlonglevel=0;
//静态数据存储在数据段中
intstack_var;
//局部变量,存储在栈区
if(++level==5)
{return;
stack_varisat:
%p\n"
stack_var);
//打印局部变量的地址
//递归执行afunc函数
4、介绍几个内存管理函数
1)Malloc/free函数
原型:
externvoid*malloc(size_tnum_bytes);
头文件:
include<
stdlib.h>
功能:
分配长度为num_bytes字节的内存块
返回值:
如果分配成功则返回指向被分配内存首地址的指针,否则返回空指针NULL。
说明:
该函数返回为void型指针,因此必要时要进行类型转换。
当内存不再使用时,应使用free()函数将内存块释放。
为什么不再使用时,要用free()释放掉所申请的内存?
由于内存区域总是有限的,不能无限制地分配下去。
程序应该尽可能地去节省资源,当申请的堆空间不再使用时,应该释放掉,交由其它进程来使用。
注意:
不能用free()来释放非malloc(),calloc(),realloc()函数所创建的堆空间,否则会发生错误。
2)new/delete(在C++中)
使用new/delete运算符实现内存管理比malloc/free函数更有优越性。
它们的定义如下:
Staticvoid*operatornew(size_tsz);
Staticvoid*operatordelete(void*p);
先看一段C++代码:
voidtest(void)
//申请一个sizeof(obj)大小的一块动态内存,并把头指针赋值给obj类型的指针变量a
obj*a=newobj;
deletea;
//清除并且释放所申请的内存
下面通过一段代码具体介绍一下new/delete的用法:
ClassA
Public:
A(){count<
<
“Aishere!
”<
endl;
}//构造函数
~A(){count<
}//析构函数
Private:
IntI;
};
A*pA=newA;
//调用new运算符申请空间
deletepA;
//删除pA
其中,语句newA完成了一下两个功能:
A.调用new运算符,在堆上分配一个sizeof(A)大小的内存空间。
B.调用构造函数A(),在所分配的内存空间上初始化对象。
语句deletepA完成的是相反的两件事:
A.调用析构函数~A(),销毁对象。
B.调用运算符delete,释放内存。
使用new比使用malloc()有以下优点:
A.New自动计算要分配给对象的内存空间大小,不使用sizeof运算符,这样一来简单,而来可以避免错误。
B.自动地返回正确的指针类型,不用进行强制类型转换。
C.用构造函数给分配的对象进行初始化。
使用malloc函数和new分配内存的时候,本身并没有对这块内存空间做清零等任何工作。
因此,申请内存空间后,其返回的新分配的空间是没有用零填充的,程序员须使用memset()函数来初始化内存。
3)realloc函数(更改已经配置的内存空间)
函数定义:
void*realloc(void*ptr,size_tsize)
参数ptr为先前由malloc、calloc和realloc所返回的内存指针,而参数size为新配置的内存大小。
realloc函数用来从堆上分配内存,当需要扩大一块内存空间时,realloc()试图直接从堆上当前内存段后面的字节中获得更多的内存空间:
如果能够分配成功,则返回指向这块新内存空间的首地址,而将原来的指针(realloc函数的参数指针)指向的空间释放掉;
如果当前内存段后面的空闲字节不够,那么就使用堆上第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,而将原来的数据块释放掉;
如果内存不足,重新申请空间失败,则返回NULL,此时原来的指针(realloc函数的参数指针)仍有效。
intmain(intargc,char*argv[],char*envp[])//主函数
intinput;
intn;
int*numbers1;
int*numbers2;
numbers1=NULL;
if((numbers2=(int*)malloc(5*sizeof(int)))==NULL)//numbers2指针申请空间
printf("
mallocmemoryunsuccessful"
//free(numbers2);
//numbers2=NULL;
exit
(1);
for(n=0;
n<
5;
n++)//初始化(0-4)
{
*(numbers2+n)=n;
numbers2'
sdata:
%d\n"
*(numbers2+n));
//把0-4打印出来
Enteranintegervalueyouwanttoremalloc(enter0tostop)\n"
//新申请空间大小
scanf("
%d"
input);
numbers1=(int*)realloc(numbers2,(input+5)*sizeof(int));
//重新申请空间
if(numbers1==NULL)
Error(re)allocatingmemory"
exit
(1);
for(n=0;
n<
n++)//这5个数是从numbers2(原指针空间)拷贝而来
thenumbers1s'
sdatacopyfromnumbers2:
*(numbers1+n));
input;
n++)//新数据初始化(0-input)
*(numbers1+5+n)=n*2;
printf("
nummber1'
snewdata:
*(numbers1+5+n));
//numbers1++;
free(numbers1);
//释放numbers1
//free(numbers2);
//不能再释放numbers2,因为number2早已被系统自动释放
return0;
前面我们已经分析了linux如何利用伙伴系统,slab分配器分配内存,用这些方法得到的内存在物理地址上都是连续的,然而,有些时候,每次请求内存时,系统都分配物理地址连续的内存块是不合适的,可以利用小块内存“连接”成大块可使用的内存.这在操作系统设计中也被称为“内存拼接”,显然,内存拼接在需要较大内存,而内存访问相比之下不是很频繁的情况下是比较有效的.
在linux内核中用来管理内存拼接的接口是vmalloc/vfree.用vmalloc分配得到的内存在线性地址是平滑的,但是物理地址上是非连续的.
一:
准备知识:
Linux用vm_struct结构来表示vmalloc使用的线性地址.vmalloc所使用的线性地址区间为:
VMALLOC_STARTVMALLOC_END.借用<
Understanding.the.Linux.Kernel.3rd>
>
中的一副插图,如下示:
从上图中我们可以看到每一个vmalloc_area用4KB隔开,这样做是为了很容易就能捕捉到越界访问,因为中间是一个“空洞”.
二:
相关的数据结构
下面来分析一下vmallocarea的数据结构:
structvm_struct{
void*addr;
//虚拟地址
unsignedlongsize;
//vm的大小
unsignedlongflags;
//vm的标志
structpage**pages;
//vm所映射的page
unsignedintnr_pages;
//page个数
unsignedlongphys_addr;
//对应的起始物理地址
structvm_struct*next;
//下一个vm.用来形成链表
}
全局变量vmlist用来管理vm构成的链表
全局变量vmlist用于访问vmlist所使用的信号量
对于vm_struct有两个常用的操作:
get_vm_area/remove_vm_area
get_vm_area:
用来分配一个合适大小的vm结构,分配成功之后,将其链入到vmlist中,代码在mm/vmalloc.c中.如下示:
//size为vm的大小
structvm_struct*get_vm_area(unsignedlongsize,unsignedlongflags)
{
//在VMALLOC_START与VMALLOC_END找到一段合适的空间
return__get_vm_area(size,flags,VMALLOC_START,VMALLOC_END);
//参数说明:
//start:
起始地址end:
结束地址size空间大小
structvm_struct*__get_vm_area(unsignedlongsize,unsignedlongflags,
unsignedlongstart,unsignedlongend)
structvm_struct**p,*tmp,*area;
unsignedlongalign=1;
unsignedlongaddr;
//如果指定了VM_IOREMAP.则调整对齐因子
if(flags&
VM_IOREMAP){
intbit=fls(size);
if(bit>
IOREMAP_MAX_ORDER)
bit=IOREMAP_MAX_ORDER;
elseif(bit<
PAGE_SHIFT)
bit=PAGE_SHIFT;
align=1ul<
bit;
//将起始地址按照对齐因子对齐
addr=ALIGN(start,align);
//分配一个vm_struct结构空间
area=kmalloc(sizeof(*area),GFP_KERNEL);
if(unlikely(!
area))
returnNULL;
//PAGE_SIZE:
在i32中为4KB,即上面所说的间隔空洞
size+=PAGE_SIZE;
size)){
kfree(area);
write_lock(&
vmlist_lock);
//遍历vmlist:
找到合适大小的末使用空间
for(p=&
vmlist;
(tmp=*p)!
=NULL;
p=&
tmp->
next){
//若起始地址落在某一个vm区间,则调整起始地址为vm区间的末尾
if((unsignedlong)tmp->
addr<
addr){
if((unsignedlong)tmp->
addr+tmp->
size>
=addr)
addr=ALIGN(tmp->
size+
(unsignedlong)tmp->
addr,align);
continue;
//size+addr<
addr?
除非size==0
if((size+addr)<
addr)
gotoout;
//中间的空隙可以容纳下size大小的vm.说明已经找到了这样的一个vm
if(size+addr<
=(unsignedlong)tmp->
addr)
gotofound;
//调整起始地址为vm的结束地址
size+(unsignedlong)tmp->
//如果超出了范围
if(addr>
end-size)
found:
//找到了合适大小的空间,将area->
addr赋值为addr,然后链入vmlist中
area->
next=*p;
*p=area;
flags=flags;
addr=(void*)addr;
size=size;
pages=NULL;
nr_pages=0;