# 30 Day os day 3
這幾天有點忙我們來把記憶體管理的基礎打好,接下來下一個地方可能會去研究不同任務的切換,有大致上讀過,linux kernel 0.11也是跟這個地方在不同任務的調度做不同的演算法,透過gtd 去動態切換 tss這邊,這邊看能不能直接幫這個作業系統改一些linux kernel 0.11 實驗上去
https://mooc.study.163.com/course/1000002008?tid=2403044007
但是還是要詳細抽取相關聯的地方,到時候再來決定要在linux kernel 0.11 上實作gui還是 在這個os 上實現其他功能等等
# memory 統計
這邊前面有稍微提到 計算機結構 memory spatial localit、、的一些東西,就是假設在一個迴圈裡,裡面的變數其實那些varable 的位置 可以被存在緩存裡,[spatial ](https://zh.wikipedia.org/wiki/%E8%AE%BF%E9%97%AE%E5%B1%80%E9%83%A8%E6%80%A7)
![](https://i.imgur.com/oe8tOwz.png)
這一章可能會教大家怎麼去讀出一共使用多少memory 空間
内存检查时,要往内存里随便写入一个值,然后马上读取,来检查读取的值与写入的值是否
相等。如果内存连接正常,则写入的值能够记在内存里。如果没连接上,则读出的值肯定是乱七
八糟的。方法很简单。但是,如果CPU里加上了缓存会怎么样呢?写入和读出的不是内存,而是
缓存。结果,所有的内存都“正常”,检查处理不能完成。
這個就是他使用的方式
直接上code來看
## bootpack.c
會覺得難懂,可能是不太知道
這邊要直接跑code 去看比較清楚
define 和 unsigned int 之間要怎麼轉
```c=
#include <stdio.h>
#include <stdlib.h>
#define TEST 0x00040000
int main (){
int test = 0;
unsigned int i = 0xfffbffff;
i = i| TEST;
printf("%x\n",i);
return 0;
}
```
> 簡單來說下面這段程式碼,就是 eflag 我們在以前的章節有提過這是假設有發生 進位 這邊的 eflag 就會 更改 ,而我們現在這些動作就是 先把 eflag 讀近來,再去做 or 他這邊的意思是,假設不管一開始有沒有怎樣都當嘗試把 ac-flag 進行寫入
> 他用這樣的方式去判斷 cpu 到底是386 還是 486 這邊可以看到程式碼又透過io_store_eflags(eflg); 再把修改完後的eflg 再存入 這邊的化可以看到假設是386的話他還是會跑為0 ,因為根本就沒這個ac-flag 當然你不能寫所以下一次階段還是會維持之前的數值,反之則你是 flag486
> 然後往下可以看到不管怎樣對這個 ac-flag 進行讀寫他這邊又用and 反向過後的 eflag 則 假設有值的話你還是會有值 最後復原原先的 eflag
> 接下來則去呼叫 memtest_sub 來檢查memory使用率
> 這就是他這段程式碼要做的事情
```c =
#define EFLAGS_AC_BIT 0x00040000
#define CR0_CACHE_DISABLE 0x60000000
![](https://i.imgur.com/6mb9D5V.png)
簡單來說
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;
}
```
## memtest_sub
這裡算是第一代的計算memory 總共有多少容量的code
```c=
memtest_sub unsigned int memtest_sub(unsigned int start, unsigned int end)
{
unsigned int i, *p, old, pat0 = 0xaa55aa55, pat1 = 0x55aa55aa;
for (i = start; i <= end; i += 4)
{
p = (unsigned int *)i;
old = *p; /* 先记住修改前的值 */
*p = pat0; /* 试写 */
*p ^= 0xffffffff; /* 反转 */
if (*p != pat1)
{ /* 检查反转结果 */
not_memory:
*p = old;
break;
}
*p ^= 0xffffffff; /* 再次反转 */
if (*p != pat0)
{ /* 检查值是否恢复 */
goto not_memory;
}
*p = old; /* 恢复为修改前的值 */
}
return i;
}
```
## memtest_sub sec quick version
他原因就是假設如果只要單純計算容量的話,應該就不用再去嘗試去讀寫,預設所有memory 都可讀可寫
她就每4k開始算,總容量為 32mb
```c=
unsigned int memtest_sub(unsigned int start, unsigned int end)
{
unsigned int i, *p, old, pat0 = 0xaa55aa55, pat1 = 0x55aa55aa;
for (i = start; i <= end; i += 0x1000) {
p = (unsigned int *) (i + 0xffc);
old = *p; /* 先记住修改前的值 */
*p = pat0; /* 试写 */
*p ^= 0xffffffff; /* 反转 */
if (*p != pat1)
{ /* 检查反转结果 */
not_memory:
*p = old;
break;
}
*p ^= 0xffffffff; /* 再次反转 */
if (*p != pat0)
{ /* 检查值是否恢复 */
goto not_memory;
}
*p = old; /* 恢复为修改前的值 */
}
return i;
}
```
## bootpack.c
他這邊,因為compiler 幫他做最佳化導致他的程式碼
```c=
i = memtest(0x00400000, 0xbfffffff) / (1024 * 1024);
sprintf(s, "memory %dMB", i);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 32, COL8_FFFFFF, s);
```
暂时先使用以上程序对0x00400000~0xbfffffff范围的内存进行检查。这个程序最大可以识别
3GB范围的内存。0x00400000号以前的内存已经被使用了(参考8.5节的内存分布图),没有内存,
程序根本运行不到这里,所以我们没做内存检查。如果以byte或KB为单位来显示结果不容易看明
白,所以我们以MB为单位。
也不知道能不能正常运行。如果在QEMU上运行,根据模拟器的设定,内存应该为32MB。
运行“make run”。
變成這樣
## memtest_sub compiler optimization
```c=
unsigned int memtest_sub(unsigned int start, unsigned int end)
{
unsigned int i;
for (i = start; i <= end; i += 0x1000) { }
return i;
}
```
所以作者又乾脆去寫一個asm 版本的統計 asm
## memtest_sub asm version
可能會懷疑的就是 push 會影響 esp 位置 ,再來就是這些register 被拿來當作變數使用難度應該還可以主要對照
memtest_sub sec quick version 程式碼搬過來
```asm
_memtest_sub: ; unsigned int memtest_sub(unsigned int start, unsigned int end)
PUSH EDI ; (由于还要使用EBX, ESI, EDI) esp+4
PUSH ESI esp+4
PUSH EBX esp+4
MOV ESI,0xaa55aa55 ; pat0 = 0xaa55aa55;
MOV EDI,0x55aa55aa ; pat1 = 0x55aa55aa;
MOV EAX,[ESP+12+4] ; i = start; = 12 繼續只向第一個 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
```
![](https://i.imgur.com/GRixWcB.png)
到這一節可以看到這邊書中的內容可以去統計 memory 的容量從
0x00400000, 0xbfffffff 每 4k 統計
# 管理 memory
這一節前面有大致提到過怎麼去統計這一節是要講怎麼分配,在一台電腦裡每個不同的 application 都有各自不同的記憶體,這邊是再說要怎麼去 free
## bootpack.c
### HariMain
```c=
#define MEMMAN_ADDR 0x003c0000
void HariMain(void)
{
(中略)
unsigned int memtotal;
struct MEMMAN *memman = (struct MEMMAN *)MEMMAN_ADDR;
(中略)
memtotal = memtest(0x00400000, 0xbfffffff);
memman_init(memman);
memman_free(memman, 0x00001000, 0x0009e000); /* 0x00001000 - 0x0009efff */
memman_free(memman, 0x00400000, memtotal - 0x00400000);
(中略)
sprintf(s, "memory %dMB free : %dKB",
memtotal / (1024 * 1024), memman_total(memman) / 1024);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 32, COL8_FFFFFF, s);
```
### alloc
```c=
#define MEMMAN_FREES 4090 /* 大约是32KB*/
struct FREEINFO
{ /* 可用信息 */
unsigned int addr, size;
};
struct MEMMAN
{ /* 内存管理 */
int frees, maxfrees, lostsize, losts;
struct FREEINFO free[MEMMAN_FREES];
};
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; /* 没有可用空间 */
}
```
### free
```c=
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; /* 失败 */
}
```
0x00000000 - 0x000fffff : 虽然在启动中会多次使用,但之后就变空。(1MB)
0x00100000 - 0x00267fff : 用于保存软盘的内容。(1440KB)
0x00268000 - 0x0026f7ff : 空(30KB)
0x0026f800 - 0x0026ffff : IDT (2KB)
0x00270000 - 0x0027ffff : GDT (64KB)
0x00280000 - 0x002fffff : bootpack.hrb(512KB)
0x00300000 - 0x003fffff : 栈及其他(1MB)
0x00400000 - : 空
這個是8.5 章節 作者提到的這邊有看到作者的用於記憶體管理的分布圖,他說他只是隨便挑其中,並沒有什麼跟硬體知識做關聯
memman_free(memman, 0x00001000, 0x0009e000); /* 0x00001000 - 0x0009efff */
這塊記憶體是幹嘛用的,可能是測試用的,可能後面看完才能來解釋這一塊,不前不知道用途
memtotal = memtest(0x00400000, 0xbfffffff);
memman_init(memman);
memman_free(memman, 0x00400000, memtotal - 0x00400000);
memtotal = memtest(0x00400000, 0xbfffffff);
假設上限到 0x00400000 - 0xbfffffff 32mb
幾乎上一段程式碼只會走下面這些流程,依照他說的,這邊可能只是去free某一塊 記憶體然後標註為 free
也就是取一段範圍並設置
man->free[i].addr = addr;
man->free[i].size = size;
```c=
/* 既不能与前面归纳到一起,也不能与后面归纳到一起 */
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; /* 成功完成 */
}
```
## 搭配 alloc 和 free 流程
這邊實際應用可能要結合 alloc 比較看得懂
![](https://i.imgur.com/QbgCynQ.png)
也就是
同一個 memman 可能會管理很多free 陣列 可以透過上層的 free 來計算目前可用的free陣列有多少,一個 free[0]
裡面又有分 size 和 addr 則他的意思就是
我先宣告有一塊記憶體位置
> 也就是 free[0]
> memman_free(memman, 0x00001000, 0x0009e000); /* 0x00001000 - 0x0009efff */
> 現在我要去申請兩塊記憶體
> address1 = memman_alloc(memman,0x00001000);
> address2 = memman_alloc(memman,0x00001000);
> 則分別存到 unsigned int 的位置
> 分別可得到
> 0x1000
> 0x2000
> 也就是記憶體目前的位移
> 從 free[0].addr + size 0x00001000+0x00001000 得到下一個addr的偏移量
> 和減去 free[0].size 預先的 free[0].size 當 size =0 則 這塊free 就沒辦法再用了
> 這一段就是 alloc 的程式碼內容
那麼在看 free程式碼就會比較好懂了
也就是他內容的就是在找 free陣列裡的addr + size 的偏移 能夠剛好符合你所要記憶體區段
也就是很像 glibc 裡面 malloc 和 free
https://wzt.ac.cn/2019/03/15/glibc_analyse2/
glibc 裡面free
那麼free這個程式主要就是要怎麼把這一塊記憶體回收,避免某些記憶體不能去做讀寫與釋放
作者這段程式碼來解釋就是
> 第一段
> 先找前面addr 到+ size 能不能符合你所釋放的區段有的話則進行合併,假設真的找到了則直接return 0
> 否則就進入到
> 第二段
> 要注意 man->free[i].addr 比 addr 大 所以 i 會小於 man->frees
> 假設第一段程式碼判斷完還是不行則代表 全部已經找完一遍了所以就找符合
> (addr + size == man->free[i].addr)
> 這段程式碼又代表 不一定是最後但是 後面addr +size 還有其他記憶體位置
> 則更新 free 位置範圍
> man->free[i].addr = addr;
> man->free[i].size += size;
> 則這一塊的 free [i] 位置從 free[i].addr ~ free[i].addr+size
> reutnr 0
> 第三段
> 最後則在小於MEMMAN_FREES 區段最大值 && if (i < man->frees) 第二區段條件符合但是
> 但是還是沒辦法alloc
> addr + size == man->free[i].addr
> 找不到符合的區段,所以要產生一塊記憶體位置空間了,則假設下面片段
> 假設記憶體區段 9 和 10
```
i= 9
j= 10
10 9
```
> 可以看到 i =9 的話 假設 j = 10 將原本10記憶體範圍則會指向 9的記憶體範圍(後面位置都要重新規劃)
> ,則下一次10位置 可能會被重新分配成新的記憶體範圍
> 而當前記憶體區段 9的位置將被更新成目前新的記憶體範圍
```c=
man->frees++;
if (man->maxfrees < man->frees)
{
man->maxfrees = man->frees; /* 更新最大值 */
}
man->free[i].addr = addr;
man->free[i].size = size;
```
```c=
#define MEMMAN_ADDR 0x003c0000
void HariMain(void)
{
struct BOOTINFO *binfo = (struct BOOTINFO *) ADR_BOOTINFO;
char s[40], mcursor[256], keybuf[32], mousebuf[128];
int mx, my, i;
struct MOUSE_DEC mdec;
unsigned int memtotal;
unsigned int address1;
unsigned int address2;
struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
init_gdtidt();
init_pic();
io_sti(); /* IDT/PIC的初始化已经完成,于是开放CPU的中断 */
fifo8_init(&keyfifo, 32, keybuf);
fifo8_init(&mousefifo, 128, mousebuf);
io_out8(PIC0_IMR, 0xf9); /* 开放PIC1和键盘中断(11111001) */
io_out8(PIC1_IMR, 0xef); /* 开放鼠标中断(11101111) */
init_keyboard();
enable_mouse(&mdec);
memtotal = memtest(0x00400000, 0xbfffffff);
init_palette();
init_screen8(binfo->vram, binfo->scrnx, binfo->scrny);
memman_init(memman);
memman_free(memman, 0x00001000, 0x0009e000); /* 0x00001000 - 0x0009efff */
sprintf(s, "memman : %x time",memman->free[0].size );
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 112, COL8_FFFFFF, s);
address1 = memman_alloc(memman,0x00001000);
sprintf(s, "memman : %x time",memman->free[0].size );
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 128, COL8_FFFFFF, s);
address2 = memman_alloc(memman,0x00001000);
memman_free(memman, 0x00400000, memtotal - 0x00400000);
mx = (binfo->scrnx - 16) / 2; /* 计算画面中心坐标 */
my = (binfo->scrny - 28 - 16) / 2;
init_mouse_cursor8(mcursor, COL8_008484);
putblock8_8(binfo->vram, binfo->scrnx, 16, 16, mx, my, mcursor, 16);
sprintf(s, "(%d, %d)", mx, my);
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 0, COL8_FFFFFF, s);
sprintf(s, "memory %dKB free : %dKB", memtotal / (1024), memman_total(memman) / 1024,((memtotal / (1024))- (memman_total(memman) / 1024)));
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 32, COL8_FFFFFF, s);
sprintf(s, "usage : %dKB", ((memtotal / (1024))- (memman_total(memman) / 1024)));
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 48, COL8_FFFFFF, s);
sprintf(s, "memman : %x time",memman->frees );
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 64, COL8_FFFFFF, s);
sprintf(s, "memman : %x time",address1 );
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 80, COL8_FFFFFF, s);
sprintf(s, "memman : %x time",address2 );
putfonts8_asc(binfo->vram, binfo->scrnx, 0, 96, COL8_FFFFFF, s);
```
# alloc & free 4k
我们要编写一些总是以0x1000字节为单位进行内存分配和释放的函数,它们会把指定
的内存大小按0x1000字节为单位向上舍入( roundup),而之所以要以0x1000字节为单位,是因为
笔者觉得这个数比较规整。另外,0x1000字节的大小正好是4KB
## memory.c
```c=
unsigned int memman_alloc_4k(struct MEMMAN *man, unsigned int size)
{
unsigned int a;
size = (size + 0xfff) & 0xfffff000;
a = memman_alloc(man, size);
return a;
}
```
```c=
int memman_free_4k(struct MEMMAN *man, unsigned int addr, unsigned int size)
{
int i;
size = (size + 0xfff) & 0xfffff000;
i = memman_free(man, addr, size);
return i;
}
```