建立磁盘空间管理模块【c语言实现】

1. 课程设计任务、要求、目的

1.1 课程设计任务

空闲磁盘存储空间的管理:建立磁盘空间管理模块。
建立相应的数据结构;
磁盘上建立一个文件,文件长度设为10MB,用该文件来模拟一个磁盘,磁盘的物理块大小为512字节。
建立进程的数据结构;
时间的流逝可用下面几种方法模拟:(a)按键盘,每按一次可认为过一个时间单位;(b)响应WM_TIMER;
使用两种方式产生进程对磁盘的请求:(a)自动产生,(b)手工输入
将一批进程对磁盘的请求的情况存磁盘文件,以后可以读出并重放;
显示每次磁盘的请求和空间释放后的相关数据结构的状态;
显示每次磁盘的请求和空间释放后状态;
支持的管理方法:空闲表法、空闲链表法、位示图法。

1.2 课程设计目的和要求
磁盘初始化时把磁盘存储空间分成许多块(扇区),这些空间可以被多个用户共享。用户作业在执行期间常常要在磁盘上建立文件或把已经建立在磁盘上的文件删去,这就涉及到磁盘存储空间的分配和回收。一个文件存放到磁盘上,可以组织成顺序文件(连续文件)、链接文件(串联文件)、索引文件等,因此,磁盘存储空间的分配有两种方式,一种是分配连续的存储空间,另一种是可以分配不连续的存储空间。怎样有效地管理磁盘存储空间是操作系统应解决的一个重要问题,通过这个课程设计可以使我们更好地熟悉掌握磁盘存储管理的原理和分配与回收算法,进一步掌握软件开发方法并提高解决实际问题的能力。

2. 开发环境

VS2017

3. 相关原理及算法

a) 进程等待队列与就绪队列
进程因为一些执行条件不满足,会进入等待队列中进行等待。当条件满足时,将进入就绪队列中运行,由CPU决策运行。
b) 空闲区表法
  空闲表法属于连续分配方式。它与内存管理中的动态分区分配方式雷同。将外存空间上一个连续未分配区域称为“空闲区”。操作系统为磁盘外存上所有空闲区建立一张空闲表,每个表项对应一个空闲区,空闲表包含“序号,第一空闲盘块号,空闲盘块数”等信息。它适用于连续文件结构。
它为每个文件分配一个连续的存储空间。系统为外存上的所有空闲区建立一张空闲表,每个空闲区对应于一个空闲表项。
c) 最佳适应算法(BestF比)
该算法总是把既能满足要求,又是最小的空闲分区分配给作业。为了加速查找,该算法要求将所有的空闲区按其大小排序后,从递增顺序形成一个空自链。这样每次找到的第一个满足要求的空闲区,必然是最优的。孤立地看,该算法似乎是最优的,但事续上并不一定。因为每次分配后剩余的空间一定是最小的,在存储器中将留下许多难从利用的小空闲区。同时每次分配后必须重新排序,这也带来了一定的开销。
特点:每次分配给文件的都是最合适该文件大小的分区。
缺点:内存中留下许多难从剎利用的小的空闲区。
d) 空闲链表法
   是将所有的空闲盘区拉成一条空闲链。根据构成链的基本元素的不同,可有两种链表方式:空闲盘块链、空闲盘区链。
  空闲盘块链
   它是将磁盘上的所有空闲存储空间,以盘块为基本元素拉成一条链。优点是用于分配和回收一个盘块的过程非常简单;缺点是空闲盘块链可能很长。
  空闲盘区链
这是将磁盘上的所有空闲盘区(每个盘区可包含若干个盘块)拉成一条链。在每个盘区上除了含有用于指示下一个空闲盘区的指针外,还应标有指明本盘区大小(盘块数)的信息。这方法分配和回收过程较复杂,但空闲盘区链较短。
e) 显式链接
使用文件分配表(File Allocation Table,FAT)。每个磁盘分区的开始部分用于存储FAT表。磁盘上的每个块都在该表中登记,并占用一个表项,FAT表可以通过块的编号来索引,FAT的每个表项含有文件的下一块的块号,这样FAT就可以像链表一样使用。系统首先根据目录文件中的第一块的块号去检索FAT表,从中得到文件的下一个盘块号,以此类推,直到该文件的最后一块,该块对应FAT表的值为文件结束标志。在FAT表中,未使用的块用0表示,因此,当一个文件需要分配新的存储空间时,就在FAT表中查找第一个标志为0的块,用新分配块的块号替换该条目的值,并把该块链接到文件的尾部。

