标签:运行程序 符号 可能性 rgb 文件系统 表示 空间 方式 平台
仅仅保存数据符号,不保存值。
链接库信息:本运行文件链接到的动态库信息。
无论是Windows的PE文件还是UNIX的ELF文件,存放的无非就是上面这些东西,复杂性在于,它们的格式不同,细节太多。
注意。此步骤并不载入动态库,载入动态库的职责由用户态负责。通常是解释器的事情。
假设你运行一下以下的序列。就知道动态库是什么时候由谁载入的了:
readelf -a /bin/ls
ldd /bin/ls
strace -e trace=open /bin/ls
我看过非常多文章,都没有谈到这个细节。对于Linux而言,都说是在load_elf_library中完毕的动态库载入。甚至毛德操也是这么说的。他们根本没有注意到C库的行为。难道搞内核的人就如此高深吗?非常多人都认为仅仅要事情在内核完毕的,那就高深无比。其实根本不是这回事!
在上面的第2步最后,程序已经返回用户态运行了,一般而言此时运行的是一个叫做解释器的程序,它会解析须要载入的动态库信息,然后逐一载入动态库的。随后会解决一些符号重定向的问题。
其实。在直接运行main函数之前,有非常多工作要做。载入动态库仅仅是当中最复杂的之中的一个。
须要明白的是,仅仅要有动态库,进入main之前的工作就会无比复杂,这是必定的代价。收益就是在load_elf_library中的工作量大大降低了,换句话说,你的可运行文件的负担降低了,假设想为main前行为减负,就用静态链接吧。
以现有的Linux为例,实际上除了保留了一个brk来表示堆之外,代码段,数据段等古老的段已经全然弱化了,甚至退化成了例行公事或者纯粹为了唤起人的记忆。在我看来。堆也是应该摒弃的。仅仅有栈,因为和处理器极度相关。因此须要保留。
如今的内存管理方式是保护模式下虚拟内存管理。对于一个进程而言。32位的虚拟地址拥有4G的空间,对于64位的虚拟地址而言。则更大,还有必要将内存依照属性划分区段吗?其实,在实模式的时代,有专门的硬件寄存器来表示这些区段的起始地址,因为处理器就是这么设计的。因此必定要划分内存为一系列内部连续的区段,可是到了32位保护模式以后,非常多这中划分就没有必要了,段寄存器在平坦模式下依旧例行公事但不再起作用,无论是代码段还是数据段,都是占领整个地址空间。
堆是怎么回事呢?堆就是heap,也是一个古老的概念,它在地址空间是一段连续的空间,一般位于栈的以下。和栈向下增长不同,堆是向上增长的。
在史前时期,大家是共享内存地址空间的,每个进程都在一个连续的空间范围内。而且有确定的大小。和数据段,代码段空间连续的意义一样,堆也是一段连续的空间,因为和栈的增长方向相反。就保证了程序使用的内存在越界之前就会出错。
然而如今都是独享虚拟内存了,怎样管理物理内存已经被剥离出去了,因此就没有必要採用这些原始的方式了。
注意,以上并没有涉及内碎片和外碎片的问题,因为在保护模式时代,大得多的独享地址空间能够利用充分且设计良好的内存管理算法来高效避免两种碎片。而在史前。极小的共享的内存不足以容纳复杂的内存管理程序本身,也就仅仅能设计一个简单一致的分段约定来依照数据的属性将空间分为不同区段来避免内存碎片。
其实,标准的做法是,在malloc申请少于128K的内存时。採用堆上分配(眼下Linux还在使用堆),大于128K的时候使用mmap来分配。
假设你非常精通Linux内存的使用。就会认为以上我对堆的抨击简直就是一派胡言。因为我忽略了堆内存和mmap的申请和释放机制全然不同。某种意义上确实是,但不幸的是,我对这样的不同相同也是深刻理解的。假设man一下mallopt,就会发现M_MMAP_THRESHOLD以下有一句话:
mmap()-allocated memory can be immediately returned to the OS when it is freed, but this is not true for all mem‐
ory allocated with sbrk(); however, memory allocated by mmap() and later freed is neither joined nor reused, so
the overhead is greater. Default: 128*1024.
这不正是在否定mmap,这不正是和我的提议相反吗?是的!
可是。brk的高效性和用户程序怎样使用内存有极大的关系。堆上的内存释放。假设不是边缘的话,就不会释放给操作系统。而是继续留在地址空间内,而这部分空间是由C库的malloc来管理的。它可能在malloc内部实现了一个相似伙伴系统的管理算法,使得能够自己管理自己的内存区。可是你不得不信赖这样的C库相关的一切实现,这样的针对mmap的优势并非操作系统级别的,而是C库级别的。对于mmap而言,仅仅要你调用free,那么底层就会调用munmap,此时这段内存在地址空间就不再可用了,频繁的mmap/munmap操作当然会带来不小的开销,而且因为每次mmap的地址可能距离比較远,因此也会丧失局部性的优势。
我认为。讨论这个问题和讨论TCP在内核中的各种优化属于一类问题。终于的结果就是,不要在内核做这样的比較了,全然交给用户程序或者库自身,相信用户程序能够完美解决。
内核仅仅要把内存给用户就能够了,怎么管理怎么使用全部用户程序自己负责。操作系统内核应该把这样的事情剥离出去。对于堆这样的内存区段而言。它在内核中的地位确实非常尴尬。它本来代表着一种内存管理方式,即一段连续的内存随着程序的分配和释放被切分为不同大小的小段,这些段组成一个堆数据结构,确实。在史前的实模式时代。物理内存确实是这么管理的,可是当保护模式明白区分了内核空间和用户空间时。内存的堆式管理就被移植到了C库,实际上它们一直都在C库,仅仅是说后来C库被安排到了用户空间而已。结果内核态的进程地址空间中就空留了一个堆的影子。却早就没有了堆的实质。这样的heap夹在程序和库之间的可能性是存在的,你自己通过遍历/proc/xx/maps就能够知道有多少,造成这样的尴尬的原因在于可运行文件布局原则和进程空间布局原则的不一致性,解决这样的不一致性当然要以可运行文件的建议为准,因为它是最先载入的。
还有一个尴尬的地方在于。假设一个进程的堆被夹在了可运行程序映像和动态库映像之间,例如以下:
可运行程序|heap|动态库1|大量空暇|...
那么堆的扩展空间无疑受到了限制,紧跟着动态库1的有大量的本能够用于堆扩展的空间没,却因为动态库1本身被取消了资格,因为堆必须是连续的,在Linux上它仅仅是添加brk来进行扩展。
假设将堆的概念取消,那么地址空间变成以下这样:
可运行程序|空暇1|动态库1|空暇2|...
因此空暇1和空暇2能够组织成一个链表。在该链表中能够採用伙伴系统或者别的不论什么的管理算法。堆空间的连续性当初仅仅是为了让内存使用更加紧凑,这是时代的遗留,就像当初确切划分空间连续的代码段,数据段,BSS段而如今不须要了一样,对堆的连续性也要有新的理解。
毕竟在资源丰盈的年代做事就不能过于小家子气。
标签:运行程序 符号 可能性 rgb 文件系统 表示 空间 方式 平台
原文地址:http://www.cnblogs.com/jzdwajue/p/7082147.html