?? memory.c
字號:
* 這些頁面可以與內(nèi)核共享。因此這是nr=xxxx 的特殊情況(nr 在程序中指頁面數(shù))。*///// 復(fù)制指定線性地址和長度(頁表個數(shù))內(nèi)存對應(yīng)的頁目錄項和頁表,從而被復(fù)制的頁目錄和//// 頁表對應(yīng)的原物理內(nèi)存區(qū)被共享使用。// 復(fù)制指定地址和長度的內(nèi)存對應(yīng)的頁目錄項和頁表項。需申請頁面來存放新頁表,原內(nèi)存區(qū)被共享;// 此后兩個進程將共享內(nèi)存區(qū),直到有一個進程執(zhí)行寫操作時,才分配新的內(nèi)存頁(寫時復(fù)制機制)。intcopy_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 的內(nèi)存邊界地址上。否則出錯,死機。 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);// 計算要復(fù)制的內(nèi)存塊占用的頁表數(shù)(也即目錄項數(shù))。 size = ((unsigned) (size + 0x3fffff)) >> 22;// 下面開始對每個占用的頁表依次進行復(fù)制操作。 for (; size-- > 0; from_dir++, to_dir++) {// 如果目的目錄項指定的頁表已經(jīng)存在(P=1),則出錯,死機。 if (1 & *to_dir) panic ("copy_page_tables: already exist");// 如果此源目錄項未被使用,則不用復(fù)制對應(yīng)頁表,跳過。 if (!(1 & *from_dir)) continue;// 取當前源目錄項中頁表的地址??from_page_table。 from_page_table = (unsigned long *) (0xfffff000 & *from_dir);// 為目的頁表取一頁空閑內(nèi)存,如果返回是0 則說明沒有申請到空閑內(nèi)存頁面。返回值=-1,退出。 if (!(to_page_table = (unsigned long *) get_free_page ())) return -1; /* Out of memory, see freeing */// 設(shè)置目的目錄項信息。7 是標志信息,表示(Usr, R/W, Present)。 *to_dir = ((unsigned long) to_page_table) | 7;// 針對當前處理的頁表,設(shè)置需復(fù)制的頁面數(shù)。如果是在內(nèi)核空間,則僅需復(fù)制頭160 頁,否則需要// 復(fù)制1 個頁表中的所有1024 頁面。 nr = (from == 0) ? 0xA0 : 1024;// 對于當前頁表,開始復(fù)制指定數(shù)目nr 個內(nèi)存頁面。 for (; nr-- > 0; from_page_table++, to_page_table++) { this_page = *from_page_table; // 取源頁表項內(nèi)容。 if (!(1 & this_page)) // 如果當前源頁面沒有使用,則不用復(fù)制。 continue;// 復(fù)位頁表項中R/W 標志(置0)。(如果U/S 位是0,則R/W 就沒有作用。如果U/S 是1,而R/W 是0,// 那么運行在用戶層的代碼就只能讀頁面。如果U/S 和R/W 都置位,則就有寫的權(quán)限。) this_page &= ~2; *to_page_table = this_page; // 將該頁表項復(fù)制到目的頁表中。// 如果該頁表項所指頁面的地址在1M 以上,則需要設(shè)置內(nèi)存頁面映射數(shù)組mem_map[],于是計算// 頁面號,并以它為索引在頁面映射數(shù)組相應(yīng)項中增加引用次數(shù)。 if (this_page > LOW_MEM) {// 下面這句的含義是令源頁表項所指內(nèi)存頁也為只讀。因為現(xiàn)在開始有兩個進程共用內(nèi)存區(qū)了。// 若其中一個內(nèi)存需要進行寫操作,則可以通過頁異常的寫保護處理,為執(zhí)行寫操作的進程分配// 一頁新的空閑頁面,也即進行寫時復(fù)制的操作。 *from_page_table = this_page; // 令源頁表項也只讀。 this_page -= LOW_MEM; this_page >>= 12; mem_map[this_page]++; } } } invalidate (); // 刷新頁變換高速緩沖。 return 0;}/** This function puts a page in memory at the wanted address.* It returns the physical address of the page gotten, 0 if* out of memory (either when trying to access page-table or* page.)*//** 下面函數(shù)將一內(nèi)存頁面放置在指定地址處。它返回頁面的物理地址,如果* 內(nèi)存不夠(在訪問頁表或頁面時),則返回0。*///// 把一物理內(nèi)存頁面映射到指定的線性地址處。// 主要工作是在頁目錄和頁表中設(shè)置指定頁面的信息。若成功則返回頁面地址。unsigned longput_page (unsigned long page, unsigned long address){ unsigned long tmp, *page_table;/* NOTE !!! This uses the fact that _pg_dir=0 *//* 注意!!!這里使用了頁目錄基址_pg_dir=0 的條件 */// 如果申請的頁面位置低于LOW_MEM(1Mb)或超出系統(tǒng)實際含有內(nèi)存高端HIGH_MEMORY,則發(fā)出警告。 if (page < LOW_MEM || page >= HIGH_MEMORY) printk ("Trying to put page %p at %p\n", page, address);// 如果申請的頁面在內(nèi)存頁面映射字節(jié)圖中沒有置位,則顯示警告信息。 if (mem_map[(page - LOW_MEM) >> 12] != 1) printk ("mem_map disagrees with %p at %p\n", page, address);// 計算指定地址在頁目錄表中對應(yīng)的目錄項指針。 page_table = (unsigned long *) ((address >> 20) & 0xffc);// 如果該目錄項有效(P=1)(也即指定的頁表在內(nèi)存中),則從中取得指定頁表的地址??page_table。 if ((*page_table) & 1) page_table = (unsigned long *) (0xfffff000 & *page_table); else {// 否則,申請空閑頁面給頁表使用,并在對應(yīng)目錄項中置相應(yīng)標志7(User, U/S, R/W)。然后將// 該頁表的地址??page_table。 if (!(tmp = get_free_page ())) return 0; *page_table = tmp | 7; page_table = (unsigned long *) tmp; }// 在頁表中設(shè)置指定地址的物理內(nèi)存頁面的頁表項內(nèi)容。每個頁表共可有1024 項(0x3ff)。 page_table[(address >> 12) & 0x3ff] = page | 7;/* no need for invalidate *//* 不需要刷新頁變換高速緩沖 */ return page; // 返回頁面地址。}//// 取消寫保護頁面函數(shù)。用于頁異常中斷過程中寫保護異常的處理(寫時復(fù)制)。// 輸入?yún)?shù)為頁表項指針。// [ un_wp_page 意思是取消頁面的寫保護:Un-Write Protected。]voidun_wp_page (unsigned long *table_entry){ unsigned long old_page, new_page; old_page = 0xfffff000 & *table_entry; // 取原頁面對應(yīng)的目錄項號。// 如果原頁面地址大于內(nèi)存低端LOW_MEM(1Mb),并且其在頁面映射字節(jié)圖數(shù)組中值為1(表示僅// 被引用1 次,頁面沒有被共享),則在該頁面的頁表項中置R/W 標志(可寫),并刷新頁變換// 高速緩沖,然后返回。 if (old_page >= LOW_MEM && mem_map[MAP_NR (old_page)] == 1) { *table_entry |= 2; invalidate (); return; }// 否則,在主內(nèi)存區(qū)內(nèi)申請一頁空閑頁面。 if (!(new_page = get_free_page ())) oom (); // Out of Memory。內(nèi)存不夠處理。// 如果原頁面大于內(nèi)存低端(則意味著mem_map[]>1,頁面是共享的),則將原頁面的頁面映射// 數(shù)組值遞減1。然后將指定頁表項內(nèi)容更新為新頁面的地址,并置可讀寫等標志(U/S, R/W, P)。// 刷新頁變換高速緩沖。最后將原頁面內(nèi)容復(fù)制到新頁面。 if (old_page >= LOW_MEM) mem_map[MAP_NR (old_page)]--; *table_entry = new_page | 7; invalidate (); copy_page (old_page, new_page);}/** This routine handles present pages, when users try to write* to a shared page. It is done by copying the page to a new address* and decrementing the shared-page counter for the old page.** If it's in code space we exit with a segment error.*//** 當用戶試圖往一個共享頁面上寫時,該函數(shù)處理已存在的內(nèi)存頁面,(寫時復(fù)制)* 它是通過將頁面復(fù)制到一個新地址上并遞減原頁面的共享頁面計數(shù)值實現(xiàn)的。** 如果它在代碼空間,我們就以段錯誤信息退出。*///// 頁異常中斷處理調(diào)用的C 函數(shù)。寫共享頁面處理函數(shù)。在page.s 程序中被調(diào)用。// 參數(shù)error_code 是由CPU 自動產(chǎn)生,address 是頁面線性地址。// 寫共享頁面時,需復(fù)制頁面(寫時復(fù)制)。voiddo_wp_page (unsigned long error_code, unsigned long address){#if 0/* we cannot do this yet: the estdio library writes to code space *//* stupid, stupid. I really want the libc.a from GNU *//* 我們現(xiàn)在還不能這樣做:因為estdio 庫會在代碼空間執(zhí)行寫操作 *//* 真是太愚蠢了。我真想從GNU 得到libc.a 庫。*/ if (CODE_SPACE (address)) // 如果地址位于代碼空間,則終止執(zhí)行程序。 do_exit (SIGSEGV);#endif// 處理取消頁面保護。參數(shù)指定頁面在頁表中的頁表項指針,其計算方法是:// ((address>>10) & 0xffc):計算指定地址的頁面在頁表中的偏移地址;// (0xfffff000 &((address>>20) &0xffc)):取目錄項中頁表的地址值,// 其中((address>>20) &0xffc)計算頁面所在頁表的目錄項指針;// 兩者相加即得指定地址對應(yīng)頁面的頁表項指針。這里對共享的頁面進行復(fù)制。 un_wp_page ((unsigned long *) (((address >> 10) & 0xffc) + (0xfffff000 & *((unsigned long *) ((address >> 20) & 0xffc)))));}//// 寫頁面驗證。// 若頁面不可寫,則復(fù)制頁面。在fork.c 第34 行被調(diào)用。voidwrite_verify (unsigned long address){ unsigned long page;// 判斷指定地址所對應(yīng)頁目錄項的頁表是否存在(P),若不存在(P=0)則返回。 if (!((page = *((unsigned long *) ((address >> 20) & 0xffc))) & 1)) return;// 取頁表的地址,加上指定地址的頁面在頁表中的頁表項偏移值,得對應(yīng)物理頁面的頁表項指針。 page &= 0xfffff000; page += ((address >> 10) & 0xffc);// 如果該頁面不可寫(標志R/W 沒有置位),則執(zhí)行共享檢驗和復(fù)制頁面操作(寫時復(fù)制)。 if ((3 & *(unsigned long *) page) == 1) /* non-writeable, present */ un_wp_page ((unsigned long *) page); return;}//// 取得一頁空閑內(nèi)存并映射到指定線性地址處。// 與get_free_page()不同。get_free_page()僅是申請取得了主內(nèi)存區(qū)的一頁物理內(nèi)存。而該函數(shù)// 不僅是獲取到一頁物理內(nèi)存頁面,還進一步調(diào)用put_page(),將物理頁面映射到指定的線性地址// 處。voidget_empty_page (unsigned long address){ unsigned long tmp;// 若不能取得一空閑頁面,或者不能將頁面放置到指定地址處,則顯示內(nèi)存不夠的信息。// 279 行上英文注釋的含義是:即使執(zhí)行g(shù)et_free_page()返回0 也無所謂,因為put_page()// 中還會對此情況再次申請空閑物理頁面的,見210 行。 if (!(tmp = get_free_page ()) || !put_page (tmp, address)) { free_page (tmp); /* 0 is ok - ignored */
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -