Linux 0.95 内存管理

Linux 版本是从 0.12 到 0.95 的,具体情况感兴趣的自行搜索。这一系列的文章从 0.01 到 0.12,历经 4 个版本,除了 0.01 版本详尽解释所有的函数,其余版本都是在前一版本的基础上做新内容的介绍,这篇文章原先也是打算如此,但发现隔了几天没看源码,又是有些许生疏,更不用说读者了,而且对于 0.01 版本那个样式的代码解释,我看得不是很满意,注释离代码有点儿远,看着不是很舒服,因此这一篇文章将和 0.01 版本一样详尽的介绍每一个函数,但有些知识依旧不会像 0.01 版本说的那么基础。如果遇到某些词看得不是很懂,请到 0.01 版本翻看下是否有解释,如果没有,请善用搜索引擎或欢迎评论~

源码下载

Linux 源码下载路径位于 https://mirrors.edge.kernel.org/pub/linux/kernel/,这篇博客所需要的 0.95 版本源码通过点击链接 https://mirrors.edge.kernel.org/pub/linux/kernel/Historic/old-versions/linux-0.95.tar.gz 可下载。

大纲

0.95 版本 mm 目录下主要是两个文件:memory.cswap.cmemory.c 是一些基础的内存管理,如页面分配,页表释放,共享页面这些,swap.c 则是虚拟内存交换。

memory.c

下面将依次介绍 memory.c 文件中的函数。

free_page

/*
 * Free a page of memory at physical address 'addr'. Used by
 * 'free_page_tables()'
 */
/* 释放物理地址 addr 所处的一页内存 */
void free_page(unsigned long addr)
{
	/* addr 要处于主内存区域 */
	if (addr < LOW_MEM) return;
	if (addr < HIGH_MEMORY) {
		/* 进入这里说明 addr 处于主内存区域,
		 * 这里 addr 减去 LOW_MEM,然后右移 12 位,
		 * 用意是求出 addr 在 mem_map 数组中的索引,
		 * include/linux/mm.h 中有定义了一个 MAP_NR 的宏做这件事,
		 * 不清楚这里咋不用。
		 * /
		addr -= LOW_MEM;
		addr >>= 12;
		/* 地址范围在主内存区域时,mem_map 数组里面的值大于等于 1 表示页面被引用,
		 * 大于 1 是说页面共享,等于 0 则表示页面没被使用,所以如果进去这个 if 语句,
		 * 就表示是正常释放,进不去表示 mem_map[addr] 的值为 0,表示要释放一个原先
		 * 就没被分配的页面,打印出错,可能时内存被破坏了
		 */
		if (mem_map[addr]--)
			return;
		/* 因为前面 mem_map[addr]-- 了,所以恢复成 0 */
		mem_map[addr]=0;
	}
	printk("trying to free free page: memory probably corrupted");
}

free_page_tables

/*
 * This function frees a continuos block of page tables, as needed
 * by 'exit()'. As does copy_page_tables(), this handles only 4Mb blocks.
 */
/* 函数功能:释放几块页表,也就是释放几块连续的 4M 线性内存
 * 参数: from,这是要释放的 4M 线性内存的起始地址,因此其要 4M 对齐
 * size:要释放的内存大小
 */
int free_page_tables(unsigned long from,unsigned long size)
{
	unsigned long page;
	unsigned long page_dir;
	unsigned long *pg_table;
	unsigned long * dir, nr;
	/* 检测 from 是否 4M 对齐,没有则 panic */
	if (from & 0x3fffff)
		panic("free_page_tables called with wrong alignment");
	/* 即使已经 4M 对齐了,但 from 如果是 0,表示要释放内核所在页面,可怕,panic */
	if (!from)
		panic("Trying to free up swapper memory space");
	/* size 向上取整成 4M 对齐,如 size 是 1.5M,则求出为 1,
	 * 如果为 4.2M,则为 2.
	 */
	size = (size + 0x3fffff) >> 22;
	/* 从线性地址 from 得到 from 在页目录中对于索引的地址 */
	dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
	for ( ; size-->0 ; dir++) {
		/* 该页目录项不存在,那就不用释放咯, continue */
		if (!(page_dir = *dir))
			continue;
		/* 页目录项存在,要释放整个页,反正页表地址保存在 page_dir 中了,先把该项置 0 */
		*dir = 0;
		/* 嗯~页目录项 P 位为 0,表示页表不存在,continue */
		if (!(page_dir & 1)) {
			printk("free_page_tables: bad page directory.");
			continue;
		}
		/* 取得页表基地址 */
		pg_table = (unsigned long *) (0xfffff000 & page_dir);
		/* 每个页表有 4K,每个页表项占 4 个字节,所以总共有 1024项 */
		for (nr=0 ; nr<1024 ; nr++,pg_table++) {
			/* 页表项为 0,表示没有对应物理页 */
			if (!(page = *pg_table))
				continue;
			/* 有对应物理页,置项为 0 */
			*pg_table = 0;
			/* 页面在内存中 */
			if (1 & page)
				free_page(0xfffff000 & page);
			/* 页面在交换设备中 */
			else
				swap_free(page >> 1);
		}
		/* 释放页表 */
		free_page(0xfffff000 & page_dir);
	}
	/* 将有效页表项修改了,要刷新 TLB */
	invalidate();
	/* last_pages 数组是记录上 CHECK_LAST_NR 次缺页进入内存的地址,
	 * 这个函数释放了大块内存,以 4M 为基准,全部管理内存才不到 16M,
	 * 所以有很大概率释放了 last_pages 数组记录的地址,因此将它们置 0,
	 * 在 free_page 函数中没有这个操作,猜想是因为释放了 4K 的页面,释放
	 * 了记录地址的几率较小,然后就不做这一步了,这个 last_pages 数组
	 * 相当于一种优化
	 */
	for (page = 0; page < CHECK_LAST_NR ; page++)
		last_pages[page] = 0;
	return 0;
}

