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

這一章的記憶體管理便是作業系統作為boss程式的一種展現。作業系統管理記憶體是因為,計算機要運作程式需要記憶體,是以作業系統作為程式的大boss,來負責管理并給應用程式(小弟們)配置設定、回收記憶體。
首先,記憶體管理要涉及緩存的概念。在我們做記憶體檢查時要将緩存設為OFF。
高速緩存位于主存與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)
編輯
收藏
舉報