操作系統-第9天 內存的管理



前言:在《30天自制操作系統》中,第九天介紹了內存的兩種管理方式,在此理清一下思路。

1.爲什麼需要管理內存
          管理內存無非就是管理一些地址,那麼爲什麼要管理地址呢? 如果程序A需要分配100KB的大小的內存,程序B需要分配200KB的大小的內存,那麼如果我們隨便分配(其實分配說白了就是指定一個內存的地址)的話,A程序的在內存中的數據會與B程序的數據互相覆蓋,導致程序出錯;而且如果程序結束了,那麼內存中的數據不再需要,因此會釋放(將使用完的內存歸還給內存管理程序)內存。所以這些都是我們要考慮的問題。
 

2.管理內存方式一:   
以4KB爲一個單位進行管理,每一個單位配置一個標誌位表示這4KB在不在使用(使用中:1 未使用:0)如下圖所示:


(假設內存128MB) 這部分代碼很簡單,只要創建一個數組將裏面的數據都初始化爲0就可以,那麼現在程序A需要分配一段100KB的內存空間,那麼我們只需要遍歷數組找到連續的25個0即可,程序如下:

<span style="font-size:18px;">        int a[32768],i=0,j=0;
	for(;i<25;i++)
	{
	    if(a[i+j]!=0)
	   {
               j=j+i;
		if(j<32768-25)
		{
                      return 0;
		}
	   }
	}</span>

找到數組的位置j(j表示25個連續0的第一個位置)之後,這個j只是在數組中的位置,我們要知道在內存中的地址,由於數組每個元素管理4KB=0x1000B因此實際的地址是addr=j*0x1000;將這25個數組標記爲1,表示正在使用。代碼:

<span style="font-size:18px;">  for(i=0;i<25;i++)       
  {
	    //addr代表內存中的地址
            a[addr+i]=1;
  } </span>


下面A程序結束,需要釋放內存,那麼就將這25個數組標記爲0,代碼:

  <span style="font-size:18px;">          j=addr/0x1000
            for(i=0;i<25;i++)
            {
	      a[addr+i]=0;
            }</span>
上面就是第一種方案一(windows的軟盤管理方法),讓我們計算一下算法的複雜度:(假設內存大小爲128MB)

(1)首先從空間來看:每個4KB的小內存塊都要分配1B的內存用於記錄,那麼需要128/4KB=32KB的存儲空間來存儲,大約佔總內存的32KB/128MB=0.02%(並不是太大)。

(2)其次從時間來看:剛開始給標記數組分配需要for循環32*1024次,而且分配內存的時候需要查找連續的單元。

(3)我們也可以將標記數組用BYTE(位)來表示,那麼需要的存儲空間大小爲32/8=4KB(一種優化)


3.內存的管理方式二(列表管理):

               這種方式與第一種有點像,同樣是分配數組記錄每一塊內存的信息,但是數組裏保存的並不是(4K內存塊是否使用)標誌位了,而保存的是每一塊空閒內存的起始地址和大小。看下圖



在這裏我們需要建立管理內存的‘機制’,即結構體,想想需要哪些結構體,結構體又保存了什麼?

首先每個空閒內存塊需要有個結構體表示,至少含有開始地址和(空閒)內存塊大小,然後需要一個大的結構體來保存這麼多的空閒塊的信息,即保存空閒塊信息的數組以及空閒塊的個數。建立結構體如下:

<span style="font-size:18px;">struct FREEINFO
{
	unsigned int addr,size;//空閒塊開始地址和空閒塊的大小 
};
struct MEMMAN
{
	unsigned int frees;//空閒塊的個數
	struct FREEINFO freeInfo[MEMMAX_FREE]; 
} </span>
接下來討論分配內存:

首先我們需要找到內存中的空閒塊,這個空閒塊比我們需要分配的大小要大,依次遍歷空閒塊數組直至找到滿足大小的爲止。

<span style="font-size:18px;">unsigned int mem_alloc(struct MEMMAN *m,unsigned int size)//size是需要分配的內存塊的大小 
{
	unsigned int i,a; 
	for(i=0;i<frees;i++)
	{
		//如果找到了合適的空閒內存塊 
		if(m->freeInfo[i].size>=size)
		{
			//保存地址 
			a=m->freeInfo[i].addr;
			m->freeInfo[i].size-=size;
			m->freeInfo[i].addr+=size;
			//如果與空閒內存大小剛好相等 ,
			//那麼空閒內存總數減一,並且從i開始的每個數組元素往前移一位 
			if(m->freeInfo[i].size==0)
			{
				m->frees--;
				for(;i<m->frees;i++)
				{
					m->freeInfo[i]=m->freeInfo[i+1];
				}
			}
		}
	}
	//返回分配內存的起始地址 
	return a;
}</span>
接下來比較麻煩的就是釋放內存,我們可以預見以下情況。

(1)當釋放的內存上邊界和上一個空閒內存塊相接壤(地址鄰接),那麼內存就需要與上一塊歸併;

(2)當釋放的內存下邊界和下一個空閒內存塊相接壤(地址鄰接),那麼內存就需要與下一塊歸併;

(3)以上兩種情況都符合

(4)以上兩種情況都不發生

<span style="font-size:18px;">//釋放內存
void mem_free(struct MEMMAN *m,unsigned int addr,unsigned int size)
{
	int i;
	for(i=0;i<m->frees;i++)
	{
		if(m->freeInfo[i].addr>addr)
		{
			break;
		}
	}
	//1.釋放之後與上一個內存塊相接壤
	if(i>0)
	{
		//與前面接壤,不接壤就退出 
		if(m->freeInfo[i-1].addr+m->freeInfo[i-1].size==addr)
		{
			m->freeInfo[i-1].size+=addr;
			if(i<m->frees)
			{
				if(addr+size==m->freeInfo[i].addr)
				{
					m->freeInfo[i].addr=addr;
					m->freeInfo[i-1].size+=m->freeInfo[i].size;
					m->frees--;
					for(;i<m->frees;i++)
					{
						m->freeInfo[i]=m->freeInfo[i+1];
					}
				}
			}
			return;
		}
	}
	
	
	//2.釋放之後與下一個內存塊相接壤
	if(i<m->frees)
	{
		if(addr+size==m->freeInfo[i].addr)
		{
			m->freeInfo[i].addr=addr;
			m->freeInfo[i].size+=size;
			return; 
		}
	}
	//3.釋放之後與兩邊都不接壤 
	if(m->frees<MEMMAX_FREES)
	{
		for(;i<m->frees;i++)
		{
			m->freeInfo[i]=m->freeInfo[i+1];
		}
		m->frees++;
		if(m->frees>m->freeMax)
		{
			m->freeMax++;
		}
		m->freeInfo[i].addr=addr;
		m->freeInfo[i].size=size;
	}
} </span>

上面是《30天自制操作系統》的內存的兩種管理方式,正在學習其他類型的內存管理方式,因此後面我會補充更多的內容。



 


    





發佈了35 篇原創文章 · 獲贊 55 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章