【Linux环境编程】内存管理:函数栈空间,虚拟内存及其分配
2015-11-22 21:19
519 查看
一.函数调用栈空间的分配与释放
函数执行时有自己的临时栈空间,c++成员函数有两个临时栈空间,一个是成员函数的还有一个是对象的。函数的参数是压进临时栈中,传递的实参用来初始化临时栈中的形参。
函数属性:
int __attribute__((stdcall)) add(int a, int b) { return a+b; }
一共有3种属性(调用方式):stdcall,cdecl,fastcall,他们会影响编译:
函数栈压栈的参数顺序(这三个都是从右到左)
函数栈清空方式
函数的名字转换
前两点可反汇编看看:
gcc XXX.c -S cat XXX.s
第三点可以:
nm 程序
可以看到c程序 add 函数名字就叫add c++因为有重载机制会 把名字 变成 列如 Z3addPis Z 是都有的,3表示函数名字的长度,后面的是函数形参类型——C++的重载其实是一个假象!编译之后函数的符号是不一样的。
二.虚拟内存
#include<stdio.h> #include<malloc.h> int main() { int *p = malloc(4); printf("%p\n",p); while(1); return 0; }
结果:0xb7786018
#include<stdio.h> #include<malloc.h> int main() { int *p = (int*)0xb7786018; printf("%d\n",*p); return 0; }
可能会出现段错误,或随机的值。
#include<stdio.h> #include<malloc.h> int main() { int *p = malloc(0); *p = 999; printf("%d\n",*p); return 0; }
输出:999
问题:一个程序不能访问另外一个程序的地址指向的空间。
理解:
1.每个程序的开始地址是相同的
2.程序中使用的地址不是物理地址,而是逻辑地址。
(逻辑地址仅仅是个编号,它使用int 4字节整数表示 (4G),每个程序提供了4G的访问能力。)
逻辑地址与物理地址之间存在映射,这个过程叫做内存映射。
strace 程序 #跟踪程序的执行过程
虚拟内存禁止了用户直接访问物理存储设备, 提高了系统的稳定性。
对于第一个程序,a分配了空间,即有了内存映射了,所以正确执行。
对于第二个程序,p指向了地址0xb7786018,而这个地址在本程序中并没有映射到物理内存中,所以访问会出错。
#include<stdio.h> #include<malloc.h> int main() { int *p = malloc(4); *(p) = 1; *(p+1) = 2; *(p+1000) = 3; printf("%p\n",p); while(1); return 0; }
上面程序执行并不会发生段错误。
原因:内存映射的基本单位是 4k (即1000,16进制,叫内存页)。哪怕我们要1个字节,它映射的也是4k空间,malloc 第一次被调用的时候 不管你分不分配空间,它至少调用4k的空间。(所以上面的代码以及第三个代码不会出现段错误)
段错误:地址没有映射到一个物理空间,无效访问
合法访问:比如malloc分配的多余空间可以访问,但属于非法访问!
三.虚拟内存的分配
栈:编译器自动生成代码维护堆:地址是否映射,映射的空间是否被管理
brk / sbrk 内存映射函数
man brk #查看说明文档
int brk(void *end_data_segment);
分配空间,释放空间
void *sbrk(intptr_t increment);
返回空间地址
应用:
1.使用sbrk分配空间
2.使用sbrk得到没有映射的虚拟地址(参数为0)
3.使用brk分配空间
4.使用brk释放空间
sbrk与brk后台系统维护一个指针。
指针初始化为null。
调用sbrk时,判断指针是否为null,如果是,得到大块空闲空间的首地址来初始化指针,同时把指针+increment;否则,先返回当前指针,并且把指针位置+increment;
在映射的时候,都是映射一个页,以此来提高效率。
#include <stdio.h> #include <unistd.h> main() { int *p = sbrk(4);//得到大块空闲空间的首地址来初始化指针并返回,然后把维护的指针+4 *p = 8888; printf("p:%p\n", p); printf("*p:%d\n", *p); }
结果:
p:0x93c3000
*p:8888
一切正常。
我们看看参数为0的情况:
#include <stdio.h> #include <unistd.h> main() { int *p = sbrk(0);//初次调用,且参数为0,获得没有映射的虚拟地址 *p = 8888; printf("p:%p\n", p); printf("*p:%d\n", *p); }
结果:段错误 (核心已转储)
我们加一个死循环,进入/proc/${pid}/maps 看看:
#include <stdio.h> #include <unistd.h> main() { int *p = sbrk(0);//获得没有映射的虚拟地址 printf("p:%p\n", p); printf("pid:%d\n", getpid()); while(1); }
结果:
p:0x9482000
pid:2933
但是我们进入/proc/2933/maps中查看之后,并找不到p所指向的内存空间地址!说明该虚拟地址并没有映射到物理地址上,所以上一个程序当然会出现段错误了。
我们再看看下面一个例子:
#include <stdio.h> #include <unistd.h> main() { int *p = sbrk(0);//获得没有映射的虚拟地址,得到大块空闲空间的首地址来初始化指针,同时把指针+increment brk(p+1);//分配空间,映射 *p = 8888; }
该程序执行没有出错,首先,sbrk(0)返回了一个虚拟地址,但是还没映射。之后调用brk,brk检测到p+1还未映射,于是分配空间,进行映射。
brk(p);//释放空间 *p = 8888;
如果我们加上以上代码,又会出现段错误,因为空间被释放了!
sbrk:表示指针的相对移动;
brk:表示指针的绝对移动,如发现当前指针地址未映射,则进行映射。
为说明sbrk的相对移动以及空间的分配与释放,我们看下面的例子:
#include <stdio.h> #include <unistd.h> main() { int *p1 = sbrk(4); int *p2 = sbrk(200); int *p3 = sbrk(400); int *p4 = sbrk(-4); int *p5 = sbrk(-4); printf("%p\n", p1); printf("%p\n", p2); printf("%p\n", p3); printf("%p\n", p4); printf("%p\n", p5); }
结果:
//我们假设sbrk维护的指针叫做q 0x88b4000 //分配一个页的地址q并返回给p1,q = q+4 0x88b4004 //取得q,q = q+200 0x88b40cc //取得q,q = q+400 0x88b425c //取得q,q = q-4 0x88b4258 //取得q,q = q-4
我们可以看出,sbrk都是相对指针的当前位置进行移动的~通过参数的正负,可以实现空间的分配与释放。
异常处理:
如果成功,brk返回0,sbrk返回指针;如果失败,brk返回-1,sbrk返回(void *)-1。
检测错误: 使用 perror(“err:”);函数。
相关文章推荐
- 我的linux学习之路---配置VNC服务器(1)
- CentOS 6.5编译安装MySQL5.7.7rc
- Linux第十一次学习笔记
- linux 初体验
- 在centos7.1上安装systemd
- CentOS 7 / RHEL 7 上安装 LAMP + phpMyAdmin
- 整个linux系统的备份和还原的方法
- linux工作环境搭建
- linux下安装lamp,php无法识别配置文件路径
- linux 命令 初体验
- linux上文件格式引起的问题
- Linux查看实时带宽流量情况
- 在CentOS 6.6 64bit上基于源码安装全功能的vim 7.4实录
- Linux 内核与模块调试
- Linux 管道的用法
- 90后对IT行业的感悟,值得很多人深思!
- DHCP和TFTP配置以及CentOS 7上的服务控制
- LINUX find、ln 常用命令总结
- Linux常见问题
- Linux学习之CentOS系列文档