f) 位示图法
  这种方法是在外存上建立一张位示图(bitmap),记录文件存储器的使用情况。每一位仅对应文件存储器上的一个物理块,取值0和1分别表示空闲和占用。文件存储器上的物理块依次编号为:0、1、2、…。
  位示图是利用二进制的一位来表示磁盘中一个盘块的使用情况。当其值为“0”时,表示对应的盘块空闲;为“1”时表示已分配。由所有盘块对应的位构成一个集合,称为位示图。位示图也可描述为一个二维数组map:Var map:array[1…m,1…n]of bit;
  盘块的分配
  根据位示图进行盘块分配时,可分三步进行:
  ·顺序扫描位示图,从中找出一个或一组值均为“0”的二进制位;
  ·将找到的二进制位,转换成与之相应的盘块号;
  ·修改位示图,令map[i,j]=1。
  盘块的回收
  盘块的回收分两步:
  ·将回收盘块的盘块号转换成位于图中的行号和列号。转换公式为:
  i=(b-1)DIVn+1
  j=(b-1)MODn+1
·修改位示图。
令map[i,j]=0。

4. 系统结构和主要的算法设计思路

该系统用于模拟进程所占用磁盘块的管理。分为四部分,分别是进程与时间流的模拟、空闲盘块法、空闲链表法、位示图法。
其运行流程图如图1所示。
在这里插入图片描述 图1
在这里插入图片描述

图2
空闲盘块表最佳适应算法 申请块
在这里插入图片描述

图3
空闲盘块最佳适应算法 归还块

空闲链表法的设计思想:
首先使用一个显示链表,用int型数组来表示这个链表,其长度为20480,也就是总快数,即每一块都有一个对应的位置。
申请物理块时,进程记录空闲链表开头的物理块位置,并依照请求的物理块数,在显示链表中寻找尚未分配的物理块(即对应位置标记为0的物理块),将对应位置的标记置为下一物理块所在位置,将最后一个位置的标记置为-1即可。
归还物理块时,进程根据其记录的开头位置,顺着显示链表直到结尾(即为-1的位置),并将经历的位置标记均置为0。
位示图法的设计思想:
该位示图使用了一维int型数组,其长度为640,其中每一个int型数据占据32位,每一位都代表了一个物理块的占用情况,位为1表示被占用,为0表示空闲。
申请物理块时,按照从低位到高位的顺序将int数组置为1,假设申请的位置为第s块,那么其对应int数组的位置为第(s/32)个int数,第(s%32)位,只需要将其置为1即可。注意,这个过程中,进程需要记录下请求的每一个物理块的位置,便于接下来的归还物理块。
归还物理块时,按照进程记录的物理块位置,将对应位置的int数位置为0,具体计算规则是:假设申请的位置为第s块,那么其对应int数组的位置为第(s/32)个int数,第(s%32)位,只需要将其置为0即可。最后释放进程用以记录位置的空间。

5. 程序实现—主要数据结构

进程的数据结构

struct Process {
	int PID;//进程ID
	char *name;//进程名
	int size;//进程大小
	int BlockNum;//进程所占磁盘块数
	int state;//进程的7种状态,1~7分别对应:创建、就绪、执行、阻塞、挂起阻塞、挂起就绪、终止
	int Rtime;//间隔时间(到达时间)
	int time;//运行时间
	struct Process*next;//下一个进程(这里使用了进程的链表)
	int start;//记录进程所占据磁盘号的开始位置
	int *Block;//位示图时,记录的位置指针
}*ReadyQueue,*RunningQueue;//就绪队列和执行队列

说明:此处的Rtime是为了模拟进程依次经过随机时间到达,是为了模拟时间间隔。
空闲盘块表的数据结构

struct FreeTableItem {
	int ID;//空闲块ID
	int first;//空闲块开始的第一块
	int BlockNum;//空闲块块数
	int state;//空闲块状态
}FreeTable[MAXLENGTH];//申请空闲盘快表,其最大长度为1024

空闲链表的数据结构

int PAT[BLOCKNUMBER];//显式链表的存储方法

位示图的数据结构

int BitMap[BLOCKNUMBER / 32];//int占据32位,此位示图共row=640行

6. 程序实现—主要程序清单

空闲表法核心函数:
ApplyBlock1()表示申请磁盘块,ReturnBlock1表示归还磁盘块

