1、实验目的:1、掌握操作系统存储管理中,动态分区分配中首次适应算法进行内存分配的的实现原理;2、模拟编程实现内存分配;3,判断算法特性。
2、实验原理:
①、动态分配:
分配方式可分为四类:单一连续分配、固定分区分配、动态分区分配以及动态可重定位分区分配算法四种方式,其中动态分区分配算法就是此实验的实验对象。动态分区分配又称为可变分区分配,它是根据进程的实际需要,动态地为之分配内存空间。在实现动态分区分配时,将涉及到分区分配中所用的数据结构、分区分配算法和分区的分配与回收操作这样三方面的问题。动态分区分配又称为可变分区分配,它是根据进程的实际需要,动态地为之分配内存空间。在实现动态分区分配时,将涉及到分区分配中所用的数据结构、分区分配算法和分区的分配与回收操作这样三方面的问题。
②、首次适应算法:
空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
3、程序代码实现
#include <stdio.h>
#include <stdlib.h>
typedef int phys_addr_t;
#define NR_HOLES 128 /* 可登记空闲分区的表项总数 */
#define NR_PROCESS 10 /* 进程的总数 */
#define RUNNING 1 /* 进程处于执行、就绪等状态(不考虑阻塞)*/
#define NOTEXISIT 0 /* 进程控制块空闲 */
struct hole {
phys_addr_t h_base; /* 空闲分区起始地址 */
phys_addr_t h_len; /* 空闲分区大小 */
struct hole* h_next; /* 指向空闲分区链中下一个空闲分区 */
} hole[NR_HOLES];
struct hole* hole_head; /* 空闲分区链链首指针 */
struct hole* free_slots; /* 可用表项链链首:每个表项可用来登记一个空闲分区*/
struct pcb {
phys_addr_t p_base; /* 分配到的内存起始地址 */
phys_addr_t p_len; /* 分配到的内存大小 */
int p_state; /* 进程状态 */
} pcb[NR_PROCESS];
void del_slot(struct hole* prev_ptr, struct hole* hp);
void merge(struct hole* hp);
phys_addr_t alloc_mem(phys_addr_t m_len);
void free_mem(phys_addr_t m_base, phys_addr_t m_len);
void mem_init(phys_addr_t base, phys_addr_t size);
void pcb_init();
void show();
/*=========================================================================*
* * alloc_mem *
* *========================================================================*/
phys_addr_t alloc_mem(phys_addr_t m_len) {
/* 采用首次适应算法分配一块连续的内存空间
* * 参数:
* * m_len :申请内存块的长度;
* * 返回值:
* * 分配成功,返回分配到的内存块的起始地址;失败返回-1.
* */
struct hole* hp, * prev_ptr;
phys_addr_t old_base;
hp = hole_head;
while (hp != NULL) {
if (hp->h_len >= m_len) {
/* 找到足够大的空闲分区,并使用该空闲分区. */
old_base = hp->h_base; /* 记录该空闲分区的起始地址 */
hp->h_len -= m_len; /* 从中分一块出去,剩余部分(新空闲区)大小 */
hp->h_base += m_len; /* 剩余部分(新空闲区)起始地址 */
/* 如果空闲区只是被部分分配出去, 调整大小后返回. */
if (hp->h_len != 0) return(old_base);
/* 整个空闲区被分配出去,则从空闲分区链中删除所分配出去的节点,然后返回. */
del_slot(prev_ptr, hp);
return(old_base);
}
prev_ptr = hp;
hp = hp->h_next;
}
return(-1); /* 找不到合适的空闲分区,返回-1. */
}
/*========================================================================*
* * free_mem *
* *========================================================================*/
void free_mem(phys_addr_t m_base, phys_addr_t m_len) {
/* 释放一块连续的内存空间.
* * 参数:
* * m_base :释放区的起始地址;
* * m_len :释放区的长度
* * 释放时要进行空闲分区的合并。
* */
struct hole* hp, * new_ptr, * prev_ptr;
if (m_len == 0) return;
if ((new_ptr = free_slots) == NULL) {
printf("登记空闲分区的表项已耗尽!!!\n");
exit(-1);
}
new_ptr->h_base = m_base;
new_ptr->h_len = m_len;
free_slots = new_ptr->h_next;
hp = hole_head;
/* 如果释放区的起始地址比空闲分区链首的小,或者目前系统中没有空闲分区,
* * 则将释放区插入空闲分区链的链首
* */
if (hp == NULL || m_base <= hp->h_base) {
new_ptr->h_next = hp;
hole_head = new_ptr;
merge(new_ptr);
return;
}
/* 释放区不是插在链首. */
while (hp != NULL && m_base > hp->h_base) {
prev_ptr = hp;
hp = hp->h_next;
}
/* 将释放区插在 'prev_ptr'的后面 */
new_ptr->h_next = prev_ptr->h_next;
prev_ptr->h_next = new_ptr;
merge(prev_ptr); /* 顺序是 'prev_ptr', 'new_ptr', 'hp' */
}
/*========================================================================*
* * del_slot *
* *========================================================================*/
void del_slot(struct hole* prev_ptr, struct hole* hp) {
/* 从空闲分区链中删除一个指定的节点,并将登记该空闲分区的表项插入可用表项链中
* * 参数:
* * prev_ptr:指向空闲分区链表中欲删除节点前面的节点;
* * hp :指向空闲分区链表中要被删除的节点
* */
if (hp == hole_head)
hole_head = hp->h_next;
else
prev_ptr->h_next = hp->h_next;
hp->h_next = free_slots;
free_slots = hp;
}
/*========================================================================*
* * merge *
* *========================================================================*/
void merge(struct hole* hp) {
/* 合并空闲分区
* * 参数:
* * hp:指向可能需合并的空闲分区
* */
struct hole* next_ptr;
/* 如 'hp'指向最后一个空闲分区, 则无需合并;否则尝试着合并空闲分区,
* * 合并后,需释放登记被合并的后面空闲分区的登记表项。
* */
if ((next_ptr = hp->h_next) == NULL) return;
if (hp->h_base + hp->h_len == next_ptr->h_base) {
hp->h_len += next_ptr->h_len; /* 合并两个连续分区的大小 */
del_slot(hp, next_ptr);
}
else {
hp = next_ptr;
}
/* 如 'hp'指向最后一个空闲分区, 则无需合并;否则尝试着合并空闲分区,
* * 合并后,需释放登记被合并的后面空闲分区的登记表项。
* */
/* 在分区的回收时,若回收区hp插入空洞链中间或链尾时,是通过merge(prev_ptr)进行分区的合并的,故合并的操作必须做两次 */
if ((next_ptr = hp->h_next) == NULL) return;
if (hp->h_base + hp->h_len == next_ptr->h_base) {
hp->h_len += next_ptr->h_len;
del_slot(hp, next_ptr);
}
}
/*========================================================================*
* * mem_init *
* *========================================================================*/
void mem_init(phys_addr_t base, phys_addr_t size) {
/* 初始化空闲表项链和空闲分区链:
* * 空闲表项链是指可用来登记空闲分区的空表项组成的链表;
* * 空闲分区链中每个节点对应着内存中的一个空闲分区。
* * 参数:
* * base:初始用户空间的起始地址;
* * size:初始用户空间的长度。
* */
struct hole* hp;
/* 将所有用来登记空闲分区的表项插入空闲表项链中. */
for (hp = &hole[0]; hp < &hole[NR_HOLES]; hp++) hp->h_next = hp + 1;
hole[NR_HOLES - 1].h_next = NULL;
free_slots = &hole[0];
/* 初始化空闲分区链. */
hole_head = NULL;
free_mem(base, size);
}
/*========================================================================*
* * pcb_init *
* *========================================================================*/
void pcb_init() {
/*
* * 初始化进程控制块,初始状态下,系统中没有任何用户进程存在。
* */
int i;
/* 将所有pcb的状态置为空闲状态 */
for (i = 0; i < NR_PROCESS; i++) pcb[i].p_state = NOTEXISIT;
}
/*========================================================================*
* * show *
* *========================================================================*/
void show() {
/*
* * 显示所有进程的详细情况以及空闲分区的详细情况。
* */
int i;
struct hole* hp;
printf("==========================================\n");
/* 显示所有进程的详细情况 */
printf("进程号 起始地址 长度\n");
for (i = 0; i < NR_PROCESS; i++) {
if (pcb[i].p_state != NOTEXISIT) {
printf("%4d %8d %8d\n", i, pcb[i].p_base, pcb[i].p_len);
}
}
printf("==========================================\n");
/* 显示所有空闲区的详细情况 */
printf("空闲区序号 起始地址 长度\n");
hp = hole_head;
i = 1;
while (hp != NULL) {
printf("%4d %8d %8d\n", i, hp->h_base, hp->h_len);
hp = hp->h_next;
i++;
}
printf("==========================================\n");
}
/*========================================================================*
* * main *
* *========================================================================*/
int main() {
/* 测试程序:
* * 1.初始状态下,内存只有一个大的空闲分区,输入它的起始地址
* * 和长度,并对所有的数据结构进行初始化。
* * 2.分配和回收的模拟:
* * (1)输入"a n size"表示进程n申请长度为size的内存空间;
* * (2)输入"f n"表示进程n释放所其所占用的内存空间;
* * (3)输入"e"结束整个程序。
* */
phys_addr_t base, size, psize;
char op[10];
int pno;
/* 输入初始用户内存空间的起始地址和长度 */
printf("请输入初始用户内存空间的起始地址:");
scanf("%d", &base);
printf("请输入初始用户内存空间的长度:");
scanf("%d", &size);
/* 进行内存的初始化工作 */
mem_init(base, size);
pcb_init();
show();
while (1) {
scanf("%s", op);
switch (op[0]) {
case 'e':
return 0;
case 'a':
scanf("%d%d", &pno, &psize);
printf("\n动作:%c %4d %8d\n", op[0], pno, psize);
if (pcb[pno].p_state == NOTEXISIT) {
if ((pcb[pno].p_base = alloc_mem(psize)) != -1) {
pcb[pno].p_len = psize;
pcb[pno].p_state = RUNNING;
}
else {
printf("申请失败\n");
}
}
else {
printf("进程%d已经存在\n", pno);
}
break;
case 'f':
scanf("%d", &pno);
printf("\n动作:%c %4d\n", op[0], pno);
if (pcb[pno].p_state == NOTEXISIT) {
printf("进程%d不存在", pno);
}
else {
free_mem(pcb[pno].p_base, pcb[pno].p_len);
pcb[pno].p_state = NOTEXISIT;
}
break;
}
show();
}
}
4、程序流程图
5、结果实现
这一次实验是对内存的管理,用了首次适应算法来分配和回收内存空间,这个算法的优点是高址部分的大的空闲分区得到保留,为大作业的内存分配创造了条件。
实验数据分析:
①在程序开始运行时输入初始的用户空间的起始地址0和长度640K(空闲区);
②给作业1分配130K的内存,空闲区有一块剩余510K;
③给作业2分配60K的内存,空闲区有一块剩余450K;
④给作业3分配100K的内存,空闲区有一块剩余350K;
⑤释放作业2所占有的内存空间60K,空闲区有一块350K,还有一块60K;
⑥给作业4分配200K的内存,空闲区有一块150K,还有一块60K;
⑦释放作业3所占有的内存空间100K,与空闲区一块60K的合并为160K,还有一块150K;
⑧释放作业1所占有的内存空间130K,与空闲区一块160K的合并为290K,还有一块150K;
⑨给作业5分配140K的内存,空闲区剩余有两块150K;
⑩给作业6分配60K的内存,空闲区剩余一块90K和150K;
⑪给作业7分配50K的内存,空闲区剩余一块40K和150K;
⑫释放作业6所占有的内存空间60K,空闲区剩余一块40K、60K和150K;
⑬输入e结束整个程序。