📄 001_arch_i386_mm_fault_.html
字号:
* We need the page table lock to synchronize with
kswapd<br>
* and the SMP-safe atomic PTE updates.<br>
*/<br>
spin_lock(&mm->page_table_lock);<br>
entry = *pte;<br>
if (!pte_present(entry)) { //pte不存在,映射有问题,又分为两种情况<br>
/*<br>
* If it truly wasn't present, we
know that kswapd<br>
* and the PTE updates will not
touch it later. So<br>
* drop the lock.<br>
*/<br>
spin_unlock(&mm->page_table_lock);<br>
if (pte_none(entry))
//第一种情况:根本没有建立,或者已经被断开<br>
return
do_no_page(mm, vma, //页面不存在, 就是没有,或者被try_to_swap_out<br>
address, // 断开, 这种页可以属于page cache<br>
write_access,<br>
pte // 参见filemap_nopage 了解page cache
的换入<br>
);<br>
<br>
return do_swap_page(mm, vma, //第二种情况: not present,
那么就属于swapper_space管理<br>
address, pte,<br>
pte_to_swp_entry(entry),<br>
write_access<br>
);<br>
}<br>
<br>
//如果是read 时产生陷入或者时pte 的映射问题<br>
//或者是一个非法的操作, 已经在do_page_fault 过滤掉了<br>
if (write_access) { //由于页面写保护产生的陷入, 而OS 却容许用户写入<br>
if (!pte_write(entry))<br>
return
do_wp_page( mm, //就是处理COW 的第二步<br>
vma, //Copy on Write<br>
address, //第一步是建立一个不容许写的页<br>
pte, entry //却在vma 中赋予用户写的权限<br>
);<br>
<br>
entry = pte_mkdirty(entry);<br>
}<br>
entry = pte_mkyoung(entry);<br>
establish_pte(vma, address, pte, entry);<br>
spin_unlock(&mm->page_table_lock);<br>
return 1;<br>
}<br>
<br>
这里要强调的是页面的换入.
有以下途经分配/换入页面,从而建立页面<br>
映射.<br>
I)第一种情况:根本没有建立,或者已经被断开.<br>
换入函数 do_no_page(mm/memory.c),
首先尝试 vma->vm_ops->nopage,<br>
如果vma没有提供nopage操作就使用do_anonymous_page分配一个空闲页面.<br>
(本次分析细节到此结束)<br>
II)第二种情况是被交换到了swap space<br>
则通过 do_swap_page从swap
文件读入内存(或者从swap cache 找回).<br>
<br>
此函数还涉及计COW, 这里不再讨论.<br>
<br>
页面的释放和换出这里页简单提提,首先定义匿名页面是<br>
page->mapping(address mapping)为NULL的页面.页面换出当然是try_to_swap_out<br>
所为 .try_to_swap_out换出非匿名页的时候是直接断开页面映射.匿名的clean页<br>
面的映射也是直接断开,因为不需要写入交换文件.<br>
如果是dirty的匿名页,则分配一个swap页(swap
file中的一个页面)然后将<br>
swap 页的entry写入pte,并置pte为页面不在内存.<br>
<br>
try_to_swap_out区别对待这三种页面,和 handle_pte_fault换入页面的不同<br>
操作相对应.<br>
<br>
总结一下页面的周转:从try_to_swap_out和handle_pte_fault看过去, 换<br>
出要不就是swap
space,要不就是page->mapping->a_ops->writepage(见mm/vmscan.c<br>
函数page_launder,应该注意到无论是swap cache还是page cahche都使用writepage<br>
换出页面).换入要不就是swap space,要不就是vma->vm_ops->nopage(分配干净页面<br>
就不算进页面周转了).<br>
<br>
<br>
下面的问题就是 page->mapping->a_ops->writepage 和
vma->vm_ops->nopage<br>
到底是什么函数,怎样赋值?<br>
<br>
先看page->mapping->a_ops->writepage,搜索page->mapping,在函数<br>
do_generic_file_read(mm/filemap.c)中发现page->mapping来源于<br>
inode->i_mapping. do_generic_file_read->__add_to_page_cache读入一个页面的<br>
文件后,将页面加入page cache,同时赋予page->mapping以addr space. 另外在做搜<br>
索的过程中应该注意函数add_to_page_cache_locked(mm/filemap.c)此函数建立page<br>
cache内页面的page->mapping映射.搜索调用add_to_page_cache_locked的函数得知,<br>
shmem_nopage(mm/shmem.c)建立的page->mapping来源于inode;add_to_swap_cache<br>
(mm/swap_state.c)建立的的page->mapping映射是swapper_space(也在swap_state.c).<br>
顺着这些线索,可以找到,其实写页面最终和一个inode相关联,这也于理相符. 我们推<br>
断换入用的vma->vm_ops->nopage最终也和inode相关.<br>
看看vma->vm_ops->nopage的情况. 直接搜索nopage,发现有两种vm_ops,<br>
filemap_nopage(* area,address, no_share)(mm/filemap.c), shmem_nopage( *
vma,<br>
address, no_share)(mm/shmem.c).<br>
现在看来,vma可以获取文件映射(mmap,参考函数generic_file_mmap),shemem,<br>
或者匿名页,来建立其终物理页面的映射.从这些线索分析,filemap_nopage利用<br>
page-mapping的readpage从文件读入页面内容,呼应了刚才的猜想.shmem_nopage<br>
只是通过vma相关联的文的inode之mapping在swap space寻找被交换出去的shemem<br>
页面,并不用来从文件读取页面内容.寻找到的页面(或者新分配的)就加入page cache<br>
(如果是从swap cache找到的则需要从swap chache移出).到这里再看page cache和<br>
swap cache的界定,page->maping的值如果是swapper_space, 则页面处于swap cache,<br>
如果page->maping的值是其他add space,比如filemap, shemem设置的mapping,则称<br>
页面在page cache 中.<br>
<br>
希望还没有迷路.<br>
<br>
<br>
2. asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long
error_code)<br>
<br>
不是想在这里详细介绍i386 cpu的处理机制,而是给出中断处理在linux中的<br>
线索,以资参考。<br>
<br>
中断或异常发生后,i386终止执行当前运行的指令流(参考:),保存必要的<br>
状态根据当前的运行级别(内核/用户),从idt选择合适的gate,执行指定地址<br>
的处理函数。<br>
我们以页面异常为例。<br>
<br>
首先看页面异常门的设置。首先要看idt地址。在系统启动的时候,boot程序已<br>
经初始化了一个idt,但是在kernel中,还要重新指定定idt地址并用lidt加载内核<br>
的idt。从 arch/i386/kernel/traps.c 的函数trap_init 看过去 .<br>
宏 set_trap_gate:<br>
<br>
static void __init set_trap_gate(unsigned int n, void *addr)<br>
{<br>
_set_gate(idt_table+n,15,0,addr);<br>
}<br>
<br>
引用了idt地址, idt_table,定义在traps.c 文件的开始 处:<br>
<br>
<br>
/*<br>
* The IDT has to be page-aligned to simplify the Pentium<br>
* F0 0F bug workaround.. We have a special link segment<br>
* for this.<br>
*/<br>
struct desc_struct idt_table[256]
__attribute__((__section__(".data.idt")))= { {0, 0}, };<br>
<br>
<br>
<br>
由此得知在内核链接的时候预留了一个section .data.idt 作为idt表.<br>
从init/main.c 的函数start_kernel开始内核 初始化经历 ->trap_init->cpu_init:<br>
__asm__ __volatile__("lidt %0": "=m" (idt_descr));<br>
<br>
至此,trap初始化,idt表设置已经完成. 深入下去的话include/asm-i386/desc.h<br>
中定义:<br>
#define idt_descr (*(struct Xgt_desc_struct *)((char
*)&idt - 2))<br>
并声明<br>
extern struct desc_struct *idt, *gdt;<br>
而idt是编译链接程序生成的符号,地址即是.data.idt<br>
<br>
<br>
函数trap_init 中 初始化14号异常,即页面异常入口为page_fault<br>
set_trap_gate(14,&page_fault);<br>
<br>
page_falut定义在arch/i386/kernel/entry.s:<br>
<br>
ENTRY(page_fault)<br>
pushl $ SYMBOL_NAME(do_page_fault)<br>
jmp error_code<br>
<br>
在这里调用页面异常处理函数
do_page_fault(arch/i386/mm/fault.c).<br>
下面分析此函数, 分析在代码中以注释形式出现.<br>
/*<br>
* 页面faults(故障) 处理入口. 主要任务时确定异常<br>
* 发生的地址和原因,然后把不同的故障传入不同<br>
* 的入口函数.<br>
*<br>
* error_code:<br>
* bit 0 == 0 means no page found, 1 means
protection fault<br>
* bit 1 == 0 means read, 1 means write<br>
* bit 2 == 0 means kernel, 1 means user-mode<br>
*<br>
* 有三种情况下陷入此函数:<br>
* 1. pmd, pgt, pte 有一个为空, 即未建立映射或已经撤销.<br>
* 2. 页面不在内存, 即,为内核交换到了磁盘.<br>
* 3. 权限不正确.<br>
* <br>
* 情况1. 此种页面属于已经撤销的, 此种页面由page cache
(address_space)管理.<br>
*
try_to_swap_out 把这种页面的映射断开, 即,把相应的pte 置成0 .<br>
* <br>
* 情况2. 中的页面属于swapper_space 管理. try_to_swap_out
不会把这种 pte<br>
* 置0,
而会换成相应的swp_entry_t.<br>
* <br>
* 转交下一级函数处理时, 所有非法操作都在这个函数中<br>
* 处理掉了<br>
*/ <br>
asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long
error_code)<br>
{<br>
struct task_struct *tsk;<br>
struct mm_struct *mm;<br>
struct vm_area_struct * vma;<br>
unsigned long address;<br>
unsigned long page;<br>
unsigned long fixup;<br>
int write;<br>
siginfo_t info;<br>
<br>
/* 确定异常发生的地址*/<br>
__asm__("movl %%cr2,%0":"=r" (address));<br>
<br>
tsk = current;<br>
<br>
/*<br>
* 由于demand 我们陷入了内核虚拟空间. 我们使用<br>
* 的 page table 是init_mm.pgd.<br>
*<br>
* NOTE! We MUST NOT take any locks for this case. We
may<br>
* be in an interrupt or a critical region, and
should<br>
* only copy the information from the master page
table,<br>
* nothing more.<br>
*/<br>
if (address >= TASK_SIZE)<br>
goto vmalloc_fault;
//异常发生于内核空间<br>
<br>
mm = tsk->mm;<br>
info.si_code = SEGV_MAPERR;<br>
<br>
/*<br>
* 如果异常发生在中断中或者没有用户<br>
* context(环境), we must not take the fault..<br>
*/<br>
if (in_interrupt() || !mm)<br>
goto no_context;<br>
<br>
down(&mm->mmap_sem);<br>
<br>
//下面的主要思路是根据异常发生的地址和vma 的<br>
//关系,分成几种不同情况进行处理<br>
<br>
vma = find_vma(mm, address); // 试着找一个vma , 其结束地址大于异常点<br>
if (!vma)<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -