• 007:_main 和 main(转)


    不进入__main,一样能跑程序,因为__main实现的功能,我们自己也都能实现。至于怎么做,看我在最后转的帖子,看完你就了解了。
    所谓C库,就是一堆写好的C代码,比如你上面说的malloc,这些代码有的是封装好的(能调用但看不到源码),有的是没封装的(能调用能看源码)。

    __main() 和 main()(转载)

    因为我们通常在BOOTLOADER中都已做好了比较细致的初始化工作,包括代码的搬运,所以我们最好别再调用库函数__main(),因为__main()作为ADS集成好的库函数,会对系统进行初始化设置,可能会与我们的初始化发生冲突,故在我们做好初始化后最好别调用__main()。仿真时若调了__main()且没设置entry会报警告,__main()库函数代码不太了解,估计跟ADS初始化有关,库函数__main()要慎用。

        当所有的系统初始化工作完成之后,就需要把程序流程转入主应用程序,即呼叫主应用程序。最简单的一种情况是:

    IMPORT   main
    B        main

        直接从启动代码跳转到应用程序的主函数入口,当然主函数名字可以由用户随便定义。
        在ARM ADS环境中,还另外提供了一套系统级的呼叫机制。
    IMPORT   __main
    B        __main

        __main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到main()。所以说,前者是库函数,后者就是我们自己编写的main()主函数;

        因此我们用的B __main其实是执行库函数,然后该库函数再调用我们的main() 函数,因此在单步调试时会看到先要跑一段程序(其实是库函数),然后再单步到我们自己的main函数(这个同时也说明如果有B __main 则就对应必须有main函数,否则编译出错),如果我们用 B main来进入我们的主函数的话,那在单步调试时就看到直接进入到我们自己的main函数了,中间不会看到其他程序;

        那么用B __main和用B main 这两这进入我们的main函数方式有什么不同呢?
        如果采用前者则会由编译器加入一段"段拷贝"程序,即我们说的从加载域到执行域转化程序;而采用后者就没有这个了,因此如果要进行 "段拷贝"只能自己动手编写程序来实现了,完成段拷贝后就可以进入我们的主函数了,当然这个主函数不一定是叫做main(),可以起个其他好听的名字,这个有别于使用B __main方式;不管采用哪种方式进入我们的程序,都要有一段"段拷贝"程序,跑完了段拷贝后才能可以进入我们主程序了!(顺便提一下:startup.s这个文件并没有所谓的"段拷贝"功能,再看也无益!)

        对含有启动程序来说,"执行地址与加载地址相同"不容易实现:
        如果执行地址与加载地址相同哪当然不需要做"段拷贝",但是个人理解编译器还会加入"段拷贝"程序(如果用B __main的话),只是因为条件不满足而不执行而已;但是对含有启动程序来说,"执行地址与加载地址相同"就不容易了.因为启动程序是要烧到非易失存储器里,用来在上电执行的,而这个程序必定会有RW段,如果RW放在非易失存储器,如FLASH,那就不好实现RW功能了,因此要给RW移动到能够实现RW功能的存储器,如SRAM等.因此,对含有启动程序来说,"执行地址与加载地址相同"就不容易实现;程序的入口点在C 库中的__main 处,在该点,库代码执行以下操作:

    1. 将非零(只读和读写)运行区域从其载入地址复制到运行地址。
    2. 清零ZI 区域。
    3. 跳转到__rt_entry。   KEIL ARM的帮助文件里对__main的描述:
    程序的入口点在 C 库中的__main  处,在该点,库代码执行以下操作:
    将非根运行区(只读和读写)从其载入地址复制到运行地址。同如果任何区被压缩,将它们从载入地址解压到运行地址。更多信息,请参阅链接器用户指南 。
    清零 ZI 区域。
    跳转到 __rt_entry。
    如果不希望库执行这样的操作,可以如 例 2.1 中所示定义跳转到 __rt_entry的自有__main。
    例 2.1. __main 和 __rt_entry

    IMPORT __rt_entry
        EXPORT __main
        ENTRY
    __main
        B     __rt_entry
        END

    库函数__rt_entry() 运行程序步骤如下:
    调用 __rt_stackheap_init() 建立栈和堆。
    调用 __rt_lib_init() 初始化引用的库函数、初始化语言环境 (locale),如果必要,还将为main()函数建立argc 和 argv。对于 C++,为任何顶级对象调用构造函数。
    对于C++,为任何顶级对象调用构造函数作为 __cpp_initialize__aeabi_。更多信息,请参阅 C++ 初始化,建立和销毁 。
    调用 main()函数 ———— 应用程序的用户级根。
    从main()函数中,应用程序除了调用其他函数,还可调用库函数。有关详细信息,请参阅从 main()函数中调用库函数。从main()函数中调用库函数。
    使用main()函数返回的值调用exit()。
    从上面就可以看到__user_initial_stackheap的返回值到底是在哪里用到了,
    就是“调用 __rt_stackheap_init() 建立栈和堆”这里,实际上还有其它的C库函数有用到,但肯定都是在__main之后,因为__main是C库函数的入口。

    我说上面的代码没有调用__user_initial_stackheap,因为很明显,把STARTUP.S从头读到尾,根本没有调用__user_initial_stackheap的语句。那它在哪呢?“rt_misc.h”这个文件里有定义。

    /*
    * This can be defined to override the standard memory models' way
    * of determining where to put the initial stack and heap.
    *
    * The input parameters R0 and R2 contain nothing useful. The input
    * parameters SP and SL are the values that were in SP and SL when
    * the program began execution (so you can return them if you want
    * to keep that stack).
    *
    * The two `limit' fields in the return structure are ignored if
    * you are using the one-region memory model: the memory region is
    * taken to be all the space between heap_base and stack_base.
    */
    struct __initial_stackheap {
        unsigned heap_base;                /* low-address end of initial heap */
        unsigned stack_base;               /* high-address end of initial stack */
        unsigned heap_limit;               /* high-address end of initial heap */
        unsigned stack_limit;              /* low-address end of initial stack */
    };
    extern __value_in_regs struct __initial_stackheap
    __user_initial_stackheap(unsigned /*R0*/, unsigned /*SP*/,
                             unsigned /*R2*/, unsigned /*SL*/);

    由于ARM应用的灵活性,可以通过分散加载文件来定义代码和变量的位置,所以堆栈的地址并不固定,这样,在ARM C库里的函数用到堆栈的地址时候,就用上面定义的变量来代替。

    说它是不是多余,其实也不算,因为一般的STARTUP.S都只初始了栈,就是SP。没有初始堆,所以在这个函数里初始堆还是有必要的,而且这个函数的作用主要是返回堆栈的地址,这个地址除了初始化,还可以有其它的作用。毕竟我们不大方便在C语言里直接取SP的地址。

    如果没有 B __mian 的话,就没有调用C库,也就用不到初始化C库。而不调用__main,直接进入main()当然是可以实现的,具体方法网上也有,你可以查一下。

  • 相关阅读:
    tcl tk lappend
    file join
    [转载]强指针和弱指针
    DisplayHardware
    Android 十大调试方法
    C语言程序的外部变量与函数
    DisplayHardware
    Android 十大调试方法
    wifi连接流程分析
    [转载]强指针和弱指针
  • 原文地址:https://www.cnblogs.com/afeibfp/p/2851152.html
Copyright © 2020-2023  润新知