当一个二进制文件运行时,它是否将其整个二进制数据一次性复制到内存中? 我可以改变吗?

它执行之前是否将整个二进制文件复制到内存? 我对这个问题很感兴趣,想把它改成其他的方式。 我的意思是,如果二进制是100M大(似乎不可能),我可以运行它,而我把它复制到内存中。 这可能吗?

或者你能告诉我如何看待它的运行方式? 我需要哪些工具?

应用程序级编程人员的理论模型使其看起来如此。 事实上,正常的启动过程(至少在Linux 1.x中,我相信2.x和3.x是经过优化但类似的)是:

  • 内核创建一个进程上下文(或多或少的虚拟机)
  • 在该进程上下文中,它定义了一个从RAM地址映射到可执行文件开始的虚拟内存映射
  • 假设你是动态链接的(默认/通常),程序头文件中定义的ld.so程序(例如/lib/ld-linux.so.2 )为共享库设置了内存映射
  • 内核在程序的启动例程中执行一个jmp (对于C程序来说,就像crtprec80 ,它调用main )。 由于它只设置了映射,而没有实际加载任何页面(*),所以会引起CPU的内存管理单元的页面错误,这是内核的一个中断(异常,信号)。
  • 内核的页面错误处理程序将程序的某些部分(包括导致页面错误的部分)加载到RAM中。
  • 当你的程序运行时,如果它访问一个没有RAM备份的虚拟地址,就会发生页面错误,导致内核暂时暂停程序,从光盘载入页面,然后将控制权交还给程序。 这一切都发生在“指示之间”,通常无法检测到。
  • 当你使用malloc / new ,内核创建RAM的读写页面(没有光盘备份文件),并将它们添加到你的虚拟地址空间。
  • 如果通过尝试访问在虚拟内存映射中设置的内存位置来抛出Page Fault,则会出现分段违例信号(SIGSEGV),这通常是致命的。
  • 当系统耗尽物理RAM时,RAM的页面被删除; 如果它们是已经在光盘上的东西的只读副本(如可执行文件或共享对象文件),则它们只是取消分配并从源文件重新加载; 如果它们是读写的(就像使用malloc “创建”的malloc ),它们会被写入(page file = swap file = swap partition = on-disc virtual memory)。 访问这些“释放”的页面会导致另一个页面错误,并重新加载。

但是,一般来说,直到你的过程比可用的RAM更大 – 而且数据几乎总是比可执行文件大得多 – 你可以安全地假装你独自一人在这个世界上,没有任何这种需求分页的事情正在发生。

所以:实际上,内核已经在加载程序的时候运行你的程序了(如果你永远不会跳进那个代码/引用那个数据,甚至可能永远不会加载一些页面)。

如果你的启动特别慢,你可以看看prelink系统来优化共享库加载。 这减少了ld.so在启动时(在你的程序的execmain调用之间,以及你第一次调用库函数之间)所要做的工作量。

有时候,静态链接可以提高程序的性能,但是由于RAM的主要开销 – 因为你的库不共享,所以除了每个其他程序正在使用的共享libc以外,还要复制“ libc ” 。 这通常只在你的程序在机器上运行得多或少的嵌入式系统中有用。

(*)事实上,内核有点儿聪明,通常会预装一些页面来减少页面错误的数量,但是理论是一样的,不管优化

不,它只将必要的页面加载到内存中。 这是按需分页。

我不知道一个能够实时显示的工具,但你可以看看/proc/xxx/maps ,其中xxx是你的进程的PID。

当你问一个有效的问题时,我不认为这是你需要担心的事情。 首先,100M的二进制不是不可能的。 其次,系统加载器会将它需要的页面从ELF(Executable and Linkable Format,可执行和可链接的格式)加载到内存中,并执行各种重定位等,如果需要的话,将使其工作。 它也将以相同的方式加载所有必需的共享库依赖项。 但是,这不是一个非常耗时的过程,并且不需要进行优化。 可以说,任何“优化”都会有一个很大的开销,以确保它不会尝试使用那些在适当时候没有被加载的东西,而且可能效率会更低

如果你很好奇映射的东西,就像fge说的那样,你可以检查/ proc / pid / maps。 如果你想看看一个程序如何加载,你可以尝试用strace来运行一个程序,例如:

 strace ls 

这是相当详细的,但它应该给你一些mmap()调用等的想法。