Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> Linux0.11內核--內存管理之2.配合fork,linux0.11fork

Linux0.11內核--內存管理之2.配合fork,linux0.11fork

編輯:關於android開發

Linux0.11內核--內存管理之2.配合fork,linux0.11fork


【版權所有,轉載請注明出處。出處:http://www.cnblogs.com/joey-hua/p/5598451.html 】

 在上一篇的fork函數中,首先一上來就調用get_free_page為新任務的數據結構申請一頁內存,在memory.c中:

/*
* 獲取首個(實際上是最後1 個:-)空閒頁面,並標記為已使用。如果沒有空閒頁面,
* 就返回0。
*/
//// 取空閒頁面。如果已經沒有可用內存了,則返回0。
// 輸入:%1(ax=0) - 0;%2(LOW_MEM);%3(cx=PAGING PAGES);%4(edi=mem_map+PAGING_PAGES-1)。
// 輸出:返回%0(ax=頁面起始地址)。
// 上面%4 寄存器實際指向mem_map[]內存字節圖的最後一個字節。本函數從字節圖末端開始向前掃描
// 所有頁面標志(頁面總數為PAGING_PAGES),若有頁面空閒(其內存映像字節為0)則返回頁面地址。
// 注意!本函數只是指出在主內存區的一頁空閒頁面,但並沒有映射到某個進程的線性地址去。後面
// 的put_page()函數就是用來作映射的。
unsigned long
get_free_page (void)
{
  register unsigned long __res asm ("ax");

  __asm__ ("std ; repne ; scasb\n\t"	// 方向位置位,將al(0)與對應每個頁面的(di)內容比較,
	   "jne 1f\n\t"											// 如果沒有等於0 的字節,則跳轉結束(返回0)。
	   "movb $1,1(%%edi)\n\t"					// 將對應頁面的內存映像位置1。
	   "sall $12,%%ecx\n\t"						// 頁面數*4K = 相對頁面起始地址。
	   "addl %2,%%ecx\n\t"						// 再加上低端內存地址,即獲得頁面實際物理起始地址。
	   "movl %%ecx,%%edx\n\t"				// 將頁面實際起始地址??edx 寄存器。
	   "movl $1024,%%ecx\n\t"				// 寄存器ecx 置計數值1024。
	   "leal 4092(%%edx),%%edi\n\t"		// 將4092+edx 的位置??edi(該頁面的末端)。
	   "rep ; stosl\n\t"									// 將edi 所指內存清零(反方向,也即將該頁面清零)。
	   "movl %%edx,%%eax\n"					// 將頁面起始地址??eax(返回值)。
"1:": "=a" (__res): "" (0), "i" (LOW_MEM), "c" (PAGING_PAGES), "D" (mem_map + PAGING_PAGES - 1):"di", "cx",
	   "dx");
  return __res;										// 返回空閒頁面地址(如果無空閒也則返回0)。
}

上面有幾個指令比較陌生,先介紹repne scasb,其對應的等價指令是:

scans:inc edi
    dec ecx
    je loopdone
    cmp byte [edi-1],al
    jne scans
loopdone:

sall $12,%eax表示將%eax的值左移12位,相當於eax=eax*4096.

STOSL指令相當於將EAX中的值保存到ES:EDI指向的地址中。

所以第一句指令的意思是把al即%0的值0與di內容比較(倒序),edi為mem_map+PAGING_PAGES-1,即內存映射數組的最後一個可分頁的下標內容,如果有等於0的字節表示還未使用,就將對應頁面的內存映像位置1.

然後把ecx,此時不再是PAGING_PAGES,乘以4096得到相對頁面的起始地址,再加上LOW_MEM得到頁面實際物理起始地址。然後把這整頁內存清0.最後返回這個頁面的起始地址。

接下來看最關鍵的copy_page_tables函數:

// 刷新頁變換高速緩沖宏函數。
// 為了提高地址轉換的效率,CPU 將最近使用的頁表數據存放在芯片中高速緩沖中。在修改過頁表
// 信息之後,就需要刷新該緩沖區。這裡使用重新加載頁目錄基址寄存器cr3 的方法來進行刷新。
// 下面eax = 0,是頁目錄的基址。
#define invalidate() \
__asm__( "movl %%eax,%%cr3":: "a" (0))

/*
* 好了,下面是內存管理mm 中最為復雜的程序之一。它通過只復制內存頁面
* 來拷貝一定范圍內線性地址中的內容。希望代碼中沒有錯誤,因為我不想
* 再調試這塊代碼了?。
*
* 注意!我們並不是僅復制任何內存塊 - 內存塊的地址需要是4Mb 的倍數(正好
* 一個頁目錄項對應的內存大小),因為這樣處理可使函數很簡單。不管怎樣,
* 它僅被fork()使用(fork.c 第56 行)。
*
* 注意2!!當from==0 時,是在為第一次fork()調用復制內核空間。此時我們
* 不想復制整個頁目錄項對應的內存,因為這樣做會導致內存嚴重的浪費 - 我們
* 只復制頭160 個頁面 - 對應640kB。即使是復制這些頁面也已經超出我們的需求,
* 但這不會占用更多的內存 - 在低1Mb 內存范圍內我們不執行寫時復制操作,所以
* 這些頁面可以與內核共享。因此這是nr=xxxx 的特殊情況(nr 在程序中指頁面數)。
*/
//// 復制指定線性地址和長度(頁表個數)內存對應的頁目錄項和頁表,從而被復制的頁目錄和
//// 頁表對應的原物理內存區被共享使用。
// 復制指定地址和長度的內存對應的頁目錄項和頁表項。需申請頁面來存放新頁表,原內存區被共享;
// 此後兩個進程將共享內存區,直到有一個進程執行寫操作時,才分配新的內存頁(寫時復制機制)。
int
copy_page_tables (unsigned long from, unsigned long to, long size)
{
  unsigned long *from_page_table;
  unsigned long *to_page_table;
  unsigned long this_page;
  unsigned long *from_dir, *to_dir;
  unsigned long nr;

// 源地址和目的地址都需要是在4Mb 的內存邊界地址上。否則出錯,死機。
  if ((from & 0x3fffff) || (to & 0x3fffff))
    panic ("copy_page_tables called with wrong alignment");
// 取得源地址和目的地址的目錄項(from_dir 和to_dir)。參見對115 句的注釋。
  from_dir = (unsigned long *) ((from >> 20) & 0xffc);	/* _pg_dir = 0 */
  to_dir = (unsigned long *) ((to >> 20) & 0xffc);
// 計算要復制的內存塊占用的頁表數(也即目錄項數)。
  size = ((unsigned) (size + 0x3fffff)) >> 22;
// 下面開始對每個占用的頁表依次進行復制操作。
  for (; size-- > 0; from_dir++, to_dir++)
    {
// 如果目的目錄項指定的頁表已經存在(P=1),則出錯,死機。
      if (1 & *to_dir)
	panic ("copy_page_tables: already exist");
// 如果此源目錄項未被使用,則不用復制對應頁表,跳過。
      if (!(1 & *from_dir))
	continue;
// 取當前源目錄項中頁表的地址??from_page_table。
      from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
// 為目的頁表取一頁空閒內存,如果返回是0 則說明沒有申請到空閒內存頁面。返回值=-1,退出。
      if (!(to_page_table = (unsigned long *) get_free_page ()))
	return -1;		/* Out of memory, see freeing */
// 設置目的目錄項信息。7 是標志信息,表示(Usr, R/W, Present)。
      *to_dir = ((unsigned long) to_page_table) | 7;
// 針對當前處理的頁表,設置需復制的頁面數。如果是在內核空間,則僅需復制頭160 頁,否則需要
// 復制1 個頁表中的所有1024 頁面。
      nr = (from == 0) ? 0xA0 : 1024;
// 對於當前頁表,開始復制指定數目nr 個內存頁面。
      for (; nr-- > 0; from_page_table++, to_page_table++)
	{
	  this_page = *from_page_table;			// 取源頁表項內容。
	  if (!(1 & this_page))								// 如果當前源頁面沒有使用,則不用復制。
	    continue;
// 復位頁表項中R/W 標志(置0)。(如果U/S 位是0,則R/W 就沒有作用。如果U/S 是1,而R/W 是0,
// 那麼運行在用戶層的代碼就只能讀頁面。如果U/S 和R/W 都置位,則就有寫的權限。)
	  this_page &= ~2;
	  *to_page_table = this_page;				// 將該頁表項復制到目的頁表中。
// 如果該頁表項所指頁面的地址在1M 以上,則需要設置內存頁面映射數組mem_map[],於是計算
// 頁面號,並以它為索引在頁面映射數組相應項中增加引用次數。而對於位於1MB 以下的頁面,說明
// 是內核頁面,因此不需要對mem_map[]進行設置。因為mem_map[]僅用於管理主內存區中的頁面使用
// 情況。因此,對於內核移動到任務0 中並且調用fork()創建任務1 時(用於運行init()),由於此
//時
// 復制的頁面還仍然都在內核代碼區域,因此以下判斷中的語句不會執行。只有當調用fork()的父進程
// 代碼處於主內存區(頁面位置大於1MB)時才會執行。這種情況需要在進程調用了execve(),裝載並
// 執行了新程序代碼時才會出現。
	  if (this_page > LOW_MEM)
	    {
// 下面這句的含義是令源頁表項所指內存頁也為只讀。因為現在開始有兩個進程共用內存區了。
// 若其中一個內存需要進行寫操作,則可以通過頁異常的寫保護處理,為執行寫操作的進程分配
// 一頁新的空閒頁面,也即進行寫時復制的操作。
	      *from_page_table = this_page;		// 令源頁表項也只讀。
	      this_page -= LOW_MEM;
	      this_page >>= 12;
	      mem_map[this_page]++;
	    }
	}
    }
  invalidate ();		// 刷新頁變換高速緩沖。
  return 0;
}