copy_page_tables

这个函数架构和 free_page_tables 函数是差不多的,看懂上一个函数对这个函数的理解还是很有帮助的,Linus 的注释里面写着这是 mm 里面最复杂的函数之一,当然,这是写代码的人说的,我们阅读代码可能并不会有那么大的感受。

/*
 *  Well, here is one of the most complicated functions in mm. It
 * copies a range of linerar addresses by copying only the pages.
 * Let's hope this is bug-free, 'cause this one I don't want to debug :-)
 *
 * Note! We don't copy just any chunks of memory - addresses have to
 * be divisible by 4Mb (one page-directory entry), as this makes the
 * function easier. It's used only by fork anyway.
 *
 * NOTE 2!! When from==0 we are copying kernel space for the first
 * fork(). Then we DONT want to copy a full page-directory entry, as
 * that would lead to some serious memory waste - we just copy the
 * first 160 pages - 640kB. Even that is more than we need, but it
 * doesn't take any more memory - we don't copy-on-write in the low
 * 1 Mb-range, so the pages can be shared with the kernel. Thus the
 * special case for nr=xxxx.
 */
/* 函数功能:拷贝页表
 * 参数:from,线性地址,要将 from 所在页表拷贝到线性地址 to 所在页表,
 * 这里要求 from 和 to 都要 4M 对齐,这样这个函数就是将 to 处 4M 的线性地址
 * 所对应的物理页面和 from 处 4M 线性地址映射的一样
 * size 和 free_page_tables 函数的参数 size 是一模一样的
 */
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 new_page;
	unsigned long nr;
	/* from 和 to 要 4M 对齐 */
	if ((from&0x3fffff) || (to&0x3fffff))
		panic("copy_page_tables called with wrong alignment");
	/* from 对应页目录项索引地址 */
	from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
	/* to 对应页目录项索引地址 */
	to_dir = (unsigned long *) ((to>>20) & 0xffc);
	/* size 向上取整成 4M 对齐,如 size 是 1.5M,则求出为 1,
	 * 如果为 4.2M,则为 2.
	 */
	size = ((unsigned) (size+0x3fffff)) >> 22;
	/* copy size 个 4M 内存 */
	for( ; size-->0 ; from_dir++,to_dir++) {
		/* to_dir 有值,这里应该还要判断下 P 位存不存在,这里没这个判断,
		 * 这里表示已经有对应页表项,即 to 这 size * 4M 地址有映射了,还来拷贝,
		 * 那可能是内存被破坏了,不过这里没有 return v_v
		 */
		if (*to_dir)
			printk("copy_page_tables: already exist, "
				"probable memory corruption\n");
		/* from_dir 不存在,继续 */
		if (!*from_dir)
			continue;
		/* from_dir P 位为 0,页表被交换出去了?正常没有这样的操作(其实感觉可以有)
		 * 将 from_dir 置为 0,表示不存在,然后 continue
		 */
		if (!(1 & *from_dir)) {
			printk("copy_page_tables: page table swapped out, "
				"probable memory corruption");
			*from_dir = 0;
			continue;
		}
		/* 页表基地址 */
		from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
		/* 分配一页给 to 放置页表 */
		if (!(to_page_table = (unsigned long *) get_free_page()))
			return -1;	/* Out of memory, see freeing */
		/* 设置其页表属性 */
		*to_dir = ((unsigned long) to_page_table) | 7;
		/* 内核页面 640K 足够覆盖到了,640K / 4K = 0xA0 */
		nr = (from==0)?0xA0:1024;
		for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
			/* 页表项 */
			this_page = *from_page_table;
			/* 页表项不存在 */
			if (!this_page)
				continue;
			/* 页面在交换设备 */
			if (!(1 & this_page)) {
				/* 内存中分配页面,将交换设备页面读进来 */
				if (!(new_page = get_free_page()))
					return -1;
				read_swap_page(this_page>>1, (char *) new_page);
				/* to 设为之前页面,from 是交换设备页面,为什么 to 不也是交换设备页面呢?
				 * 不清楚,但这样也有一定的合理性,等到访问 to 这个页面时再读交换设备,不过
				 * 这样又多了一次读交换设备的操作。。。可能这样更符合 copy 的意思吧
				 */
				*to_page_table = this_page;
				*from_page_table = new_page | (PAGE_DIRTY | 7);
				continue;
			}
			/* 页面在内存中,因为写时拷贝,所以将页面设置为只读,这里要注意的是
			 * 当处理器运行在特权级别(0、1、2环)时,R/W 位不起作用
			 */
			this_page &= ~2;
			/* 设置映射 */
			*to_page_table = this_page;
			/* mem_map 管理的是 [LOW_mem,HIGH_MEMORY),我觉得这里和低于 LOW_MEM 是
			 * 任务 0 区域没啥关系,因为它不在乎 R/W 位
			 */
			if (this_page > LOW_MEM) {
				*from_page_table = this_page;
				this_page -= LOW_MEM;
				this_page >>= 12;
				mem_map[this_page]++;
			}
		}
	}
	/* 修改了有效页表项,刷新 TLB */
	invalidate();
	return 0;
}