int ApplyBlock1(struct Process **aProcess)
/*返回值为1表示申请成功,返回值为0表示申请失败*/
{
	for (int i = 0; i < TableItemNum; i++)
	{
		if ((*aProcess)->BlockNum<= FreeTable[i].BlockNum)
		{
			(*aProcess)->start = FreeTable[i].first;//进程记录起始位置
			FreeTable[i].first = FreeTable[i].first + (*aProcess)->BlockNum;
			FreeTable[i].BlockNum = FreeTable[i].BlockNum - (*aProcess)->BlockNum;
			if (FreeTable[i].BlockNum == 0)//如果减去后等于0,则从空闲块表中删除这块
			{
				DeleteFreeItem(i);
			}
			LeftMoveFreeItem(i);//申请之后可能变小了,此时需要左移
			return 1;
		}
	}
	return 0;
}
int ReturnBlock1(struct Process aProcess)
{
	for (int i = 0; i < TableItemNum; i++)//如果可以向上或者向下合并
	{
		if (aProcess.start == FreeTable[i].first + FreeTable[i].BlockNum)//如果可以向上合并
		{
			FreeTable[i].BlockNum += aProcess.BlockNum;
			//向上合并之后,再找找有没有可以向下合并的
			for (int j = 0; j < TableItemNum; j++)
			{
				if (FreeTable[i].first + FreeTable[i].BlockNum == FreeTable[j].first)
				{
					FreeTable[i].BlockNum += FreeTable[j].BlockNum;
					DeleteFreeItem(j);//向下合并之后,删除下面那个表项
					break;
				}
			}
			RightMoveFreeItem(i);//后移第i块,使空闲表由小到大排列
			return 1;//合并成功
		}
		if (aProcess.start + aProcess.BlockNum == FreeTable[i].first)//如果可以向下合并
		{
			FreeTable[i].BlockNum += aProcess.BlockNum;
			FreeTable[i].first = aProcess.start;
			//向下合并之后,再找找有没有可以向上合并的
			for (int j = 0; j < TableItemNum; j++)
			{
				if (FreeTable[i].first == FreeTable[j].first+FreeTable[j].BlockNum)
				{
					FreeTable[j].BlockNum += FreeTable[i].BlockNum;
					DeleteFreeItem(i);//向上合并之后,删除下面那个表项
					break;
				}
			}
			RightMoveFreeItem(i);//后移第i块,使空闲表由小到大排列
			return 1;//合并成功
		}
	}
	//既不能向上,也不能向下合并,此时需要添加一个新表项
	int ID;
	for (int i = 0; i < TableItemNum; i++)
	{
		ID = newFreeTableID();
		if (aProcess.BlockNum < FreeTable[i].BlockNum)//因为空闲表内部已经由小到大排列,所以可以直接从头到尾,向后寻找一块合适的位置
		{
			if (TableItemNum >= MAXLENGTH) return 0;//超过了表最大可容纳项目数,此时就算归还了物理块,也会导致无法记录,因此退回
			for (int j = TableItemNum; j > i; j--)//第i块开始整体后移
			{
				FreeTable[j] = FreeTable[j - 1];
			}

			//写入新的表项
			FreeTable[i].ID = ID;
			FreeTable[i].BlockNum = aProcess.BlockNum;
			FreeTable[i].first = aProcess.start;
			FreeTable[i].state = 1;
			TableItemNum++;
			aProcess.state = 7;//进入停止运行状态
			return 1;
		}
		if (i == TableItemNum)//运行到最后还是没发现比当前表项大的表项,所以这一表项插入最后
		{
			//写入新的表项
			FreeTable[i].ID = newFreeTableID();
			FreeTable[i].BlockNum = aProcess.BlockNum;
			FreeTable[i].first = aProcess.start;
			FreeTable[i].state = 1;
			TableItemNum++;
			aProcess.state = 7;//进入停止运行状态
			return 1;
		}
	}
	return 0;//特殊情况,例如表没有初始化
}

空闲链表法

