OS学习周报告-2
内存分配算法模拟-bf
- 计算机中的程序需要装入内存才能执行,如何为程序分配内存是人们一直以来思考的问题。
连续分配方式是最早出现的一种内存分配方式,该方法为程序分配一块连续的内存空间,具体又可分为单一连续分配,固定分区分配和动态分区分配三种。
- 单一连续分配只能在单道程序环境下实现,它将内存分为系统区和用户区,系统区给OS使用,因为单道环境同一时间只运行一个程序,所以用户区由该程序独占。
在这种分配方式下,若是用户区容量不足以放下整个程序时怎么办呢?
覆盖技术就是解决这一问题的,程序在执行时并不是任何时候都要访问程序及数据的各个部分,那么我们只需要保证程序当前执行的部分在内存中即可。
-
既然说道覆盖就不能不提另一概念——交换(swapping)
在多道程序环境中,内存中往往存放有多个用户进程,当某些进程处于等待状态时,若还将其存放在内存中,便白白浪费了这些内存空间。这时将这些程序从内存移动到硬盘中,腾出内存空间,这一过程成为换出,而把准备好要竞争CPU的进程从硬盘再移动到内存中,这一过程称为换入。
-
对比下两者,可以看出覆盖是解决单道程序环境下小内存放大程序的问题的技术,而交换是提升多道程序环境下内存利用率的技术。
另一点重要区别则是,覆盖技术要求程序员在编写程序时就确定好程序中不同部分的调用次序,而交换技术则对用户是透明的,这些换入换出的操作都有OS来完成。
-
固定分区分配是最简单的多道程序内存管理方式,它将内存空间划分为若干大小的分区,每个分区只装入一道作业。而分区大小可分为等大分区或大小不等分区。
这种分区方式的缺点也很明显,一是若程序过大放不进任何一个分区中,那么用户将不得不使用覆盖技术。二是当小程序放入大分区中,会产生内部碎片,降低内存的利用率。
-
动态分区分配,不预先进行内存划分,而是进程装入内存时,根据进程的大小动态建立分区。
动态分区在开始分配时,性能很好,但经过多次分配后,内存中会出先许多小的空闲内存块,称为外部碎片。随着时间推移,这些碎片会越来越多,与内部碎片一样,也会降低内存利用率。
这时需要通过紧凑(Compacion)技术来将这些分散的空闲内存整合成一块大的内存块,由OS将内存中的进程进行移动,整理。
可以想到内存中的进程的物理地址在移动会发生变化,那cpu如何找到移动后的进程呢?
动态运行时装入(动态重定位) ,当程序链接完后,由装入程序将装入模块转入内存中,但并不立即将程序中的逻辑地址转换为内存中的物理地址,而是等到程序真正要执行时才完成地址的转换。由重定位寄存器记录每次移动后程序的最小物理地址值,cpu通过逻辑地址+重定位寄存器中的基址,就可以得到相应的物理地址。
-
动态分区分配算法
-
首次适应(FF)算法
从低地址开始,找到第一个满足作业大小的空闲区即进行分配。由于每次分配都是从低地址开始,多次分配后会在低地址区域留下大量外部碎片,降低查找效率。
-
循环首次适应(NF)算法
对FF算法的改进,每次寻找空闲区后,指针留在原位。下次查找从指针所指位置开始。虽然使得外部碎片均匀分部在内存空间中,但却减少了大容量空闲区的数量。
-
最佳适应(BF)算法
顾名思义,每次查找空闲区,总是找到满足作业大小的最小的空闲区。
-
最坏适应(WF)算法
每次查找,总是找到满足作业大小的最大的空闲区。
-
-
算法模拟
-
BF算法
通过建立链表,模拟空闲分区链
typedef struct node { int no; //状态位,-1表示空闲,否则记录作业号 int start; //分区始址 int size; //分区大小 struct node *next; }node;
代码核心部分,按照BF算法进行内存分配:
int add(node *head,int no,int size) //内存分配模块 { int min=INF; //bf算法首先找到所有大于待分配作业大小的空闲区,再从中找出容量最小的空闲区,完成分配 node *p,*q; for(p=head->next;p;p=p->next) { if(p->no==-1&&p->size>=size) //先找满足容量大于作业大小的空闲区 { if(p->size
size; q=p; } } } if(q==NULL) return 0; //如果没找到满足要求的空闲区,则分配失败 if(q->size-size>SIZE) //SIZE是我们设置的碎片大小,若分配后空闲区剩余空间容量大于碎片大小,则需为剩余空间新建一个链表结点来记录它 { node *temp=(node*)malloc(sizeof(node)); temp->no=-1; temp->start=q->start+size; //为剩余空间创建新结点,它的始址为该空闲块原始址+作业大小 temp->size=q->size-size; //该结点大小即为原空闲块大小-作业大小 temp->next=q->next; //将新建结点插入原空闲结点之后 q->next=temp; } q->no=no; //将作业存入该空闲区中 q->size=(q->size-size>SIZE?size:q->size); //若为剩余空间创建了新结点,则存入作业后,该结点大小即为作业大小 return 1; }
执行情况:
请输入内存空间大小: 100 请输入碎片大小: 1 ... *************************************************** 1: 为新作业分配内存 2: 撤销作业释放内存 3: 查看buddy算法内存分配 4: 退出 *************************************************** 请输入操作: 3 已分配分区 空闲分区 (分区号, 作业, 始址, 大小) (分区号, 始址, 大小) 2, 2, 20, 61 1, 0, 20 3, 81, 19 *************************************************** 1: 为新作业分配内存 2: 撤销作业释放内存 3: 查看buddy算法内存分配 4: 退出 *************************************************** 请输入操作:
完成上述操作后,容量为100的内存空间被分为3部分,
1号空闲区容量为20,3号空闲区容量为19,3号空闲区位于高地址部分。这时我们为大小为18的作业分配内存。
*************************************************** 1: 为新作业分配内存 2: 撤销作业释放内存 3: 查看buddy算法内存分配 4: 退出 *************************************************** 请输入操作: 1 请输入作业id和作业大小size: 4 18 分配成功 *************************************************** 1: 为新作业分配内存 2: 撤销作业释放内存 3: 查看buddy算法内存分配 4: 退出 *************************************************** 请输入操作: 3 已分配分区 空闲分区 (分区号, 作业, 始址, 大小) (分区号, 始址, 大小) 2, 2, 20, 61 1, 0, 20 3, 4, 81, 19 *************************************************** 1: 为新作业分配内存 2: 撤销作业释放内存 3: 查看buddy算法内存分配 4: 退出 *************************************************** 请输入操作:
可以看到4号作业没有分配到低始址的1号空闲区,而是分容量更为接近的3号空闲区中。
-
WF算法
内存分配策略与BF算法刚好相反,先找到满足作业大小的空闲区,再找到它们之中容量最大的那个即可。
int add(node *head,int no,int size) //内存分配模块 { int max=0; //wf算法首先找到所有大于待分配作业大小的空闲区,再从中找出容量最大的空闲区,完成分配 node *p,*q; for(p=head->next;p;p=p->next) { if(p->no==-1&&p->size>=size) //先找满足容量大于作业大小的空闲区 { if(p->size>max) //再找容量与作业大小最接近的空闲区 { max=p->size; q=p; } } } ...
执行情况:
*************************************************** 1: 为新作业分配内存 2: 撤销作业释放内存 3: 查看wf算法内存分配 4: 退出 *************************************************** 请输入操作: 3 已分配分区 空闲分区 (分区号, 作业, 始址, 大小) (分区号, 始址, 大小) 2, 2, 19, 61 1, 0, 19 3, 80, 20 *************************************************** 1: 为新作业分配内存 2: 撤销作业释放内存 3: 查看wf算法内存分配 4: 退出 *************************************************** 请输入操作: 1 请输入作业id和作业大小size: 3 18 分配成功 *************************************************** 1: 为新作业分配内存 2: 撤销作业释放内存 3: 查看wf算法内存分配 4: 退出 *************************************************** 请输入操作: 3 已分配分区 空闲分区 (分区号, 作业, 始址, 大小) (分区号, 始址, 大小) 2, 2, 19, 61 1, 0, 19 3, 3, 80, 18 4, 98, 2
大小为18的3号作业,存入了容量较大的3号空闲区。
-
-
在了解了这么多内存分配算法后,我们不禁要问,它们之中是性能最优的算法呢?
性能最优首先这种算法要“最快”,它要能在最短时间找到可分配的空闲块。另外,还要“最好”,它要能有较高的内存利用率,能在内存中放入尽量多的作业。
最佳分配算法bf,为每个作业找到容量最适合的空闲区,充分利用了每一块内存,似乎就是最好的算法。但仔细想想,每一次分配后,剩下的空闲区都是最小的。多次分配后,我们发现内存中的空闲区都是些难以利用的小块。
有如下的特例:
*************************************************** 1: 为新作业分配内存 2: 撤销作业释放内存 3: 查看bf算法内存分配 4: 退出 *************************************************** 请输入操作: 3 已分配分区 空闲分区 (分区号, 作业, 始址, 大小) (分区号, 始址, 大小) 2, 2, 3, 2 1, 0, 3 3, 5, 5 ***************************************************
我们后面的作业序列大小为,2 ,3 ,3.
使用bf分配的结果:
*************************************************** 请输入操作: 1 请输入作业id和作业大小size: 5 3 分配失败 *************************************************** 1: 为新作业分配内存 2: 撤销作业释放内存 3: 查看bf算法内存分配 4: 退出 *************************************************** 请输入操作: 3 已分配分区 空闲分区 (分区号, 作业, 始址, 大小) (分区号, 始址, 大小) 1, 3, 0, 3 4, 8, 2 2, 2, 3, 2 3, 4, 5, 3
而使用wf算法我们可以完成分配:
*************************************************** 请输入操作: 1 请输入作业id和作业大小size: 5 3 分配成功 *************************************************** 1: 为新作业分配内存 2: 撤销作业释放内存 3: 查看wf算法内存分配 4: 退出 *************************************************** 请输入操作: 3 已分配分区 空闲分区 (分区号, 作业, 始址, 大小) (分区号, 始址, 大小) 1, 4, 0, 3 2, 2, 3, 2 3, 3, 5, 2 4, 5, 7, 3
bf的查找速度也不如wf,wf算法只要将空闲分区按容量从大到小排列,每次取出第一个结点即可。而bf算法则需要从空闲区容量从小到大的序列中,找到第一个满足作业容量的空闲区。
而wf虽然有最快的查找速度,但总是小程序放到大内存中,会很快消耗掉内存中的大空闲块,性能也不是最佳。
理论上说FF是最简单,通常也是最快和最好的算法。
-
这里我想能不能用我们的模拟算法来做个实验验证下到底哪个算法更好
我想了两种测试方法:
-
只放不取
设置好内存空间,和测试次数后,每轮测试随机放入一个大小在内存容量五分之一范围内的作业,直到出现连续3次放入失败位置,记录下放入作业的个数,以此来评价算法的优劣。
int main() { printf("请输入内存空间大小: "); scanf("%d",&memory_capacity); printf("请输入碎片大小: "); scanf("%d", &SIZE); int test_count; printf("请输入测试次数:"); scanf("%d",&test_count); int taskcount=0; srand((unsigned int)time(NULL)); for(int i=0;i
next; while(q!=NULL) { free(p); p=q,q=q->next; } } printf("%d\n",taskcount); printf("平均任务个数:%.2f\n",(float)taskcount/test_count); return 1; } 结果如下:
hawl29@hawl29-PC:~/Desktop/cprogram/OS_experiment$ ./ff_test2_execfile 请输入内存空间大小: 10000 请输入碎片大小: 1 请输入测试次数:100000 1053311 平均任务个数:10.53 hawl29@hawl29-PC:~/Desktop/cprogram/OS_experiment$ ./bf_test2_execfile 请输入内存空间大小: 10000 请输入碎片大小: 1 请输入测试次数:100000 1053942 平均任务个数:10.54 hawl29@hawl29-PC:~/Desktop/cprogram/OS_experiment$ ./wf_test2_execfile 请输入内存空间大小: 10000 请输入碎片大小: 1 请输入测试次数:100000 1053605 平均任务个数:10.54
从这个结果中看不出来3种算法的区别。
-
随机存取
随机存取若干次后,统计最终存入的作业数。
int main() { printf("请输入内存空间大小: "); scanf("%d",&memory_capacity); printf("请输入碎片大小: "); scanf("%d", &SIZE); int test_count; printf("请输入测试次数:"); scanf("%d",&test_count); int taskcount=0; int no=0; srand((unsigned int)time(NULL)); node *head=init_link_table(memory_capacity); int rep[test_count]; int k,j,m,n; for(int i=0;i
0) { j=rand()%no; m=n=j; for(int i=0;m>=0&&n 0) m--; if(n 结果如下:
hawl29@hawl29-PC:~/Desktop/cprogram/OS_experiment$ ./ff_test3_execfile 请输入内存空间大小: 10000 请输入碎片大小: 1 请输入测试次数:100000 存入任务个数782 hawl29@hawl29-PC:~/Desktop/cprogram/OS_experiment$ ./bf_test3_execfile 请输入内存空间大小: 10000 请输入碎片大小: 1 请输入测试次数:100000 存入任务个数791 hawl29@hawl29-PC:~/Desktop/cprogram/OS_experiment$ ./wf_test3_execfile 请输入内存空间大小: 10000 请输入碎片大小: 1 请输入测试次数:100000 存入任务个数695
这次的结果可以看出,ff和bf算法较wf来说略好。
-
-
调试总结
在写测试代码时,出现了2个错误。
-
hawl29@hawl29-PC:~/Desktop/cprogram/OS_experiment$ ./bf_test2_execfile 请输入内存空间大小: 100 请输入碎片大小: 1 请输入测试次数: 100 浮点数例外
提示浮点数例外,在网上查一般是2种原因造成的:
一是高版本GCC编译的程序在低版本GCC环境下运行导致。
二是程序中出现除0的情况。
我这里是第二种情况,仔细检查代码后发现的确有除0的地方,改正后OK。
-
hawl29@hawl29-PC:~/Desktop/cprogram/OS_experiment$ ./bf_test2_execfile 请输入内存空间大小: 1000 请输入碎片大小: 1 请输入测试次数:11 段错误 hawl29@hawl29-PC:~/Desktop/cprogram/OS_experiment$ ./bf_test2_execfile 请输入内存空间大小: 1000 请输入碎片大小: 1 请输入测试次数:10 内存利用率为:0.00% 已分配分区 空闲分区 (分区号, 作业, 始址, 大小) (分区号, 始址, 大小) 1, 1, 0, 701 4, 933, 67 2, 2, 701, 184 3, 3, 885, 48
提示段错误,但程序又不是每次都无法执行。
在网上查原因很多:访问不存在的内存地址,访问系统保护的内存地址,空指针废弃,堆栈溢出等等。
这里利用gdb工具定位问题。
hawl29@hawl29-PC:~/Desktop/cprogram/OS_experiment$ gcc -g bf_test2.c -o bf_test2_execfile
gdb执行后提示:
Program received signal SIGSEGV, Segmentation fault. 0x0000555555554a03 in add (head=0x555555757830, no=1, size=126) at bf_test2.c:41 41 if(q->size-size>SIZE) //SIZE是我们设置的碎片大小,若分配后空闲区剩余空间容量大于碎片大小,则需为剩余空间新建一个链表结点来记录它 (gdb) q A debugging session is active.
在定位的地方找到了如下代码:
node型的指针在声明后并没有初始化,for循环要是没有执行
q=p
时,后面的if中q指针可能指向了一个不确定的地方。node *p,*q; for(p=head->next;p;p=p->next) { if(p->no==-1&&p->size>=size) //先找满足容量大于作业大小的空闲区 { if(p->size
size; q=p; } } } if(q==NULL) return 0; //如果没找到满足要求的空闲区,则分配失败
-
完整代码