内存管理学习(一)简单实现一个固定大小的内存池【转载】

1.内存池的引入 


       (1)内存池顾名思义就是存放内存的池子,直接分配一大块内存存起来,然后慢慢地切出来使用,用完了再还回到池子,后续还能再利用。

    (2)如果我们需要申请一块空间,我们一般都会动态开辟一块空间(new或malloc),它们两个的共同点就是在堆上去申请空间,当然也是有一定限制的。如果我们频繁的去申请释放空间,就会造成许多的内存碎片,内存的浪费也就产生了。如果我们可以将自己需要大小的空间保存起来(不交给操作系统管理),下次需要的时候,从自己保存的内存池子里去取,这样不仅效率高,而且还不会造成太大的浪费。

    (3)内存池的设计主要是为了应对一些特殊场景,比如说:操作系统频繁的分配和释放小块内存,不仅影响分配效率而且易造成外碎片问题,在某种程度上内存池就能很好地解决这类问题,并且内存池还可以避免内存泄露等问题,既然内存池有这么多的优点,那我们该如何去设计一个内存池呢?

源自韦易笑作者的一篇博文中,他在实现一个固定大小的内存分配器的过程中是这样思考的:


即实现一个 FreeList,每个 FreeList 用于分配固定大小的内存块,比如用于分配 32字节对象的固定内存分配器,之类的。每个固定内存分配器里面有两个链表,OpenList 用于存储未分配的空闲对象,CloseList用于存储已分配的内存对象,那么所谓的分配就是从 OpenList 中取出一个对象放到 CloseList 里并且返回给用户,释放又是从 CloseList 移回到 OpenList。分配时如果不够,那么就需要增长 OpenList:申请一个大一点的内存块,切割成比如 64 个相同大小的对象添加到 OpenList中。这个固定内存分配器回收的时候,统一把先前向系统申请的内存块全部还给系统。
(3)有关内碎片和外碎片的理解

造成堆利用率很低的一个主要原因就是内存碎片化。如果有未使用的存储器,但是这块存储器不能用来满足分配的请求,这时候就会产生内存碎片化问题。内存碎片化分为内部碎片和外部碎片。
1>内碎片:
  内部碎片是指一个已分配的块比有效载荷大时发生的,内部碎片的大小就是已经分配的块的大小和它们的有效载荷之差。

2>外碎片:
  外部碎片就是操作系统频繁分配和释放小块内存,导致有足够的内存但不能再连续分配出系统请求的空间大小。如图:


2.内存池的实现

1>这个内存池的思路很简单:每次分配一个node,一个node相当于一个小的对象池,这个小池子用完了,再分配一个尺寸大一倍的node,这些node是以链表的方式链接起来的。(每一个节点管理一块内存,设定各个内存块存储对象的多少按2倍增长)

如图内存池实现机制:

2>内存是如何分配的?

这里分三种情况:

        @优先使用以前释放的空间;
        @如果没有先前释放的空间了,且当前节点还剩下未使用的内存就去内存块中申请;
        @当前节点下的内存不足以我们用来分配,就开辟新的节点以获得新节点下维护的内存对象个数.  

情况一如下图:

上面还有个小问题要注意:

情况二如图:

情况三:

这种情况就是需要开辟新的节点以获得新节点下维护的内存对象个数,需要注意两点就是,新开辟出来的节点在用计数要置为0;链表尾指针last要变动(刚开始一个节点的时候_first和_last指向相同),_last=_last->_next;      

3.下面是代码实现:                          

#pragma once
 
#include<vector>
#include<string>
#include<cstdlib>
#include<iostream>
using namespace std;
 
//用链表实现内存池,每个节点下面挂一块内存
template<class T>
class ObjPool
{
    struct BlockNode
    {
        void* _memory;   //内存块
        BlockNode* _next; //指向下一个节点的指针
        size_t _ObjNum;    //内存对象的个数
 
        BlockNode(size_t ObjNum)
            :_ObjNum(ObjNum)
            ,_next(NULL)
        {
            _memory=malloc(_ItemSize*_ObjNum); //单个对象的大小与内存对象的个数的乘积
            if(_memory==NULL)
            {
                cout<<"out of memory"<<endl;
                exit(1);
            }
        }
 