int ApplyBlock2(struct Process **aProcess)
{
	if ((*aProcess)->BlockNum > surplus2)//超过剩余块数,分配失败
		return 0;
	int i = 0;
	for (; i < BLOCKNUMBER; i++)
	{
		if (PAT[i] == 0)
			break;
	}
	int Head = i;//找到了可以用的头部
	(*aProcess)->start = Head;//进程的开始位置,指向头部
	int BlockNum = (*aProcess)->BlockNum;//需要占用的磁盘块数
	int Num = 1;//已经占用的磁盘块数
	for (int j = i+1; j < BLOCKNUMBER; j++)
	{
		if (PAT[j] == 0)
		{
			Num++;
			PAT[i] = j;
			i = j;
		}
		if (Num == BlockNum)//如果达到了要求的数目,则将其下一个指向的空闲位置变成-1,表示链表结束
		{
			PAT[i] = -1;
			surplus2 -= BlockNum;//剩余块数减少
			return 1;
		}
	}
	return 0;
}
int ReturnBlock2(struct Process aProcess)
{
	int now = aProcess.start;
	int pre = now;//上一个位置
	now = PAT[now];//当前位置
	while (PAT[now] != -1)
	{
		PAT[pre] = 0;
		pre = now;
		now = PAT[now];
	}
	PAT[pre] = 0;//最后一个非-1的数归零
	PAT[now] = 0;//最后一个-1也归零
	//全部归0之后
	surplus2 += aProcess.BlockNum;
	return 1;//释放成功
}

位示图法

int ApplyBlock3(struct Process **aProcess)
{
	if ((*aProcess)->BlockNum > surplus3) 
		return 0;//超过剩余块数,无法分配
	int BlockNum = (*aProcess)->BlockNum;//待分配的块数
	if((*aProcess)->Block==NULL)
		(*aProcess)->Block = (int *)malloc(sizeof(int)*BlockNum);//为进程申请一段内存,用以存储每一块的位置信息
	int index = 0;
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < 32; j++)
		{
			if (Numis(BitMap[i], j) == 0)//找到为0的位置
			{
				SetBit(BitMap[i], j);//将其置为1
				((*aProcess)->Block)[index] = i * 32 + j;//位置记录下来
				++index;//索引后推
				--BlockNum;//剩余待分配块数-1
				--surplus3;//剩余可分配块数-1
				if (BlockNum <= 0)//分配结束了
					return 1;
			}
		}
	}
	return 0;
}
int ReturnBlock3(struct Process aProcess)
{
	int *temp = aProcess.Block;
	int a;
	for (int i = 0; i < aProcess.BlockNum; i++)
	{
		a = temp[i];
		ClearBit(BitMap[a / 32], a % 32);//将对应位置置为0
	}
	surplus3 += aProcess.BlockNum;//剩余块数增加
	free(aProcess.Block);//释放这块空间,这之后,即使将指针置空也没用,因为这函数的操作数据是复制来的,这里的指针会变成野指针,所以需要自己手动置空
	return 1;
}

7. 程序运行的主要界面和结果截图

在这里插入图片描述

在这里插入图片描述

空闲表法

在这里插入图片描述

如屏幕中所显示,一行有4个项目,这是为了在空闲表中项目数较大的情况下,便于观察。因为此处采用的是首次适应算法,队列中表项按照由小到大的顺序排列。例如图中前5项显示为12、16、18、25、39.
在这里插入图片描述

100个随机进程运行结束时,空闲表回归到只剩下1块的情况,磁盘成功分配,又成功回收。
运行结束后,按任意键返回主菜单
在这里插入图片描述

此时如果有存储,会提醒上次存储的进程,可以直接读取
在这里插入图片描述

如上,便读取了100个进程,第二次我们使用位示图法来模拟
位示图法

在这里插入图片描述
在这里插入图片描述

通过位示图,我们能看到许多数被填充为-1,这是int型数据,即-1代表了32位全1,位示图正常运行。
在这里插入图片描述

运行到后期,通过观察位示图可以发现,当前磁盘空间几乎被填满。紧接着,磁盘块开始归还,如下图。
在这里插入图片描述

最终磁盘块完全归还,位示图变为全0
在这里插入图片描述

空闲链表法
因为显式链表有20480位,故太大不方便展示,这里仅展示前1000位。
在这里插入图片描述

由图可知,正常分配。

688个时间单位后,显式链表变为全0,分配结束。
在这里插入图片描述

手动创建进程的示例:

在这里插入图片描述

8. 总结和感想体会

这次课程设计,让我充分回顾了操作系统中关于物理块分配的内容,加深了理解,通过一系列的努力,终于完成。这其中还涉及到了windows的定时机制,让我大开眼界。使用c语言来编写代码,给人的感觉是很麻烦,无论是申请还是释放内存都要格外注意,否则很可能导致无法存储或者内存泄露。而完成了这样一个系统,用到了链表和顺序表,也复习了数据结构,这样一次动手实践,让我的动手能力很好的得到了提升。

完整代码请于此处下载:https://download.csdn.net/download/qq_42361182/12787808

  • 6
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值