Linux0.11內核--記憶體管理之2.配合fork

来源:http://www.cnblogs.com/joey-hua/archive/2016/06/19/5598451.html
-Advertisement-
Play Games

【版權所有,轉載請註明出處。出處:http://www.cnblogs.com/joey-hua/p/5598451.html 】 在上一篇的fork函數中,首先一上來就調用get_free_page為新任務的數據結構申請一頁記憶體,在memory.c中: 上面有幾個指令比較陌生,先介紹repne s ...


【版權所有,轉載請註明出處。出處: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),最後釋放該頁目錄項的內容。

至此分析結束!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • var myDate = new Date();myDate.getYear(); //獲取當前年份(2位)myDate.getFullYear(); //獲取完整的年份(4位,1970-????)myDate.getMonth(); //獲取當前月份(0-11,0代表1月)myDate.getDa ...
  • 註:本實例JS部分均以原生JS編寫,不善用原生JS的,可用jQuery等對三方框架改寫 先上效果圖:(樣式有點醜,可以忽略一下下,效果出來了就好,後期加到其他項目中方便更改0.0) 類似翻書效果,原本的意思是使用JS來控制的,點擊一次之後使用setInterval去控制書頁翻過去的動畫,當書頁翻轉1 ...
  • 前端之MVC應用 1、indexedDB(Model) -- 前端瀏覽器對象型資料庫,一般我們後臺用的資料庫都是關係型資料庫。那麼indexeddb有什麼特點呢: 首先,從字義上它是索引型資料庫,從實際使用中可以體現為,它需要為表創建索引才可以根據某個欄位來獲取數據,而在關係型資料庫中,這明顯是不需 ...
  • 簡要:$.Callbacks是一個生成回調管家Callback的工廠,Callback提供一系列方法來管理一個回調列表($.Callbacks的一個私有變數list),包括添加回調函數, 刪除回調函數等等...,話不多說看正文: memory的值由傳入$.Callbacks的形參對象決定,具有狀態記 ...
  • 因為有萬惡的 IE 存在,所以當Web項目初始化併進入開發階段時。 如果是項目經理,需要很明確的知道客戶將會用什麼瀏覽器來訪問系統。 明確知道限定瀏覽器的情況下,你才能從容的讓手下的封裝必要的前端組件。 本篇文章試圖從常見的上傳方式和組件進行分析,僅局限與前端,至於後端需依據業務複雜度,自行拿捏實現 ...
  • 首先要加入類庫GDataXMLNode和JSON 解析本地文件Students.txt <students> <student> <name>湯姆 </name> <age>20</age> <phone>13049640144</phone> </student> <student> <name> ...
  • 在iOS學習23之事件處理中,小編詳細的介紹了事件處理,在這裡小編敘述一下它的相關原理 1、UITouch對象 在觸摸事件的處理方法中都會有一個存放著UITouch對象的集合,這個參數有什麼用呢? (1)UITouch 對象的簡介 當用戶用一根手指觸摸屏幕時,會創建一個與手指相關聯的 UITouch ...
  • 誤解一:安卓是iOS的後輩 不知不覺,安卓已經成為了世界上最流行的移動智能系統,就市場占有率來看,安卓甚至要高於引領了智能機和平板電腦革命的iOS。安卓的紅火深遠地影響了IT行業,全球最大的社交網路Facebook甚至倡議員工棄用iOS改換安卓手機以更深入地瞭解用戶體驗 但是,流行總伴隨著流言,安卓 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...