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

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

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

这在我的脑海中产生了一个疑问,那就是装载机的function呢? 因为其中一些任务也是装载机的责任。

所以,我的问题:

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

编辑

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

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

Wikipedia将运行时库定义为:

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

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

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

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

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

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

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

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

  • 初始化并将argcargv传递到程序的main

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

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

  • 吨更多。

您可能有兴趣查看运行时的glibc实现 ,可在“csu”(C启动)目录中找到。 (此目录之外有一些特定于机器的部分。)

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

现在:

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

有许多类型的加载器,也取决于运行时环境。 对于带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对此进行了更为全面的报道,题为“ 如何在Linux上运行静态链接程序 ”。
  3. 运行时加载器/动态链接器/无论你想要什么叫它们。 我将向您介绍“ Linux动态库剖析 ”一文,以获取有关这些工作原理的信息。 当然, dlopen(3) / dlsym(3) / dlclose(3) / dlerror(3)函数集只是用于与动态加载器交互的API。 我强烈建议您阅读这些界面的手册页,以便深入了解Linux动态加载程序支持的function集,以及加载程序的function。