从find_vma和find_vma_prev看内核

首先看一个问题:
Linux has both find_vma_prev and find_vma routines. find_vma_prev returns both the current and previous VMAs. Therefore, find_vma could be written as a simple layer routine over find_vma_prev that discards the pointer to the previous VMA. Give one reason why Linux goes to the trouble of providing two separate full-blown routines.
那么问题的答案是什么呢?如果按照应用程序的编程观点,能封装的就封装,能重用的尽可能重用,linux内核的设计就是一种糟糕的设计,根本不符合应用程 序的设计原则,其实要明白内核的设计和应用的设计在原则上是根本不同的,比如应用程序能利用丰富的异常处理机制,几乎每个编程语言都有这样的机制,最著名的莫过于try-catch块了,但是到了内核一切都要小心翼翼,为何对内核这样苛刻呢?原因有:1.内核根本就不是让用户用的;2.内核在原则上不能占 用太多的资源;3.内核的开发者默认都是非常有实力的;4.内核的作用是非常不灵活的,它存在的意义就那么几点:内存管理;进程管理;文件管理;...管 理。从这几点原因来看内核根本不能被娇惯,反过来说应用程序却是被内核娇生惯养的,内核要帮忙处理应用程序自己处理不了的错误(谁能帮内核处理呢?难道硬件吗?),应用程序的开发者鱼龙混杂,比如我就是一个菜鸟,另外应用程序的内容十分丰富,逻辑有的十分复杂,各个应用之间的差别非常之大(比如cpu消耗 型和io消耗型,大型人工智能程序和web服务器。内核呢?windows和linux包括unix的机制并没有多大不同,都是向上提供那么几种服务,向 下与硬件接口,只是实现不同罢了)。因此不要用应用程序的原则来要求内核。
既然知道了内核和应用的原则差别,那么开始的问题就比较好回答了,内核在非必要情况下绝对不会提供冗余函数,具体的冗余由用户应用程序来设置和重组,库函数经常这么干。这是为什么呢?用户程序强调维护,因此它过分强调接口,程序员只需要和接口打交道,这样我们编码的人就会更多的关注应用逻辑而不必关注接口 的实现,毕竟应用程序的卖点就是它的应用逻辑;但是内核就不同了,它就是一个二传手,可以说是一个代理,向上为用户展示了一个多道程序设计的机器,内部包含多个虚拟机器,并且管理着这多么台虚拟机,向下与硬件接口,将应用程序的要求转交给硬件,并代理硬件向应用程序传送结果,内核也就是这么多事情。举个例 子,我们都去过饭店,为我们服务的就是服务员,如果说哪家饭店的服务员比顾客多的话,那么多半是对这家饭店的侮辱,顾客应该比服务员多,而服务员的任务就是向上为顾客服务,向下与厨师接口,仅此而已。操作系统内核就是服务员,而应用程序就是顾客,厨师就是硬件,这个例子还算清晰。只要有个冗余,那么就要占 用一定的资源,好的内核根本不会也不允许其本身占用过多的资源,因此只要在内核中出现的函数就是必要的,非必要的冗余函数一定会被砍掉,因此问题中的 find_vma和find_vma_prev绝对是两个必要的函数,其功能是不同的,它们是不冗余的,下面就看一下它们:
struct vm_area_struct * find_vma(struct mm_struct * mm, unsigned long addr)
{
         struct vm_area_struct *vma = NULL;
         if (mm) {
                 vma = mm->mmap_cache;  //这个缓存就是关键,根据就是局部性原理,因此将最近访问过的vma置为缓存
                 if (!(vma && vma->vm_end > addr && vma->vm_start <= addr)) {
                         struct rb_node * rb_node;
                         rb_node = mm->mm_rb.rb_node;
                         vma = NULL;
                         while (rb_node) {
                                 struct vm_area_struct * vma_tmp;
                                 vma_tmp = rb_entry(rb_node, struct vm_area_struct, vm_rb);
                                 if (vma_tmp->vm_end > addr) {
                                         vma = vma_tmp;
                                         if (vma_tmp->vm_start <= addr)
                                                 break;
                                         rb_node = rb_node->rb_left;
                                 } else
                                         rb_node = rb_node->rb_right;
                         }
                         if (vma)
                                 mm->mmap_cache = vma;  //访问到了vma,因此更新了缓存
                 }
         }
         return vma;
}
struct vm_area_struct * find_vma_prev(struct mm_struct *mm, unsigned long addr, struct vm_area_struct **pprev)
{        //这个函数没有用缓存
         struct vm_area_struct *vma = NULL, *prev = NULL;
         struct rb_node * rb_node;
         if (!mm)
                 goto out;
         vma = mm->mmap;
         rb_node = mm->mm_rb.rb_node;
         while (rb_node) {
                 struct vm_area_struct *vma_tmp;
                 vma_tmp = rb_entry(rb_node, struct vm_area_struct, vm_rb);
                 if (addr < vma_tmp->vm_end) {
                         rb_node = rb_node->rb_left;
                 } else {
                         prev = vma_tmp;
                         if (!prev->vm_next || (addr < prev->vm_next->vm_end))
                                 break;
                         rb_node = rb_node->rb_right;
                 }
         }
out:
         *pprev = prev;
         return prev ? prev->vm_next : vma;
}
在 内核中用高速缓存是一件很好很高效的事情,但是为何要有一个find_vma_prev并且它不用缓存呢?查一下都在哪里调用了 find_vma_prev函数,查找发现内核很少调用这个函数,所有调用的都是将要写vma的时候,比如删除,而并vma操作之前调用它,既然要写 vma了,就有很大的可能刷新缓存,使缓存无效,这样的情况再用缓存就没有什么意义了,净浪费几行代码空间和几个cpu周期时间。反观find_vma函 数,它主要在读操作的时候调用,众所周知的就是do_page_fault缺页中断,既然没有写操作,那么局部性原理就是完全有效的,所以就在函数内部使 用了vma高速缓存。
其实总结起来就是读操作利用vma高速缓存,而写操作不利用vma高速缓存。这个事情使我想起了bsd的虚拟内存组织方式--splay树,splay树 本身就是一棵缓存树,而不像linux的vma的红黑树还要手动设置mm->mmap_cache,bsd的方式很巧妙,但是效果是否就一定比 linux的方式好呢?答案是不一定,这主要涉及到硬件高速缓存的问题,bsd每访问一次虚存结构都要将该结构置于树根的位置,就是说要调整树的结构,调 整结构就意味着写操作,而写操作就可能刷新硬件高速缓存,于是为了软件的高速缓存而失效了硬件高速缓存,纯粹为了软件的“艺术性”而忽略了硬件实际。
如果一个内核设计得好的话,内核里的东西都是有道理的,绝对不是无用的或者是冗余的,linux内核是个开源的内核,就算你不把冗余砍掉,我也会砍掉的,大家都有修改权,要记住,开发内核绝对不要优先考虑什么封装啊,接口啊,应用逻辑啊,内核没那么多事,它的应用逻辑很简单就那几样功能,开发内核要考虑的 是稳定性,安全性,性能。windows nt的内核很复杂,也很规整,完全实现了封装,接口易用性,扩展容易,也相当美观,但是它却占用了大量资源,性能比不过linux,稳定性干不过 unix,图啥,就是个T台模特儿,中看不中用,每次选个什么美啊都能搬回很多大奖,但是真正嫁入豪门的,很可能就是一个乡村姑娘。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值