1、这个问题其实不重要
这个问题其实不重要,你知道这个问题答案,也不能为你找到更好的工作,或者说明你编程能力有多牛,你甚至在工作中用不到这个结论。但是为什么要写这篇文章呢?
因为,通过这个问题,我感觉到了解决问题的过程好像比知道问题的答案更有趣。
2、如果我们没有google怎么解决这个问题?
很多人包括我,当遇到问题的时候,发现bug的时候,总是习惯第一时间去google、百度。因为,快速,因为不需要动脑子,因为,可以抄答案,这种解决问题的方式带来的快感跟占了大便宜是一个道理。但是这样真的好吗?也许不好。
2.1、为什么有这个问题呢?
因为,我吃饭的时候突然想起一个问题,既然用户与内核都使用虚拟内存,都要经过页表才能找到最后的物理页,那么为什么在进程切换的代码中找不到内核切换的代码?只是切换了mm?压根就没提内核空间。我没有多想,就把这个问题记录下来了,回头仔细查查代码。
2.2、问题探索过程
我也是这个套路,一顿google下来,没发现什么有价值的信息。其实,真正的技术问题,或者有价值的技术问题,很难找到答案,大家可以思考下为什么。
大家,可以试试,Linux很多的问题,你要想通过google找到答案非常难,如果找到了,只能说明,可能这个问题比较简单,你自己仔细想想也能想清楚。
那么,只有一条路了,好好看代码。
2.3、看代码之前
在过去的1年里,我读了很多的Linux源代码,我本能就非常不想这样,一是,代码不好读,二是,因为当你读代码的时候会发现更多你不懂的问题。就像一个深度优先遍历一样,越来越多的问题把你的问题栈压垮,让你没有信心再看下去。
这次,我打算改变一下。
3、问题驱动
对,就像标题,以我看内核代码的经验,这个方式似乎是最好的。但是,也是最虐心的。
方法是,
- 1、先在脑海里自己给出不同的答案,然后推导他们的可能性。比如这个问题,无非就是两个答案:1是,2不是。
- 2、根据答案,推导下看是不是合理。
比如:
- 如果是共享的,那么意味着一个进程的页表就包含了用户态页表与内核态页表两个部分,但是,内核态的地址空间是所有进程共享的,这不是个矛盾吗?如果一个进程在陷入内核后新建了很多内核对象,那其他进程怎么看到?而且,这么重的内存分配发生在频繁的进程创建与销毁上,这不是linux的哲学啊。比如在fork->dup_mm中就会为新的进程分配mm,伴随着mm->pgd的分配。shell执行命令频繁,这个操作感觉比较重。
- 后来,我突然想到了copy_to_user这个函数,意思是从内核拷贝数据到用户态,看看有没有cr3的切换?
- 发现并没有,大概率是共享的:源代码长这样
- 如果不共享。似乎也有道理,内核与用户态本来就不是一路的,分开的话对体系结构更友好。而且,在书上也经常可以看到内核页表的字眼,比如:
虽然我觉得,大概率会共享,因为内核要频繁访问、写入用户空间数据,比如,网络连接要写入用户态缓冲区等等,不能每次要写数据就切换cr3,这个更加违背linux的哲学;但是,这个kernel page table是啥?
3.1、问题变成了这样
现在不再是去找内核与用态是否共享页表这个问题的答案了,现在只需要明确这个问题:
- 如果共享页表,那么内核的页表在哪里?在哪里初始化的?应该在fork里面吧,因为,这里创建了新进程的mm;
- 而Kernel Page Tables是什么鬼?
问题的搜索难度是不是降低了?嗯,这个过程类似于数学证明,将原问题,分解成很多难度更低的小问题,解决小问题就解决了等价的大问题。难度其实是降低了。
4、开始翻代码
最后其实是找到了一个我平时忽略了的函数pgd_ctor在arch/x86/mm/pgtable.c底下。这个函数就是给用户态页表附上内核页表的过程,调用地方大概是在fork->dup_mm->init_mm->pgd_alloc->pgd_ctor这个不重要。
函数长这个样子:
这个函数一下子解决了上面提出的两个问题:
- 确实,每个进程都单独有自己的内核页表存在,这使得copy_to_user成为可能;
- 内核页表也是存在的就是swapper_pg_dir,其实是init进程的mm,这就是所谓的kernel pagetables了(由init0号进程创建的页表);
- 其实,确实是所有的进程都有自己的内核页表,但是大部分是共享的,各自备份的只有pgd顶级页目录而已,其他的下级目录,都是共享的。相当于只共享了一个指针,通过这个指针可以找到内核的数据页(内核中的直接映射区、vmemmap都在下面的映射中,完全共享,全局唯一)。而拷贝一个‘指针’当然代价很小啦(当然实际并不是个指针这么小)。
- 现在确实体会到了页表这个结构的妙处,方便共享数据也是个好的用途。
5、总结
我感觉,Linux内核代码的学习不能跟着在线课去学,尽管有很多这样的课程。因为,答案立马就知道了,你体会不到这里面的深意,牵着鼻子走,听完了也跟没听一样,好像懂了,好像又问题一堆。
主要是因为没有去思考问题,如果要真的看懂Linux内核源代码,要带着问题去看代码,遇到问题先想想如果是自己设计内核,会怎么实现?然后去找答案,这是一个比较好的方式。
亲测,有效!
本文暂时没有评论,来添加一个吧(●'◡'●)