天天看點

自制作業系統(九) 記憶體管理 - eques

自制作業系統(九) 記憶體管理

2016.07.04  2016.07.05 2016.07.06

參考書籍:《30天自制作業系統》、《自己動手寫作業系統》

        作業系統本質是一個程式,一個大型軟體工程(商業化os的情況)。而程式的本質---一方面是所謂的“資料結構+算法”,另一方面則是 封裝+抽象。作業系統作為一個程式,一方面是控制硬體啟動開機,并且作為第一個在計算機上運作的軟體,另一方面,作業系統負責管理計算機的資源(記憶體管理,檔案管理,I\O),協助使用者要運作的程式在計算機上運作,甚至是多個程式同步運作(程序管理)。是以你可看到,作業系統本質上和那些b\s模式的企業管理網站本質沒有任何差別,都是管理。隻不過作業系統要管理的是計算機資源(在對計算機底層抽象的基礎上),和程式,作為一個boss級别的程式管理程式(這裡你要了解程式運作所需的那些基礎,記憶體、寄存器、cpu等等……了解了這些基礎才能知道作業系統作為一個boss程式怎麼管理應用程式)。

      是以你可以看到,計算機的目的就是要運作程式。而作業系統是一個在計算機上運作的程式,目的是幫助計算機在人類的操作下更好的運作程式。

自制作業系統(九) 記憶體管理 - eques

      這一章的記憶體管理便是作業系統作為boss程式的一種展現。作業系統管理記憶體是因為,計算機要運作程式需要記憶體,是以作業系統作為程式的大boss,來負責管理并給應用程式(小弟們)配置設定、回收記憶體。

      首先,記憶體管理要涉及緩存的概念。在我們做記憶體檢查時要将緩存設為OFF。

自制作業系統(九) 記憶體管理 - eques

       高速緩存位于主存與CPU之間,作為一種緩沖,因為CPU太快,主存存取速度太慢。高速緩存則是一種存取速度接近CPU的存儲器。其工作方式就在于:通路記憶體前,先将要通路的位址和内容存入高速緩存裡。往記憶體裡寫入資料時也一樣。首先更新高速緩存的資訊,再寫入記憶體。

      更細緻地講,高速緩存的記憶體空間與記憶體是存在一定映射規則的:1.全相連映射  2.直接相連映射  3.組相連映射    具體不多談。因為本篇并不涉及那麼深的内容。

      本章需要的是記憶體管理,最基本的是記憶體檢查。要檢查記憶體,需要往記憶體裡随便寫入一個值,然後讀取,來檢查讀取的值與寫入的值是否相等。若相等(說明這裡是可用的記憶體),這時需要先關閉緩存,否則會寫在緩存裡,而不是記憶體。

       下面C函數的目标是關閉高速緩存:

unsigned int memtest(unsigned int start, unsigned int end)
{
    char flg486 = 0;
    unsigned int eflg, cr0, i;

    /* 确認CPU是386還是486以上的 */
    eflg = io_load_eflags();
    eflg |= EFLAGS_AC_BIT; /* AC-bit = 1 */
    io_store_eflags(eflg);
    eflg = io_load_eflags();
    if ((eflg & EFLAGS_AC_BIT) != 0) { /* 如果是386、即使設定AC=1,AC的值還是會回到0 */
        flg486 = 1;
    }
    eflg &= ~EFLAGS_AC_BIT; /* AC-bit = 0 */
    io_store_eflags(eflg);

    if (flg486 != 0) {
        cr0 = load_cr0();
        cr0 |= CR0_CACHE_DISABLE; /* 禁止緩存 */
        store_cr0(cr0);
    }

    i = memtest_sub(start, end);

    if (flg486 != 0) {
        cr0 = load_cr0();
        cr0 &= ~CR0_CACHE_DISABLE; /* 允許緩存 */
        store_cr0(cr0);
    }

    return i;
}      

上面C語言需要調用下面兩個彙編函數,因為為了禁止緩存,需要對CR0寄存器的某一标志位進行操作:

_load_cr0:        ; int load_cr0(void);
        MOV        EAX,CR0
        RET

_store_cr0:        ; void store_cr0(int cr0);
        MOV        EAX,[ESP+4]
        MOV        CR0,EAX
        RET      

至于記憶體檢查部分,用的是彙編語言函數:

_memtest_sub:    ; unsigned int memtest_sub(unsigned int start, unsigned int end)
        PUSH       EDI                        
        PUSH       ESI
        PUSH       EBX
        MOV        ESI,0xaa55aa55            ; pat0 = 0xaa55aa55;
        MOV        EDI,0x55aa55aa            ; pat1 = 0x55aa55aa;
        MOV        EAX,[ESP+12+4]            ; i = start;
mts_loop:
        MOV        EBX,EAX
        ADD        EBX,0xffc                ; p = i + 0xffc;
        MOV        EDX,[EBX]                ; old = *p;
        MOV        [EBX],ESI                ; *p = pat0;
        XOR        DWORD [EBX],0xffffffff    ; *p ^= 0xffffffff;
        CMP        EDI,[EBX]                ; if (*p != pat1) goto fin;
        JNE        mts_fin
        XOR        DWORD [EBX],0xffffffff    ; *p ^= 0xffffffff;
        CMP        ESI,[EBX]                ; if (*p != pat0) goto fin;
        JNE        mts_fin
        MOV        [EBX],EDX                ; *p = old;
        ADD        EAX,0x1000                ; i += 0x1000;
        CMP        EAX,[ESP+12+8]            ; if (i <= end) goto mts_loop;
        JBE        mts_loop
        POP        EBX
        POP        ESI
        POP        EDI
        RET