記得從fork傳遞過來的三個參數依次是old_data_base,new_data_base,data_limit。其中old_data_base是原進程局部描述符表中數據段的基地址(線性地址空間),new_data_base為新進程在線性地址空間中的基地址(任務號*64MB),data_limit為原進程的局部描述符表中數據段描述符中的段限長。

首先取源地址和目的地址的頁目錄項,因為一頁內存為4K即4096,所以4096對應的是一個頁表項,由於一個頁表有1024個表項,所以一個頁表為1024*4096=4194304,又由於一個完整的頁表對應的是一個頁目錄項,所以頁目錄號即為地址除以4194304(即右移22位)。因為每項占4個字節,並且由於頁目錄是從物理地址0開始(head.s),因此實際的頁目錄項指針=頁目錄號*4(即左移2)。和0xffc(4092)相與表示不能超出1024個頁目錄項的范圍。

緊接著計算限長的頁目錄項數,也即所占頁表數,(size+4M)/4M。

然後用一個for循環依次復制每個占用的頁表,首先取源目錄項中的頁表地址0xfffff000 & *from_dir,根據PDE的結構,12-31位為頁表基地址,0-11位為各種屬性。所以用0xfffff000清除低12位,獲取高20位的頁表基址。

接下來為目的頁表申請一頁空白內存,此頁表的起始地址存在to_page_table中,並置前三位為1.再將這個地址值賦值給目的頁目錄項。

然後又用一個for循環復制以from_page_table為頁表起始地址的一整個頁表的頁表項內容,首先取第一個源頁表項的內容*from_page_table,其實就是某個頁的地址和一些屬性。然後將該頁表項內容this_page賦值給*to_page_table。

後面一小段代碼是設置只讀。

最後一句為刷新頁變換高速緩沖,沒什麼好說的。

上面的函數執行如果出錯,則會調用free_page_tables來釋放申請的內存:

