libc,glib,glibc,eglibc,libc++,libstdc++,gcc,g++
libc
libc是Linux下原来的标准C库,也就是当初写hello world时包含的头文件#include < stdio.h> 定义的地方。
glibc
1.libc后来逐渐被glibc取代,也就是传说中的GNU C Library。现在只要知道用的最多的是glibc就行了,主流的一些linux操作系统如 Debian, Ubuntu,Redhat等用的都是glibc(或者其变种,下面会说到).
2.那glibc都做了些什么呢? glibc是Linux系统中最底层的API,几乎其它任何的运行库都要依赖glibc。 glibc最主要的功能就是对系统调用的封装,你想想看,你怎么能在C代码中直接用fopen函数就能打开文件? 打开文件最终还是要触发系统中的sys_open系统调用,而这中间的处理过程都是glibc来完成的。
3.这篇文章详细介绍了glibc是如何与上层应用程序和系统调用交互的。除了封装系统调用,glibc自身也提供了一些上层应用函数必要的功能,如string,malloc,stdlib,linuxthreads,locale,signal等等。
elibc
eglibc又是什么? 这里的e是Embedded的意思,也就是前面说到的变种glibc。eglibc的主要特性是为了更好的支持嵌入式架构,可以支持不同的shell(包括嵌入式),但它是二进制兼容glibc的,就是说如果你的代码之前依赖eglibc库,那么换成glibc后也不需要重新编译。
-----------------------------------------小总结-----------------------------------------
看到这里,你应该知道这些库有多重要了吧? 你写的C代码在编译的过程中有可能出现明明是这些库里面定义的变,却量还会出现’Undefined’, ‘Unreference’等错误,这时候你可能会怀疑是不是这些库出问题了? 是不是该动手换个gilbc/eglibc了? 这里强调一点,在你准备更换/升级这些库之前,你应该好好思考一下,你真的要更换/升级吗?你要知道你自己在做什么!你要时刻知道glibc/eglibc的影响有多大,不管你之前部署的什么程序,linux系统的ls,cd,mv,ps等等全都得依赖它,很多人在更换/升级都有过惨痛的教训,甚至让整个系统奔溃无法启动。所以,强烈不建议更换/升级这些库!
-----------------------------------------C++-----------------------------------------
当然如果你写的是C++代码,还有两个库也要非常重视了,libc++/libstdc++,这两个库有关系吗?有。两个都是C++标准库。libc++是针对clang编译器特别重写的C++标准库,那libstdc++自然就是gcc的事儿了。libstdc++与gcc的关系就像clang与libc++. 其中的区别这里不作详细介绍了。
再说说libstdc++,glibc的关系。 libstdc++与gcc是捆绑在一起的,也就是说安装gcc的时候会把libstdc++装上。 那为什么glibc和gcc没有捆绑在一起呢?
相比glibc,libstdc++虽然提供了c++程序的标准库,但它并不与内核打交道。对于系统级别的事件,libstdc++首先是会与glibc交互,才能和内核通信。相比glibc来说,libstdc++就显得没那么基础了。
说完了这些库,这些库最终都是拿来干嘛的?当然是要将它们与你的程序链接在一起! 这时候就不得不说说gcc了(当然还有前文提到的clang以及llvm等编译器,本文就不细说它们的区别了)。
那g++是做什么的? 慢慢说来,不要以为gcc只能编译C代码,g++只能编译c++代码。 后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的。在编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,需要这样,gcc -lstdc++, 所以如果你的Makefile文件并没有手动加上libstdc++库,一般就会提示错误,要求你安装g++编译器了。
-----------------------------------------还有哪些常用库-----------------------------------------
/lib/ld-linux.so.2,这是动态库加载模块自身所需要的运行时部分。
libgcc_s.so.1,这是GCC的组件,编译时候运行时候都需要,一个版本GCC编译的程序常常不能在装有另一个版本GCC的平台上运行,就是这个原因。可以在编译阶段选择连接.a的静态库来静态编译
libstd_c++.so.6,这是上面其实说过了,libgcc的C++扩展。
libc.so.6,最底层的库,操作系统和其中所有应用程序几乎都依赖,是应用程序能够跟操作系统通信的基础。会碰到的版本有原本UNIX中的libc和GNU开发的第三方版本glibc,像这里的名字虽然是libc,但事实上就是glibc,功能没有太大差别。
libm.so.6,是对libc里面的数学部分优化后的版本。
因此,一个Linux操作系统的最重要参数,除了内核版本之外,就是libc或者glibc的版本了;两者一前一后,一个是从硬件到系统,一个是从系统到应用,构成了一切程序的基础。大部分应用程序只要考虑libc就行了;可是对于驱动模块,作为一个硬件到应用的桥梁,就必须同时针对这两个东西来进行设计,否则就是不兼容。
-----------------------------------------linux-vdso.so.1介绍-----------------------------------------
程序经常链接到linux-vdso.so.1这个东西,这个东西到底是什么呢,我找到了下面一篇介绍,讲的很好:
下面的文章内容比较常,如果简单总结的话,大概就是这么几点:
1.内核和glibc相互兼容,更新的时候各种问题,glibc跟不上内核,还有内核不同版本的差异也大
2.这个相当于内核帮助glibc单独做好的一个封装,插在以前glibc和内核之间,这样,内核自己在合适的时机选择合适的方法即可,封装给glibc的接口不变,glibc就很爽了。
注意:下面的libc都是指的glibc
往往内核添加了一个功能,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 等等。因此,采取灵活的手段,针对不同的系统调用采取不同的方式,才能得到最优化的性能和实现最完美的功能。