loader和C运行时初始化角色之间的差异

loader和C运行时初始化角色之间的差异,c,runtime,loader,C,Runtime,Loader,我从以下链接了解了C运行时初始化的角色: 它说运行时初始化完成了设置堆栈之类的任务,在后面的页面中,它还详细说明了它用零初始化bss段。在其他一些地方,我也读到它初始化数据和其他一些段 这在我的脑海中产生了一个疑问,装载机会做什么?因为其中一些任务也是装载机的责任 所以,我的问题是: 运行时初始化或c运行时实际上做什么 加载器实际上做什么 编辑 好的,如果这个链接专门描述了嵌入式系统的运行时初始化的角色,那么它在普通系统上有什么角色呢。 在我看来,运行时初始化将只调用main,没有其他工作留给它

我从以下链接了解了C运行时初始化的角色:

它说运行时初始化完成了设置堆栈之类的任务,在后面的页面中,它还详细说明了它用零初始化bss段。在其他一些地方,我也读到它初始化数据和其他一些段

这在我的脑海中产生了一个疑问,装载机会做什么?因为其中一些任务也是装载机的责任

所以,我的问题是:

  • 运行时初始化或c运行时实际上做什么
  • 加载器实际上做什么
  • 编辑

    好的,如果这个链接专门描述了嵌入式系统的运行时初始化的角色,那么它在普通系统上有什么角色呢。 在我看来,运行时初始化将只调用main,没有其他工作留给它

  • 运行时初始化或c运行时实际上做什么
  • 维基百科将a定义为:

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

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

    根据问题注释中的回答,您可能已经了解到,程序为其环境引导的过程随目标环境的数量而变化。考虑到C现在和过去所支持的平台和操作系统的数量,没有可能列举C运行时已经或当前工作的所有方式

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

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

    • 解析并调用任何构造函数/析构函数接口(
      \u init
      \u fini
      等)

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

    • 优雅地处理分离线程的退出

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

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

    • 因此,C运行时需要设置TLS

    • 还有很多

    您可能有兴趣浏览“csu”(C启动)目录中的。(此目录之外有一些特定于计算机的部分。)

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

    现在:

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

    在现代操作系统中,有许多加载程序

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

  • 果仁。当你执行一个程序时,为了让它运行,有大量的事情在幕后进行。假设您在某些类Unix操作系统中从shell运行程序,加载过程可能如下所示:

    • 您的shell尝试在您配置的
      路径中的某个位置查找二进制文件。这是通过对内核发出大量系统调用来解决不同路径序列下的文件名问题的
      
    • 假设找到了文件,shell通常会
      fork(2)
      execve(2)
      fork(2)
      调用导致内核创建一个新进程;
      execve(2)
      调用将克隆的二进制文件替换为新的二进制文件
    • 内核从其存储介质(磁盘、网络、内存等)读取文件的第一页,并尝试找出如何执行它。
      • 如果它是一个ELF二进制文件,它可以通过二进制文件的头来确定。然后,内核根据ELF节h中指定的偏移量,将二进制文件的部分加载到内存中的某个位置