        ~BlockNode()
        {
            free(_memory);  //_memory是通过malloc在堆上开辟的
            _memory=NULL;
            _next=NULL;
            _ObjNum=0;
        }
    };
public:
    //固定大小的内存池
    ObjPool(size_t InitNum=32,size_t MaxNum=100000)//一个节点下面先默认开32字节大小的内存
        :_CountIn(0)
        ,_MaxNum(MaxNum)
        ,_LastDelete(NULL)
    {
        _first=_last=new BlockNode(InitNum);
    }
    ~ObjPool()
    {
        //释放节点下面挂的内存块
        BlockNode* cur=_first;
        while(cur)
        {
            BlockNode* del=cur;
            cur=cur->_next;
            delete del;   //调用节点的析构函数
            del=NULL;
        }
        _first=_last=NULL;
        _CountIn=0;
    }
    //申请空间
    T* New()
    {
        //1.优先使用以前释放的空间
        //2.去内存块中申请
        //3.申请新的节点对象
        if(_LastDelete)
        {
            T* obj=_LastDelete;
            //强转为二级指针再解引用就能正确的取到该指针所指向的内容,解决了32位程序和64为程序的限制
            _LastDelete=*((T**)_LastDelete);
            //使用的是还回来的内存,所以没必要再对当前节点在用计数_CountIn++
            return new(obj)T;  //new定位表达式
        }
        //所有结点的内存块都没有可以使用的,则重新开辟新的节点
        if(_CountIn==_last->_ObjNum)
        {
            size_t NewNodeObjNum=_last->_ObjNum * 2;
            if(NewNodeObjNum>=_MaxNum)
                _MaxNum=NewNodeObjNum;
 
            _last->_next=new BlockNode(NewNodeObjNum);
            if(_last->_next==NULL)
            {
                throw(-1);
            }
            _CountIn=0; //新开的节点,当前节点在用计数置为0
            _last=_last->_next;
        }
 
        //在还没用的内存块下面去分配使用
        T* obj = (T*)((char*)_last->_memory + _CountIn*_ItemSize);
        _CountIn++;
        return new(obj)T;
    }
 
    //使用完小块内存后还给内存池时,Delete先调对象的析构函数,然后将这块内存头插入自由链表中
    void Delete(T* ptr)
    {
        if(ptr)
        {
            ptr->~T();
        }
        *(T**)ptr=_LastDelete;
        _LastDelete=ptr;
    }
 
    static size_t Get_ItemSize()
    {
    //BlockNode中存储了void* 的一个指针,所以最低限度要开出一个能存放void*指针大小的对象空间
        if(sizeof(void*) >= sizeof(T))
            return sizeof(void*);
        else
            return sizeof(T);
    }
protected:
    size_t _CountIn;   //当前节点在用的计数
    BlockNode* _first; //指向链表的头
    BlockNode* _last; //指向链表的尾
    size_t _MaxNum;  //节点申请内存块的对象个数
    static size_t _ItemSize;  //单个对象的大小
    T* _LastDelete;       //指向最后释放的空间
};
template<class T>
size_t ObjPool<T>::_ItemSize=ObjPool<T>::Get_ItemSize(); //类外调用静态成员函数

 ———————————————— 
版权声明:本文为优快云博主「lc_29503203」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.youkuaiyun.com/qq_29503203/article/details/53488832

