码迷,mamicode.com
首页 > 其他好文 > 详细

操作系统实验——工作集模型下的内存管理模拟

时间:2015-05-31 09:22:18      阅读:122      评论:0      收藏:0      [点我收藏+]

标签:操作系统   内存管理   

实验要求

现有若干进程,每个进程的页面访问顺序已经给出,并且这些进程交替地访问页面
设定一个工作集窗口Δ和内存页面数M
用一个数据结构维护每个进程的工作集,这个数据结构可以是数组或链表
根据进程访问页面的顺序,动态更新每个进程的工作集合和内存的空闲页面数
内存页面不足时,暂停某些进程。并在内存足够时,再将其唤醒
对给出的几个进程,利用工作集模型,进行内存的管理。
内存页面总数设为1000
工作集窗口初始可设为500左右,然后改变工作集窗口的大小,观察其对实验结果的影响
跟踪每个进程访问页面过程中页错误率的变化趋势,并将其记录到相应的文件中。
利用记录的数据生成折线图,然后做出分析。(生成折线图,可使用Excel工具)

分析

用到的数据结构

虽然是C语言的程序,不过这里先采用面向对象的思路进行分析。因为根据要求,我们会需要一些数据结构,最明显的:集合(Set),因为Set的特点是元素唯一性,所以可以使用Set保存当前工作集窗口下用到的页面。
既然是面向对象,那么把工作重心放到名词上,先抽出类。
每一个进程都有一个工作集合,在每一时刻工作集合下存着当前该进程访问的页面。另外,由于内存不足或充裕的变动,进程可能被暂停,所以保留一个标志位表明进程的运行状态。进程的访问页面顺序由题目给出,因此用一个数组保留所有访问顺序
每一个工作集窗口对应一个进程,由于颠簸,在进程访问页面时会有访问错误,因此要记录访问错误次数

考虑到方便起见,我先用高级语言实现,省去了自己封装Set还有处理指针的麻烦,由于我使用的是Xcode环境,所以用的是oc,代码在后面的附录中,仅供参考。接下来依旧会用C语言进行实现和讲解。

程序的实现思路

几个进程依次访问页面,对于每一个进程,每次将要访问的页存入工作集,同时将不再访问的页从工作集中移除,这样工作集中的页就是要调入到内存中的页。
技术分享
下一时刻,当进程准备访问新的页时,先查看工作集中有没有该页,如果有,那么可以直接访问而不会发生错误,否则会发生页访问错误,需要将新的页装入工作集。
为了方便起见,我们假定页不动,将工作集看成窗口(也就是所谓的工作集窗口),每次访问时工作集窗口后移。起初窗口从起点开始,这时访问的页还不足以填充满这个窗口,直到窗口移到最后一个元素,说明进程执行完毕。
这样,我们就能基本上确定每个类需要的操作了。主要是窗口类,要有一个将窗口后移的函数

/**
 *  工作窗口后移至某一值,将移出窗口并且不用的页从内存删除
 */
void window_moveTo(ModelSetWindow *window, int end);

内存不足时将进程暂停

/**
 *  内存空间不够时将该进程暂停
 */
void window_stop(ModelSetWindow *window);

对进程而言,需要每次将进程用到的页调入内存

/**将该进程用到的新的页调入内存*/
void process_addPage(Process *process, int pageNumber);

实现

常量

本次实验最麻烦的就是这些难以理解、容易混淆的概念。这里理顺一下。
内存页面总数:整个内存能容纳的最大页面数,所有进程的所有工作集窗口内页面总数不允许超过该值。
工作集窗口大小:每个进程的工作集大小,例如上图中大小为10。(虽然某一时刻下由于相同页面的出现,工作集内元素不一定等于10,上图中分别是5和2)

将它们和其它常量定义一下

//CONST.h

//bool 类型
typedef int BOOL;
#define YES 1
#define NO  0

//数组最大长度
#define MAX_LENGTH  40000

//本次实验中以int表示一个页
typedef int ELEM_TYPE;

//获取较大值
#define MAX(a,b) (((a)>(b))?(a):(b))

/**
 *  内存页面总数
 */
#define kMemoryPageCount    1000
/**
 *  工作集窗口大小
 */
#define kModelSetWindowSize 400

第一个数据结构:集合(Set)

Set在数据结构上的定义是,无序的,不重复的collection,这里因为是用C语言写,所以用数组表示,在方法中限制。

/**集合*/
typedef struct Set {
    ELEM_TYPE data[MAX_LENGTH];
    int length;
} Set;

void set_init(Set *set);
/**为集合添加新元素,如果新元素已经在集合中存在,则不会添加*/
void set_add(Set *set, ELEM_TYPE elem);
/**判断当前集合是否包含某元素*/
BOOL set_containsObject(Set *set, ELEM_TYPE elem);

这个Set的实现非常简单,这里就不赘述了。

进程

按照之前的分析,不难写出进程的数据结构

/**进程*/
typedef struct Process {
    /**进程id*/
    int processID;
    /**进程页面数*/
    int processPageCount;
    /**进程工作集合*/
    Set *set;
    /**是否正在执行*/
    BOOL isRunning;
    /**内存访问顺序*/
    ELEM_TYPE sequence[MAX_LENGTH];
    /**内存访问顺序的大小*/
    int sequence_count;
} Process;