/*
* 下面函數釋放頁表連續的內存塊,'exit()'需要該函數。與copy_page_tables()
* 類似,該函數僅處理4Mb 的內存塊。
*/
//// 根據指定的線性地址和限長(頁表個數),釋放對應內存頁表所指定的內存塊並置表項空閒。
// 頁目錄位於物理地址0 開始處,共1024 項,占4K 字節。每個目錄項指定一個頁表。
// 頁表從物理地址0x1000 處開始(緊接著目錄空間),每個頁表有1024 項,也占4K 內存。
// 每個頁表項對應一頁物理內存(4K)。目錄項和頁表項的大小均為4 個字節。
// 參數:from - 起始基地址;size - 釋放的長度。
int
free_page_tables (unsigned long from, unsigned long size)
{
  unsigned long *pg_table;
  unsigned long *dir, nr;

  if (from & 0x3fffff)									// 要釋放內存塊的地址需以4M 為邊界。
																  //不能<4M,小於4M就等於本身,大於4M就等於0
    panic ("free_page_tables called with wrong alignment");
  if (!from)													// 出錯,試圖釋放內核和緩沖所占空間。
    panic ("Trying to free up swapper memory space");
// 計算所占頁目錄項數(4M 的進位整數倍),也即所占頁表數。(size+4M)/4M
//一個頁是4KB,一整個頁表有1024個頁,所以4KB*1024=4M就是一整個頁表所對應的size容量
//然後一整個頁表對應的是一個頁目錄項
  size = (size + 0x3fffff) >> 22;
// 下面一句計算起始目錄項。對應的目錄項號=from>>22,因每項占4 字節,並且由於頁目錄是從
// 物理地址0 開始,因此實際的目錄項指針=目錄項號<<2,也即(from>>20)。與上0xffc 確保
// 目錄項指針范圍有效。
  dir = (unsigned long *) ((from >> 20) & 0xffc);	/* _pg_dir = 0 */
  for (; size-- > 0; dir++)
    {																// size 現在是需要被釋放內存的目錄項數。
      if (!(1 & *dir))										// 如果該目錄項無效(P 位=0),則繼續。
	continue;												// 目錄項的位0(P 位)表示對應頁表是否存在。
      pg_table = (unsigned long *) (0xfffff000 & *dir);	// 取目錄項中頁表地址。
      for (nr = 0; nr < 1024; nr++)
	{																// 每個頁表有1024 個頁項。
	  if (1 & *pg_table)								// 若該頁表項有效(P 位=1),則釋放對應內存頁。
	    free_page (0xfffff000 & *pg_table);
	  *pg_table = 0;										// 該頁表項內容清零。
	  pg_table++;											// 指向頁表中下一項。
	}
      free_page (0xfffff000 & *dir);			// 釋放該頁表所占內存頁面。但由於頁表在
																	// 物理地址1M 以內,所以這句什麼都不做。
      *dir = 0;												// 對相應頁表的目錄項清零。
    }
  invalidate ();											// 刷新頁變換高速緩沖。
  return 0;
}

這個函數和上面的函數類似,首先計算所占頁目錄項數,然後計算起始目錄項地址。

然後用一個for循環先取到目錄項中的頁表地址,再用一個for循環把頁表中的1024個頁項清空,這裡又用到一個函數free_page:

/*
* 釋放物理地址'addr'開始的一頁內存。用於函數'free_page_tables()'。
*/
//// 釋放物理地址addr 開始的一頁面內存。
// 1MB 以下的內存空間用於內核程序和緩沖,不作為分配頁面的內存空間。
//a = i--;//先a = i ; 然後 i = i - 1;
void
free_page (unsigned long addr)
{
  if (addr < LOW_MEM)
    return;											// 如果物理地址addr 小於內存低端(1MB),則返回。
  if (addr >= HIGH_MEMORY)	// 如果物理地址addr>=內存最高端,則顯示出錯信息。
    panic ("trying to free nonexistent page");
  addr -= LOW_MEM;						// 物理地址減去低端內存位置,再除以4KB,得頁面號。
  addr >>= 12;
  if (mem_map[addr]--)
    return;											// 如果對應內存頁面映射字節不等於0,則減1 返回。
  mem_map[addr] = 0;					// 否則置對應頁面映射字節為0,並顯示出錯信息,死機。
  panic ("trying to free free page");
}

這個函數是釋放一頁內存,首先得到頁面號,然後把內存映射數組對應的下標的內容減1.比較簡單。

所以free_page (0xfffff000 & *pg_table);的含義是先取頁表項的內容,也就是對應的某一頁內存的地址,然後釋放這一頁內存。

釋放完這一頁內存後,就把該頁表項內容清零*pg_table=0.

接著再釋放該頁表所占的內存頁面(4K),最後釋放該頁目錄項的內容。

至此分析結束!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved