异常处理:
在开启分页的状态下,CPU执行线性地址到物理地址转化的过程中检测到如下条件,就会引起页异常(Page fault)int 14:
1、页目录或者页表项中的存在位为0;
2、当前程序没有足够的权限访问指定页面。
这时CPU会做以下操作:
1、将错误码压入栈中,在14号中断中只有最低三位有效。位0(P):0/1 页或者页表不存在/存在;位1(R/W):0/1代表读/写操作出错 ;位2(U/S):0/1代表用户/系统页。
2、将出错的线性地址放入CR2中。
写时复制&&需求加载:Linux/Unix系统的两项非常重要的内存使用策略。
主内存管理:
1、内核使用字节数组mem_map[]来保存1MB以上空间中物理页的使用状态。0表示未占用,有数字表示被占用了N次。
2、在mem_init()初始化内存的时候,先将所有主内存页都标记为占用,然后把高速缓冲和虚拟盘以外的内存标记为未占用。
3、get_free_page()和free_page()用来申请和回收主内存中的一页。申请成功时会把内存页清零,回收是把mem_map[]对应元素-1,如果回收了超过物理内存或者已经空闲的页面会panic,回收内核区域内存则立刻无操作返回。参数或者返回值都是物理地址。
4、free_page_tables()和copy_page_tables()用来复制和回收页表空间,其参数为线性地址和页表个数。free_page_tables()检查线性地址是否4M对齐,是否不等于0,否的话就回painc,是则释放对应页表中的页,以及页表,最后使TLB失效。copy_page_tables()会检查源/目的地址是否4M对齐目的页目录项是否存在。申请空间来放置目的页表,并把页目录项联系到目的页表(属性为U|W|P),然后复制页表项,并把页表项设置为只读,对应页面引用计数自增,最后使得TLB失效。需要注意的是处理1M以内物理地址的时候是不会把源页表项设置为只读的。而且对于地址0起始的页表,仅仅复制了160页。
5、put_page()用于把指定物理页面映射到指定线性地址处。先检查页面是否处于主内存区,物理页面是否已申请,否则打印警告。然后检查对应页表是否存在,不存在申请一页内存来存放页表,然后映射到物理页,并且置页表项和页目录项属性为U|W|P。
6、do_wp_page() 是页写保护异常处理函数,实现了写时候复制技术。
7、do_no_page() 是缺页异常处理函数,该函数首先判断当前程序是否刚刚建立或者缺页是否在代码和数据区以外,如果是就直接映射一个空闲页面并返回。如果不是则要判断调用share_page()去判断有没有别的进程已经拥有了该页,如果有而且没有dirty,就共享(只读)给当前进程并返回。剩下的情况就申请一块空闲页面,然后把内容从设备中读取出来,并清除掉多余内容,映射即可。
8、其中可以经常看到如下代码
dir = (unsigned long *) ((from>>20) & 0xffc);
用来取得页目录项地址。其中右移20位逻辑与0xffc是个trick,因为一个表/目录项正好占4个字节。
page.s中有异常中断处理程序,通过调用memroy.c中的do_no_page()处理缺页异常,调用do_wp_page()来处理写保护异常。
疑问:
try_to_share()函数的作用是用来共享程序中某页。有三句代码如下:
from_page = to_page = ((address>>20) & 0xffc); from_page += ((p->start_code>>20) & 0xffc); to_page += ((current->start_code>>20) & 0xffc);
这三句是用来计算源页目录(from_page)目的页目录(to_page)。但是有个问题,如果start_code所在页表项不是页表的第一个的话,这里计算就会产生误差,也就是说start_code必须页表(4M)对齐。虽然在前面的内容中了解到,linux0.11内核中,程序都是占用64M的虚拟空间并且空间是64M对齐的,但是没有说明程序代码必须放到进程空间开始处(当然也没有不这么做的必要),只能等到看完进程的内容才能解释了。