void process_initWithIDAndPageCount(Process *process, int processID, int count);

/**将该进程用到的新的页调入内存*/
void process_addPage(Process *process, int pageNumber);

/**从文件读取生成页面访问序列*/
void process_getSequence(Process *process);

addPage的做法就是调用该进程的set的set_add方法,保证工作集内页面唯一。这里看一下读文件的getSequence方法。

void process_getSequence(Process *process) {
    FILE *fp;
    char fileName[255];
    sprintf(fileName, "%s/process_0%d", kFileAbsoluteLocation, process->processID);

    fp = fopen(fileName, "r");
    if (fp == NULL) {
        printf("error occured when read file %s\n", fileName);
    }

    char buffer[255];
    for (int i = 0; i < process->processPageCount; i++) {

        if (feof(fp)) {
            return;
        }

        char *number = fgets(buffer, 1000, fp);
        process->sequence[i] = atoi(number);
        process->sequence_count++;
    }
}

1、C语言中的格式字符串生成:使用sprintf。sprintf是输出格式化字符串到一字符数组,规则和printf一样。当然有输出就有输入,由于实验中用不到,就不做介绍了。
2、按行读数据:使用fgets读入一行,第二个参数n表示读入的长度,由于每次要在读入的字符串后添加换行符,所以本质上只能读入n-1长度,当然如果读到n-1之前遇到了行末,则不再往下读取。

工作集窗口

/**工作集窗口*/
typedef struct ModelSetWindow{
    /**该窗口对应的进程*/
    Process *process;
    /**错误次数,用于计算错误率*/
    int wrongCount;
    /**对应进程是否访问结束*/
    BOOL hasfinished;
    /**当前对象是否已经把结果打印出来了*/
    BOOL hasLoggedResult;
    /**当前访问到的步数*/
    int currentStep;

}ModelSetWindow;

void window_initWithProcess(ModelSetWindow *window, Process *process);

/**
 *  获取下一个页
 */
ELEM_TYPE window_getNextPage(ModelSetWindow *window, int end);
/**
 *  工作窗口后移至某一值,将移出窗口并且不用的页从内存删除
 */
void window_moveTo(ModelSetWindow *window, int end);
/**
 *  内存空间不够时将该进程暂停
 */
void window_stop(ModelSetWindow *window);
/**
 *  工作集合窗口开始工作,后移一位,表示模拟页调用,返回当前该进程占用内存页面数
 */
int window_run(ModelSetWindow *window);

1、moveTo方法
理论上,对于每次被移除窗口的页面x,需要判断是否还应该存在于集合中。如果新进来的页面或未被移除的页面中包含x,那么x不用从集合中删除,否则应删除x,在实现上会非常复杂,所以这里干脆每次清空工作集,重新对当前窗口中页面依次加入。
还要注意开始阶段窗口未完全滑入时,元素不满的情况下起点问题。

/**
 *  工作窗口后移至某一值,将移出窗口并且不用的页从内存删除
 */
void window_moveTo(ModelSetWindow *window, int end) {
    if (window->process->set == NULL) {
        window->process->set = (Set *)malloc(sizeof(Set));
        set_init(window->process->set);
    }
    //有可能当前窗口还没完全移入
    int start = MAX(0, end - kModelSetWindowSize + 1);
    //把用到的加入内存
    for (int i = start; i <= end; i++) {
        process_addPage(window->process, window->process->sequence[i]);
    }
}

