嵌入式学习之路(二十四)——UC高级(2)

来源:互联网 发布:apache进程几十兆 编辑:程序博客网 时间:2024/04/29 15:42
嵌入式学习之路(二十四)——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;

}
内存管理的函数我们差不多都讲好了,大家也好好消化一下,写程序来调试一下!
0 0
原创粉丝点击