关于页表同步问题
本文是原「蜗窝讨论区」的历史存档(2016-06-01),来自版块「Linux kernel技术问答」,共 6 帖。讨论区已停止服务,此处仅供查阅。
QQ群中的MBeginner问了这样的一个问题: 如果进程A、B都访问了某个虚拟地址x,此时A、B的页表都与init_mm同步后,然后进程A修改了地址x的映射关系,修改的是init_mm的页表,此时如果不更新进程B的页表,那么进程B再次访问地址X的时候会认为当前页表是有效的,不会发生缺页中断,从而导致实际上访问的是错误的物理地址, 这种情况可能出现么?
整理大家的回答如下: 这种情况不会发生。 首先,内核空间可以分成下面几种: 1、 directed mapped memory region,就是mapping到main memory的那段虚拟地址空间,VM和PM之间有一个固定的offset。这个memory region的页表是初始化建立的,是不会动态变化的。 2、 permanent kernel mapping、temporary kernel mapping和noncontiguous memory allocation(就是大家熟悉的vmalloc适用的memory region)。这个段的页表可以被更新。 因此,我相信大家说到页表同步问题都是指的第二段memory region的页表同步问题。
关于你说的场景,我分步描述如下: Step 1:A进程调用vmalloc分配了一段虚拟内存,首地址是x。这时候,所有进程的页表并没有建立关于x的项目,唯一完整建立x地址段的页表是init_mm的PGD。
Step 2:当进程A、B都访问了某个虚拟地址x,此时A、B的页表都与init_mm同步后,A和B也都建立好了关于x这个虚拟地址段的页表。但是,这里需要强调的是:对于x虚拟地址段,A进程和B进程有不同的PGD,但是它们PGD中关于x地址段的entry们都是指向相同的PMD。当然PMD entry指向的PTE也是相同的。换句话说,对于x地址段,所有进程还有init mm而言,它们的PMD和PTE都是共享的。
Step 3:进程A调用vfree函数释放了地址x,当然操作的依然是init mm的PGD以及其下级的页表们。需要说明的是:所有根是init mm PGD的那些PMD和PTE的内存不会回收,因此x地址段的PMD和PTE页表本身的内存并不会释放,vfree只是将x地址段对应的页表项全部清除。
Step 4:进程B访问x地址段的时候,PGD entry指向了大家共享的PMD,PMD的entry指向了大家共享的PTE,但是,PTE中的具体的entry已经被清除,因此产生page fault
harvey网友的问题: 关于你前面提到的“init_mm”,下面的理解是否正确 1,init_mm 本身是内核的一个数据结构,其中包括一个红黑树的跟,任何进程或线程在内核中申请内存后,内存管理模块就以申请空间对应的虚拟地址作为红黑树的key值,挂到这棵红黑树上。当然此时并没有真正分配页面。当进程或线程真正访问该内存空间是就会产生缺页中断,在中断中再真正分配内存。init_mm 本身是给软件用的。 2,关于申请内核空间的进程,内存管理模块会将空间对应的PMD(PTE表项直接填写到了PMD中了)表项填写到进程自己的PGD表中,只是填写了一个entry。这个PGD表是给硬件使用的,即访问时又硬件自动查表。
所有和进程地址空间相关的内容都被封装到一个叫做mm_struct的数据结构中,被称作memory descriptor。每个用户空间进程都有其对应的memory descriptor,具体位于进程task_struct的mm成员中。内核线程是否有memory descriptor呢?没有,因此其进程描述符的mm成员指向了NULL。不过,对于linux而言,内核的最小调度单位是线程,因此存在内核线程和用户线程之间的切换问题,每当切换的时候,需要切换进程地址空间(切换页表,进程的页表信息位于其mm_struct的pgd成员中),用户线程到用户线程的切换当然没有问题,但是用户线程和内核线程之间的切换就会有问题,因为内核线程没有memory descriptor,怎么办?只能是借用了,因此,在进程描述符(task_struct)中有一个active_mm的成员,表示该进程(内核线程)当前正在使用的memory descriptor(注意:“使用”并不表示“拥有”)。 对于普通用户进程,它拥有自己的memory descriptor,因此进程描述符的active_mm和mm成员指向同一个memory descriptor(自己有memory descriptor,当然使用自己的了)。对于内核线程,它不可能”拥有“memory descriptor(mm等于NULL),因此active_mm需要借用其他进程的memory descriptor,可以参考下面的代码(代码来自4.1.10): -------------------- static inline struct rq * context_switch(struct rq rq, struct task_struct prev, struct task_struct next) { struct mm_struct mm, *oldmm;
prepare_task_switch(rq, prev, next);
mm = next->mm;
oldmm = prev->active_mm;
/*
- For paravirt, this is coupled with an exit in switch_to to
- combine the page table reload and the switch backend into
- one hypercall.
*/
arch_start_context_switch(prev);
if (!mm) {----------------切换到一个内核线程(next的mm成员为NULL)
next->active_mm = oldmm;-------借用上一个的使用的memory descriptor
atomic_inc(&oldmm->mm_count);
enter_lazy_tlb(oldmm, next);
} else-------------------切换到普通进程
switch_mm(oldmm, mm, next);
if (!prev->mm) {
prev->active_mm = NULL;---------如果是从一个内核线程切换到某个其他进程,那么借用期结束
rq->prev_mm = oldmm;
}
/*
- Since the runqueue lock will be released by the next
- task (which is an invalid locking op but in the case
- of the scheduler it’s an obvious special-case), so we
- do an early lockdep release here:
*/
spin_release(&rq->lock.dep_map, 1, THIS_IP);
context_tracking_task_switch(prev, next);
/* Here we just switch the register state and the stack. */
switch_to(prev, next, prev);
barrier(;
return finish_task_switch(prev);
} -------------------------- 讲了这么多,似乎还是没有到init_mm,呵呵,那么init_mm到底是属于哪一个进程呢?其实init_mm不属于任何的进程或者内核线程。当然,在启动阶段,swapper进程(idle进程,pid等于0的那个进程)曾经借用国init_mm,但是初始化完成,调度器正常运作之后(至少发生了一次进程切换涉及到了idle进程),init_mm不和任何的进程相关了。
mm_struct中有两个成员和用户地址空间相关: 1、mmap(双向链表) 2、mm_rb(红黑树) 每当进程分配一段用户地址空间(注意:和内核空间无关),都会分配一个vm_area_struct,被挂入上面的链表和红黑树中。因此init_mm中的红黑树和链表都是没有任何意义的,因为它主要是for内核地址空间的。
我们再回到内核地址空间来看,就Translation table而言,和内核地址空间相关的ranslation table包括: 1、Master kernel PGD 2、各个用户进程的PGD 3、内核空间的PMD和PTE 在上面的ranslation table而言,所有的用户空间进程以及内核线程都共享item 3. 各个用户进程拥有自己的PGD,内核线程没有自己的PGD,只是借用某个用户进程的PGD(借用哪一个是看缘分,上面说了,是和进程切换相关)。Master kernel PGD不属于任何的用户进程或者内核线程,它就是一个共用的模板而已。当申请了内核空间的虚拟地址段的时候,内核只是修改了Master kernel PGD以及其child translation table的内容,也就是说仅仅是在模板上设立好了Translation table,而各个进程的Translation table还都是空的,这些是在真正访问的时候,发生异常,在异常处理流程中,根据模板中的数据建立自己的页表数据。
@linuxer 这方面的内容,画图的话应该会清晰很多
时间不允许啊,这个问题在QQ群上提出有一段时间了,我终于腾出一点时间回答,毕竟还是要养家糊口啊。
其实,在中国,linux牛人绝对不缺,只是缺少有闲的牛人来给初学者科普而已。
