Chinaunix首页 | 论坛 | 博客
  • 博客访问: 85049
  • 博文数量: 34
  • 博客积分: 1640
  • 博客等级: 上尉
  • 技术积分: 395
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-17 14:37
文章分类
文章存档

2008年(34)

我的朋友
最近访客

分类: LINUX

2008-04-17 16:03:06

今天心血来潮,研究了一下Linux-0.11的源码。主要看了一下关于memoy管理的部分。这个部分的主要内容是在memory.c中。
 
linux 用的是virtual memory management。每个page 有4k。它用到了二级页表结构。第一级页表存在物理地址空间0开始的位置,一共有1024个表项,每个表项有4个字节。也就是说,一级页表占了one page。二级页表和一级页表的结构相同。页表目录(也就是一级页表)表项和页表(这里指的是二级页表)表项每个有4字节,也即32位,他们的前2031-12)为对应的页表或物理页的基地址,后面的12位为其他信息,如当前页面是否在内存中等。在Linux中,每个虚拟地址(32)可以认为有三部分组成:页表目录索引(31-22),页表索引(21-12),物理页的偏移(11-0)。
 

对于所有physical pages的管理用的是一个char数组mem_map, 用于记录每个物理页面当前被引用的次数。当然,如果物理页面为free的话,该mem_map中对应的值为0. 如果mem_map中的值大于1的话,表示该物理页为多个虚拟页面共享(可以是不同进程的虚拟页面,从而实现进程间的共享)。在对内存的处理中,几个经常用到的操作,是根据虚拟地址找到它所在的页表目录以及页表。使用的公式为:

dir = (unsigned long*)(addr >> 20) & 0xffc; 

page_table = (unsigned long*)(0xfffff000 & *dir);

physic_addr = (unsigned long*)(0xfffff000& *page_table);

要了解这些公式的含义,首先需要弄明白在linux中虚拟地址的结构以及页表目录表项和页表表项的结构。现在来分析上面三个公式的含义:

1. dir = (addr >> 20) & 0xffc; 等价于 dir = ((addr >> 22) << 2) & 0xffc。根据地址结构,addr >> 22得到的是页表目录的索引,但每个页表目录表项占用了4个地址并且页表目录在内存中的起始位置是0,所以(addr >> 22) << 2也就是该地址对应的页表目录表项在内存中的物理地址。这里&0xffc是为了保证所得的地址的位数为12位(10位为页表目录索引,而每个页表目录表项占用了4个字节,所有另外的两位相当于这4个字节里面的索引。)。

2. page_table = (0xfffff000 & *dir); *dir获得页表目录的表项内容。0xfffff000 & *dir获取该表项中的前20位,所得结果就是页表所在物理内存中的地址。我们可以利用page_table++page_table--在页表中游走,从而得到不同的虚拟页面对应的物理地址。

3. physic_addr(0xfffff000 & *page_table);和上面这个公式的含义差不多,只不过我们这里得到的是虚拟页面对应的物理页面的地址。

整个memory.c文件中包含的函数有:

get_free_page:使用汇编代码写的。没有看懂。但基本原理用该是扫描mem_map数组找出第一个遇见的mem_map0的物理页面(first-fit)。

free_page(addr): 注意这里的参数应该为物理地址。这个函数比较简单,也就是设置addr对应的mem_map中的表项值为0

free_page_tables(from, size):  释放从地址from开始,长度为size大小的线性地址空间。注意这里from是虚拟地址空间的地址,而不是物理地址。为了释放释放这些地址,我们首先需要根据from找到对应的页表目录,以及页面。

copy_page_tables(from, to, size): 从地址from开始,copy大小为size的地址空间到地址to处。这里copy只是页表的内容,而不是实际的虚拟内存中的内容。

put_page(page, address): 这个用于将物理页面page对应到虚拟地址address所在的页面。这个函数所作的主要是更改页表目录以及页表的相关表项而已。

un_wp_page(table_entry): 实现写时复制的功能。table_entry指向需要写保护的页面所在的页表表项的地址。当然如果该表项没有别共享(也就是只有一个页表项指向它,此时该页对因的mem_map项的值为1),我们不做任何操作。Otherwise, 申请一个新的页,然后利用copy_page函数将原有的页拷贝到新页,就可以了。

do_wp_page(error_code, address): 对地址address所在的页面进行写保护。它主要就是计算address所在的页表项,然后调用un_wp_page函数。计算公式为:(unsigned long*)((address >>10) &0xffc) + (0xfffff000 & *((unsigned long*)((address >> 20) & 0xffc)))+号后面计算的是该地址所在的页表,+号前面计算的是该地址在该页表中的偏移。这样得到的也就是该地址所在的页表表项的地址了。

write_verify(address): 检查地址address所在的页面是否是写保护的,如果是的话,就调用do_wp_page进行写时复制的操作。

get_empty_page(address): 获得一个空的物理页,然后将其对应在地址address所在的虚拟页面。

try_to_share(address, struct task_struct *p):主要功能就是将进程p中地址address所在的页面与当前进程进程共享。这个函数中的大部分操作是在进行页表目录以及页表的处理。首先根据address,以及进程p的信息,找到address所在的虚拟页面对应的物理页面,然后就是修改当前进程地址空间中address所在的页面以及页面目录信息等,以使当前进程的address与进程paddress所在的页面相同。

share_page(address): 从所有的进程中查找可以和当前进程共享地址空间的进程。

do_no_page(error_code, address): 处理缺页中断。从磁盘中读取address对应的文件信息到内存中。其步骤主要为申请一个free page, 然后从磁盘中读取address对应的文件信息(这些信息存在每个进程的task_struct结构中),然后利用磁盘信息填充页面。

mem_init(long start_mem, long end_mem): 对变量mem_map进行初始化。

calc_mem: 对当前内存的使用情况进行显示。包括空闲页面的数量,每个页表目录项使用了多少个物理页等等。

总结:看了这些代码之后,发觉里面的大部分代码都是在对页表目录和页表进行查询和维护。还有就是对虚拟地址的分析,如得到其对应的页表目录项,对应的页表项等。这里用到的一些技术包括:进程间页面的共享,写时复制等。

看完了之后总体感觉是没有特别难懂的地方。刚开始的时候,遇到的主要问题是在弄懂虚拟地址,页表表项,页表目录表项之间的换算关系上。里面太多的这种换算令我很头痛。后来发现,只要掌握了刚开始那三个公式的含义,理解其他的公式相对来说就很容易了。整个程序中没有用到什么的数据结构和算法,主要就是几个表格而已。算法主要也就是一些程序逻辑问题而已,一般的操作系统书籍都有讲到。只要理解了书中讲到的一些知识,要了解这些程序中的逻辑也就不是很难了。我想在几天之后再来研究研究Liunx中文件处理,看看里面有什么好的东西。

阅读(701) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~