2、run方法
从上面可以看到之前说的“清空”并没有体现,实际上主要的操作都在run方法中,run方法要先判断本次是否有页错误,如果有那么计数值要增加,另外关于是否被暂停、是否完成的操作也在run中,最后,为了方便在main方法中获取所有进程的占用页的总和,这里将单个进程的占用页数返回。
注:写博客的时候突然发现如果没有页错误,不需要进行“清空、重新增加”的操作。懒得改了:-(

/**
 *  工作集合窗口开始工作,后移一位,表示模拟页调用,返回当前该进程占用内存页面数
 */
int window_run(ModelSetWindow *window) {
    //如果新掉进来的页在原来中找不到,则说明此次出现页错误
    if (window->process->set != NULL && set_containsObject(window->process->set, window_getNextPage(window, window->currentStep)) == NO) {
        window->wrongCount++;
//        printf("进程%d发生页错误,wrongCount = %d\n", window->process->processID, window->wrongCount);
    }

    //把全部的清除,表示把“不用的删除”,因为接下来会重新对窗口添加一遍
    free(window->process->set);
    window->process->set = NULL;

    //如果当前进程被暂停
    if (window->process->isRunning == NO) {
        return 0;
    }
    //如果当前进程执行完毕
    if (window->currentStep >= window->process->sequence_count) {
        window->hasfinished = YES;
        if (window->hasLoggedResult == NO) {
            printf("进程%d执行完毕,页错误次数%d,页错误率%f\n", window->process->processID, window->wrongCount, (float)window->wrongCount / window->process->processPageCount);
            window->hasLoggedResult = YES;
        }
        return 0;
    }

    window_moveTo(window, window->currentStep);
    window->currentStep++;
//    printf("currentStep = %d, window count = %d\n", window->currentStep, (int)window->process->set->length);
    return (int)window->process->set->length;
}

Main方法

main方法中需要判断当前所有进程是否都结束,如果没有的话一直执行操作,同时监听超出内存容量的情况,如果进程占用页数超过内存页面总数,那么要将某个进程暂停,空闲时恢复。这里将空闲定义为有300(hard code)个空闲页。

int main(int argc, const char * argv[]) {

    //准备生成随机数
    srand((unsigned)time(NULL));

    //第一个进程
    Process p1;
    process_initWithIDAndPageCount(&p1, 1, 40000);
    //第二个进程
    Process p2;
    process_initWithIDAndPageCount(&p2, 2, 39000);
    //第三个进程
    Process p3;
    process_initWithIDAndPageCount(&p3, 3, 38000);
    //第四个进程
    Process p4;
    process_initWithIDAndPageCount(&p4, 4, 40000);

    //四个进程对应的窗口
    ModelSetWindow window1;
    window_initWithProcess(&window1, &p1);
    ModelSetWindow window2;
    window_initWithProcess(&window2, &p2);
    ModelSetWindow window3;
    window_initWithProcess(&window3, &p3);
    ModelSetWindow window4;
    window_initWithProcess(&window4, &p4);

    ModelSetWindow windows[kProcessCount] = {window1, window2, window3, window4};

    while (1) {
        //查看是否全部进程都结束了
        BOOL hasFinish = YES;
        for (int i = 0; i < kProcessCount; i++) {
            ModelSetWindow *w = &windows[i];
            if (w->hasfinished == NO) {
                hasFinish = NO;
                break;
            }
            hasFinish = YES;
        }

        if (hasFinish == NO) {
            int count = 0;

            for (int i = 0; i < kProcessCount; i++) {
                ModelSetWindow *w = &windows[i];
                count += window_run(w);
            }
//            printf("%d\n", count);

            if (count > kMemoryPageCount) {
                ModelSetWindow *selectedWindow;
                //选一个正在运行的进程
                do {
                    int random = rand() % kProcessCount;
                    selectedWindow = &windows[random];
                } while (selectedWindow->process->isRunning != YES || selectedWindow->hasfinished == YES);

                window_stop(selectedWindow);
                printf("本次超出内存容量,暂停进程%d\n", selectedWindow->process->processID);
            } else if (kMemoryPageCount - count > 500) {
                //找一个暂停的进程继续运行
                for (int i = 0; i < kProcessCount; i++) {
                    ModelSetWindow *w = &windows[i];
                    if (w->process->isRunning == NO) {
                        printf("内存空闲,将进程%d执行\n", w->process->processID);
                        w->process->isRunning = YES;
                        break;
                    }
                }
            }

        } else {
            break;
        }
    }

    printf("执行完毕\n");

    return 0;
}

实验结果

技术分享

技术分享

技术分享

技术分享

源代码

本文源码都可以在这里找到,其中MemoryManagement-OC是最开始用高级语言写的,其他的为C语言版,非Xcode环境可以找到其中所有的.h和.c文件,拷到对应的环境下运行。
上述代码在Xcode6.3.1 llvm编译环境下执行通过。

总结

1、个人感觉,对于这次实验而言,难点在于对题意和概念的理解,理解题意后操作上没有什么问题。毕竟我们不是模拟那些调度算法。
但是,一次实验彻底暴露了C功底。。。各种BAD_ACCESS各种崩有没有- -C语言不像高级语言,不仅体现在自己管理内存上(之前没有free掉set导致每次运行都要占用我的小air的2G+的内存),还体现在新手杀手——指针上。
总结两个经常崩的问题和解决:
①对于需要传指针的函数,直接声明一个指针后穿进去会crash,原因:野指针访问。
例如:

char *fileName;
sprintf(fileName, "....");     //crash~~fileName刚声明出来是野指针

//应改为
char *fileName[255];
sprintf(fileName, "....");

②原理同上,自定义“对象”的初始化,声明栈“对象”然后传地址

//错误的初始化
Process *p1;
process_initWithIDAndPageCount(p1, 1, 40000);    
//正确的写法
Process p1;
process_initWithIDAndPageCount(&p1, 1, 40000);

2、面向对象的思维方式。
之前一直觉得所谓面向对象不过是在结构体中加方法,没有真正理解提出这一概念的意义。这次实验中能明显感觉出面向对象的好处,应该说,面向对象更多的是让我们切换一种思维方式,不再从算法入手,而是从对象入手。

曾经疑惑过,就算是面向对象也要自己在类中写方法,即“每个类中面向过程”,不过由于对象已经被抽象和封装,每个类只写自己的功能。以类为单元,这个程序就不再是零散的零件了。

操作系统实验——工作集模型下的内存管理模拟

标签:操作系统   内存管理   

原文地址:http://blog.csdn.net/u013604612/article/details/46279175

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!