Linux内核特性之VDSO
这段时间看Linux内核源码的时候,经常碰到vdso这个东西(像在Feature-fixup中,获取时间等操作时),网上搜了一下,才知道了含义,原来这是Linux为了解决和glibc兼容而想出的绝招啊。下面是从Fedora中文邮件列表转过来的,和大家分享一下。
往往内核添加了一个功能,glibc要花很久才会用上。本来linux那边为这个功能是否进入内核已经吵半天了,glibc这边又要为是否使用这个内核新特性再次吵架半天(glibc不是Linux专有的,还得考虑BSD(虽然人家也不用glibc),SysV Windows(诶,这没办法),还有sun那消亡的solaris,还有,自家的Hurd。然后,总之,这样新特性让人的接受上。。。太慢了。
说近点的,fnotify glibc还没有对应的包装函数呢,futex和NPTL又是花了许久才进入主流的。libc是app和内核的桥梁,libc理应快速跟上内核的接口变化,但是glibc和内核不是一块开发的,所以,这只是理想罢了。glibc还要去兼容不同版本的内核呢!
而内核也要去兼容不同版本的glibc.双方都背负了太多的历史包袱。glibc至今保留Linux Threads兼容2.4版本的古老内核。Linux对已经没用,甚至有bug(接口的问题导致一些bug是必须的)的系统调用也必须保留,谁知道用户会用哪个版本的glibc呢?虽然新的glibc会使用新的调用,但是提供和老的调用一致的API来兼容,但是,用户只升级内核而不升级glibc是常有的事情。就算升级了glibc,你新版本的glibc一定就用上内核的新接口?还是再等几年等glibc的开发者吵架结束吧。
于是乎,Linux的大牛们再次使出绝招:让libc变成VDSO进驻内核。
这里普及一下VDSO这个小知识,知道的人跳过,不知道的人读一下:VDSO就是Virtual Dynamic Shared Object,就是内核提供的虚拟的.so,这个.so文件不在磁盘上,而是在内核里头。内核把包含某.so的内存页在程序启动的时候映射入其内存空间,对应的程序就可以当普通的.so来使用里头的函数。比如syscall()这个函数就是在linux-vdso.so.1里头的,但是磁盘上并没有对应的文件.可以通过ldd/bin/bash看看。
这样,随内核发行的libc就唯一的和一个特定版本的内核绑定到一起了。注意,VDSO只是随内核发行,没有在内核空间运行,这个不会导致内核膨胀。这样内核和libc都不需要为兼容多个不同版本的对方而写太多的代码,引入太多的bug了。
当然,libc不单单有到内核的接口,还有很多常用的函数,这些函数不需要特别的为不同版本的内核小心编写,所以,我估计Linux上会出现两个libc,一个libc在内核,只是系统调用的包裹,另一个libc还是普通的libc,只是这个libc再也不需要花精力去配合如此繁多的kernel了。
姑且一个叫kernellibc,一个叫glibc:printf()这些的还在glibc。open(),read(),write(),socket()这些却不再是glibc的了,他们在kernellibc。
Linux下传统的系统调用是通过软中断(0x80)实现的,在一个Kernel.org的邮件列表中,有一封邮件讨论了“"Intel P6 vs P7 system call performance”,最后得出的结论是采用传统的int 0x80的系统调用浪费了很多时间,而sysenter/sysexit可以弥补这个缺点,所以才最终决定在linux内核中用后都替换前者(最终在2.6版本的内核中才加入了此功能,即采用sysenter/sysexit)。
如何用替换sysenter/sysexit替换以前的int 0x80呢?linux kenerl 需要考虑到这点:有的机器并不支持sysenter/sysexit,于是它跟glibc说好了,“你以后调用系统调用的时候就从我给你的这个地址调用,这个地址指向的内容要么是int 0x80调用方式,要么是sysenter/sysexit调用方式,我会根据机器来选择其中一个”(kernel与glibc的配合是如此的默契),这个地址便是vsyscall的首地址。
可以将vdso看成一个shared objdect file(这个文件实际上不存在),内核将其映射到某个地址空间,被所有程序所共享。(我觉得这里用到了一个技术:多个虚拟页面映射到同一个物理页面。即内核把vdso映射到某个物理页面上,然后所有程序都会有一个页表项指向它,以此来共享,这样每个程序的vdso地址就可以不相同了)
“快速系统调用指令”比起中断指令来说,其消耗时间必然会少一些,但是随着 CPU 设计的发展,将来应该不会再出现类似 Intel Pentium4 这样悬殊的差距。而"快速系统调用指令"比起中断方式的系统调用方式,还存在一定局限,例如无法在一个系统调用处理过程中再通过"快速系统调用指令"调用别的系统调用。因此,并不一定每个系统调用都需要通过"快速系统调用指令"来实现。比如,对于复杂的系统调用例如 fork,两种系统调用方式的时间差和系统调用本身运行消耗的时间来比,可以忽略不计,此处采取"快速系统调用指令"方式没有什么必要。而真正应该使用"快速系统调用指令"方式的,是那些本身运行时间很短,对时间精确性要求高的系统调用,例如 getuid、gettimeofday 等等。因此,采取灵活的手段,针对不同的系统调用采取不同的方式,才能得到最优化的性能和实现最完美的功能。
摘自 JuanA1的专栏