{"id":21396986,"url":"https://github.com/tcl606/memorymanagement","last_synced_at":"2026-04-18T15:36:44.176Z","repository":{"id":165104494,"uuid":"637062070","full_name":"TCL606/MemoryManagement","owner":"TCL606","description":"操作系统存储管理的算法实现与演示","archived":false,"fork":false,"pushed_at":"2023-05-06T11:46:52.000Z","size":824,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-15T18:56:44.472Z","etag":null,"topics":["csharp","dotnet","memory-management","operating-system","os","tsinghua-university","wpf"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TCL606.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-05-06T11:44:40.000Z","updated_at":"2024-03-25T12:31:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"c533f1ed-9e1f-42fc-8c87-b14c887001e8","html_url":"https://github.com/TCL606/MemoryManagement","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/TCL606/MemoryManagement","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TCL606%2FMemoryManagement","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TCL606%2FMemoryManagement/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TCL606%2FMemoryManagement/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TCL606%2FMemoryManagement/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TCL606","download_url":"https://codeload.github.com/TCL606/MemoryManagement/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TCL606%2FMemoryManagement/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31974902,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","memory-management","operating-system","os","tsinghua-university","wpf"],"created_at":"2024-11-22T14:31:14.086Z","updated_at":"2026-04-18T15:36:44.156Z","avatar_url":"https://github.com/TCL606.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 存储管理：动态分区存储管理\n\n## 问题描述\n\n基于空闲内存分区链表的存储管理，设计一个动态分区存储管理程序，支持包括首次适配法、下次适配法、最佳适配法和最坏适配法在内的不同分区分配算法。\n\n## 实现要求\n\n1. 维护一个记录已分配内存分区和空闲内存分区的链表；\n\n2. 设计申请、释放函数循环处理用户的请求；\n\n3. 实现首次适配法、下次适配法、最佳适配法和最坏适配法四种分区分配算法；\n\n4. 可视化展示内存使用情况。\n\n## 实验平台\n\nWindows OS；C# 10（.NET 6）\n\n## 实现思路\n\n### 0.基础数据结构\n\n为了方便编程，我使用 C# 中内置的 `List\u003cT\u003e` 数据结构模拟存储管理中链表的操作。`List\u003cT\u003e` 数据结构提供了添加元素的方法 `Add`、删除元素的方法 `DeleteAt`、以及数组访问运算符 `[]`。尽管 `List\u003cT\u003e` 底层并不是用链表实现的，但是不影响用它来模拟链表的操作。这里可以将它视为一个单向链表，封装了增加、删除、访问等操作。\n\n### 1.总体架构\n\n建立内存管理对象 `MemoryManager`，维护其中的空闲内存块链表 `BlankList` 和分配内存块链表 `AllocateList` 。设置内存管理对象中有两个回调函数：分配函数 `Allocate` 和释放函数 `Free`。这两个函数作为参数传入内存管理对象的构造函数中，构成特定适配方法的内存管理对象。内存管理类的定义如下：\n\n~~~C#\npublic class MemoryManager\n{\n    public List\u003cMemoryBlock\u003e BlankList { get; set; } = new();\n    public List\u003cMemoryBlock\u003e AllocateList { get; set; } = new();\n    public int MemorySize { get; init; }\n    public int StartAddr { get; init; }\n    private Fit fit;\n    public bool Allocate(int memorySize) =\u003e fit.Allocate(memorySize, BlankList, AllocateList, out _);\n    public bool Allocate(int memorySize, out MemoryBlock? retBlock) =\u003e fit.Allocate(memorySize, BlankList, AllocateList, out retBlock);\n    public bool Free(int startAddr) =\u003e fit.Free(startAddr, BlankList, AllocateList, out _);\n    public bool Free(int startAddr, out MemoryBlock? retBlock) =\u003e fit.Free(startAddr, BlankList, AllocateList, out retBlock);\n    public MemoryManager(int memorySize, int startAddr, Fit fit)\n    {\n        MemorySize = memorySize;\n        StartAddr = startAddr;\n        this.fit = fit;  \n        BlankList.Add(new MemoryBlock(memorySize, startAddr));\n    }\n}\n~~~\n\n### 2.首次适配法\n\n首次适配法，按空闲分区的起始地址排序，从前往后依次查找，找到符合要求的第一个空闲分区，将其分配。\n\n为了避免每次适配时都重新对空闲链表排序，因此在释放分配内存、将释放的内存块加入空闲分区时，应保证空闲分区链表仍然是按起始地址有序的。\n\n同时，每次释放分配内存块并将其加入空闲分区链表时，都应对空闲链表重新检查，将能合并的空闲内存块合并。\n\n首次适配法的分配与释放的代码如下：\n\n~~~C#\npublic class FirstFit: Fit\n{\n    public override bool Allocate(int memorySize, List\u003cMemoryBlock\u003e blankList, List\u003cMemoryBlock\u003e allocateList, out MemoryBlock? retBlock)\n    {\n        for (int i = 0; i \u003c blankList.Count; i++)\n        {\n            if (blankList[i].MemorySize \u003e= memorySize)\n            {\n                var memoryBlock = new MemoryBlock(memorySize, blankList[i].StartAddr);\n                allocateList.Add(memoryBlock);\n                retBlock = memoryBlock;\n                blankList[i].MemorySize -= memoryBlock.MemorySize;\n                if (blankList[i].MemorySize == 0)\n                {\n                    blankList.RemoveAt(i);\n                }\n                else\n                {\n                    blankList[i].StartAddr += memoryBlock.MemorySize;\n                }\n                return true;\n            }\n        }\n        retBlock = null;\n        return false;\n    }\n\n    public override bool Free(int startAddr, List\u003cMemoryBlock\u003e blankList, List\u003cMemoryBlock\u003e allocateList, out MemoryBlock? retBlock)\n    {\n        retBlock = null;\n        var freeIdx = allocateList.FindIndex(x =\u003e x.StartAddr == startAddr);\n        if (freeIdx == -1)\n            return false;\n        var freeBlock = allocateList[freeIdx];\n        retBlock = freeBlock;\n        allocateList.RemoveAt(freeIdx);\n        int index = blankList.FindIndex(item =\u003e item.StartAddr \u003e freeBlock.StartAddr);\n        if (index == -1)\n            blankList.Insert(blankList.Count, freeBlock);\n        else\n            blankList.Insert(index, freeBlock);\n        int i = 1;\n        while (i \u003c blankList.Count)\n        {\n            if (blankList[i - 1].StartAddr + blankList[i - 1].MemorySize == blankList[i].StartAddr)\n            {\n                blankList[i - 1].MemorySize += blankList[i].MemorySize;\n                blankList.RemoveAt(i);\n                continue;\n            }\n            i++;\n        }\n        return true;\n    }\n}\n~~~\n\n### 3.下次适配法\n\n下次适配法，从上次分配的分区后开始查找，找到符合要求的第一个分区。如果查找到最后分区时，下次查找就从头开始。\n\n具体实现上，下次适配法中在每次分配时，都需要记录上次分配的内存块在空闲链表中的位置 `lastAllocIdx`，并维护这个值。当有内存块从分配链表中释放时，根据要释放的内存块的起始地址大小，维护 `lastAllocIdx`。若释放内存的起始地址小于当前空闲链表第 `lastAllocIdx` 位置的内存块起始地址，则说明释放内存块加入链表后，会使 `lastAllocIdx++`。当释放内存块加入空闲链表后，还需要对空闲链表搜索，合并能合并的空闲块。当完全合并了 `lastAllocIdx` 之前了内存块时，需要将 `lastAllocIdx--`。\n\n代码上整体与首次适配法相似，如下\n\n~~~C#\npublic class NextFit: Fit\n{\n    private int lastAllocIdx = 0;\n\n    public override bool Allocate(int memorySize, List\u003cMemoryBlock\u003e blankList, List\u003cMemoryBlock\u003e allocateList, out MemoryBlock? retBlock)\n    {\n        for (int j = 0; j \u003c blankList.Count; j++)\n        {\n            int i = (j + lastAllocIdx) % blankList.Count;\n            if (blankList[i].MemorySize \u003e= memorySize)\n            {\n                var memoryBlock = new MemoryBlock(memorySize, blankList[i].StartAddr);\n                allocateList.Add(memoryBlock);\n                retBlock = memoryBlock;\n                lastAllocIdx = i;\n                blankList[i].MemorySize -= memoryBlock.MemorySize;\n                if (blankList[i].MemorySize == 0)\n                {\n                    blankList.RemoveAt(i);\n                }\n                else\n                {\n                    blankList[i].StartAddr += memoryBlock.MemorySize;\n                }\n                return true;\n            }\n        }\n        retBlock = null;\n        return false;\n    }\n\n    public override bool Free(int startAddr, List\u003cMemoryBlock\u003e blankList, List\u003cMemoryBlock\u003e allocateList, out MemoryBlock? retBlock)\n    {\n        retBlock = null;\n        var freeIdx = allocateList.FindIndex(x =\u003e x.StartAddr == startAddr);\n        if (freeIdx == -1)\n            return false;\n        var freeBlock = allocateList[freeIdx];\n        retBlock = freeBlock;\n        allocateList.RemoveAt(freeIdx);\n        int index = blankList.FindIndex(item =\u003e item.StartAddr \u003e freeBlock.StartAddr);\n        if (index == -1)\n            blankList.Insert(blankList.Count, freeBlock);\n        else\n            blankList.Insert(index, freeBlock);\n        if (index \u003c= lastAllocIdx)  // record the last allcate index of blank list\n            lastAllocIdx++;\n        int i = 1;\n        while (i \u003c blankList.Count)\n        {\n            if (blankList[i - 1].StartAddr + blankList[i - 1].MemorySize == blankList[i].StartAddr)\n            {\n                blankList[i - 1].MemorySize += blankList[i].MemorySize;\n                blankList.RemoveAt(i);\n                if (i \u003c= lastAllocIdx)  // lastAllocIdx-- when merging the blank blocks\n                    lastAllocIdx--;\n                continue;\n            }\n            i++;\n        }\n        return true;\n    }\n}\n~~~\n\n### 4.最佳适配法\n\n每次分配时，找到其大小与要求相差最小的空闲分区进行分配。\n\n由于这里需要按照内存大小进行查找，因此空闲分区链表可以按照空闲内存块大小进行排序。这样在查找时，只需找到第一个比要申请的内存大小大的内存块，就可以进行分配。\n\n在释放内存时，由于空闲分区链表并不是按照内存块起始地址排序的，所以在合并时，要先将空闲分区链表按起始地址排序，这样方便合并相邻的空闲块。合并好后，再将空闲链表按内存块大小排序。\n\n代码如下：\n\n~~~C#\npublic class BestFit: Fit\n{\n    public override bool Allocate(int memorySize, List\u003cMemoryBlock\u003e blankList, List\u003cMemoryBlock\u003e allocateList, out MemoryBlock? retBlock)\n    {\n        retBlock = null;\n        int idx = -1;\n        for (int i = 0; i \u003c blankList.Count; i++)\n        {\n            if (blankList[i].MemorySize \u003e= memorySize)\n            {\n                idx = i;\n                break;\n            }\n        }\n        if (idx == -1)\n            return false;\n\n        var allocBlock = new MemoryBlock(memorySize, blankList[idx].StartAddr);\n        allocateList.Add(allocBlock);\n        retBlock = allocBlock;\n        blankList[idx].MemorySize -= allocBlock.MemorySize;\n        if (blankList[idx].MemorySize == 0)\n        {\n            blankList.RemoveAt(idx);\n        }\n        else\n        {\n            blankList[idx].StartAddr += allocBlock.MemorySize;\n            blankList.Sort((x, y) =\u003e x.MemorySize.CompareTo(y.MemorySize));\n        }\n        return true;\n    }\n\n    public override bool Free(int startAddr, List\u003cMemoryBlock\u003e blankList, List\u003cMemoryBlock\u003e allocateList, out MemoryBlock? retBlock)\n    {\n        retBlock = null;\n        var freeIdx = allocateList.FindIndex(x =\u003e x.StartAddr == startAddr);\n        if (freeIdx == -1)\n            return false;\n        var freeBlock = allocateList[freeIdx];\n        retBlock = freeBlock;\n        allocateList.RemoveAt(freeIdx);\n        blankList.Insert(blankList.Count, freeBlock);\n        int i = 1;\n        blankList.Sort((x, y) =\u003e x.StartAddr.CompareTo(y.StartAddr));\n        while (i \u003c blankList.Count)\n        {\n            if (blankList[i - 1].StartAddr + blankList[i - 1].MemorySize == blankList[i].StartAddr)\n            {\n                blankList[i - 1].MemorySize += blankList[i].MemorySize;\n                blankList.RemoveAt(i);\n                continue;\n            }\n            i++;\n        }\n        blankList.Sort((x, y) =\u003e x.MemorySize.CompareTo(y.MemorySize));\n        return true;\n    }\n}\n~~~\n\n### 5.最坏适配法\n\n每次分配时，找到最大的空闲分区进行分配。\n\n最坏适配法的实现几乎与最佳适配法一样。只是在分配时，我们可以直接使用最大的空闲分区（即按内存块大小排序的空闲块链表的最后一个元素）进行分配。如果连最大的空闲分区都无法满足要求，则内存申请失败。\n\n最坏适配法的内存释放函数与最佳适配法一模一样；内存申请函数有改动，如下：\n\n~~~C#\npublic class WorstFit : Fit\n{\n    public override bool Allocate(int memorySize, List\u003cMemoryBlock\u003e blankList, List\u003cMemoryBlock\u003e allocateList, out MemoryBlock? retBlock)\n    {\n        retBlock = null;\n        int idx = blankList.Count - 1;\n        if (idx == -1 || blankList[idx].MemorySize \u003c memorySize)\n            return false;\n        var allocBlock = new MemoryBlock(memorySize, blankList[idx].StartAddr);\n        allocateList.Add(allocBlock);\n        retBlock = allocBlock;\n        blankList[idx].MemorySize -= allocBlock.MemorySize;\n        if (blankList[idx].MemorySize == 0)\n        {\n            blankList.RemoveAt(idx);\n        }\n        else\n        {\n            blankList[idx].StartAddr += allocBlock.MemorySize;\n            blankList.Sort((x, y) =\u003e x.MemorySize.CompareTo(y.MemorySize));\n        }\n        return true;\n    }\n\n    public override bool Free(int startAddr, List\u003cMemoryBlock\u003e blankList, List\u003cMemoryBlock\u003e allocateList, out MemoryBlock? retBlock)\n}\n~~~\n\n## 界面设计\n\n可视化界面如下\n\n![interface](img/interface.png)\n\n1. 首先需要设置整个内存的大小、起始地址、使用适配方法，点击 `Start` 开始模拟。\n2. 然后可以自行设计每次要申请的内存大小、要释放的内存块首地址，分别点击 `Allocate` 或 `Free` 完成相应功能。\n3. 内存的实际情况将在右侧框图内显示。\n4. 所有的记录信息将在左下的 `Info` 标签下显示。\n5. 如果想重新开始模拟过程，可以点击左边的 `Reset` 按钮，重置内存设置。\n\n## 实验模拟\n\n总内存大小 1000，起始地址 0，分别使用 `FirstFit`、`NextFit`、`BestFit`、`WorstFit` 进行模拟。\n\n内存的申请与释放顺序如下（注意：其中**申请**的参数为**内存块大小**、**释放**的参数为**内存块起始地址**）：\n\n1. 申请 100\n2. 申请 100\n3. 申请 200\n4. 申请 300\n5. 申请 400\n6. 释放 100\n7. 释放 300\n8. 申请 50\n9. 申请 100\n10. 释放 100\n11. 申请 150\n12. 释放 400\n13. 申请 50\n14. 申请 200\n15. 申请 100\n\n### 1.首次适配法\n\n上述步骤 1~7，运行结果如下。其中步骤 5 申请内存失败，步骤 7 内存释放失败。\n\n![ff1-7](img/ff1-7.png)\n\n执行 8~9，依次申请 50、100 的内存，根据首次适配算法，应在起始地址为 100 处申请得到 50 的空间，在起始地址为 700 处申请得到 100 的空间。结果如下：\n\n![ff8-9](img/ff8-9.png)\n\n执行 10~12，释放 100、申请 150、释放 400，结果如下：\n\n![ff10-12](img/ff10-12.png)\n\n然后进行 13~15，依次申请 50、200、100。根据首次适配算法，应分别在起始地址为 100、400、600 处申请 50、200、100 的内存。结果如下\n\n![ff13-15](img/ff13-15.png)\n\n可以看到，首次适配算法工作正确，说明算法实现无误。\n\n### 2.下次适配法\n\n上述步骤 1~7，和具体适配方法无关，因此结果与首次适配法中的一样\n\n![nt1-7](img/nt1-7.png)\n\n执行 8~9，依次申请 50、100 的内存，根据下次适配算法，应从起始地址 700 往后申请，结果如下\n\n![nt8-9](img/nt8-9.png)\n\n执行 10~12，释放 100、申请 150、释放 400。由于下次适配算法，申请 150 时从上次申请完毕的 850 地址处开始，结果如下：\n\n![nt10-12](img/nt10-12.png)\n\n然后进行 13~15，依次申请 50、200、100。根据下次适配算法，应从起始地址为 100 处申请 50，然后在起始地址为 400、600 处申请 200、100。结果如下\n\n![nt13-15](img/nt13-15.png)\n\n可以看到，下次适配算法工作正确，说明算法实现无误。\n\n### 3.最佳适配法\n\n上述步骤 1~7，和具体适配方法无关，因此结果与首次适配法中的一样。\n\n![bf1-7](img/bf1-7.png)\n\n执行 8~9，依次申请 50、100 的内存，根据最佳适配算法，应从起始地址 100 往后申请 50，起始地址 700 往后申请 100。结果如下\n\n![bf8-9](img/bf8-9.png)\n\n执行 10~12，释放 100、申请 150、释放 400。结果如下\n\n![bf10-12](img/bf10-12.png)\n\n然后进行 13~15，依次申请 50、200、100。根据最佳适配算法，前两处应分别从起始地址 950、400分配 50、200。第三处既可以从起始地址 100 处分配，也可以从起始地址 600 处分配。结果如下\n\n![bf13-15](img/bf13-15.png)\n\n可以看到，最佳适配算法执行无误，说明算法实现无误。\n\n### 4.最坏适配法\n\n上述步骤 1~7，和具体适配方法无关，因此结果与首次适配法中的一样。\n\n![wf1-7](img/wf1-7.png)\n\n执行 8~9，依次申请 50、100 的内存，根据最坏适配算法，应该在首地址为 700、750 处申请 50、100 内存。结果如下\n\n![wf8-9](img/wf8-9.png)\n\n执行 10~12，释放 100、申请 150、释放 400。结果如下\n\n![wf10-12](img/wf10-12.png)\n\n然后进行 13~15，依次申请 50、200、100。根据最坏适配算法，应该分别在首地址为 400、450、100 处申请到 50、200、100 的内存。结果如下\n\n![wf13-15](img/wf13-15.png)\n\n可以看到，最坏适配算法执行无误，说明算法实现无误。\n\n## 思考题解答\n\n### 1.基于位图和空闲链表的存储管理各有什么优劣？如果使用基于位图的存储管理，有何额外注意事项？\n\n使用位图储存，是将内存划分为小的分配单位，每个分配单位对应位图中的一个位。1 表示占用，0 表示空闲。当一个占用 k 个分配单位的进程调入内存时，操作系统要搜索位图，找到连续的 k 个 0 进行内存分配。\n\n基于链表的存储管理，用链表结构记录并维护已分配内存块、空闲内存块。链表中的每一项或者表示一个进程，或者表示两个进程间的一个空闲分区，至少包含：指示空闲区或进程的标志、内存块起始地址、内存块大小、指下下一个表项的指针。\n\n使用位图的优势在于，由于内存大小有限，所以位图大小固定。当空闲内存块较多时，位图更省空间。但位图的劣势在于，分配内存时，由于需要查找连续多个 0 的块，算法更加复杂；且由于位图方法提前切割了的内存，所以可能会导致有内碎片，造成空间浪费。\n\n是用空闲链表的优势在于，分配内存时只需遍历链表，算法简单，且速度也比位图更快；当空闲内存块较少时，能省空间。但链表的劣势在于，占用空间大小不固定，得动态分配；且当空闲块小而多时，占用空间大。\n\n当使用基于位图的存储管理时，要注意切割内存片的大小。内存片大小过小，位图分配空间时搜索连续 0 的时间将更长；内存片大小过大，可能造成更多的内碎片。\n\n使用位图的存储管理，由于多进程可能同时访问位图，在对内存分配或释放时，应控制对位图的访问是多进程安全的。\n\n## 文件说明\n\n`MemoryGUI/`：GUI 界面代码\n\n`MemoryManage/`：四种适配方法代码\n\n`publish/`：可执行程序及其依赖项\n\n`run.cmd`：启动脚本\n\n构建方式：打开 `MemoryManage/MamoryManage.sln`，以 `MemoryGUI` 项目为启动方式，开始运行\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftcl606%2Fmemorymanagement","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftcl606%2Fmemorymanagement","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftcl606%2Fmemorymanagement/lists"}