加载器和C运行时初始化的角色之间的差异

时间:2014-12-22 05:01:24

标签: c runtime loader

我正在阅读来自此链接的C运行时初始化的角色:http://www.embecosm.com/appnotes/ean9/html/ch05s02.html

它表示运行时初始化执行诸如设置堆栈之类的任务,在更详细的页面中,它还说它用零初始化bss段。在其他一些地方,我还读到它初始化数据和其他一些段。

这让我怀疑装载机的功能是什么?因为其中一些任务也是装载机的责任。

所以,我的问题:

  1. 运行时初始化或c运行时实际上做了什么?
  2. 装载机实际上做了什么?
  3. 修改

    好的,如果该链接具体描述了嵌入式系统的运行时初始化的作用,那么它对普通系统有什么作用。 据我所知,运行时初始化只会调用main,而没有剩下的其他工作。

1 个答案:

答案 0 :(得分:8)

  
      
  1. 运行时初始化或c运行时实际上做了什么?
  2.   

维基百科将runtime library定义为:

  

编译器用于调用运行时环境的某些行为的一组低级例程,方法是将对运行时库的调用插入已编译的可执行二进制文件中。

对于C程序,运行时库在引导程序之外几乎没有什么可做的。编译器调用C运行时来引导各种环境事物,然后通过调用main基本上将控制权交给用户。

鉴于您的问题评论中的回答,您可能已经发现程序针对其环境进行自举的过程因目标环境的数量而异。鉴于C现在和过去支持的平台和操作系统的数量,没有可能的方法来枚举C运行时工作或当前工作的所有方式。

每个C库都有自己的C运行时,每个支持C的环境都可能有不同的引导问题和要求。这些要求在很大程度上取决于操作系统或硬件的功能以及C实现的完整性。但是,我可以回答一些C运行时通常在您熟悉的环境中所做的事情。

  • 由于C运行时负责调用main,因此通过atexit(3)注册的调用函数将由C运行时负责。

  • 解析并调用任何构造函数/析构函数接口(_init_fini等)

  • 初始化并调用实时加载程序(负责解析和加载在链接时注册并在运行时加载的动态共享对象)。

  • 正常处理分离线程的退出。

  • 初始化并将argcargv传递到该计划的main

  • 定义并初始化各种C库全局符号。例如,它为环境正确设置errno(现代系统将errno定义为线程安全的,因此它需要存在于TLS中)。 environ是在调用main之前需要初始化的另一个全局符号。

  • 就此而言,C运行时需要设置TLS。

  • 吨多。

您可能有兴趣浏览{" csu"中找到的glibc implementation of the runtime。 (C启动)目录。 (此目录之外有一些特定于机器的部分。)

不同的系统会有不同的要求。正如您所读到的,嵌入式系统可能对运行时有更多的工作,因为它们可能负责从寄存器初始化到程序加载和执行(任何内核都不提供)的任务。 " C运行时"之间的区别和"内核"如果在嵌入式目标上有足够复杂的独立项目,可能会变得模糊。

现在:

  
      
  1. [a]装载机实际上做了什么?
  2.   

有许多类型的加载器,也取决于运行时环境。对于带有EEPROM的小型嵌入式环境,加载器可能是一些固件,可以开始执行在地址0处找到的任何内容。您可能还将自己视为加载器,手动将二进制文件写入EEPROM。

在现代操作系统中,有许多装载机。

  1. 引导程序。从历史上看,这些操作方式是BIOS选择引导设备,查看地址,将512字节数据读入内存,然后从那里开始执行。我现在已经离开这个世界一段时间了,所以我不确定与EFI / UEFI有什么不同,除了它们是更完整(和复杂)的引导环境。

  2. 内核。当你执行一个程序时,大量的东西正在引擎盖下进行。假设您在类似Unix的操作系统中从shell运行程序,加载过程可能会遵循以下内容:

    • 您的shell会尝试在环境配置的PATH中的某个位置查找二进制文件。这是通过向内核发出许多系统调用来解析不同路径序列下的文件名来完成的。
    • 假设找到该文件,shell通常会fork(2)execve(2)fork(2)调用导致内核创建一个新进程; execve(2)调用用新的替换克隆的二进制文件。
    • 内核从其存储介质(磁盘,网络,内存等)读取文件的第一页,并尝试找出如何执行它。
      • 如果它是ELF二进制文件,它可以从二进制文件头中确定。然后,内核根据ELF节标题中指定的偏移量,将二进制文件的部分加载到内存中,为堆栈设置映射区域等等,然后根据条目地址(也是ELF标题的一部分)开始执行。此入口点可能是_start,是C运行时的一部分。
      • 如果它不是ELF二进制文件,它仍然可以通过解释器执行。内核将尝试从文件的开头解析解释器(例如#!/bin/bash),解析它并执行它。最终它会找到一个ELF可执行文件,否则它将失败。
    • 内核开始执行二进制文件,可能是_start,如上所述。
    • Eli Bendersky对此进行了更为全面的撰写,标题为" How statically linked programs run on linux"。
  3. 运行时加载器/动态链接器/您想要调用它们的任何内容。我会将您推荐给" Anatomy of Linux dynamic libraries"有关这些如何工作的信息的文章。当然,dlopen(3) / dlsym(3) / dlclose(3) / dlerror(3)函数集只是用于与动态加载程序交互的API。我强烈建议您阅读这些界面的手册页,以便深入了解Linux动态加载程序支持的功能集,以及加载程序的功能。