mts_fin:
        MOV        [EBX],EDX                ; *p = old;
        POP        EBX
        POP        ESI
        POP        EDI
        RET      

     上述代碼很好懂,from start to end循環向記憶體寫入0xaa55aa55(在那之前先将記憶體原本值存入edx寄存器),再使得該記憶體位址與0xffffffff做XOR異或操作。若此時該記憶體裡值為0x55aa55aa,則該記憶體有效。将edx寄存器裡的值放回去就好。

      以上便是記憶體檢查的部分的函數,在記憶體檢查的基礎上,記憶體管理要做的兩件事一是記憶體配置設定,一是記憶體釋放。例如現在要啟動一個應用程式,需要nKB記憶體,其需要的記憶體便由作業系統來配置設定。應用程式結束後再收回。

關于記憶體管理,很多前輩大神設計過很多牛b的算法~當然那是要應用于大型商業作業系統的情況,我們的sonnos隻是個小demo,是以隻需要一個簡單的方法就好。

那就是清單管理法:

      把類似“從xxx号位址開始的yyy位元組的空間是空着的”這種資訊列在表裡。

struct FREEINFO {    /* 可用記憶體資訊 */
    unsigned int addr, size;
};

struct MEMMAN {        /* 記憶體管理 */
    int frees, maxfrees, lostsize, losts;
    struct FREEINFO free[MEMMAN_FREES];
};      

      如果用java描述,大概是這樣的結構:

class  
{
    private List<FREEINFO> freeInfos;
    private int frees;
    private int maxfrees;
    private int lostsize;
    private int losts;
}      

      可以看出FREEINFO是這個記憶體List的單元,該單元,用addr和size标志位址和大小。

基于這樣的資料結構,寫出的記憶體配置設定程式:

void memman_init(struct MEMMAN *man)
{
    man->frees = 0;            /* 可用資訊數目*/
    man->maxfrees = 0;        /* 用于觀察可用狀況:frees的最大值 */
    man->lostsize = 0;        /* 釋放失敗的記憶體大小總和 */
    man->losts = 0;            /* 釋放失敗次數 */
    return;
}

unsigned int memman_total(struct MEMMAN *man)
/* 報告空餘記憶體大小的合計 */
{
    unsigned int i, t = 0;
    for (i = 0; i < man->frees; i++) {
        t += man->free[i].size;
    }
    return t;
}

unsigned int memman_alloc(struct MEMMAN *man, unsigned int size)
/* 配置設定 */
{
    unsigned int i, a;
    for (i = 0; i < man->frees; i++) {
        if (man->free[i].size >= size) {
            /* 找到了足夠大的記憶體 */
            a = man->free[i].addr;
            man->free[i].addr += size;
            man->free[i].size -= size;
            if (man->free[i].size == 0) {
                /* 如果free[i]變成了0,就減掉一條可用資訊 */
                man->frees--;
                for (; i < man->frees; i++) {
                    man->free[i] = man->free[i + 1]; /* 代入結構體 */
                }
            }
            return a;
        }
    }
    return 0; /* 沒有可用空間 */
}      

釋放記憶體函數:

int memman_free(struct MEMMAN *man, unsigned int addr, unsigned int size)
/* 釋放 */
{
    int i, j;
    /* 為便于歸納記憶體,将free[]按照addr的順序排列*/
    /* 是以,先決定應該放在哪裡 */
    for (i = 0; i < man->frees; i++) {
        if (man->free[i].addr > addr) {
            break;
        }
    }
    /* free[i - 1].addr < addr < free[i].addr */
    if (i > 0) {
        /* 前面有可用記憶體 */
        if (man->free[i - 1].addr + man->free[i - 1].size == addr) {
            /* 可以與前面的可用記憶體歸納到一起 */
            man->free[i - 1].size += size;
            if (i < man->frees) {
                /* 後面也有 */
                if (addr + size == man->free[i].addr) {
                    /* 也可以與後面的可用記憶體歸納到一起 */
                    man->free[i - 1].size += man->free[i].size;
                    /* man->free[i]删除 */
                    /* free[i]變成0後歸納到前面去 */
                    man->frees--;
                    for (; i < man->frees; i++) {
                        man->free[i] = man->free[i + 1]; /* 結構體指派 */
                    }
                }
            }
            return 0; /* 成功完成 */
        }
    }
    /* 不能與前面的可用空間歸納到一起*/
    if (i < man->frees) {
        /* 後面還有 */
        if (addr + size == man->free[i].addr) {
            /* 可以與後面的内容歸納到一起 */
            man->free[i].addr = addr;
            man->free[i].size += size;
            return 0; /* 成功完成 */
        }
    }
    /* 既不能與前面的歸納到一起,也不能與後面的歸納到一起 */
    if (man->frees < MEMMAN_FREES) {
        /* free[i]之後的,向前移動,騰出一點可用空間 */
        for (j = man->frees; j > i; j--) {
            man->free[j] = man->free[j - 1];
        }
        man->frees++;
        if (man->maxfrees < man->frees) {
            man->maxfrees = man->frees; /* 更新最大值 */
        }
        man->free[i].addr = addr;
        man->free[i].size = size;
        return 0; /* 成功完成*/
    }
    /* 不能往後移動 */
    man->losts++;
    man->lostsize += size;
    return -1; /* 失敗 */
}      

posted on

2016-07-04 22:35 

eques 

閱讀(439) 

評論(0) 

編輯 

收藏 

舉報