⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 memory.c

📁 linux0.11的vc下的源代码 注释很详细
💻 C
📖 第 1 页 / 共 2 页
字号:
/* passed *  linux/mm/memory.c * *  (C) 1991  Linus Torvalds */#include <set_seg.h>
/* * 需求加载是从01.12.91 开始编写的- 在程序编制表中似乎是最重要的程序, * 并且应该是很容易编制的- linus *//* * OK,需求加载是比较容易编写的,而共享页面却需要有点技巧。共享页面程序是
 * 02.12.91 开始编写的,好象能够工作- Linus。 * * 通过执行大约30 个/bin/sh 对共享操作进行了测试:在老内核当中需要占用多于
 * 6M 的内存,而目前却不用。现在看来工作得很好。 * * 对"invalidate()"函数也进行了修正- 在这方面我还做的不够。 */// 信号头文件。定义信号符号常量,信号结构以及信号操作函数原型。
#include <signal.h>

// 系统头文件。定义了设置或修改描述符/中断门等的嵌入式汇编宏。#include <asm/system.h>
// 调度程序头文件,定义了任务结构task_struct、初始任务0 的数据,
// 还有一些有关描述符参数设置和获取的嵌入式汇编函数宏语句。#include <linux/sched.h>
// head 头文件,定义了段描述符的简单结构,和几个选择符常量。#include <linux/head.h>
// 内核头文件。含有一些内核常用函数的原形定义。#include <linux/kernel.h>void do_exit(long code);// 进程退出处理函数,在kernel/exit.c。
//// 显示内存已用完出错信息,并退出。static _inline void oom(void){	printk("out of memory\n\r");	do_exit(SIGSEGV);// do_exit()应该使用退出代码,这里用了信号值SIGSEGV(11)
						// 相同值的出错码含义是“资源暂时不可用”,正好同义。}
// 刷新页变换高速缓冲宏函数。
// 为了提高地址转换的效率,CPU 将最近使用的页表数据存放在芯片中高速缓冲中。
// 在修改过页表信息之后,就需要刷新该缓冲区。这里使用重新加载页目录基址
// 寄存器cr3 的方法来进行刷新。下面eax = 0,是页目录的基址。//#define invalidate() \//__asm__("movl %%eax,%%cr3"::"a" (0))#define invalidate() \
_asm{_asm xor eax,eax _asm mov cr3,eax}
/* 下面定义若需要改动,则需要与head.s 等文件中的相关信息一起改变 */
// linux 0.11 内核默认支持的最大内存容量是16M,可以修改这些定义以适合更多的内存。#define LOW_MEM 0x100000	// 内存低端(1MB)。#define PAGING_MEMORY (15*1024*1024)// 分页内存15MB。主内存区最多15M。#define PAGING_PAGES (PAGING_MEMORY>>12)// 分页后的物理内存页数。#define MAP_NR(addr) (((addr)-LOW_MEM)>>12)// 指定物理内存地址映射为页号。#define USED 100// 页面被占用标志。
// 该宏用于判断给定地址是否位于当前进程的代码段中。#define CODE_SPACE(addr) \((((addr)+4095)&~4095) < current->start_code + current->end_code)static unsigned long HIGH_MEMORY = 0;// 全局变量,存放实际物理内存最高端地址。
// 复制1 页内存(4K 字节)。//#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}}
// 内存映射字节图(1 字节代表1 页内存),每个页面对应
// 的字节用于标志页面当前被引用(占用)次数。static unsigned char mem_map [ PAGING_PAGES ] = {0,};/* * 获取首个(实际上是最后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){
//	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)与对应(di)每个页面的内容比较,
		jne l1		// 如果没有等于0 的字节,则跳转结束(返回0)。
		mov byte ptr [edi+1],1	// 将对应页面的内存映像位置1。
		sal ecx,12	// 页面数*4K = 相对页面起始地址。
		add ecx,LOW_MEM	// 再加上低端内存地址,即获得页面实际物理起始地址。
		mov edx,ecx	// 将页面实际起始地址 -> edx 寄存器。
		mov ecx,1024	// 寄存器ecx 置计数值1024。
		lea edi,[edx+4092]// 将4092+edx 的位置 -> edi(该页面的末端)。
		rep stosd	// 将edi 所指内存清零(反方向,也即将该页面清零)。
//		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)与对应每个页面的(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" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
//	"D" (mem_map+PAGING_PAGES-1)
//	:"di","cx","dx");
//return __res;// 返回空闲页面地址(如果无空闲也则返回0)。
//}
/* * 释放物理地址'addr'开始的一页内存。用于函数'free_page_tables()'。 */
//// 释放物理地址addr 开始的一页面内存。
// 1MB 以下的内存空间用于内核程序和缓冲,不作为分配页面的内存空间。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");}/* * 下面函数释放页表连续的内存块,'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 为边界。		panic("free_page_tables called with wrong alignment");	if (!from)// 出错,试图释放内核和缓冲所占空间。		panic("Trying to free up swapper memory space");
// 计算所占页目录项数(4M 的进位整数倍),也即所占页表数。	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;}/* * 好了,下面是内存管理mm 中最为复杂的程序之一。它通过只复制内存页面
 * 来拷贝一定范围内线性地址中的内容。希望代码中没有错误,因为我不想 * 再调试这块代码了 :-) * * 注意!我们并不是仅复制任何内存块- 内存块的地址需要是4Mb 的倍数(正好
 * 一个页目录项对应的内存大小),因为这样处理可使函数很简单。不管怎样,
 * 它仅被fork()使用(fork.c) * * 注意!!当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++) {		if (1 & *to_dir)// 如果目的目录项指定的页表已经存在(P=1),则出错,死机。			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[],于是计算
// 页面号,并以它为索引在页面映射数组相应项中增加引用次数。			if (this_page > LOW_MEM) {
// 下面这句的含义是令源页表项所指内存页也为只读。因为现在开始有两个进程共用内存区了。
// 若其中一个内存需要进行写操作,则可以通过页异常的写保护处理,为执行写操作的进程分配
// 一页新的空闲页面,也即进行写时复制的操作。				*from_page_table = this_page;// 令源页表项也只读。				this_page -= LOW_MEM;				this_page >>= 12;				mem_map[this_page]++;			}		}	}	invalidate();// 刷新页变换高速缓冲。	return 0;}/* * 下面函数将一内存页面放置在指定地址处。它返回页面的物理地址,如果
 * 内存不够(在访问页表或页面时),则返回0。 */
//// 把一物理内存页面映射到指定的线性地址处。
// 主要工作是在页目录和页表中设置指定页面的信息。若成功则返回页面地址。unsigned long put_page(unsigned long page,unsigned long address){	unsigned long tmp, *page_table;/* 注意!!!这里使用了页目录基址_pg_dir=0 的条件 */
// 如果申请的页面位置低于LOW_MEM(1Mb)或超出系统实际含有内存高端HIGH_MEMORY,则发出警告。	if (page < LOW_MEM || page >= HIGH_MEMORY)		printk("Trying to put page %p at %p\n",page,address);
	// 如果申请的页面在内存页面映射字节图中没有置位,则显示警告信息。	if (mem_map[(page-LOW_MEM)>>12] != 1)		printk("mem_map disagrees with %p at %p\n",page,address);
	// 计算指定地址在页目录表中对应的目录项指针。	page_table = (unsigned long *) ((address>>20) & 0xffc);
// 如果该目录项有效(P=1)(也即指定的页表在内存中),则从中取得指定页表的地址 -> page_table。

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -