每个进程的局部段描述符LDT都作为一个独立的段而存在,在全局描述表GDT中要有一个表项指向这个段的起始地址,并说明该段的长度和其它一些参数,此外,每个进程还有一个 TSS 结构也是一样。所以,每个进程都要在全局段描述表GDT中占据两个表项。在段寄存器中用作GDT表下标的位段宽度是13位,所以GDT中可以有(2 ** 13) 8192个描述项。除去一些系统开销(如GDT中的
第1项永远为0,
第2, 3项分别用于__KERNEL_CS, __KERNEL_DS;
第4, 5项永远用于当前进程的__USER_CS, __USER_DS)
以外,尚有8180个表项可供使用,所以理论上系统中最大的进程数量为 8180/2 = 4090项.
当DMA所需的缓冲区超过一个物理页面的大小时,就要求两个页面在物理上连续,因为此时DMA控制器不能依靠在CPU内部的MMU将连续的虚存页面映射到物理上不连续的页面,而在i386cpu中,页式存储管理的硬件支持是在CPU内部实现的,而不像另有些CPU那样由一个单独的MMU提供,所以DMA不经过MMU提供的地址映射。
bit 0 == 0: 没有找到page, 1: 保护错误;
bit 1 == 0: 读; 1: 写;
bit 2 == 0: 内核模式; 1: 用户模式;
bit 3 == 1: 探测到使用了保留位;
bit 4 == 1: 是一个取指错误;
何时会发生缺页中断:
1.相应的页面目录项或页面表项为空,即该线性地址与物理地址的映射关系尚未建立,或已撤销。
2.相应的物理页面不再内存。
3.指令中规定的访问方式和页面权限不符。
页面异常服务程序的主体入口处为 do_page_fault(),其处理流程为:
1.当i386 CPU产生缺页中断异常时,CPU将导致映射失败的线性地址放到控制寄存器CR2中。
2.内核的中断/异常响应机制传来两个参数。(pt_regs *regs, u32 error_code)regs指向例外
发生前夕CPU中各寄存器内容的一份副本。这是由内核的中断响应机制保存下来的"现场"。
error_code则进一步指明映射失败的具体原因。
3.获取当前进程的task_struct数据结构 task_struct *tsk = current
4.检测两个特殊情况。
a.
in_interrupt()返回非0,说明映射失败发生在某个中断服务程序中,
所以与当前进程毫无关系.
b. c
urrent->mm == NULL, 说明该进程的映射还未建立,所以也与当前进程无关。说明这次异
常发生在某个 中断/异常 服务程序中。此时,用goto跳转到 no_cotext()
5.判断发生异常的线性地址是否落在某个已经建立起映射的区间。或者指出在那个区间:find_vma()
a.
如果地址在3G以上,则也是越界,即访问了系统空间,此时控制流转向 bad_area.
b.
如果找到了,说明给定的地址恰好在落在这个区间,控制流转向 good_area.
c. 剩下一种情况是,
所给的线性地址正好落在两个区间之间的地址空洞里。也就是说该地址所在
页面的映射尚未建立或已经撤销。在用户虚存空间中,可能有2中不同的空洞。
1. 只能有一个,即堆栈区以下的那个大空洞。它代表着供动态分配的剩余空间。当
VM_GROWSDOWN为 1 时,说明空洞上方的区间是堆栈区间。
2. 虚存空间之间的空洞。可以有多个。如果该线性地址的vm_flags中的标VM_GROWSDOWN
为0,则说明空洞上方的区间并非堆栈区说明,则这个空洞是因为一个映射区间被撤销留
下的。或者在建立映射时跳过了一块地址。
6.当控制流到达bad_area后,已经不再需要互斥了(因为不再对mm_struct结构进行操作),所以通
过up()退出临界区。接着就要进一步考察error_code,看看失败的具体原因:
error_code:
bit2 == 1,表面失败是当CPU处于用户模式时发生的。说明所给的线性地址为虚存空间的空洞
则对当前进程task_struct结构内的一些成分进行一些设置,就向该进程发送一
个强制信号: SIGSEGV。 至此,本次例外服务结束。
越界访问有时候是正常的。这只发生在一种情况下:当用户堆栈过小,但是因越界访问而"因祸得福"得
以延展的情况。扩展流程如下:
1.判断空洞上方的区间是否是堆栈区间。当VM_GROWSDOWN == 1 时,为堆栈区间。
2.当映射失败发生在用户空间时(即 bit2 == 1),则因堆栈操作引起的越界是作为特殊情况对待的
3.检查发生异常时的地址是否紧挨着堆栈指针所指的地方。X86中考虑到(pusha指令的存在,一次将
32个字节压栈),所以
越界地址要 > %esp - 32。若小于,则转向 bad_area.
4.至此,则该地址为正常的堆栈扩展要求,所以从空洞的顶部开始分配若干页面建立映射,并将之并
入堆栈区间,使其得以扩展。上述操作通过调用expand_stack()即可(include/linux/mm.h).
- expand_stack(struct vm_area_struct *vma, unsigned long address)
vm_area_struct *vma 代表一个区间,在这里代表着用户空间堆栈所在的区间。
unsigned long address 代表越界地址。
1. 首先,将地址按页面边界对齐,并计算需要增长几个页面才能把给定地址包括进来(通常1个)
2. 每个进程的task_struct结构中有个rlim结构数组,规定了对每种资源分配使用的限制,而
RLIMIT_STACK就是对用户空间堆栈大小的限制。所以,先要进行检查。
3. 如果分配失败,则返回-ENOMEM,在do_page_fault()也会转向bad_area。如果分配成功
则返回0,一般都会成功的。
4. expend_stack()只是改变了堆栈区的vm_area_struct结构,而并未建立起新扩展的页面对
物理内存的映射。这个任务由接下来的good_area完成。
中断或者陷阱(trap指令)发生时: cpu都会将下一条指令地址压栈作为中断服务的返回地址。
异常发生时: cpu将因无法完成而夭折的指令本身的地址压入栈中,这样,就可以在异常处理
返回时完成未完成的工作。此特性由CPU内部电路实现。
物理页面的使用和周转
阅读(1169) | 评论(0) | 转发(0) |