今天心血来潮,研究了一下Linux-0.11的源码。主要看了一下关于memoy管理的部分。这个部分的主要内容是在memory.c中。
linux用的是virtual memory management。每个page 有4k。它用到了二级页表结构。第一级页表存在物理地址空间0开始的位置,一共有1024个表项,每个表项有4个字节。也就是说,一级页表占了one page。二级页表和一级页表的结构相同。页表目录(也就是一级页表)表项和页表(这里指的是二级页表)表项每个有4字节,也即32位,他们的前20(31-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);和上面这个公式的含义差不多,只不过我们这里得到的是虚拟页面对应的物理页面的地址。