?? memory.c
字號(hào):
/* passed * linux/mm/memory.c * * (C) 1991 Linus Torvalds */#include <set_seg.h>
/* * 需求加載是從01.12.91 開始編寫的- 在程序編制表中似乎是最重要的程序, * 并且應(yīng)該是很容易編制的- linus *//* * OK,需求加載是比較容易編寫的,而共享頁面卻需要有點(diǎn)技巧。共享頁面程序是
* 02.12.91 開始編寫的,好象能夠工作- Linus。 * * 通過執(zhí)行大約30 個(gè)/bin/sh 對(duì)共享操作進(jìn)行了測(cè)試:在老內(nèi)核當(dāng)中需要占用多于
* 6M 的內(nèi)存,而目前卻不用。現(xiàn)在看來工作得很好。 * * 對(duì)"invalidate()"函數(shù)也進(jìn)行了修正- 在這方面我還做的不夠。 */// 信號(hào)頭文件。定義信號(hào)符號(hào)常量,信號(hào)結(jié)構(gòu)以及信號(hào)操作函數(shù)原型。
#include <signal.h>
// 系統(tǒng)頭文件。定義了設(shè)置或修改描述符/中斷門等的嵌入式匯編宏。#include <asm/system.h>
// 調(diào)度程序頭文件,定義了任務(wù)結(jié)構(gòu)task_struct、初始任務(wù)0 的數(shù)據(jù),
// 還有一些有關(guān)描述符參數(shù)設(shè)置和獲取的嵌入式匯編函數(shù)宏語句。#include <linux/sched.h>
// head 頭文件,定義了段描述符的簡(jiǎn)單結(jié)構(gòu),和幾個(gè)選擇符常量。#include <linux/head.h>
// 內(nèi)核頭文件。含有一些內(nèi)核常用函數(shù)的原形定義。#include <linux/kernel.h>void do_exit(long code);// 進(jìn)程退出處理函數(shù),在kernel/exit.c。
//// 顯示內(nèi)存已用完出錯(cuò)信息,并退出。static _inline void oom(void){ printk("out of memory\n\r"); do_exit(SIGSEGV);// do_exit()應(yīng)該使用退出代碼,這里用了信號(hào)值SIGSEGV(11)
// 相同值的出錯(cuò)碼含義是“資源暫時(shí)不可用”,正好同義。}
// 刷新頁變換高速緩沖宏函數(shù)。
// 為了提高地址轉(zhuǎn)換的效率,CPU 將最近使用的頁表數(shù)據(jù)存放在芯片中高速緩沖中。
// 在修改過頁表信息之后,就需要刷新該緩沖區(qū)。這里使用重新加載頁目錄基址
// 寄存器cr3 的方法來進(jìn)行刷新。下面eax = 0,是頁目錄的基址。//#define invalidate() \//__asm__("movl %%eax,%%cr3"::"a" (0))#define invalidate() \
_asm{_asm xor eax,eax _asm mov cr3,eax}
/* 下面定義若需要改動(dòng),則需要與head.s 等文件中的相關(guān)信息一起改變 */
// linux 0.11 內(nèi)核默認(rèn)支持的最大內(nèi)存容量是16M,可以修改這些定義以適合更多的內(nèi)存。#define LOW_MEM 0x100000 // 內(nèi)存低端(1MB)。#define PAGING_MEMORY (15*1024*1024)// 分頁內(nèi)存15MB。主內(nèi)存區(qū)最多15M。#define PAGING_PAGES (PAGING_MEMORY>>12)// 分頁后的物理內(nèi)存頁數(shù)。#define MAP_NR(addr) (((addr)-LOW_MEM)>>12)// 指定物理內(nèi)存地址映射為頁號(hào)。#define USED 100// 頁面被占用標(biāo)志。
// 該宏用于判斷給定地址是否位于當(dāng)前進(jìn)程的代碼段中。#define CODE_SPACE(addr) \((((addr)+4095)&~4095) < current->start_code + current->end_code)static unsigned long HIGH_MEMORY = 0;// 全局變量,存放實(shí)際物理內(nèi)存最高端地址。
// 復(fù)制1 頁內(nèi)存(4K 字節(jié))。//#define copy_page(from,to) \//__asm__("cld ; rep ; movsl"::"S" (from),"D" (to),"c" (1024):"cx","di","si")#define copy_page(from,to) _copy_page((void *)(from),(void *)(to))
_inline void _copy_page(void *from, void *to)
{_asm{ pushf
mov ecx,1024
mov esi,from
mov edi,to
cld
rep movsd popf}}
// 內(nèi)存映射字節(jié)圖(1 字節(jié)代表1 頁內(nèi)存),每個(gè)頁面對(duì)應(yīng)
// 的字節(jié)用于標(biāo)志頁面當(dāng)前被引用(占用)次數(shù)。static unsigned char mem_map [ PAGING_PAGES ] = {0,};/* * 獲取首個(gè)(實(shí)際上是最后1 個(gè):-)物理空閑頁面,并標(biāo)記為已使用。如果沒有空閑頁面, * 就返回0。 */
//// 取物理空閑頁面。如果已經(jīng)沒有可用內(nèi)存了,則返回0。
// 輸入:%1(ax=0) - 0;%2(LOW_MEM);%3(cx=PAGING PAGES);%4(edi=mem_map+PAGING_PAGES-1)。
// 輸出:返回%0(ax=頁面起始地址)。
// 上面%4 寄存器實(shí)際指向mem_map[]內(nèi)存字節(jié)圖的最后一個(gè)字節(jié)。本函數(shù)從字節(jié)圖末端開始向前掃描
// 所有頁面標(biāo)志(頁面總數(shù)為PAGING_PAGES),若有頁面空閑(其內(nèi)存映像字節(jié)為0)則返回頁面地址。
// 注意!本函數(shù)只是指出在主內(nèi)存區(qū)的一頁空閑頁面,但并沒有映射到某個(gè)進(jìn)程的線性地址去。后面
// 的put_page()函數(shù)就是用來作映射的。unsigned long get_free_page(void){
// unsigned long __res = mem_map+PAGING_PAGES-1; __asm { pushf
xor eax, eax
mov ecx,PAGING_PAGES
// mov edi,__res
mov edi,offset mem_map + PAGING_PAGES - 1
std
repne scasb // 方向位置位,將al(0)與對(duì)應(yīng)(di)每個(gè)頁面的內(nèi)容比較,
jne l1 // 如果沒有等于0 的字節(jié),則跳轉(zhuǎn)結(jié)束(返回0)。
mov byte ptr [edi+1],1 // 將對(duì)應(yīng)頁面的內(nèi)存映像位置1。
sal ecx,12 // 頁面數(shù)*4K = 相對(duì)頁面起始地址。
add ecx,LOW_MEM // 再加上低端內(nèi)存地址,即獲得頁面實(shí)際物理起始地址。
mov edx,ecx // 將頁面實(shí)際起始地址 -> edx 寄存器。
mov ecx,1024 // 寄存器ecx 置計(jì)數(shù)值1024。
lea edi,[edx+4092]// 將4092+edx 的位置 -> edi(該頁面的末端)。
rep stosd // 將edi 所指內(nèi)存清零(反方向,也即將該頁面清零)。
// mov __res,edx // 將頁面起始地址 -> __res(返回值)。
mov eax,edx l1: popf
}
// return __res;// 返回空閑頁面地址(如果無空閑也則返回0)。}//unsigned long get_free_page(void)
//{
//register unsigned long __res asm("ax");
//
//__asm__("std ; repne ; scasb\n\t" // 方向位置位,將al(0)與對(duì)應(yīng)每個(gè)頁面的(di)內(nèi)容比較,
// "jne 1f\n\t" // 如果沒有等于0 的字節(jié),則跳轉(zhuǎn)結(jié)束(返回0)。
// "movb $1,1(%%edi)\n\t"// 將對(duì)應(yīng)頁面的內(nèi)存映像位置1。
// "sall $12,%%ecx\n\t"// 頁面數(shù)*4K = 相對(duì)頁面起始地址。
// "addl %2,%%ecx\n\t"// 再加上低端內(nèi)存地址,即獲得頁面實(shí)際物理起始地址。
// "movl %%ecx,%%edx\n\t"// 將頁面實(shí)際起始地址 -> edx 寄存器。
// "movl $1024,%%ecx\n\t"// 寄存器ecx 置計(jì)數(shù)值1024。
// "leal 4092(%%edx),%%edi\n\t"// 將4092+edx 的位置 -> edi(該頁面的末端)。
/// "rep ; stosl\n\t"// 將edi 所指內(nèi)存清零(反方向,也即將該頁面清零)。
// "movl %%edx,%%eax\n"// 將頁面起始地址 -> eax(返回值)。
// "1:"
// :"=a" (__res)
// :"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
// "D" (mem_map+PAGING_PAGES-1)
// :"di","cx","dx");
//return __res;// 返回空閑頁面地址(如果無空閑也則返回0)。
//}
/* * 釋放物理地址'addr'開始的一頁內(nèi)存。用于函數(shù)'free_page_tables()'。 */
//// 釋放物理地址addr 開始的一頁面內(nèi)存。
// 1MB 以下的內(nèi)存空間用于內(nèi)核程序和緩沖,不作為分配頁面的內(nèi)存空間。void free_page(unsigned long addr){ if (addr < LOW_MEM) return;// 如果物理地址addr 小于內(nèi)存低端(1MB),則返回。 if (addr >= HIGH_MEMORY)// 如果物理地址addr>=內(nèi)存最高端,則顯示出錯(cuò)信息。 panic("trying to free nonexistent page"); addr -= LOW_MEM;// 物理地址減去低端內(nèi)存位置,再除以4KB,得頁面號(hào)。 addr >>= 12; if (mem_map[addr]--) return;// 如果對(duì)應(yīng)內(nèi)存頁面映射字節(jié)不等于0,則減1 返回。 mem_map[addr]=0;// 否則置對(duì)應(yīng)頁面映射字節(jié)為0,并顯示出錯(cuò)信息,死機(jī)。 panic("trying to free free page");}/* * 下面函數(shù)釋放頁表連續(xù)的內(nèi)存塊,'exit()'需要該函數(shù)。與copy_page_tables() * 類似,該函數(shù)僅處理4Mb 的內(nèi)存塊。 */
//// 根據(jù)指定的線性地址和限長(zhǎng)(頁表個(gè)數(shù)),釋放對(duì)應(yīng)內(nèi)存頁表所指定的內(nèi)存塊并置表項(xiàng)空閑。
// 頁目錄位于物理地址0 開始處,共1024 項(xiàng),占4K 字節(jié)。每個(gè)目錄項(xiàng)指定一個(gè)頁表。
// 頁表從物理地址0x1000 處開始(緊接著目錄空間),每個(gè)頁表有1024 項(xiàng),也占4K 內(nèi)存。
// 每個(gè)頁表項(xiàng)對(duì)應(yīng)一頁物理內(nèi)存(4K)。目錄項(xiàng)和頁表項(xiàng)的大小均為4 個(gè)字節(jié)。
// 參數(shù):from - 起始基地址;size - 釋放的長(zhǎng)度。int free_page_tables(unsigned long from,unsigned long size){ unsigned long *pg_table; unsigned long * dir, nr; if (from & 0x3fffff)// 要釋放內(nèi)存塊的地址需以4M 為邊界。 panic("free_page_tables called with wrong alignment"); if (!from)// 出錯(cuò),試圖釋放內(nèi)核和緩沖所占空間。 panic("Trying to free up swapper memory space");
// 計(jì)算所占頁目錄項(xiàng)數(shù)(4M 的進(jìn)位整數(shù)倍),也即所占頁表數(shù)。 size = (size + 0x3fffff) >> 22;
// 下面一句計(jì)算起始目錄項(xiàng)。對(duì)應(yīng)的目錄項(xiàng)號(hào)=from>>22,因每項(xiàng)占4 字節(jié),并且由于頁目錄是從
// 物理地址0 開始,因此實(shí)際的目錄項(xiàng)指針=目錄項(xiàng)號(hào)<<2,也即(from>>20)。與上0xffc 確保
// 目錄項(xiàng)指針范圍有效。 dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */ for ( ; size-->0 ; dir++) {// size 現(xiàn)在是需要被釋放內(nèi)存的目錄項(xiàng)數(shù)。 if (!(1 & *dir))// 如果該目錄項(xiàng)無效(P 位=0),則繼續(xù)。 continue;// 目錄項(xiàng)的位0(P 位)表示對(duì)應(yīng)頁表是否存在。 pg_table = (unsigned long *) (0xfffff000 & *dir);// 取目錄項(xiàng)中頁表地址。 for (nr=0 ; nr<1024 ; nr++) {// 每個(gè)頁表有1024 個(gè)頁項(xiàng)。 if (1 & *pg_table)// 若該頁表項(xiàng)有效(P 位=1),則釋放對(duì)應(yīng)內(nèi)存頁。 free_page(0xfffff000 & *pg_table); *pg_table = 0;// 該頁表項(xiàng)內(nèi)容清零。 pg_table++;// 指向頁表中下一項(xiàng)。 } free_page(0xfffff000 & *dir);// 釋放該頁表所占內(nèi)存頁面。但由于頁表在
// 物理地址1M 以內(nèi),所以這句什么都不做。 *dir = 0;// 對(duì)相應(yīng)頁表的目錄項(xiàng)清零。 } invalidate();// 刷新頁變換高速緩沖。 return 0;}/* * 好了,下面是內(nèi)存管理mm 中最為復(fù)雜的程序之一。它通過只復(fù)制內(nèi)存頁面
* 來拷貝一定范圍內(nèi)線性地址中的內(nèi)容。希望代碼中沒有錯(cuò)誤,因?yàn)槲也幌?/span> * 再調(diào)試這塊代碼了 :-) * * 注意!我們并不是僅復(fù)制任何內(nèi)存塊- 內(nèi)存塊的地址需要是4Mb 的倍數(shù)(正好
* 一個(gè)頁目錄項(xiàng)對(duì)應(yīng)的內(nèi)存大小),因?yàn)檫@樣處理可使函數(shù)很簡(jiǎn)單。不管怎樣,
* 它僅被fork()使用(fork.c) * * 注意!!當(dāng)from==0 時(shí),是在為第一次fork()調(diào)用復(fù)制內(nèi)核空間。此時(shí)我們
* 不想復(fù)制整個(gè)頁目錄項(xiàng)對(duì)應(yīng)的內(nèi)存,因?yàn)檫@樣做會(huì)導(dǎo)致內(nèi)存嚴(yán)重的浪費(fèi)- 我們
* 只復(fù)制頭160 個(gè)頁面- 對(duì)應(yīng)640kB。即使是復(fù)制這些頁面也已經(jīng)超出我們的需求,
* 但這不會(huì)占用更多的內(nèi)存- 在低1Mb 內(nèi)存范圍內(nèi)我們不執(zhí)行寫時(shí)復(fù)制操作,所以
* 這些頁面可以與內(nèi)核共享。因此這是nr=xxxx 的特殊情況(nr 在程序中指頁面數(shù))。 */
//// 復(fù)制指定線性地址和長(zhǎng)度(頁表個(gè)數(shù))內(nèi)存對(duì)應(yīng)的頁目錄項(xiàng)和頁表,從而被復(fù)制的頁目錄和
//// 頁表對(duì)應(yīng)的原物理內(nèi)存區(qū)被共享使用。
// 復(fù)制指定地址和長(zhǎng)度的內(nèi)存對(duì)應(yīng)的頁目錄項(xiàng)和頁表項(xiàng)。需申請(qǐng)頁面來存放新頁表,原內(nèi)存區(qū)被共享;
// 此后兩個(gè)進(jìn)程將共享內(nèi)存區(qū),直到有一個(gè)進(jìn)程執(zhí)行寫操作時(shí),才分配新的內(nèi)存頁(寫時(shí)復(fù)制機(jī)制)。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 的內(nèi)存邊界地址上。否則出錯(cuò),死機(jī)。 if ((from&0x3fffff) || (to&0x3fffff)) panic("copy_page_tables called with wrong alignment");
// 取得源地址和目的地址的目錄項(xiàng)(from_dir 和to_dir)。參見對(duì)115 句的注釋。 from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */ to_dir = (unsigned long *) ((to>>20) & 0xffc);
// 計(jì)算要復(fù)制的內(nèi)存塊占用的頁表數(shù)(也即目錄項(xiàng)數(shù))。 size = ((unsigned) (size+0x3fffff)) >> 22;
// 下面開始對(duì)每個(gè)占用的頁表依次進(jìn)行復(fù)制操作。 for( ; size-->0 ; from_dir++,to_dir++) { if (1 & *to_dir)// 如果目的目錄項(xiàng)指定的頁表已經(jīng)存在(P=1),則出錯(cuò),死機(jī)。 panic("copy_page_tables: already exist"); if (!(1 & *from_dir))// 如果此源目錄項(xiàng)未被使用,則不用復(fù)制對(duì)應(yīng)頁表,跳過。 continue;
// 取當(dāng)前源目錄項(xiàng)中頁表的地址 -> from_page_table。 from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
// 為目的頁表取一頁空閑內(nèi)存,如果返回是0 則說明沒有申請(qǐng)到空閑內(nèi)存頁面。返回值=-1,退出。 if (!(to_page_table = (unsigned long *) get_free_page())) return -1; /* Out of memory, see freeing */
// 設(shè)置目的目錄項(xiàng)信息。7 是標(biāo)志信息,表示(Usr, R/W, Present)。 *to_dir = ((unsigned long) to_page_table) | 7;
// 針對(duì)當(dāng)前處理的頁表,設(shè)置需復(fù)制的頁面數(shù)。如果是在內(nèi)核空間,則僅需復(fù)制頭160 頁,
// 否則需要復(fù)制1 個(gè)頁表中的所有1024 頁面。 nr = (from==0)?0xA0:1024;
// 對(duì)于當(dāng)前頁表,開始復(fù)制指定數(shù)目nr 個(gè)內(nèi)存頁面。 for ( ; nr-- > 0 ; from_page_table++,to_page_table++) { this_page = *from_page_table;// 取源頁表項(xiàng)內(nèi)容。 if (!(1 & this_page))// 如果當(dāng)前源頁面沒有使用,則不用復(fù)制。 continue;
// 復(fù)位頁表項(xiàng)中R/W 標(biāo)志(置0)。(如果U/S 位是0,則R/W 就沒有作用。如果U/S 是1,而R/W 是0,
// 那么運(yùn)行在用戶層的代碼就只能讀頁面。如果U/S 和R/W 都置位,則就有寫的權(quán)限。) this_page &= ~2; *to_page_table = this_page;// 將該頁表項(xiàng)復(fù)制到目的頁表中。
// 如果該頁表項(xiàng)所指頁面的地址在1M 以上,則需要設(shè)置內(nèi)存頁面映射數(shù)組mem_map[],于是計(jì)算
// 頁面號(hào),并以它為索引在頁面映射數(shù)組相應(yīng)項(xiàng)中增加引用次數(shù)。 if (this_page > LOW_MEM) {
// 下面這句的含義是令源頁表項(xiàng)所指內(nèi)存頁也為只讀。因?yàn)楝F(xiàn)在開始有兩個(gè)進(jìn)程共用內(nèi)存區(qū)了。
// 若其中一個(gè)內(nèi)存需要進(jìn)行寫操作,則可以通過頁異常的寫保護(hù)處理,為執(zhí)行寫操作的進(jìn)程分配
// 一頁新的空閑頁面,也即進(jìn)行寫時(shí)復(fù)制的操作。 *from_page_table = this_page;// 令源頁表項(xiàng)也只讀。 this_page -= LOW_MEM; this_page >>= 12; mem_map[this_page]++; } } } invalidate();// 刷新頁變換高速緩沖。 return 0;}/* * 下面函數(shù)將一內(nèi)存頁面放置在指定地址處。它返回頁面的物理地址,如果
* 內(nèi)存不夠(在訪問頁表或頁面時(shí)),則返回0。 */
//// 把一物理內(nèi)存頁面映射到指定的線性地址處。
// 主要工作是在頁目錄和頁表中設(shè)置指定頁面的信息。若成功則返回頁面地址。unsigned long put_page(unsigned long page,unsigned long address){ unsigned long tmp, *page_table;/* 注意!!!這里使用了頁目錄基址_pg_dir=0 的條件 */
// 如果申請(qǐng)的頁面位置低于LOW_MEM(1Mb)或超出系統(tǒng)實(shí)際含有內(nèi)存高端HIGH_MEMORY,則發(fā)出警告。 if (page < LOW_MEM || page >= HIGH_MEMORY) printk("Trying to put page %p at %p\n",page,address);
// 如果申請(qǐng)的頁面在內(nèi)存頁面映射字節(jié)圖中沒有置位,則顯示警告信息。 if (mem_map[(page-LOW_MEM)>>12] != 1) printk("mem_map disagrees with %p at %p\n",page,address);
// 計(jì)算指定地址在頁目錄表中對(duì)應(yīng)的目錄項(xiàng)指針。 page_table = (unsigned long *) ((address>>20) & 0xffc);
// 如果該目錄項(xiàng)有效(P=1)(也即指定的頁表在內(nèi)存中),則從中取得指定頁表的地址 -> page_table。
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -