精华内容
下载资源
问答
  • 内存管理算法

    2011-12-15 08:45:19
    用户内存容量4页到32页; 用户虚存容量为32K。 在用户虚存中,按每K存放64条指令排列虚存地址,即2048条指令在虚存中的存放方式为: 第 0 条-第 63 条指令为第0页(对应虚存地址为[0,63]) 第64条-第127条指令为第1...
  • 众所周知,Redis的所有数据都存储在内存中,但是内存是一种有限的资源,所以为了防止Redis无限制的使用内存,在启动Redis时可以通过配置项maxmemory来指定其最大能使用的内存容量。例如可以通过以下配置来设置Redis...

    众所周知,Redis的所有数据都存储在内存中,但是内存是一种有限的资源,所以为了防止Redis无限制的使用内存,在启动Redis时可以通过配置项 maxmemory 来指定其最大能使用的内存容量。例如可以通过以下配置来设置Redis最大能使用 1G 内存:

    maxmemory 1G

    当Redis使用的内存超过配置的 maxmemory 时,便会触发数据淘汰策略。Redis提供了多种数据淘汰的策略,如下:

    • volatile-lru: 最近最少使用算法,从设置了过期时间的键中选择空转时间最长的键值对清除掉

    • volatile-lfu: 最近最不经常使用算法,从设置了过期时间的键中选择某段时间之内使用频次最小的键值对清除掉

    • volatile-ttl: 从设置了过期时间的键中选择过期时间最早的键值对清除

    • volatile-random: 从设置了过期时间的键中,随机选择键进行清除

    • allkeys-lru: 最近最少使用算法,从所有的键中选择空转时间最长的键值对清除

    • allkeys-lfu: 最近最不经常使用算法,从所有的键中选择某段时间之内使用频次最少的键值对清除

    • allkeys-random: 所有的键中,随机选择键进行删除

    • noeviction: 不做任何的清理工作,在redis的内存超过限制之后,所有的写入操作都会返回错误;但是读操作都能正常的进行

    可以在启动Redis时,通过配置项 maxmemory_policy 来指定要使用的数据淘汰策略。例如要使用 volatile-lru 策略可以通过以下配置来指定:

    maxmemory_policy volatile-lru

    LRU算法

    LRU是 Least Recently Used 的缩写,即最近最少使用,很多缓存系统都使用此算法作为淘汰策略。

    最简单的实现方式就是把所有缓存通过一个链表连接起来,新创建的缓存添加到链表的头部,如果有缓存被访问了,就把缓存移动到链表的头部。由于被访问的缓存会移动到链表的头部,所以没有被访问的缓存会随着时间的推移移动的链表的尾部,淘汰数据时只需要从链表的尾部开始即可。下图展示了这个过程: 5f09731564431a5a5beb4bfd6a576ba3.png

    Redis的LRU算法

    Redis使用了结构体 robj 来存储缓存对象,而 robj 结构有个名为 lru 的字段,用于记录缓存对象最后被访问的时间,Redis就是以 lru 字段的值作为淘汰依据。robj 结构如下:

    typedef struct redisObject {
    ...unsigned lru:24;
    ...
    } robj;

    当缓存对象被访问时,便会更新此字段的值。代码如下:

    robj *lookupKey(redisDb *db, robj *key, int flags) {
    dictEntry *de = dictFind(db->dict,key->ptr);if (de) {
    robj *val = dictGetVal(de);/* Update the access time for the ageing algorithm. * Don't do it if we have a saving child, as this will trigger * a copy on write madness. */if (server.rdb_child_pid == -1 &&
    server.aof_child_pid == -1 &&
    !(flags & LOOKUP_NOTOUCH))
    {if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {updateLFU(val);
    } else {
    val->lru = LRU_CLOCK(); // 更新lru字段的值
    }
    }return val;
    } else {return NULL;
    }
    }

    lookupKey() 函数用于查找key对应的缓存对象,所以当缓存对象被访问时便会调用此函数。

    Redis数据淘汰

    接下来我们分析一下当Redis内存使用超过配置的最大内存使用限制时的处理方式。

    Redis在处理每一个命令时都会检查内存的使用是否超过了限制的最大值,处理命令是通过 processCommand() 函数进行的,检查内存使用情况的代码如下:

    int processCommand(client *c) {
    ...if (server.maxmemory && !server.lua_timedout) {int out_of_memory = freeMemoryIfNeededAndSafe() == C_ERR;if (server.current_client == NULL) return C_ERR;if (out_of_memory &&
    (c->cmd->flags & CMD_DENYOOM ||
    (c->flags & CLIENT_MULTI && c->cmd->proc != execCommand))) {flagTransaction(c);addReply(c, shared.oomerr);return C_OK;
    }
    }
    ...
    }

    检查内存的使用情况主要通过 freeMemoryIfNeededAndSafe() 函数进行,而 freeMemoryIfNeededAndSafe() 函数最终会调用 freeMemoryIfNeeded() 函数进行处理,由于 freeMemoryIfNeeded() 函数比较庞大,所以我们分段来进行分析:

    int freeMemoryIfNeeded(void) {
    ...size_t mem_reported, mem_tofree, mem_freed;mstime_t latency, eviction_latency;long long delta;int slaves = listLength(server.slaves);
    ...if (getMaxmemoryState(&mem_reported,NULL,&mem_tofree,NULL) == C_OK)return C_OK;
    mem_freed = 0;if (server.maxmemory_policy == MAXMEMORY_NO_EVICTION)goto cant_free;

    freeMemoryIfNeeded() 函数首先会调用 getMaxmemoryState() 函数来获取Redis的内存使用情况,如果 getMaxmemoryState() 函数返回 C_OK,表示内存使用总量还没有超出限制,直接返回 C_OK 就可以了。如果 getMaxmemoryState() 函数不是返回 C_OK,表示内存使用总量已经超出限制,需要进行数据淘汰,需要淘汰数据的大小通过 mem_tofree 参数返回。

    当然,如果配置的淘汰策略为 noeviction,表示不能进行数据淘汰,所以需要返回 C_ERR 表示有错误。

    接着分析剩余的代码片段:

        latencyStartMonitor(latency);while (mem_freed < mem_tofree) {int j, k, i, keys_freed = 0;static unsigned int next_db = 0;
    sds bestkey = NULL;int bestdbid;
    redisDb *db;
    dict *dict;
    dictEntry *de;if (server.maxmemory_policy & (MAXMEMORY_FLAG_LRU|MAXMEMORY_FLAG_LFU) ||
    server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL)
    {struct evictionPoolEntry *pool = EvictionPoolLRU;while(bestkey == NULL) {unsigned long total_keys = 0, keys;for (i = 0; i < server.dbnum; i++) {
    db = server.db+i;
    dict = (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) ?
    db->dict : db->expires;if ((keys = dictSize(dict)) != 0) {evictionPoolPopulate(i, dict, db->dict, pool);
    total_keys += keys;
    }
    }if (!total_keys) break; /* No keys to evict. */for (k = EVPOOL_SIZE-1; k >= 0; k--) {if (pool[k].key == NULL) continue;
    bestdbid = pool[k].dbid;if (server.maxmemory_policy & MAXMEMORY_FLAG_ALLKEYS) {
    de = dictFind(server.db[pool[k].dbid].dict,
    pool[k].key);
    } else {
    de = dictFind(server.db[pool[k].dbid].expires,
    pool[k].key);
    }if (pool[k].key != pool[k].cached)sdsfree(pool[k].key);
    pool[k].key = NULL;
    pool[k].idle = 0;if (de) {
    bestkey = dictGetKey(de);break;
    } else {/* Ghost... Iterate again. */
    }
    }
    }
    }

    如果内存使用总量超出限制,并且配置了淘汰策略,那么就开始数据淘汰过程。在上面的代码中,mem_tofree 变量表示要淘汰的数据总量,而 mem_freed 变量表示已经淘汰的数据总量。所以在 while 循环中的条件是 mem_freed < mem_tofree,表示淘汰的数据总量一定要达到 mem_tofree 为止。

    前面介绍过,Redis的淘汰策略有很多中,所以进行数据淘汰时需要根据配置的策略进行。如果配置的淘汰策略是 LRU/LFU/TTL 的话,那么就进入 if 代码块。在 if 代码块里,首先调用 evictionPoolPopulate() 函数选择一些缓存对象样本放置到 EvictionPoolLRU 数组中。evictionPoolPopulate() 函数后面会进行分析,现在只需要知道 evictionPoolPopulate() 函数是选取一些缓存对象样本就可以了。

    获取到缓存对象样本后,还需要从样本中获取最合适的缓存对象进行淘汰,因为在选择样本时会把最合适的缓存对象放置在 EvictionPoolLRU 数组的尾部,所以只需要从 EvictionPoolLRU 数组的尾部开始查找一个不为空的缓存对象即可。

            else if (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM ||
    server.maxmemory_policy == MAXMEMORY_VOLATILE_RANDOM)
    {for (i = 0; i < server.dbnum; i++) {
    j = (++next_db) % server.dbnum;
    db = server.db+j;
    dict = (server.maxmemory_policy == MAXMEMORY_ALLKEYS_RANDOM) ?
    db->dict : db->expires;if (dictSize(dict) != 0) {
    de = dictGetRandomKey(dict);
    bestkey = dictGetKey(de);
    bestdbid = j;break;
    }
    }
    }

    如果使用随机淘汰策略,那么就进入 else if 代码块,这部分代码的逻辑很简单,如果配置的淘汰策略是 volatile-random,那么就从有过期时间的缓存对象中随机获取,否则就从所有的缓存对象中随机获取。

            if (bestkey) {
    db = server.db+bestdbid;
    robj *keyobj = createStringObject(bestkey,sdslen(bestkey));propagateExpire(db,keyobj,server.lazyfree_lazy_eviction);
    delta = (long long) zmalloc_used_memory();latencyStartMonitor(eviction_latency);// 删除缓存对象if (server.lazyfree_lazy_eviction)dbAsyncDelete(db,keyobj);elsedbSyncDelete(db,keyobj);latencyEndMonitor(eviction_latency);latencyAddSampleIfNeeded("eviction-del",eviction_latency);latencyRemoveNestedEvent(latency,eviction_latency);
    delta -= (long long) zmalloc_used_memory();
    mem_freed += delta;
    server.stat_evictedkeys++;notifyKeyspaceEvent(NOTIFY_EVICTED, "evicted",
    keyobj, db->id);decrRefCount(keyobj);
    keys_freed++;if (slaves) flushSlavesOutputBuffers();if (server.lazyfree_lazy_eviction && !(keys_freed % 16)) {if (getMaxmemoryState(NULL,NULL,NULL,NULL) == C_OK) {
    mem_freed = mem_tofree;
    }
    }
    }

    如果找到要淘汰的缓存对象,那么就开始释放缓存对象所占用的内存空间。除了需要释放缓存对象占用的内存空间外,还需要进行一些其他的操作,比如把淘汰的缓存对象同步到从服务器和把淘汰的缓存对象追加到 AOF文件 中等。

    当条件 mem_freed < mem_tofree 为假时便会退出 while 循环,说明Redis的内存使用总量已经小于最大的内存使用限制,freeMemoryIfNeeded() 函数便会返回 C_OK 表示成功执行。

    淘汰数据样本采集

    前面说了,当使用非随机淘汰策略时需要进行数据采样(volatile-lru/volatile-lfu/volatile-ttl/allkeys-lru/allkeys-lfu),数据采样通过 evictionPoolPopulate() 函数进行,由于此函数比较庞大,所以对代码分段分析:

    void evictionPoolPopulate(int dbid, dict *sampledict, dict *keydict, struct evictionPoolEntry *pool) {int j, k, count;
    dictEntry *samples[server.maxmemory_samples];
    count = dictGetSomeKeys(sampledict,samples,server.maxmemory_samples);

    evictionPoolPopulate() 函数首先调用 dictGetSomeKeys() 函数从缓存对象集合中获取一些样本,并保存在 samples 数组中。

        for (j = 0; j < count; j++) {unsigned long long idle;
    sds key;
    robj *o;
    dictEntry *de;
    de = samples[j];
    key = dictGetKey(de);if (server.maxmemory_policy != MAXMEMORY_VOLATILE_TTL) {if (sampledict != keydict) de = dictFind(keydict, key);
    o = dictGetVal(de);
    }if (server.maxmemory_policy & MAXMEMORY_FLAG_LRU) {
    idle = estimateObjectIdleTime(o);
    } else if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
    idle = 255-LFUDecrAndReturn(o);
    } else if (server.maxmemory_policy == MAXMEMORY_VOLATILE_TTL) {
    idle = ULLONG_MAX - (long)dictGetVal(de);
    } else {serverPanic("Unknown eviction policy in evictionPoolPopulate()");
    }

    上面的代码主要是获取样本缓存对象的排序权值 idel,如果使用 LRU淘汰算法,那么就调用 estimateObjectIdleTime() 函数获取排序权值,estimateObjectIdleTime() 函数用于获取缓存对象有多长时间没有被访问。排序按照 idle 的值升序排序,就是说 idle 的值越大,就排到越后。

            k = 0;while (k < EVPOOL_SIZE &&
    pool[k].key &&
    pool[k].idle < idle) k++;if (k == 0 && pool[EVPOOL_SIZE-1].key != NULL) {continue;
    } else if (k < EVPOOL_SIZE && pool[k].key == NULL) {
    } else {if (pool[EVPOOL_SIZE-1].key == NULL) {
    sds cached = pool[EVPOOL_SIZE-1].cached;memmove(pool+k+1,pool+k,sizeof(pool[0])*(EVPOOL_SIZE-k-1));
    pool[k].cached = cached;
    } else {
    k--;
    sds cached = pool[0].cached;if (pool[0].key != pool[0].cached) sdsfree(pool[0].key);memmove(pool,pool+1,sizeof(pool[0])*k);
    pool[k].cached = cached;
    }
    }int klen = sdslen(key);if (klen > EVPOOL_CACHED_SDS_SIZE) {
    pool[k].key = sdsdup(key);
    } else {memcpy(pool[k].cached,key,klen+1);sdssetlen(pool[k].cached,klen);
    pool[k].key = pool[k].cached;
    }
    pool[k].idle = idle;
    pool[k].dbid = dbid;
    }
    }

    上面这段代码的作用是:根据 idle 的值找到当前缓存对象所在 EvictionPoolLRU 数组的位置,然后把缓存对象保存到 EvictionPoolLRU 数组中。以下插图解释了数据采样的过程: c42de8eebc932e38623952163db565f8.png

    所以 EvictionPoolLRU 数组的最后一个元素便是最优的淘汰缓存对象。

    从上面的分析可知,淘汰数据时只是从样本中找到最优的淘汰缓存对象,并不是从所有缓存对象集合中查找。由于前面介绍的 LRU算法 需要维护一个LRU链表,而维护一个LRU链表的成本比较大,所以Redis才出此下策。

    展开全文
  • 目的:深入掌握内存调度算法的概念原理和实现方法。 设计要求:编写程序实现: 先进先出页面置换算法(FIFO) 最近最久未使用页面置换算法(LRU) 最佳置换页面置换算法(OPT) 专题:设计一个虚拟存储区和内存...

    页面置换算法

    目的:深入掌握内存调度算法的概念原理和实现方法。

    设计要求:编写程序实现:

    1. 先进先出页面置换算法(FIFO)
    2. 最近最久未使用页面置换算法(LRU)
    3. 最佳置换页面置换算法(OPT)

    专题:设计一个虚拟存储区和内存工作区,编程序演示以上三种算法的具体实现过程,并计算访问命中率。演示页面置换的三种算法。通过随机数产生一个指令序列,将指令序列转换成为页地址流。计算并输出各种算法在不同内存容量下的命中率

    分析:

    命中率= 1 - 中断淘汰次数/命令总数

    1、先进先出页面置换算法(FIFO)

    **设内存分成3个页帧,X表示中断,{}中为CPU要调用的页面**

    该算法总是淘汰最先进入内存的页面,即选择在内存中驻留时间最久的页面予以淘汰.

    实现:设置一个数组模拟内存,用一个int一直记录内存中最先进入的页面index,每次需要中断淘汰页面时则淘汰记录在小标中那一个

    2、最近最久未使用页面置换算法(LRU)

    **设内存分成3个页帧,X表示中断,{}中为CPU要调用的页面**

    分析:利用局部性原理,根据一个作业在执行过程中过去的页面访问历史来推测未来的行为。它认为过去一段时间里不曾被访问过的页面,在最近的将来可能也不会再被访问。所以,这种算法的实质是:当需要淘汰一个页面时,总是选择在最近一段时间内最久不用的页面予以淘汰。

    实现:利用另一个数组记录每个内存页最后一次被使用的时钟;当需要淘汰时选取最久的那一页淘汰

    3、最佳置换页面置换算法(OPT)

    **设内存分成3个页帧,X表示中断,{}中为CPU要调用的页面**

    分析:从主存中移出永远不再需要的页面,如果没有这样的页面存在,那就选择最长时间不需要访问的页面,来保证最低的缺页率;但是人们目前还无法预知,一个进程在内存的若干个页面中,哪一个页面是未来最长时间内不再被访问的,因而该算法是无法实现的,但是可以利用该算法取评价其他的算法。

    实现:见代码不想写了

     

     

    JAVA代码实现如下

    测试主类Test:

    
    
    public class Test {
        public static void main(String[] args) {
            //PBSMA.FIFO(10, 3, 12);
            // PBSMA.LRU(10, 3, 12);
            PBSMA.OPT(10, 3 , 12);
        }
    }
    

    PBSM类:

    
    
    
    /**
     * Page buffer scheduling management algorithm
     */
    public class  PBSMA {
        /**
         * 先进先出页面置换算法,复杂度O(1)
         * @param posPageNum 进程页数
         * @param memoryPageFrame 内存页帧数
         * @param len  CPU命令长
         */
        public static void FIFO(int posPageNum,int memoryPageFrame,int len) {
            System.out.println("==========FIFO开始===================");
    
            String []memory = new String[memoryPageFrame];//内存区
            String[]command = VioUtil.gennerateArray(len, posPageNum);//命令队列
            int count = 0;//中断记录器
            int ponit = 0;//队列顺序记录
            VioUtil.showArr(memory, 0,"");
            int b ;
            for (String com:command){
    
                b = VioUtil.findPage(com, memory);
                if (b == -1){
    
                    memory[ponit] = com; //将页面调入内存
                    count++;//中断次数增加
                    ponit = (ponit+1)==memoryPageFrame? 0:ponit+1;//记录在内存中最久的页
    
                }
                VioUtil.showArr(memory, b,com);
            }
    
            VioUtil.showResult(count, len);
            System.out.println("============FIFO结束===============");
    
        }
    
        /**
         * 最近最久未使用页面置换算法,复杂度O(n)
         * @param posPageNum 进程页数
         * @param memoryPageFrame 内存页帧数
         * @param len   CPU命令长
         */
        public static void LRU(int posPageNum,int memoryPageFrame,int len) {
            System.out.println("=========LRU开始=============");
            String []memory = new String[memoryPageFrame];//内存区
            int []times = new int[memoryPageFrame];//页面使用时间记录器
            String[]command = VioUtil.gennerateArray(len, posPageNum);//命令队列
            int count = 0;//中断记录器
            int ponit = 0;//队列顺序记录
            int time = 0;//时钟
            VioUtil.showArr(memory, 0,"");
            int b;
            int index;//最久未使用页面位置
            for (String com:command){
                time++;//CPU每发出1次命令,时钟自增1
                b = VioUtil.findPage(com, memory);
                if(b == -1){
                    if(count >= memoryPageFrame){   //若内存已满则淘汰最久未使用页,加入新页
                        index = VioUtil.findTimeMix(times);
                        memory[index] = com;
                        times[index] = time;
                    }else {
                        memory[count] = com;
                        times [count] = time;
                    }
                    count++;
                }else {
                    times[b] = time;
                }
                VioUtil.showArr(memory, b, com);
            }
            VioUtil.showResult(count, len);
            System.out.println("=================LRU结束==============");
    
        }
    
        /**
         * 最佳页面置换算法,复杂度O(n*m)
         * @param posPageNum 进程页数
         * @param memoryPageFrame 内存页帧数
         * @param len   CPU命令长
         */
        public static void OPT(int posPageNum,int memoryPageFrame,int len) {
            System.out.println("=========OPT开始=============");
            String []memory = new String[memoryPageFrame];//内存区
            String[]command = VioUtil.gennerateArray(len, posPageNum);//命令队列
            int count = 0;//中断记录器
            VioUtil.showArr(memory, 0,"");
            int b ;
            int index = 0;//若内存中页面在后续命令都未出现默认替换内存中第一个页
            int farIndex = 0;
            int temp;
            String comTemp= " ";
            for (int i = 0;i<len;i++){
    
                b = VioUtil.findPage(command[i], memory);
                if(b == -1){
                    if(count >= memoryPageFrame){   //若内存已满则淘汰最久未使用页,加入新页
                        for (int j = 0; j<memoryPageFrame; j++){
                            temp = VioUtil.findPage(memory[j],command);
                            if (temp == -1){
                                index = j;
                                break;//发现一个后续不要的页则直接淘汰
                            }
                            else if (temp > farIndex){
                                farIndex = temp;
                                index = j;
                            }
                        }
                        memory[index] = command[i];
                        comTemp = command[i];
                        command[i] = " ";
                    }
                    else {
    
                        memory[count] = command[i];
                        comTemp = command[i];
                        command[i] = " ";
                    }
                    count++;
                }else {
                    comTemp = command[i];
                    command[i] = " ";
                }
                farIndex = 0;
                VioUtil.showArr(memory, b, comTemp);
            }
    
    
            VioUtil.showResult(count, len);
    
    
            System.out.println("=========OPT开始=============");
        }
    
        }
    

    VioUtil工具类:

    
    
    import java.net.BindException;
    import java.util.Random;
    
    public class VioUtil {
        /**
         * 生成随机数列,数列会随Random的参数变化而变化
         * @param len
         * @param max
         * @return
         */
        public static String[]  gennerateArray(int len,int max){
            String[] arr = new String[len];
            Random rand = new Random(54);
            int num;
            for (int i=0; i < len; i++){
                num = rand.nextInt(max);
                arr[i] = num+"";
            }
            return arr;
        }
    
        /**
         * 判断内存是否有该页
         * @param target
         * @param arr
         * @return
         */
        public static int findPage(String target,String[]arr){
            int b = -1; //若内存中无该页则返-1
            for (int i = 0;i<arr.length;i++){
                if (target.equals(arr[i])){
                    b = i;
                    break;
                }
            }
    
            return b;
        }
    
        /**
         * 输出内存页帧情况
         * @param arr
         * @param b
         * @param com
         */
        public static void showArr(String arr[],int b,String com){
            String s = "|";
            for (String a:arr){
    
                if ("".equals(a)){
                    s = s+" |";
                }else {
                    s = s+a+"|";
                }
    
            }
            if (b == -1){
                System.out.println(s+"X"+"{"+ com +"}");
            }else {
                System.out.println(s+" "+"{"+ com +"}");
            }
    
        }
    
        /**
         * 显示命中率和中断次数
         * @param count
         * @param len
         */
        public static void showResult(int count, int len){
            float hitRate = 1- (float)count/len;
            System.out.println("命中率:"+hitRate);
            System.out.println("中断次数"+count);
        }
    
        /**
         * LUR找出最久未使用页面
         * @param times
         * @return 最久页面位置
         */
        public static int findTimeMix(int [] times){
            int index = 0;
            for(int i=0;i<times.length;i++){
                if (times[index] > times[i]){
                    index = i;
                }
            }
            return index;
        }
    
    }
    
    
    

     

    展开全文
  • 实验四:内存分配算法 ——动态分区方式主存的分配和回收 本机操作系统:macOS Big Sur 一、前言 一个好的计算机系统不仅要有一个足够容量的、存取速度高的、稳定可靠的主存储器,而且要能合理地分配和使用这些存储...

    实验四:内存分配算法

    ——动态分区方式主存的分配和回收

    本机环境:macOS Big Sur

    一、前言
    一个好的计算机系统不仅要有一个足够容量的、存取速度高的、稳定可靠的主存储器,而且要能合理地分配和使用这些存储空间。当用户提出申请主存储器空间时,存储管理必须根据申请者的要求,按一定的策略分析主存空间的使用情况,找出足够的空闲区域分配给申请者。当作业撤离或主动归还主存资源时,则存储管理要收回作业占用的主存空间或归还部分主存空间。主存的分配和回收的实现是与主存储器的管理方式有关的,软件模拟内存的分配。

    二、实验目的
    通过本实验帮助学生理解在动态分区管理方式下应怎样实现主存空间的分配和回收。

    三、实验内容
    在动态分区管理方式下采用不同的分配算法实现主存分配和实现主存回收。

    四、实验要求

    • (1)可变分区方式是按作业需要的主存空间大小来分割分区的。当要装入一个作业时,根据作业需要的主存量查看是否有足够的空闲空间,若有,则按需要量分割一个分区分配给该作业;若无,则作业不能装入。随着作业的装入、撤离、主存空间被分成许多个分区,有的分区被作业占用,而有的分区是空闲的。例如:
      为了说明哪些区是空闲的,可以用来装入新作业,必须要有一张空区说明表,格式如下:
      其中,起址——指出一个空闲区的主存起始地址。
      长度——指出从起始地址开始的一个连续空闲区的长度。
      状态——有两种状态,一种是“未分配”状态,指出对应的由起址指出的某个长度的区域是空闲区;另一种是“空表目”状态,表示表中对应的登记项目是空白(无效),可用来登记新的空闲区(例如,作业撤离后,它所占的区域就成了空闲区,应找一个“空表目”栏登记归还区的起址和长度且修改状态)。由于分区的个数不定,所以空闲区说明表中应有适量的状态为“空表目”的登记栏目,否则造成表格“溢出”无法登记。
      上述的这张说明表的登记情况是按提示(1)中的例所装入的三个作业占用的主存区域后填写的。
    • (2)当有一个新作业要求装入主存时,必须查空闲区说明表,从中找出一个足够大的空闲区。有时找到的空闲区可能大于作业需要量,这时应把原来的空闲区变成两部分:一个部分分给作业占用;另一部分又成为一个较小的空闲区。为了尽量减少由于分割造成的“碎片”,在作业请求装入时,尽可能地利用主存的低地址部分的空闲区,而尽量保存高地址部分有较大的连续空闲区域,以利于大型作业的装入。为此,在空闲区说明表中,把每个空闲区按其地址顺序登记,即每个后继的空闲区其起始地址总是比前者大。为了方便查找还可使表格“紧缩”,总是让“空表目”栏集中在表格的后部。
    • (3)采用首次适应算法或循环首次算法或最佳适应算法分配主存空间。由于本实验是模拟主存的分配,所以当把主存区分配给作业后并不实际启动装入程序装入作业,而用输出“分配情况”来代替。(即输出当时的空闲区说明表及其内存分配表)
    • (4)当一个作业执行结束撤离时,作业所占的区域应该归还,归还的区域如果与其它空闲区相邻,则应合成一个较大的空闲区,登记在空闲区说明表中。例如,在提示(1)中列举的情况下,如果作业2撤离,归还所占主存区域时,应与上、下相邻的空闲区一起合成一个大的空闲区登记在空闲区说明表中。
    • (5)请按首次适应算法或循环首次算法或最佳适应算法设计主存分配和回收的程序。然后按(1)中假设主存中已装入三个作业,且形成两个空闲区,确定空闲说明表的初值。现有一个需要主存量为6K的作业4 申请装入主存;然后作业3 撤离;再作业2 撤离。请你为它们进行主存分配和回收,把空闲区
      说明表的初值以及每次分配或回收后的变化显示出来或打印出来。

    五、测试数据文件格式
    打印程序运行时的初值和运行结果,要求如下:

    • 打印空闲区说明表的初始状态,作业4 的申请量以及为作业4 分配后的空闲区说明表状态;
    • 再依次打印作业3 和作业2 的归还量以及回收作业3,作业2 所占主存后的空闲区说明表。

    六、源代码

    #include <iostream>
    #include <list>
    #include <iomanip>
    #include <string>
    using namespace std;
    
    struct Node{
        int start;
        int end;
        bool occupied;
        int size(){
            return end-start;
        }
        void set(int start, int end, bool occupied=0){
            this->start=start;
            this->end=end;
            this->occupied=occupied;
        }
        void printNode(){
            cout<<"start:"<<start<<" end:"<<end<<" size:"<<this->size()<<endl;
        }
        Node(int start, int end, bool occupied=0){
            this->start=start;
            this->end=end;
            this->occupied=occupied;
        }
        Node(){}
    };
    
    
    bool request(list<Node>*, int, bool); //采用首次适应算法
    bool release(list<Node>*, int);
    void printList(list<Node>*);
    void mergeList(list<Node>*);
    
    
    //bool类型请求函数 如果请求成功会返回1
    bool request(list<Node>* l, int need, bool flag_add){
        if(l->size()==0) return 0;
        for(list<Node>::iterator iter=l->begin(); iter!=l->end(); iter++){
            if(iter->size() >= need && iter->occupied==0){
                //如果找到了合适的位置 则将原iter处一分为二,在前半部分放need,occupied置一
                if(flag_add == 1){
                    Node t_rest(iter->start, iter->start+need, 1); //采用首次适应算法 按顺序找到第一个可以用的
                    list<Node>::iterator iter1;
                    iter1 = l->insert(iter , t_rest);
                    iter->start = iter->start+need;
                }
                return 1;
            }
        }
        return 0;
    }
    
    bool release(list<Node>* l, int index){
        if(l->size()==0) return 0;
        int i=0;
        for(list<Node>::iterator iter=l->begin(); iter!=l->end(); iter++){
            if(i==index && iter->occupied==1){
                iter->occupied=0;
                mergeList(l);
                return 1;
            }
            else i++;
        }
        return 0;
    }
    
    //将连续的空区域合并
    void mergeList(list<Node>* l){
        if(l->size()<=1) return;
        list<Node>::iterator foot1 = l->begin();
        list<Node>::iterator foot2 = l->begin();
        ++foot2; //foot2指向第二个
        while(true){
            if(l->size()==1) break;
            if(foot1->occupied==0 && foot2->occupied==0){
                foot1->end += foot2->size();
                if (foot2==l->end()){
                    l->pop_back();
                    break;
                }
                else if(foot2!=l->end()) foot2=l->erase(foot2);
                else break;
            }
            else{
                foot1++;
                foot2++;
            }
            if(foot2 == l->end()) break;
        }
    }
    
    //打印整个“内存”
    void printList(list<Node>* l){
        list<Node>::iterator iter = l->begin();
        for(int i=0;i<l->size();i++){
            cout<<"No."<<i<<"  /  ";
            cout<<"start:"<<setw(4)<<iter->start<<"  /  end:"<<setw(4)<<iter->end<<"  /  used:"<<iter->occupied<<endl;
            iter++;
        }
    }
    
    int main(){
        list<Node> M;
        Node initialize_m(0,1024);
        M.push_back(initialize_m);
        cout<<"//Memory Management System//"<<endl;
        cout<<"Memory size overall: 1024K"<<endl;
        while(true){
            cout<<"///"<<endl;
            cout<<"PrintMemoryList:"<<endl;
            printList(&M);
            cout<<"Enter 1 to request memory..."<<endl;
            cout<<"Enter 2 to release memory..."<<endl;
            cout<<"Enter 0 to EXIT..."<<endl;
            int i=0;
            cin>>i;
            if(i==0){
                cout<<"MMS EXIT with Code 0"<<endl;
                break;
            }
            else if(i==1){
                cout<<"Enter memory size you request..."<<endl;
                int need;
                cin>>need;
                if(request(&M, need, 1)) cout<<"SUCCEEDED!"<<endl;
                else cout<<"FAILED!"<<endl<<endl;;
            }
            else if(i==2){
                cout<<"Enter memory index you want to release..."<<endl;
                int index;
                cin>>index;
                if(release(&M, index)) cout<<"SUCCEEDED!"<<endl;
                else cout<<"FAILED!"<<endl<<endl;
            }
            else{
                cout<<"MMS EXIT with Code -1"<<endl;
                break;
            }
            cout<<"Press the ENTER key to continue..."<<endl;
            getchar();
            getchar();
        }
        
    }
    
    
    
    展开全文
  • 运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) ...当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。 你是否可以在 O(1) 时间复杂度内完成这两种操作

    运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。

    获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
    写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。如果密钥存在,则更新其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

    你是否可以在 O(1) 时间复杂度内完成这两种操作?

    import java.util.HashMap;
    
    public class LRU {
        public static class Node<K, V>{
            public K key;
            public V value;
            public Node<K, V> last;
            public Node<K, V> next;
    
            public Node(K key, V value){
                this.key = key;
                this.value = value;
            }
        }
    
        public static class NodeDoubleLinkedList<K, V>{
            private Node<K, V> head;
            private Node<K, V> tail;
            public NodeDoubleLinkedList(){
                this.head = null;
                this.tail = null;
            }
    
            public void addNode(Node<K, V> newNode){
                if (newNode == null) {
                    return;
                }
                if (this.head == null){
                    this.head = newNode;
                    this.tail = newNode;
                }else {
                    this.tail.next = newNode;
                    newNode.last = this.tail;
                    this.tail = newNode;
                }
            }
    
            public void moveToTail(Node<K, V> node){
                if (this.tail == node){
                    return;
                }
                if (node == this.head){
                    this.head = node.next;
                    this.head.last = null;
                }else{
                    node.last.next = node.next;
                    node.next.last = node.last;
                }
                node.next = null;
                node.last = this.tail;
                this.tail.next = node;
                this.tail = node;
            }
    
            public Node<K, V> removeHead(){
                if (this.head == null){
                    return null;
                }
                Node<K, V> res = this.head;
                if (this.head == this.tail) {
                    this.head = null;
                    this.tail= null;
                }else {
                   this.head = res.next;
                   res.next = null;
                   this.head.last = null;
                }
                return res;
            }
    
    
        }
    
        public static class MyCache<K, V>{
            private HashMap<K, Node<K, V>> keyNodeMap;
            private NodeDoubleLinkedList<K, V> nodeList;
            private int capacity;
            public MyCache(int capacity){
                if (capacity < 1){
                    throw new RuntimeException("should more than 0");
                }
                this.capacity = capacity;
                this.keyNodeMap = new HashMap<K, Node<K, V>>();
                this.nodeList = new NodeDoubleLinkedList<K, V>();
            }
    
            public V get(K key){
                if (keyNodeMap.containsKey(key)){
                    Node<K, V> res = keyNodeMap.get(key);
                    this.nodeList.moveToTail(res);
                    return res.value;
                }
                return null;
            }
    
            public void set(K key, V value) {
                if (this.keyNodeMap.containsKey(key)){
                    Node<K, V> res = this.keyNodeMap.get(key);
                    res.value = value;
                    this.nodeList.moveToTail(res);
                }else{
                    Node<K, V> newNode = new Node<K, V>(key, value);
                    this.keyNodeMap.put(key, newNode);
                    this.nodeList.addNode(newNode);
                    if (this.keyNodeMap.size() == this.capacity + 1){
                        removeMostUnUsedCache();
                    }
                }
            }
            public void removeMostUnUsedCache(){
                Node<K, V> head = this.nodeList.removeHead();
                K key = head.key;
                this.keyNodeMap.remove(key);
            }
        }
    
        public static void main(String[] args) {
            MyCache<String, Integer> testCache = new MyCache<>(3);
            testCache.set("a", 1);
            System.out.println(testCache.get("a"));
        }
    }
    
    
    展开全文
  • Redis中采用两种算法进行...当内存容量不足时,为了保证程序的运行,这时就不得不淘汰内存中的一些对象,释放这些对象占用的空间,那么选择淘汰哪些对象呢?LRU算法就提供了一种策略,告诉我们选择最近一段时间内...
  • 在我们内存或者cache中有两种替换算法来保证内存或者cache中都是“热点”的数据,一个是LRU和LFU。下面会先分析再给出代码! 在这里的get和set操作的时间复杂度都是O(1),因为这两种速度都是很快的,不能要求O(n...
  • Redis内存回收:LRU算法

    2018-08-20 15:32:00
    Redis技术交流群481804090 ... Redis中采用两种算法进行内存回收,引用计数算法以及LRU算法,在操作系统内存管理一节中,我们...LRU算法作为内存管理的一种有效算法,其含义是在内存有限的情况下,当内存容量不足时...
  • 为了解决效率问题,出现了一种称为“复制”(Copying)的收集算法,该算法根据容量将可用内存分为两个大小相等的块,一次仅使用一个。当该内存块用完时,会将尚存的对象复制到另一个块,然后立即清除已用的内存空间...
  • 根据时间排序,内存不够时,将最久未访问(时间最老(小))的记录删除 要求:put/get 时间复杂度O(1) getO(1)容易想到数组,但是数据结构新增删除容量变化移动难,那么就是hashmap记录指针,然后双向链表维护时间...
  • 有哪些垃圾收集算法 标记-清除算法 复制算法 标记-整理算法 分代收集算法 标记-清除算法(Mark-Sweep) 什么是标记-清除算法? 分为标记和清除两个阶段。...将可用内存容量划分为大小相等的两块
  • 作者:Adam原文:https://www.cnblogs.com/adamwong/p/10678015.html计算机操作系统内存管理是十分重要的,因为其中涉及到很多设计很多算法。《深入理解计算机系统》这本书曾提到过,现在操作系统存储的设计就是“带...
  • 最佳适应算法(最小分配)按照容量递增从小到大的顺序查找,每次分配内存按前面顺序查找,找到第一个合适的,会留下很多外部碎片 最坏适应算法(最大分配)按容量从大到小顺序查找 邻近适应算法每次分配内存时,从上次查找...
  • 虚拟内存是非连续内存分配的一个延续,非连续内存分配在存储空间内可以连续也可以不连续。虚拟内存是在非连续内存分配基础上,可以把一部分内容放到外存中去,让应用程序有更大的空间使用。 需求背景:增长迅速的...
  • 从上图可以看到,越在上面的访问速度越快,但是容量越小,今天要说的就是内存那个环节。 内存是什么 内存是可以记录二进制数据一种器件,它是连续的结构,可以随机读写,断电会丢失数据,所有程序必须从磁盘加载到...
  • 我直接扩大虚拟内存了,就是磁盘容量来增加swap内存大小, 我扩大到了50g https://www.linuxidc.com/Linux/2018-10/154708.htm 最近买了一个CentOS的云主机,因为贫穷限制了我购买的内存大小,只有500M,所以...
  • 已知作业名、作业大小、作业提交时间、内存容量、碎片大小,要求使用分区大小不等的内存分区方法和首次适应分配算法给每次到来的作业分配内存。输出内存分区情况和分配情况。 2.算法描述 分区大小不等的内存分区法:...
  • 演示页面置换的三种算法,通过随机数产生一个指令序列,将指令序列转换为页地址流,计算并输出各种算法在不同内存容量下的命中率。二 程序设计2.1 功能设计产生随机序列功能随机生成1-128之间...
  • 最佳适配算法---内存分配

    千次阅读 2019-09-23 22:06:09
    某基于动态分区存储管理的计算机,其主存容量为55MB(初始为空闲),采用最佳适配(Best Fit)算法,分配和释放的顺序为:分配15MB,分配30MB,释放15MB,分配8MB,分配6MB,此时主存中最大空闲分区的大小是(B)。...
  • 一 需求分析编写程序实现:先进先出页面置换算法(FIFO)...演示页面置换的三种算法,通过随机数产生一个指令序列,将指令序列转换为页地址流,计算并输出各种算法在不同内存容量下的命中率。二 程序设计2.1 功能...
  • ...同时有一个虚拟内存的概念,是指将内存中暂时不需要的部分写入硬盘,看上去硬盘扩展了内存容量,所以叫做“虚拟”内存。使用虚拟内存,应用程序可以使用比实际物理内存更大的内存空间。可...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,477
精华内容 590
关键字:

内存容量算法