put_page

/*
 * 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.)
 */
/* 函数功能:将页面映射到线性地址;
 * 参数:page 是物理页面,address 是线性地址
 */
static unsigned long put_page(unsigned long page,unsigned long address)
{
	unsigned long tmp, *page_table;

/* NOTE !!! This uses the fact that _pg_dir=0 */
	/* page 要在主内存区域,记得 mem_map 是管理物理地址的 */
	if (page < LOW_MEM || page >= HIGH_MEMORY) {
		printk("put_page: trying to put page %p at %p\n",page,address);
		return 0;
	}
	/* 要求 page 是新分配下来的页面,毕竟跑到这个函数里来就是要进行映射的嘛,
	 * 那肯定是不能有共享的啦
	 */
	if (mem_map[(page-LOW_MEM)>>12] != 1) {
		printk("mem_map disagrees with %p at %p\n",page,address);
		return 0;
	}
	/* 求出页目录项对应地址 */
	page_table = (unsigned long *) ((address>>20) & 0xffc);
	/* 页表存在,求得页表基地址 */
	if ((*page_table)&1)
		page_table = (unsigned long *) (0xfffff000 & *page_table);
	/* 页表不存在,分配页表 */
	else {
		if (!(tmp=get_free_page()))
			return 0;
		*page_table = tmp | 7;
		page_table = (unsigned long *) tmp;
	}
	/* 映射 */
	page_table[(address>>12) & 0x3ff] = page | 7;
	/* 因为没有修改原本有效的项,所以不需要刷新 TLB */
/* no need for invalidate */
	return page;
}

put_dirty_page

这个函数和上一个函数没啥区别,除了最后的设置为 dirty 属性,所以省略了。

/*
 * The previous function doesn't work very well if you also want to mark
 * the page dirty: exec.c wants this, as it has earlier changed the page,
 * and we want the dirty-status to be correct (for VM). Thus the same
 * routine, but this time we mark it dirty too.
 */
unsigned long put_dirty_page(unsigned long page, unsigned long address)
{
	unsigned long tmp, *page_table;

/* NOTE !!! This uses the fact that _pg_dir=0 */

	if (page < LOW_MEM || page >= HIGH_MEMORY)
		printk("put_dirty_page: 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);
	if ((*page_table)&1)
		page_table = (unsigned long *) (0xfffff000 & *page_table);
	else {
		if (!(tmp=get_free_page()))
			return 0;
		*page_table = tmp|7;
		page_table = (unsigned long *) tmp;
	}
	page_table[(address>>12) & 0x3ff] = page | (PAGE_DIRTY | 7);
/* no need for invalidate */
	return page;
}

un_wp_page

/* 函数功能: 取消页面写保护;
 * 参数:页表项地址
 */
void un_wp_page(unsigned long * table_entry)
{
	unsigned long old_page;
	unsigned long new_page = 0;
	unsigned long dirty;

repeat:
	/* 取出物理页帧地址(包含属性) */
	old_page = *table_entry;
	/* 记下原来的 dirty 属性 */
	dirty = old_page & PAGE_DIRTY;
	/* 这里咋一看 repeat 标签的位置不大对劲,但想想还是正确的,
	 * 虽然单独在这个函数里面 old_page 的 P 位没有被修改过,
	 * 但是譬如保不准这个函数运行运行着就被 swap_out 出去了,
	 * 这样 old_page 的 P 位值就变了(后面给 new_page 分配页面时也可能会 swap out)。
	 * 当 old_page 不存在时,释放在后面分配的的 new_page
	 */
	if (!(old_page & 1)) {
		if (new_page)
			free_page(new_page);
		return;
	}
	/* 取得物理页帧基地址 */
	old_page &= 0xfffff000;
	/* 
	if (old_page >= HIGH_MEMORY) {
		if (new_page)
			free_page(new_page);
		printk("bad page address\n\r");
		do_exit(SIGSEGV);
	}
	if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
		*table_entry |= 2;
		invalidate();
		if (new_page)
			free_page(new_page);
		return;
	}
	if (!new_page) {
		if (!(new_page=get_free_page()))
			oom();
		goto repeat;
	}
	copy_page(old_page,new_page);
	*table_entry = new_page | dirty | 7;
	free_page(old_page);
	invalidate();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值