嵌入式学习之路(二十四)——UC高级(2)
一.Unix/Linux的内存管理1. 相关函数
1.1 STL ---> 也是自动管理内存的
1.2 C++ ---> 的new和delete(运算符号)
1.3 C语言--> malloc()/free()
1.4 Unix系统函数 -->sbrk()和brk()
1.5 Unix 系统函数 → mmap()/munmap()
2. 一个进程的内存空间
2.1 程序和进程的概念:
2.1.1. 程序是 保存在硬盘上的可执行文件
2.1.2. 进程是 运行在内存中的程序
2.1.3. 链接的成品是程序,运行起来的程序就叫进程
2.2 进程的内存空间(从小到大的顺序)
2.2.1. 代码区:存放函数的代码,只读区,函数指针就是函数在代码区的地址
2.2.2. 只读常量区:存放字符串字面值、const修饰的全局变量
很多人都把他们合一起,因为他们离得很近,大家可以选择自己喜欢的去记
2.2.3. 全局区:存放全局变量和static修饰的变量
2.2.4. BSS段:存放 没有初始化的全局变量,main函数执行之前,自动清零bss段
2.2.5. 栈区:存放局部变量,包括函数的形参,栈区的内存管理是系统自动完成的
2.2.6. 堆区:也叫自由区,new,delete,malloc,free在堆区扽坏和回收,有程序员管理
3. Unix/Linux内存的管理机制
下面这几点,本人感觉还是挺重要的,大家好好理解,记住
3.1 Unix/Linux采用虚拟内存机制管理内存
3.1.1. 每个进程都先天具备0到4G的虚拟内存地址空间
3.1.2. 虚拟内存地址 其实就是一个整数,本身不对应任何的物理内存/硬盘
因此虚拟内存地址先天存在,但先天不能存数据
3.1.3. 虚拟内存地址 映射物理内存/硬盘才能真正存储数据
3.1.4. 这个映射的过程就是内存分配
3.1.5. 程序员只能接触到虚拟内存地址,而不能接触物理内存的真实地址
3.2 虚拟内存分为两块,用户空间(0-3G)和内核空间(3-4G)
程序员只能直接访问用户层,用户层不能直接访问内核层
3.3 内存的管理单位是字节,内存映射的基本单位是内存页,
一个内存页多大呢?我们可以用过getpagesize()来获得当前系统是多少字节
映射一次,必须映射内存页的整数倍
3.4 如果没有映射物理内存/硬盘文件就直接使用虚拟内存地址,会引发段错误
4. 内存的查看
4.1 我们知道,在Linux系统中,几乎一切都被看成了文件
目录,内存设备都看成了文件
4.2 每个进程的内存都在/proc/进程id/maps下可以查看内存页
5. 下面我们来看一个程序,看一下内存空间的分配
/**
内存空间分区演示
**/
#include <stdio.h>
#include <stdlib.h>
int i1 = 1;/*全局*/
int i2;/*bss段*/
static int i3 = 3;/*全局*/
const int i4 = 4;/*只读常量区*/
void fa(int i5)/*栈区*/
{
int i6 = 6;/*栈区*/
static int i7 = 7;/*全局*/
const int i8 = 8;/*栈区*/
int *pi = malloc(4);/*堆区*/
char *s1 = "abc";/*s1指向只读常量区*/
char s2[] = "abc";/*栈区,把"abc的值复制在栈"*/
printf("i5 = %p\n",&i5);
printf("i6 = %p\n",&i6);
printf("i7 = %p\n",&i7);
printf("i8 = %p\n",&i8);
printf("pi = %p\n",pi);
printf("s1 = %p\n",s1);
printf("s2 = %p\n",s2);
}
int main()
{
printf("i1 = %p\n",&i1);
printf("i2 = %p\n",&i2);
printf("i3 = %p\n",&i3);
printf("i4 = %p\n",&i4);
printf("fa = %p\n",fa);/*打印函数地址,代码区*/
fa(10);
printf("pid = %d\n",getpid());
while(1);/*用cat /proc/pid/maps可以直接查看*/
}
大家都可以看注释理解变量定义在哪一个区,也可以看看内存页的情况
6. 接下来我们进入真正的正题malloc()函数6.1 malloc()分配的是堆区的内存,malloc()分配的内存必须由free()释放
否则就等到进程结束
6.2 malloc()其实一次映射了33个内存页,如果一次申请了内存超过33个内存页
会分配比申请的内存多一点的内存页,具体多几页我们不知道
6.3 malloc()在分配内存时,会额外存储一些附加信息,比如说分配大小
int *p = malloc(4);
free(p);
int *p = malloc(8);
free(p);
这里我们知道,free()怎么知道要回收多少字节?所以会附加一些信息
6.4 所以我们得出一个结论,malloc()分配的内存,地址是不连续的,要存附加信息
6.5 free()只是释放虚拟地址内存,未必解除内存的映射,最后33个内存页free()不会解除
6.6 free()其实就是把已经使用的虚拟地址的状态改成未使用
不一定会解除与无力内存/硬盘文件的映射(最后33页不会解除)
6.7 经验之谈:
6.7.1. 在应用的时候,只需要分配内存用malloc(),释放内存用free();
6.7.2. malloc()分配内存的时候,不要越界使用,否则会毁坏附加数据,影响free();
6.8 malloc()演示程序
/*
malloc演示
*/
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("pid =%d\n",getpid());
int a,b,c,d;
printf("%p %p %p %p\n",&a,&b,&c,&d);
int *p1 = malloc(4);//malloc映射了33内存页
int *p2 = malloc(4);//第二次不映射,只是分配
int *p3 = malloc(4);//第三次也不映射,只是分配
printf("p1=%p,p2=%p,p3=%p\n",p1,p2,p3);
//*(p1+100) = 50;//没有超过,所以能用
//printf("%d\n",*(p1+100));
// *(p1+1024*33-1) = 50;//已经超过33页,所以越界
//printf("%d\n",*(p1+1024*33-1));
//*(p1+1024*33-4) = 50;//没有超过33页,所以越界
//printf("%d\n",*(p1+1024*33-4));
free(p3);
free(p2);
free(p1);
sleep(1);
int *p4 = malloc(1);
printf("p4 =%p\n",p4);
free(p4);
sleep(1);
while(1);
return 0;
}
7. sbrk()------Unix的系统函数(windows下面不能使用)
7.1 sbrk()和brk()函数本身都同时具备分配和回收的内存的功能7.2 但是呢?sbrk()分配内存更方便,brk()回收内存更方便
7.3 他们都是通过底层维护的位置进行内存的分配和回收
7.4 void *sbrk(int increment)
参数:increment 就是内存的大小
正数代表分配increment字节内存
负数代表回收increment 字节内存
0代表既不回收也不分配,获取当前的位置
返回值:返回移动之前的位置,对increment是负数没有意义,因为已经释放掉了
7.5 sbrk()/brk()映射内存都是一页
7.6 sbrk()/brk()在分配内存时,一一个内存页为基本单位,超了就多一页,释放了就少一页
全部释放就没有内存页,映射也随之接触,这和malloc是不一样的
8. brk()-------Unix的系统函数
8.1 int brk(void *new);
brk()就是位置移动到new这个位置
成功返回0,失败返回-1
8.2 sbrk()分配内存
brk()回收内存
sbrk()/brk()程序演示
/*
合理使用brk()和sbrk()函数演示
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
int *pi = sbrk(4);//int是4个字节的
double *pd = sbrk(8);//double是8个字节的
char *pst = sbrk(10);//10个char是10个字节的
*pi = 100;
*pd = 12000.00;
strcpy(pst,"zhangfei");
printf("%d,%lg,%s\n",*pi,*pd,pst);
brk(pi);//释放全部内存
return 0;
}
9. mmap()和munmap()-用户层中最底层
主要用于虚拟内存地址和物理内存/硬盘文件的映射
9.1 mmap():
如果多个选项想拼起来,用位或“|”
void *mmap(void *addr,size_t size,int prot,int flags,int fd,off_t offset)
参数addr 一般给0即可,内核选定首地址
size是映射的大小,以内存页为基本单位
prot是权限,一般PROT_READ|PROT_WRITE
flags是选项,主要包括:
MAP_PRIVATE MAP_SHARED 私有/共享 对映射物理内存没区别,二选一
MAP_ANONYMOUS代表映射物理内存,mmap默认映射 硬盘文件
fd和offset映射文件用,给0即可
返回映射的首地址,失败返回MAP_FAILED就是void(*)-1.
程序演示
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
int main()
{
void *p = mmap(0/*让内核选择*/,2/*映射内存大小,会补成页*/,PROT_READ|PROT_WRITE/*权限*/,MAP_PRIVATE|MAP_ANONYMOUS/*私有物理内存*/,0,0);/*文件描述符和偏移量*/
if(p == MAP_FAILED)
{
perror("mmap");
return -1;
}
printf("pid=%d",getpid());
printf("%p",p);
int *pi = p;
*pi = 100;
char *st = p+10;/*映射了一个内存页*/
strcpy(st,"abcd");/*但是不推荐这种用法*/
printf("%p",st);
printf("*pi=%d,st=%s\n",*pi,st);
while(1);
munmap(p,4);//首地址和大小
return 0;
}
内存管理的函数我们差不多都讲好了,大家也好好消化一下,写程序来调试一下!!