<think>首先,用户的问题是:“我想寻找关于段页式存储管理的模拟实现代码请问段页式存储管理模拟实现代码示例”。他们想要段页式存储管理的模拟实现代码示例。根据系统级指令:-使用中文回答。-行内数学表达式用`$...$`。-独立公式用`$$...$$`单独成段。-生成相关问题。-回答中引用的段落末尾自然地添加引用标识,如[^1]。-参考用户提供的引用内容。用户提供的引用:-[^1]:是一个文档链接关于操作系统中的段页式虚拟存储管理实验C、C++代码,但链接是book118.com上的。用户提到了它是转载的,并给了具体文档名。-[^2]:讨论段式、页式和段页式存储管理的基本概念。-[^3]:解释了段页式存储管理,类比为图书馆管理。-[^4]:讨论段式程序设计和分段存储。这些引用提供了背景知识,我需要基于它们来回答。从引用中提取相关信息:-[^1]声称有C/C++实现代码。-[^2]和[^3]解释了段页式的工作原理。-[^4]提到逻辑地址结构。用户的上一个问题(完整记录)似乎也与存储管理相关,但系统说“参考用户上次的问题和你的回答”,但这里没有显示上一个问答,所以我应该专注于当前查询。我的回答结构应该清晰:先解释概念,然后提供代码示例。既然用户要“模拟实现代码示例”,我需要提供一个代码片段。参考[^1],它有C/C++代码。我应该尝试提供一个简单示例。重要:代码必须正确。我应该基于知识给出伪代码或简单实现保证完全正确,但要真实。步骤:1.**解释段页式存储管理**:使用引用中的信息。-它是段式(逻辑段:代码、数据等)和页式(分页管理)的结合。-逻辑地址由段号(s)、页号(p)和页内偏移(d)组成。-地址转换过程:段表+页表。-从[^2][^3][^4],逻辑地址是(段号,页号,偏移)。2.**提供代码示例**:-基于[^1],它应该是C或C++,所以我选C语言。-模拟地址转换:给定一个逻辑地址,转换为物理地址。-数据结构:段表(segmenttable),指向页表;页表(pagetable),指向物理页框。-实现一个函数如translate_address。-伪代码:-初始化段表和页表。-输入逻辑地址:(seg_num,page_num,offset)-查段表:获取页表基址。-查页表:获取物理页框号。-计算物理地址:物理页框号*page_size+offset-添加错误处理(如地址无效)。3.**生成相关问题**:在结束时添加相关问题。4.**格式化**:-使用中文。-数学表达式如地址计算时,用`$...$`。-独立公式如果需要用`$$...$$`。-代码块用```python```或```c```,但既然C,用```c```。5.**参考引用**:-自然地添加[^1]等,当引用具体点。完整回答结构:-引言:介绍段页式。-关键概念:地址结构和转换。-代码示例:用一个简单的C代码。-相关:添加相关问题。现在,写代码示例:-假设一个模拟系统:16位地址,4位段号,4位页号,8位偏移(页面大小256字节)。-段表:数组,每个条目有页表基址等。-页表:数组,每个条目有物理页框号。简化版本:```c#include<stdio.h>#include<stdlib.h>#defineSEG_TABLE_SIZE4//假设有4个段#definePAGE_TABLE_SIZE16//假设每个页表有16页#definePAGE_SIZE256//页面大小256字节//段表条目typedefstruct{intbase_page_table;//页表的基址intvalid;//是否有效}SegmentEntry;//页表条目typedefstruct{intframe_number;//物理页框号intvalid;//是否有效}PageTableEntry;//全局段表和页表SegmentEntrysegment_table[SEG_TABLE_SIZE];PageTableEntrypage_tables[SEG_TABLE_SIZE][PAGE_TABLE_SIZE];//假设每个段有自己的页表//初始化函数voidinit_tables(){//初始化段表for(inti=0;i< SEG_TABLE_SIZE;i++){segment_table[i].base_page_table=i;//假设页表按索引存储segment_table[i].valid=1;//设有效}//初始化页表-简化,每个页表设些有效条目for(intseg=0;seg< SEG_TABLE_SIZE;seg++){for(intpage=0;page< PAGE_TABLE_SIZE;page++){page_tables[seg][page].frame_number=seg*PAGE_TABLE_SIZE+page;//任意映射page_tables[seg][page].valid=1;//设有效}}}//地址转换函数inttranslate_address(intseg_num,intpage_num,intoffset){if(seg_num<0||seg_num>=SEG_TABLE_SIZE||!segment_table[seg_num].valid){printf("无效段号:%d\n",seg_num);return-1;}intpt_index=page_num;//假设页号直接用作索引if(pt_index<0||pt_index>=PAGE_TABLE_SIZE||!page_tables[seg_num][pt_index].valid){printf("无效页号:%d\n",page_num);return-1;}intframe=page_tables[seg_num][pt_index].frame_number;returnframe*PAGE_SIZE+offset;//物理地址}intmain(){init_tables();//示例:转换逻辑地址(seg=1,page=2,offset=128)intphysical_addr=translate_address(1,2,128);if(physical_addr!=-1){printf("物理地址为:%d\n",physical_addr);}return0;}```这个代码是简化版,只是一个模拟。引用相关点:-在解释时引用[^2][^3]等。最后生成相关问题。</think> ### 段页式存储管理的模拟实现代码示例 段页式存储管理结合了分段和分页的优点:用户程序按逻辑关系划分成多个段(如代码段、数据段),每个段再划分成固定大小的页,以页为单位离散分配到物理内存中[^2][^3]。逻辑地址由段号(s)、页号(p)和页内偏移(d)组成,格式为 $ (s, p, d) $,地址转换过程通过段表和页表实现[^4]。以下是基于C语言的简化模拟实现代码示例,展示了逻辑地址到物理地址的转换过程。 #### 关键数据结构与假设 - **逻辑地址结构**:假设为16位地址(4位段号、4位页号、8位偏移),页面大小为 $ 2^8 = 256 $ 字节。 - **段表**:每个条目存储页表基址和有效位。 - **页表**:每个条目存储物理页框号和有效位。 - 模拟物理内存为1024字节(4个页框)。 #### C语言实现代码 ```c #include <stdio.h> #include <stdlib.h> #define SEG_TABLE_SIZE 4 // 段表大小(4个段) #define PAGE_TABLE_SIZE 16 // 每个页表的页数 #define PAGE_SIZE 256 // 页面大小256字节 #define PHYS_MEM_SIZE 1024 // 物理内存大小1024字节(4页框) // 页表条目 typedef struct { int frame_number; // 物理页框号 int valid; // 有效位(1=有效) } PageTableEntry; // 段表条目 typedef struct { int base_page_table; // 页表基址(指向页表数组) int valid; // 有效位(1=有效) } SegmentEntry; // 全局数据结构 PageTableEntry page_tables[SEG_TABLE_SIZE][PAGE_TABLE_SIZE]; // 页表数组 SegmentEntry segment_table[SEG_TABLE_SIZE]; // 段表 char physical_memory[PHYS_MEM_SIZE]; // 物理内存模拟 // 初始化段表和页表 void init_tables() { // 初始化段表:设段0和段1有效 for (int i = 0; i < SEG_TABLE_SIZE; i++) { segment_table[i].base_page_table = i; // 页表基址设为段号 segment_table[i].valid = (i < 2) ? 1 : 0; // 仅前两个段有效 } // 初始化页表:段0和段1的页表有部分有效条目 for (int s = 0; s < SEG_TABLE_SIZE; s++) { for (int p = 0; p < PAGE_TABLE_SIZE; p++) { page_tables[s][p].valid = (s < 2 && p < 4) ? 1 : 0; // 仅前4页有效 // 简化映射:物理页框号 = (段号 * PAGE_TABLE_SIZE + 页号) % 4 page_tables[s][p].frame_number = (s * PAGE_TABLE_SIZE + p) % 4; } } } // 逻辑地址转换函数 int translate_address(int seg_num, int page_num, int offset) { // 检查段号有效性 if (seg_num < 0 || seg_num >= SEG_TABLE_SIZE || !segment_table[seg_num].valid) { printf("无效段号: %d\n", seg_num); return -1; // 表示错误 } // 获取页表基址 int pt_base = segment_table[seg_num].base_page_table; // 检查页号有效性 if (page_num < 0 || page_num >= PAGE_TABLE_SIZE || !page_tables[pt_base][page_num].valid) { printf("无效页号: %d\n", page_num); return -1; } // 获取物理页框号 int frame_num = page_tables[pt_base][page_num].frame_number; // 计算物理地址: frame_num * PAGE_SIZE + offset int physical_addr = frame_num * PAGE_SIZE + offset; // 检查偏移量是否溢出 if (offset < 0 || offset >= PAGE_SIZE || physical_addr >= PHYS_MEM_SIZE) { printf("偏移量 %d 溢出(页面大小: %d)\n", offset, PAGE_SIZE); return -1; } return physical_addr; } int main() { init_tables(); // 初始化数据结构 // 示例地址转换: 段号=1, 页号=2, 偏移量=128 printf("转换 (段1, 页2, 偏移128): "); int addr = translate_address(1, 2, 128); if (addr != -1) { printf("物理地址: %d\n", addr); // 输出: 2 * 256 + 128 = 640 } // 示例地址转换: 段号=0, 页号=3, 偏移量=50 printf("转换 (段0, 页3, 偏移50): "); addr = translate_address(0, 3, 50); if (addr != -1) { printf("物理地址: %d\n", addr); // 输出: 3 * 256 + 50 = 818 } // 错误测试: 无效段号 printf("转换 (段3, 页0, 偏移0): "); addr = translate_address(3, 0, 0); // 段3无效 return 0; } ``` #### 代码说明 1. **数据结构**: - `SegmentEntry`:存储页表基址,模拟段表条目[^4]。 - `PageTableEntry`:存储物理页框号,模拟页表条目[^2][^3]。 2. **地址转换流程**: - 输入逻辑地址 $ (s, p, d) $。 - 查段表 → 获取页表基址。 - 查页表 → 获取物理页框号 $ f $。 - 计算物理地址 $ f \times \text{Page\_Size} + d $。 - 添加无效地址检测(如段/页无效或偏移溢出)。 3. **运行示例**: - 输出示例: ``` 转换 (段1, 页2, 偏移128): 物理地址: 640 转换 (段0, 页3, 偏移50): 物理地址: 818 转换 (段3, 页0, 偏移0): 无效段号: 3 ``` - 演示了正确转换和错误处理,模拟了段页式的离散分配[^3]。 此代码基于引用中描述的段页式原理(如段覆盖技术和地址结构)实现[^1][^4],是一个简化模拟示例。实际系统中还需处理缺页中断、内存共享(如通过共享页表基址)等复杂性[^2][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值