精华内容
下载资源
问答
  • 这两天写的一个STM32上的内存管理函数,实现了malloc和free以及remalloc几个函数.还实现了一个内存使用率查询的函数. 思路如下: 将内存分块管理. 内存池等分为固定大小的内存块. 建立一个内存状态表,对应每个块,...

      

    详细参考:http://www.openedv.com/posts/list/954.htm#

    先自己开一开大的内存用于堆空间


    这两天写的一个STM32上的内存管理函数,实现了malloc和free以及remalloc几个函数.还实现了一个内存使用率查询的函数.
    思路如下:
    将内存分块管理.
    内存池等分为固定大小的内存块.
    建立一个内存状态表,对应每个块,有多少个块,状态表就有多少个元素,一一对应.
    通过状态表的值判断该块内存是否可用(为0则表示可用,为其他值则表示被占用了,而且占用的内存块数量,就是该值的数字)

    初始化的时候,状态表的值全0,代表所有的内存块都未被占用.当需要分配的时候,malloc从内存块的最高地址往下查找,查找到连续的空内存大于等于要分配的内存的时候,结束此次分配,返回地址给要分配的指针,完成一次malloc. free的时候,就比较简单了,只要找到所分配的内存对应在状态表的位置,然后把状态表的值清0,及实现free.

    内存使用率则通过查询状态表有多少个非0值,来计算占用率.

    代码如下:
    malloc.h头文件:
    #ifndef __MALLOC_H
    #define __MALLOC_H
    //  
    //本程序只供学习使用,未经作者许可,不得用于其它任何用途
    //ALIENTEK 开发板
    //内存管理 代码     
    //正点原子@ALIENTEK
    //技术论坛:www.openedv.com
    //创建日期:2011/7/5 
    //版本:V1.0
    //版权所有,盗版必究。
    //Copyright(C) 正点原子 2009-2019
    //All rights reserved
    //********************************************************************************
    //没有更新信息
    //   

    typedef unsigned long  u32;
    typedef unsigned short u16;
    typedef unsigned char  u8;   
    #ifndef NULL
    #define NULL 0
    #endif

    #define MEM_BLOCK_SIZE   32          //内存块大小为32字节
    #define MAX_MEM_SIZE   10*1024       //最大管理内存 10K
    #define MEM_ALLOC_TABLE_SIZE MAX_MEM_SIZE/MEM_BLOCK_SIZE //内存表大小

    //内存管理控制器
    struct _m_mallco_dev
    {
     void (*init)(void);     //初始化 【c封装类的时候,如果要封装函数,最好没有参数】
     u8 (*perused)(void);         //内存使用率
     u8  membase[MAX_MEM_SIZE];   //内存池
     u16 memmap[MEM_ALLOC_TABLE_SIZE];  //内存管理状态表
     u8  memrdy;        //内存管理是否就绪
    };
    extern struct _m_mallco_dev mallco_dev;  //在mallco.c里面定义

    void mymemset(void *s,u8 c,u32 count);  //设置内存
    void mymemcpy(void *des,void *src,u32 n);//复制内存 

    void mem_init(void);      //内存管理初始化函数(外/内部调用)
    u32 mem_malloc(u32 size);     //内存分配(内部调用)
    u8 mem_free(u32 offset);     //内存释放(内部调用)
    u8 mem_perused(void);      //获得内存使用率(外/内部调用) 

    //用户调用函数
    void myfree(void *ptr);       //内存释放(外部调用)
    void *mymalloc(u32 size);     //内存分配(外部调用)
    void *myrealloc(void *ptr,u32 size);  //重新分配内存(外部调用)
       
    #endif


    malloc.c文件:
    #include "malloc.h"   
    //  
    //本程序只供学习使用,未经作者许可,不得用于其它任何用途
    //ALIENTEK 开发板
    //内存管理 代码     
    //正点原子@ALIENTEK
    //技术论坛:www.openedv.com
    //创建日期:2011/7/5 
    //版本:V1.0
    //版权所有,盗版必究。
    //Copyright(C) 正点原子 2009-2019
    //All rights reserved
    //********************************************************************************
    //没有更新信息
    //   

    //内存管理控制器
    struct _m_mallco_dev mallco_dev=
    {
     mem_init, //内存初始化
     mem_perused,//内存使用率
     0,   //内存池
     0,   //内存管理状态表
     0,     //内存管理未就绪
    };

    //复制内存
    //*des:目的地址
    //*src:源地址
    //n:需要复制的内存长度(字节为单位)
    void memcpy(void *des,void *src,u32 n)  
    {  
        u8 *xdes=des;
     u8 *xsrc=src; 
        while(n--)*xdes++=*xsrc++;  
    }  
    //设置内存
    //*s:内存首地址
    //c :要设置的值
    //count:需要设置的内存大小(字节为单位)
    void memset(void *s,u8 c,u32 count)  
    {  
        u8 *xs = s;  
        while(count--)*xs++=c;  
    }    
    //内存管理初始化  
    void mem_init(void)  
    {  
        memset(mallco_dev.membase, 0, sizeof(mallco_dev.membase));//内存池素有数据清零  
        mallco_dev.memrdy=1;//内存管理初始化OK  
    }  
    //获取内存使用率
    //返回值:使用率(0~100)
    u8 mem_perused(void)  
    {  
        u16 used=0;  
        u32 i;  
        for(i=0;i<MEM_ALLOC_TABLE_SIZE;i++)  
        {  
            if(mallco_dev.memmap[i])used++; 
        }  
        return used*100/MEM_ALLOC_TABLE_SIZE;  
    }  
    //内存分配(内部调用)
    //size:要分配的内存大小(字节)
    //返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址 
    u32 mem_malloc(u32 size)  
    {  
        signed long offset=0;  
        u16 nmemb; //需要的内存块数  
     u16 cmemb=0;//连续空内存块数
        u32 i;  
        if(!mallco_dev.memrdy)mallco_dev.init();//未初始化,先执行初始化 
        if(size==0)return 0XFFFFFFFF;//不需要分配

        nmemb=size/MEM_BLOCK_SIZE;   //获取需要分配的连续内存块数
        if(size%MEM_BLOCK_SIZE)nmemb++;  
        for(offset=MEM_ALLOC_TABLE_SIZE-1;offset>=0;offset--)//搜索整个内存控制区  
        {     
      if(!mallco_dev.memmap[offset])cmemb++; //连续空内存块数增加
      else cmemb=0;       //连续内存块清零
      if(cmemb==nmemb)      //找到了连续nmemb个空内存块
      {
                for(i=0;i<nmemb;i++)      //标注内存块非空 
                {  
                    mallco_dev.memmap[offset+i]=nmemb;  
                }  
                return (offset*MEM_BLOCK_SIZE);//返回偏移地址  
      }
        }  
        return 0XFFFFFFFF;//未找到符合分配条件的内存块  
    }  
    //释放内存(内部调用) 
    //offset:内存地址偏移
    //返回值:0,释放成功;1,释放失败;  
    u8 mem_free(u32 offset)  
    {  
        int i;  
        if(!mallco_dev.memrdy)//未初始化,先执行初始化
     {
      mallco_dev.init();    
            return 1;//未初始化  
        }  
        if(offset<MAX_MEM_SIZE)//偏移在内存池内. 
        {  
            int index=offset/MEM_BLOCK_SIZE;//偏移所在内存块号码  
            int nmemb=mallco_dev.memmap[index];   //内存块数量
            for(i=0;i<nmemb;i++)     //内存块清零
            {  
                mallco_dev.memmap[index+i]=0;  
            }  
            return 0;  
        }else return 2;//偏移超区了.  
    }  
    //释放内存(外部调用) 
    //ptr:内存首地址 
    void myfree(void *ptr)  
    {  
     u32 offset;  
        if(ptr==NULL)return;//地址为0.  
        offset=(u32)ptr-(u32)&mallco_dev.membase;  
        mem_free(offset);//释放内存     
    }  
    //分配内存(外部调用)
    //size:内存大小(字节)
    //返回值:分配到的内存首地址.
    void *mymalloc(u32 size)  
    {  
        u32 offset;  
        offset=mem_malloc(size);  
        if(offset==0XFFFFFFFF)return NULL;  
        else return (void*)((u32)&mallco_dev.membase+offset);  
    }  
    //重新分配内存(外部调用)
    //*ptr:旧内存首地址
    //size:要分配的内存大小(字节)
    //返回值:新分配到的内存首地址.
    void *myrealloc(void *ptr,u32 size)  
    {  
        u32 offset;  
        offset=mem_malloc(size);  
        if(offset==0XFFFFFFFF)return NULL;     
        else  
        {  
            memcpy((void*)((u32)&mallco_dev.membase+offset),ptr,size);//拷贝旧内存内容到新内存   
            myfree(ptr);               //释放旧内存
            return (void*)((u32)&mallco_dev.membase+offset);          //返回新内存首地址
        }  
    }

     

    最后测试代码如下:
    int main(void)
    {    
     u8 *ptr;
     u16 *ptr1;
     u32 *ptr2;
      u32 *ptr3;


     u8 i;
       Stm32_Clock_Init(9);//系统时钟设置
     delay_init(72);  //延时初始化
     uart_init(72,9600); //串口1初始化  
     LED_Init();
      //LCD_Init();  

     ptr=(u8*)mymalloc(100);
     if(*ptr)i=0;
     i=mallco_dev.perused();//查看使用率
     ptr1=(u16*)mymalloc(2*100);
     i=mallco_dev.perused();//查看使用率
     ptr2=(u32*)mymalloc(4*100);
     i=mallco_dev.perused();//查看使用率

     myfree(ptr); 
     i=mallco_dev.perused();//查看使用率
     ptr3=(u32*)mymalloc(4*20);
     i=mallco_dev.perused();//查看使用率

     myfree(ptr1);
     i=mallco_dev.perused();//查看使用率

     ptr=(u8*)mymalloc(8*32);
     
     myfree(ptr2);
     i=mallco_dev.perused();//查看使用率
     myfree(ptr3);
     i=mallco_dev.perused();//查看使用率

     if(i)i=0;

     usmart_dev.init();
     POINT_COLOR=RED;      
       while(1) 
     {     
      LED0=!LED0;      
      delay_ms(500); 
     }               
    }

    欢迎大家在自己的工程里面使用该内存管理代码,如有任何疑问,请回帖!谢谢.

     

     

     

     

    展开全文
  • 内存管理算法实现

    千次阅读 2016-11-03 21:08:45
    在上一节,我们得知可用内存的大小后,我们就可以开发一个简单的管理算法去管理和分配可用用内存

    本节代码的调试和实现过程可参看视频:
    Linux kernel Hacker, 从零构建自己的内核

    在上一节,我们得知可用内存的大小后,我们就可以开发一个简单的管理算法去管理和分配可用用内存。

    首先创建一个头文件mem_util.h,用来定义内存管理模块相关的数值,变量和接口:

    #define  MEMMAN_FREES  4096
    
    struct FREEINFO {
        unsigned int addr, size;
    };
    
    struct MEMMAN {
        int frees, maxfrees, lostsize, losts;
        struct FREEINFO free[MEMMAN_FREES];
    };
    
    void memman_init(struct MEMMAN *man);
    
    unsigned int memman_total(struct MEMMAN *man);
    
    unsigned int memman_alloc(struct MEMMAN *man, unsigned int size);
    
    int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size);
    
    

    FREEINFO 结构用来表示可用内存的起始地址和大小,MEMMAN 表示内存管理器,其中的frees 表示当前可用内存对应的FREEINO结构体有多少个,maxfrees 表示我们的内存管理器最多可以容纳多少个可用内存片,一个可用内存片就是一个FREEINFO结构体。当有内存释放时,有些释放后的内存块无法重现加入内存管理器,这些内存块就得丢掉,那么lostsize 就用来记录,内存管理器总共放弃了多少内存碎片,losts记录的是碎片的数量。

    memman_init 用来初始化内存管理区结构体,memman_total用来计算一个内存管理区存储着多少可用的内存,memman_alloc 用来从指定的内存管理器对象中获取可用内存,memman_free 则用于释放不再需要的内存片。

    我们再看看相关实现(mem_util.c):

    #include "mem_util.h"
    
    void memman_init(struct MEMMAN *man) {
        man->frees = 0;
        man->maxfrees = 0;
        man->lostsize = 0;
        man->losts = 0;
    }
    
    unsigned int memman_total(struct MEMMAN *man) {
        unsigned int i, t = 0;
        for (i = 0; i < man->frees; i++) {
            t += man->free[i].size;
        }
    
        return t;
    }
    
    unsigned int memman_alloc(struct MEMMAN *man, unsigned int size) {
        unsigned int i, a;
        for (i = 0; i < man->frees; i++) {
            if (man->free[i].size >= size) {
                a = man->free[i].addr;
                man->free[i].size -= size;
                if (man->free[i].size == 0) {
                    man->frees--;
                }
    
                return a;
            }
        }
    
        return 0;
    }
    
    int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size) {
        int i, j;
        for (i = 0; i < man->frees; i++) {
            if (man->free[i].addr > addr) {
                break;
            }
        }
    
        if (i > 0) {
            if (man->free[i-1].addr + man->free[i-1].size == addr) {
               man->free[i-1].size += size;
               if (i < man->frees) {
                   if (addr + size == man->free[i].addr) {
                       man->free[i-1].size += man->free[i].size;
                       man->frees--;
                   }
               }
    
               return 0;
            }
        }
    
        if (i < man->frees) {
            if (addr + size == man->free[i].addr) {
               man->free[i].addr = addr;
               man->free[i].size += size;
               return 0;
            }
        }
    
        if (man->frees < MEMMAN_FREES) {
            for (j = man->frees; j > i; j--) {
                man->free[j] = man->free[j-1];
            }
    
            man->frees++;
            if (man->maxfrees < man->frees) {
                man->maxfrees = man->frees;
            }
    
            man->free[i].addr = addr;
            man->free[i].size = size;
            return 0;
        }
    
        man->losts++;
        man->lostsize += size;
        return -1;
    }
    

    头三个函数逻辑比较简单,复杂的是内存释放时的处理逻辑。当有内存释放的时候,我们需要把释放内存的起始地址和大小作为参数传入。假定我们要释放的内存片起始地址是 0x200, 大小是0x100, 如果内存管理对象中存在着一片可用内存,起始地址是0x100, 长度为0x100, 那么当前释放的内存片就可以跟原有可用内存合并,合并后的内存块就是起始地址为0x100, 长度为0x200的一片内存块。

    如果内存管理对象不但含有起始地址为0x100, 长度为0x100的内存片,而且还含有起始地址为0x300, 长度为0x100的内存片,这样的话,三块内存片就可以合并成一块内存,也就是起始地址为0x100, 长度为0x300的一个大块内存片。

    这就是代码if( i > 0) {….} 这个模块的逻辑。

    如果不存在上面的情况,比如当前内存管理模块存在的内存块是其实地址为0x100, 长度为0x50, 另一块内存块起始地址是0x350, 长度为0x100:
    FREEINFO{ addr : 0x100; size : 0x50;};
    FREEINFO{addr: 0x350; size: 0x100};

    这样的话,我们就构建一个对应于要释放内存的FREEINFO对象,然后把这个对象插入到上面两个对象之间:
    FREEINFO{ addr : 0x100; size : 0x50;};

    FREEINFO{addr: 0x200; size: 0x100};

    FREEINFO{addr: 0x350; size: 0x100};

    这就是对应于if (i < man->frees){…} 这个分支的主要逻辑。

    如果当前所有可用内存的起始地址都大于要释放的内存块,则将释放的内存块插到最前面,例如当前可用内存块为:

    FREEINOF {addr: 0x350; size: 0x100;}
    FREEINFO {addr: 0x460; size: 0x100;}

    那么释放起始地址为0x200的内存块后,情况如下:

    FREEINFO{addr: 0x200; size: 0x100;}
    FREEINOF {addr: 0x350; size: 0x100;}
    FREEINFO {addr: 0x460; size: 0x100;}

    或者如果当前释放的内存块起始地址大于所有可用内存块,例如要释放的内存块起始地址是0x450, 其他可用的内存块起始地址分别是0x100, 0x300, 那么释放的内存块则添加到末尾:
    FREEINFO{addr: 0x100; size: 0x100;}
    FREEINOF {addr: 0x350; size: 0x50;}
    FREEINFO {addr: 0x450; size: 0x100;}

    这就是 if (man->frees < MEMMAN_FREES) {…} 的实现逻辑。

    如果以上情况都不满足的话,那么当前回收的内存块则直接丢弃。

    一般而言,操作系统的内存算法不可能如此简单,内存分配算法是系统内核最为复杂的模块之一,我们当前先从简单入手,后面在慢慢引入分页机制,实现更复杂的内存管理算法。

    由于当前内存管理模块与原来C语言实现的模块是分开的,因此它们需要单独编译,然后再把两个编译好的 .o 文件合并成一个模块,编译过程如下:

    1: 先使用命令编译mem_util.c
    gcc -m32 -fno-asynchronous-unwind-tables -s -c -o mem_util.o mem_util.c

    2: 再使用命令编译原来的C语言模块:
    gcc -m32 -fno-asynchronous-unwind-tables -s -c -o write_vga_desktop.o write_vga_desktop.c

    3:把两个编译好的模块链接成一个模块:
    ld -m elf_i386 -r write_vga_desktop.o mem_util.o -o ckernel.o

    4:把ckernel.o 反汇编,然后再添加到内核的汇编模块中一起编译:
    ./objconv -fnasm ckernel.o ckernel.asm

    把上面所以步骤完成后,执行效果如下:
    这里写图片描述

    从显示出的信息看,虚拟机总内存是1G, 3FE MB 等于 1022M, 也就是说可用内存为1022M.

    有了内存管理机制后,我们后面可以在此基础上,实现图层叠加管理,也就是窗口相互叠加时,互不影响,要实现这种功能,就需要为每个窗口分配相应的内存以便在叠加时能够互不影响。

    展开全文
  • 两年前一位同事和我说过,单片机不能实现动态内存管理,两天后我在keil上利用malloc()在coterx m3芯片上实现了一个链表,然后把代码给他看了,对固执的人摆事实好过讲道理。之后我觉得使用malloc()并不能满足我的...

    两年前一位同事和我说过,单片机不能实现动态内存管理,两天后我在keil上利用malloc()在coterx m3芯片上实现了一个链表,然后把代码给他看了,对固执的人摆事实好过讲道理。

    之后我觉得使用malloc()并不能满足我的需求,然后想自己实现一个heap管理。

    这就是这篇文章的由来,这是我今天下午突然想到的一种简单的方法,其实这很没有意义,因为我能想到的东西,别人都已经做出来了。嗯,姑且当是一个编程题的实现吧。

    数据结构很简单,就是先开辟一个大数组,然后将数组分割,每个块大小固定,我定义为16个字节,每个块用一个字节管理,然后记录下有几个块没有被使用。

    malloc的实现,输入形参和malloc一样,就是内存需求大小,申请成功返回地址,不成功返回NULL。实现思想如下,先计算的出需要几个内存块,然后检测剩余内存块是否足够,如果足够,再检测是否连续,都满足的话,将第一个内存块的管理单元赋值1,第二个赋值2,以此类推。然后将剩余内存块数量减去使用掉的数量,返回地址。

    free的实现,输入形参和free一样,就是malloc获得的地址,无返回。实现思想如下,先计算出地址相对heap起始地址的偏移,然后得出相应的内存管理单元,如果该内存管理单元为1,将该单元以下连续管理单元都清空,然后将剩余内存块加上相应的地址,返回。

    这份代码算是1.0版本,没有dubug过,不建议使用。没有debug的原因是没有想到怎么实现碎片管理的办法,我想在2.0版本上面实现,这个姑且算是一份伪代码吧。如果谁有内存碎片管理算法的一些想法的话,欢迎讨论。

    #include "string.h"
    #define HEAP_QLY 16
    
    struct general_buf
    {
    	uint32_t block_qly;			//剩余缓存块
    	uint8_t dirty[100];			//缓存空间脏标志	
    	uint8_t buffer[HEAP_QLY*100];		//缓存空间
    }Heap_Buf;
    
    /*
     *	功能:通用缓存初始化
     *  输入:无
     *  输入:无
     *  返回:无
     *  备注:
    */
    void heap_buffer_init(void)
    {
    	Heap_Buf.block_qly = sizeof(Heap_Buf.dirty);
    	memset(Heap_Buf.buffer,0,sizeof(Heap_Buf.buffer));
    	memset(Heap_Buf.dirty,0,sizeof(Heap_Buf.dirty));
    }
    
    /*
     *	功能:堆内存获取函数
     *  输入:无
     *  输入:无
     *  返回:无
     *  备注:
    */
    void* my_malloc(uint32_t size)
    {
    	uint32_t size_need=size/HEAP_QLY+(size%16?1:0);/*获得块需求*/
    	uint32_t i,j;
    	uint32_t free_num=0;
    	uint8_t* address=NULL;
    	uint8_t enought_space=0;
    	
    	if(size_need>Heap_Buf.block_qly)
    		return NULL;
    	
    	for(i=0;i<Heap_Buf.block_qly;i++){
    		if(!Heap_Buf.dirty[i]){
    			free_num++;
    			if(free_num>=size_need){
    				enought_space=1;
    				break;
    			}
    		}else
    			free_num=0;
    	}
    	
    	if(enought_space){
    		Heap_Buf.block_qly -= free_num;
    		address = &Heap_Buf.buffer[(i-(free_num-1))*HEAP_QLY];
    		for(j=0;j<free_num;j++)
    			Heap_Buf.dirty[(i-(free_num-1))+j] = j;
    	}
    	
    	return address;
    }
    
    /*
     *	功能:堆内存释放函数
     *  输入:无
     *  输入:无
     *  返回:无
     *  备注:
    */
    void my_free(void* address)
    {
    	uint32_t offset=((uint8_t*)address-Heap_Buf.buffer)/HEAP_QLY;
    	uint32_t i=0;
    	
    	if(Heap_Buf.dirty[offset] != 1){
    		return;
    	}else{
    		 
    		do{
    			offset += i;
    			i = Heap_Buf.dirty[offset];
    			Heap_Buf.dirty[offset] = 0;
    			Heap_Buf.block_qly++;
    		}while(1!=i||0!=i||offset>=sizeof(Heap_Buf.dirty));
    	}
    }
    
    
    
    
    

    写于2018年3月20日夜

    深圳

    展开全文
  • iOS内存管理——alloc/release/dealloc方法的GNUstep实现与Apple的实现接上篇关于iOS内存管理的规则考我们通过alloc/release/dealloc方法的具体实现来深入探讨内存管理。什么是GNUstep `GNUstep`是`Cocoa`框架的...

    关于阅读《Object-C高级编程-iOS与OS X多线程和内存管理》一书后的iOS内存管理系列思考

    iOS内存管理——alloc/release/dealloc方法的GNUstep实现与Apple的实现

    接上篇关于iOS内存管理的规则考我们通过alloc/release/dealloc方法的具体实现来深入探讨内存管理。

    什么是GNUstep

        `GNUstep`是`Cocoa`框架的互换框架,从源代码的实现上来说,虽然和Apple不是完全一样,但是从开发者的角度来看,两者的行为和实现方式是一样,或者说是非常相似。
        因为`NSObject`类的`Foundation`框架是不开源的,不过,`Foundation`框架使用的`Core Foundation`框架的源码和通过调用`NSObject`类进行内存管理部分的源码是公开的。
        所以,我们先来了解`GNUstep`的实现方式,有助于我们去理解`Apple`的实现方式。
    

    GNUstep更多详细内容请查看官网

    搞出些事情

    • GNUstep/modules/core/base/Source/NSObject.m alloc
    + (id)alloc {
        return [self allocWithZone:NSDefaultMallocZone()];
    }
    
    + (id)allocWithZone:(struct _NSZone *)zone {
        return NSAllocateObject(self, 0, zone);
    }

    我们可以看到内存的分配最重是通过allocWithZoneNSAllocateObject方法分配的。

    我们来看一下NSAllocateObject的声明:

    NSAllocateObject(<#Class  _Nonnull aClass#>, <#NSUInteger extraBytes#>, <#NSZone * _Nullable zone#>)

    实现:

    static obj_layout {
        NSUInteger retained;
    };
    
    inline id
    
    NSAllocateObject(Class  _Nonnull aClass, NSUInteger extraBytes, NSZone * _Nullable zone){
        int size = 计算容纳对象所需内存大小;
        id new = NSZoneMalloc(zone, size);
        memset(new, 0, zize);
        new = (id)&((static obj_layout *) new)[1];
             
    }
    

    通过上述代码可以看出,NSAllocateObject方法通过调用NSZoneMalloc方法来分配存放对象所需的内存空间,之后将该空间置0,最后返回作为对象而使用的指针。

    区域
    NSZone是什么?
    NSZone是Apple为了防止内存碎片化而引入结构。对内存分配的区域本身进行多重化管理,根据使用对象的目的,对象的大小分配内存,从而提高内存的管理效率。
    但是,现在iOS的运行时系统知识简单的忽略了区域的概念。运行时系统中的内存管理本身已经极具效率,使用区域来管理区域反而会引起效率低下以及源码复杂化的问题。
    多区域内存管理

    去掉NSZone后的alloc源码

    • GNUstep/modules/core/base/Source/NSObject.m alloc去NSZone版
    + (id)alloc {
        int size = sizeof(struct objc_layout) + 对象大小;
        struct objc_layout *p = (struct objc_layout *)calloc(1, size);
        return (id)(p+1);
    }

    objc_layout中的retained是用来记录引用计数

    此结构体在每一块对象内存的头部,盖对象内存块全部置0后返回,如图:
    alloc返回内存图

    我们可以使用retainCount实例方法获取当前的引用计数。

    NSObject *obj1 = [[NSObject alloc] init];
    
    NSLog(@"obj1 retain count = %lu", (unsigned long)[_obj1 retainCount]);

    打印结果:
    2016-12-21 16:04:07.579 acm[66972:880151] obj1 retain count = 1

    - (NSInteger)retainCount {
        return NSExtraRefCount(self) + 1;
    }
    
    inline NSUInteger
    NSExtraRefCount(id  _Nonnull object) {
        return ((struct obj_layout *)object)[-1].retained;
    }

    这段代码中 ((struct obj_layout *)object)[-1]可能对C语言不熟悉的同学不太理解,我来解释一下。

    首先[-1]的意思是指针向上移动一个对象的大小。
    前边我们的讲述中,在对象内存的上方会有一个小的内存块用来存储引用计数的结构体struct obj_layout
    (struct obj_layout *)来修饰object是为了当我们移动指针的时候移动的大小是 一个struct obj_layout的大小,而不是一个object的大小.


    前边我们也说到,每次内存分配后都会把对象的内存区域置0,这是为什么?
    这个问题我也有些蒙蔽,暂且搁置。希望有同学给予解答。

    因为为了满足内存管理的规则,在分配时全部置0,所以retained0.
    在调用retainCount的时候会return NSExtraRefCount(self)+ 1,我们可以推测出,retain方法使retained变量+1,而release方法是retained变量减1.

    • GNUstep/modules/core/base/Source/NSObject.m retain
    - (id)retain {
        NSIncrementExtraRefCount(self);
        return self;
    }
    
    inline void
    NSIncrementExtraRefCount(id  _Nonnull object)  {
    <!--retained最大值判断-->
        if (((struct obj_layout *)object)[-1].retained == UINT_MAX - 1) {
            [NSException raise:NSInternalInconsistencyException format:@"NSIncrementExtraRefCount() asked to increment too far"];
        }
    <!--没有超过最大值则加1-->          
        ((static obj_layout *)object)[-1].retained++;
    
    }
    • GNUstep/modules/core/base/Source/NSObject.m release
    - (void)release {
        if (NSDecrementExtraRefCountWasZero(self)) {
            [self dealloc];
        }
    }
    
    BOOL
    NSDecrementExtraRefCountWasZero(id  _Nonnull object) {
        if (((struct obj_layout *)object)[-1].retained == 0) {
            return YES;
        } else {
            return NO;
        }
    }

    相信大家对上边的两个方法的规则已经有一个了结,在调用retainrelease都会判断极值,分别在条件允许时调用抛异常或dealloc

    我们再来看看dealloc时如何实现的

    • GNUstep/modules/core/base/Source/NSObject.m dealloc
    - (void)dealloc {
        NSDeallocateObject(self);
    }
    
    inline void
    NSDeallocateObject(id  _Nonnull object) {
    
        struct obj_layout *o = &((struct obj_layout *)object)[-1];
    
        free(o);
    }
    

    不难发现,dealloc方法获取到从存放 obj_layout 结构的内存头部开始的指针后释放它所指向的内存块。

    GNUstep中alloc/retain/release/dealloc中的实现总结

    • Objective-C的对象中存有引用计数这个变量.
    • 调用alloc或者retain后,引用计数+1.
    • 调用release后,引用计数-1.
    • 引用计数为0时,调用dealloc废弃对象.

    连猜带想,看苹果如何实现的alloc/retain/release/dealloc

    我们看完了GNUstep中的各个方法实现方式,再来猜想推测一下苹果的实现,如今NSObject类的源码没有公开,我们利用lldb(Xcode调试器)和iOS的历史来追溯其实现方式。

    在NSObject类的alloc类方法上设置断点,追踪函数调用

    //NSObject alloc调用顺序
    +alloc
    +allocWithZone:
    class_createInstance
    calloc

    可以看出这个调用顺序和GNUstep的调用顺序非常的相似

    //GNUstep alloc调用顺序
    +alloc
    +allocWithZone:
    NSAllocateObject
    NSZoneMalloc

    通过 objc4库中的 reuntime/objc-runtime-new.mm文件进行确认。

    官方文档中的class_createInstance
    class_createInstance

    我们在来看看retainCount/retain/release的实现方式(调用栈)。

    • retainCount
    -retainCount
    __CFDoExternRefOperation
    CFBasicHashGetCountOfKey
    • retain
    -retain
    __CFDoExternRefOperation
    CFBasicHashAddValue
    • release
    -release
    __CFDoExternRefOperation
    CFBasicHashRemoveValue

    这个时候们必需去看看__CFDoExternRefOperation做了什么

    • CF/CFRuntime.c __CFDoExternRefOperation
    int __CFDoExternRefOperation(uintptr_t op,id obj) {
        CFBasicHashRef table = 取得对象对应的散列表(obj);
        int countswitch(op) {
            //retainCount
            case OPERATION_retainCount:
                count = CFBasicHashGetCountOfKey(table, obj);
                return count;
    
            //retain
            case OPERATION_retaion:
                CFBasicHashAddValue(table, obj);
                return obj;
    
            //release
            case OPERATION_release:
                count = CFBasicHashRemoveValue(table, obj);
                return 0==count;
        }
    }

    看到这里我们也就可以大致推测三个方法的实现方式了:

    - (NSUInteger)retainCount {
        return (NSUInteger)__CFDoExternRefOperation(OPERATION_retainCount, self);
    }
    
    - (id)retain {
        return (id)__CFDoExternRefOperation(OPERATION_retain, self);
    
    }
    
    - (void)release {
        return __CFDoExternRefOperation(OPERATION_release, self);
    }

    推测苹果实现alloc/retain/release/retainCount方法的总结

    我们可以从__CFDoExternRefOperation函数和由次函数调用的各个函数名看出,苹果采用了散列表(引用计数表)来管理引用计数。

    如图:

    不难看出,GNUstep将引用计数保存在对象占用内存块头部的变量中,而苹果的实现,则是保存在用用计数表的记录中,GNUstep的实现在我们看来是没有任何问题的,但是苹果这么实现也有它的道理。

    使用内存块头部管理引用计数好处:

    • 少量代码即可实现
    • 能够在同一张表中统一的管理引用计数用内存块和对象用内存块。

    使用引用计数表来管理引用计数的好处:

    • 对象用内存块的分配无需考虑头部内存块的存在。
    • 引用计数表各记录中存有内存块地址,可以从各个记录追溯到对象的内存块。即使出现故障导致对象占用的内存块损坏,但只要引用计数表没有损坏,就能确认各个内存块的位置。
    • 有助于利用工具检测各个对象的持有者是否存在,即检测内存泄漏的原理。

    参考资料

    《Object-C高级编程——iOS与OS X多线程和内存管理》--[日]Kazuki Sakamoto Tomohiko Furumoto著

    展开全文
  • C语言实现简单的内存管理机制

    千次阅读 2018-03-15 22:04:22
    在C类型程序中,栈内存比较珍贵,大部分用在局部或者类成员(因为稀少… 不适合长时间占用一块栈内存),对于大量...针对这种情况,我以自己的习惯写了一个简单的内存管理结构,加深自己对内存的理解。 首先简单说...
  • 用C++编写高效稳定的软件,有效的内存管理通常是绕不开的一个题目,尤其是对于复杂庞大的商业软件。
  • 模拟内存管理设计与实现: 模拟实现动态分区内存管理机制。设计和实现关于内存管理的内存布局初始化及内存申请分配、内存回收等基本功能操作函数,尝试对 256MB 的用户内存空间进行动态分区方式模拟管理。内存分配的...
  • 文章目录Netty如何实现高性能内存管理ByteBuf分类池化(Pooled)对象管理1 算法设计1.1 整体原理1.2 算法结构1.3 申请/释放内存1.4 巨型对象内存管理1.5 小对象内存管理2 弹性伸缩2.1 PoolChunk管理2.2 PoolSubpage...
  • Android内存管理机制详解

    万次阅读 多人点赞 2012-12-13 10:37:33
    这是Linux内存管理的一个优秀特性,在这方面,区别于 Windows的内存管理。主要特点是,无论物理内存有多大,Linux都将其充份利用,将一些程序调用过的硬盘数据读入内存,利用内存读写的高速特性来提高Linux系统的...
  • 内存管理

    万次阅读 2015-03-12 17:09:40
    内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内存管理在C++中无处不在,内存泄漏几乎在每个C++...
  • Flink内存管理源码解读之内存管理

    千次阅读 2016-04-06 22:55:54
    回顾上一篇文章我们谈了Flink自主内存管理的一些基础的数据结构。那篇中主要讲了数据结构的定义,这篇我们来看看那些数据结构的使用,以及内存的管理设计。概述这篇文章我们主要探讨Flink的内存管理类MemoryManager...
  • 内存管理我的理解是分为两个部分,一个是物理内存的管理,另一个部分是物理内存地址到虚拟地址的转换。 物理内存管理 内核中实现了很多机制和算法来进行物理内存的管理,比如大名鼎鼎的伙伴系统,以及slab分配器等等...
  • 用单链表实现内存管理

    千次阅读 2008-09-01 16:04:00
    用单链表实现内存管理 在C语言论坛中看了dgarc发表的一个贴子后,按要求写了一段代码,可以实现内存分配的管理,避免内存泄漏,欢迎各位测试,如遇bug,敬请指出,我将进行有针对性的改进,然后重新贴出来。...
  • 日期 内核版本 架构 作者 GitHub ... Linux内存管理内存管理的上下文中, 初始化(initialization)可以有多种含义. 在许多CPU上, 必须显式设置适用于Linux内核的内存模型. 例如在x86_32上需要切换到
  • javascript中的内存管理

    千次阅读 2021-03-01 19:52:50
    在c语言中,我们需要手动分配和释放对象的内存,但是在java中,所有的内存管理都交给了java虚拟机,程序员不需要在手动进程内存的分配和释放,大大的减少了程序编写的难度。 同样的,在javascript中,内存管理也是...
  • Java内存管理

    万次阅读 多人点赞 2016-03-08 21:36:06
    不过看了一遍《深入Java虚拟机》再来理解Java内存管理会好很多。接下来一起学习下Java内存管理吧。请注意上图的这个:我们再来复习下进程与线程吧:进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,...
  • Android 内存管理机制

    千次阅读 2018-09-03 14:41:45
    Linux的内存管理机制:Android毕竟是基于Linux内核实现的操作系统,因此有必要了解一下Linux的内存管理机制。 Android的内存管理相关知识:Android又不同于Linux,它是一个移动操作系统,因此其内存管理上也有...
  • Flink 原理与实现内存管理

    千次阅读 2018-09-07 11:35:13
    原文地址:https://yq.aliyun.com/articles/57815?spm=a2c4e.11153940.blogrightarea64820.29.71e5167cM5y5cc ...基于 JVM 的数据分析引擎都需要面对将大量数据存到内存中,这就不得不面对 JVM 存...
  • STM32内存管理

    千次阅读 2019-03-15 15:28:43
    内存管理实现方法有很多种,他们其实最终都是要实现 2 个函数: malloc 和 free; malloc 函数用于内存申请, free 函数用于内存释放。 介绍一种简单的实现方法,分块式内存管理: 原理: malloc分析 首先确定.....
  • 一、内存管理概述 (一)、虚拟内存实现结构 (1)内存映射模块(mmap):负责把磁盘文件的逻辑地址映射到虚拟地址,以及把虚拟地址映射到物理地址。 (2)交换模块(swap):负责控制内存内容的换入和换出,...
  • 内存寻址 、硬件中的分段与分页 、Linux内存管理 页与内存管理区 、kmalloc()和vmalloc()
  • Linux内存管理

    万次阅读 2015-11-08 15:24:57
    Linux内存管理 前言 对于内存部分需要知道: 地址映射内存管理的方式缺页异常 正文 在进程看来,内存分为内核态和用户态两部分,经典比例如下: Linux内存-虚拟地址: 从用户态到内核态...
  • malloc内存管理总结

    千次阅读 2018-08-14 13:03:49
    内存管理 内存管理主要包含两个层面的内容: 1、操作系统内核相关的内存管理:物理内存层 2、库函数层:主要是堆内存,即malloc实现层 如果用户还有需要会在用户层再做一次内存管理机制,例如SGI STL中的...
  • 前面两个是用户态在堆上分配一段连续(虚拟地址)的内存空间,然后可以通过free释放,但是,同时也会有很多人对其背后的实现机制不了解。 这篇文章则是通过介绍这三个函数,并简单的予以实现,对比现有C的标准库...
  • Java应用程序是运行在JVM上的,得益于JVM的内存管理和垃圾收集机制,开发人员的效率得到了显著提升,也不容易出现内存溢出和泄漏问题。但正是因为开发人员把内存的控制权交给了JVM,一旦出现内存方面的问题,如果不...
  • 这篇文章是《读薄「Linux 内核设计与实现」》系列文章的第 V 篇,本文主要讲了以下问题:Linux 内核中的时间概念和时间表示,硬件时钟和定时器以及时间中断和内存管理的相关知识。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,030,910
精华内容 412,364
关键字:

内存管理的实现