zoukankan      html  css  js  c++  java
  • 堆栈的存储机制学习

    这两天继续学习了皓子之前的文章,伙伴分配器的一个简单实现很有意思 http://coolshell.cn/articles/10427.html ,加上之前看了一些关于堆存储的知识,赶紧总结一下加固一下记忆。

    首先说一下堆的内存申请和释放,在c中大多在系统的应用程序中会使用malloc和free来申请和释放内存,由于malloc和free讲究快速实现申请和释放,因此这两个操作不会去进行多余的检测。在一个应用程序启动后,系统会给它分配一块内存,分别存储着代码段、栈内存、堆内存、静态存储区等。我们假想堆内存是一条线性的很长的内存条,当执行malloc(size)时,malloc会在这条线性区域中寻找满足size大小的连续内存区域然后返回首地址给对应的指针变量。free则会寻找指针变量所指的地址区域,将指定的内存区域设置为空闲状态。
    这里就有一个问题,如何在malloc中设置申请的内存大小,如何在free时确定需要将地址之后多大的内存区域还给空闲的内存。这里先需要说明,内存的空闲段有一种情况下是存储在一个链表中,即将还没分配出去的内存信息存储在一个链表中,每次malloc时在这个链表中寻找最佳的可以的内存区域分配,而free时则将一块内存还给这个链表。
    终于要讲malloc和free终究在怎么做了,malloc时根据编译器的不同会在申请时多申请4个Byte或者更多的Byte来存储这段申请的内存信息。
    例如,int * array = (int*) malloc(40 * sizeof(int));
    从代码上看32位Linux系统中编译器分配了40*4=160(Byte)的内存空间,并将首地址返回给array指针变量。实际上在array地址之前,仍然有4个Byte的空间去记录160+4=164(Byte)的信息,表示这次分配总共占用了164个Byte,当然可能还会多余存储其他信息,这个我也没有去验证过。而在free时,这个信息就能够有效地帮助编译器去获取这次free需要去释放多少个字节的资源。
    free(array);
    free会对在array之前的的4个Byte去分析,知道这个指针对应的内存对应了164个Byte,然后将这么多内存释放,还给之前讲的空闲链表。所以如果我们调用了array[-1]来修改了array之前的信息可能造成在free时的信息错误。
    另外,在malloc之后,每个指针会记录自己对应的内存地址。而这时可能有一个问题,在不断地申请和释放之后内存中会有很多的碎片,生成不连续的内存,使本身富余的内存资源在申请时找不到合适的连续内存获取。这时候就需要进行内存压缩,将这些变量对应的内存移位压缩到一起。但变量却记录着原本对应的地址。在这里就有一种方法,二级指针,通过在变量和内存之间添加多一层一级指针,变量对应的只是一级指针的地址,而这个是不变的,一级指针会根据压缩后的地址来变化其值,这些都是编译器内部的变化,不会对用户态造成什么影响。只是在用户态使用时可能需要使用Lock和Unlock来锁定变量,在操作时不允许进行优化压缩。(在移动内存内容时,如果两块内存可能出现重叠部分则需要使用memmove而不是memcopy)
    面对这种内存碎片有很多的解决方法,在malloc申请时也有各种优化算法(最小最优、最大最优、最先的最优)。现在有一种比较合理的方法是伙伴分配器。
    伙伴分配器是在内存分配时只分配2的指数大小,避免出现太多碎片,当内存被释放时检查是否可以将两块内存合并为更大的空闲内存块。整个内存可以用一个完全二叉树来管理。以下为一个简单的伙伴管理系统的实现

    偷图一张:

    在这里通过完全二叉树管理了16个单位的内存,通过完全二叉树来记录内存使用情况。深度每加1,节点对应的内存大小就减半。每个节点保存着对应的内存段中最大的可连续存储空间大小。每次申请内存时都会从根节点开始遍历,如果左孩子的对应值满足申请内存的大小则继续从左孩子向下寻找,否则从右孩子向下寻找。依次循环直到找到一个节点的对应值等于所需申请的内存大小(这个值是需要提前归一化为2的幂次方的)。当对应的某一个节点下的内存被申请出去后,节点的对应值变为0,然后依次对它的父节点作计算,更新他们对应的可用节点内存大小。当对应的内存被释放后,节点的对应值重修修改回来,父节点也被再次更新。

    整个伙伴分配器的管理结构体为:

    struct buddy {
        unsigned size;
        unsigned longest[1];
    }

    这里面的longest变量同之前学习的零长度数组一个道理,可以在申请内存时根据size大小扩容。size是指整个系统管理的内存大小,longest[i]是对应的每一个节点的最长可分配内存大小。

    再放上作者两个辅助函数,对于我这种菜鸟还是很受用的,分别时判断x是否为2的幂和将size向上取2的幂

    #define IS_POWER_OF_2(x) (!( x & (x-1)))
    
    static unsigned fixsize(unsigned size) {
        size |= size >>1;
        size |= size >>2;
        size |= size >>4;
        size |= size >>8;
        size |= size >>16;
        return size+1;
    }

    伙伴管理系统的初始化:

    struct buddy* buddy_new(int size) {
        unsigned node_size;
        struct buddy * self;
        int i;
    
        if(size < 1 || !IS_POWER_OF_2(size)) {
            return NULL;
        }
        self = (struct buddy*)malloc(size * sizeof(unsigned));
        self->size = size;
        node_size = size * 2;
        for(i = 0; i < size * 2 - 1; i++) {
            if(IS_POWER_OF_2(i+1)) {
                node_size /= 2;
            }
            longest[i] = node_size;
        }
        return self;
    }

    初始化size大小的系统,且对每一个节点进行赋值,给出对应的最长内存长度。

    系统的注销:

    void buddy_destroy(struct * buddy self) {
        free(self);
    }

    系统分配内存:

    int buddy_alloc(struct buddy * self, int size) {
        unsigned index = 0;
        unsigned node_size;
        unsigned offset = 0;
        if(self == NULL || size < 1) {
            return -1;
        }
        if(!IS_POWER_OF_2(size)) {
            size = fixsize(size);
        }
        if(size > self->longest[index]) {
            return -1;
        }
        node_size = self->size;
        while(size != self->longest[index]) {
            if(self->longest[LEFT_LEAF(index)] > size) {
                index = LEFT_LEAF(index);
            }
            else {
                index = RIGHT_LEAF(index);
            }
            node_size /= 2;
        }
        self->longest[index] = 0;
        offset = (index + 1)* node_size - self->size;
    
        while(index) {
            index = PARENT(index);
            self->longest[index] = 
                MAX(self->longest[LEFT_LEAF(index)], self->longest[RIGHT_LEAF(index)]);
        }
        return offset;
    }

    找到最小的内存快进行分配后,然后循环更新父节点的值

    系统回收内存:

    void buddy_free(struct buddy * self, int offset) {
        unsigned node_size, index = 0;
        unsigned left_longest, right_longest;
        assert(self && offset >= 0 && offset < self->size);
    
        node_size = 1;
        index = offset + self->size -1;
    
        while(self->longest[index]) {
            node_size  = node_size<<1;
            if(index == 0) {
                return;
            }
            index = PARENT(index);
        }
        self->longest[index] = node_size;
    
        while(index) {
            index = PARENT(index);
            node_size = node_size<<1;
            left_longest = self->longest[LEFT_LEAF(index)];
            right_longest = self->longest[RIGHT_LEAF(index)];
            if(left_longest + right_longest == node_size) {
                self->longest[index] = node_size;
            }
            else {
                self->longest[index] = MAX(left_longest, right_longest);
            }
        }
    }

    通过位移计算出对应节点和内存大小值,恢复,循环更新父节点

    看完这个之后对我的帮助,本身对伙伴内存管理没什么概念,看完这个也大致的得到了解。目前还在看栈空间内的内存分配,还没看完,看完之后也更新到这篇文章里,继续加油!

  • 相关阅读:
    Leetcode 191.位1的个数 By Python
    反向传播的推导
    Leetcode 268.缺失数字 By Python
    Leetcode 326.3的幂 By Python
    Leetcode 28.实现strStr() By Python
    Leetcode 7.反转整数 By Python
    Leetcode 125.验证回文串 By Python
    Leetcode 1.两数之和 By Python
    Hdoj 1008.Elevator 题解
    TZOJ 车辆拥挤相互往里走
  • 原文地址:https://www.cnblogs.com/noanswer/p/3656083.html
Copyright © 2011-2022 走看看