打印

MDK或者ADS里_user_initial_stackheap函数的作用

[复制链接]
6870|10
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
ben123one|  楼主 | 2011-1-4 08:40 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
MDK里,最后一段:
; Enter User Mode and set its Stack Pointer //用户模式的栈已经定义好了
  MSR CPSR_c, #Mode_USR
  MOV SP, R0
  SUB SL, SP, #USR_Stack_Size


; Enter the C code
;
  IMPORT __main
  LDR R0, =__main
  BX R0


; User Initial Stack & Heap
  AREA |.text|, CODE, READONLY

  IMPORT __use_two_region_memory
  EXPORT __user_initial_stackheap
__user_initial_stackheap

  LDR R0, = Heap_Mem
  LDR R1, =(Stack_Mem + USR_Stack_Size)
  LDR R2, = (Heap_Mem + Heap_Size)
  LDR R3, = Stack_Mem
  BX LR


  END
__user_initial_stackheap
这个函数说是自动被调用的,我们已经设置好了用户的栈,为何要加呢,我再往上看了些,不是很懂,太抽象,能给直观的讲讲吗?谢谢。能通俗易懂就好了,呵呵
我理解就是:进入_main后,系统库函数会自动调用这个函数,但是用来干啥的呢?RO- R3就是那个库函数的参数,实际上是库函数调用了__user_initial_stackheap后只是将几个参数放入寄存器,返回后,库函数继续利用RO-R3的值再进一步处理,可是处理的是什么呢,有人说是库函数堆栈的初始化,但是初始化堆栈前面不是已经初始化完了吗?求指教。

相关帖子

沙发
wljs012| | 2011-1-4 12:38 | 只看该作者
__user_initial_stackheap 是在进入 main 之前调用的。

从EXPORT __user_initial_stackheap 来看,应该是其它.s文件调用的。起码你上面写的代码没调用。

最后,没看明白你要问的是什么,呵呵~~

使用特权

评论回复
板凳
ben123one|  楼主 | 2011-1-4 14:26 | 只看该作者
你从哪看出来我上面的]__user_initial_stackheap没有被调用啊


__user_initial_stackheap 是在进入 main 之前调用的。

从EXPORT __user_initial_stackheap 来看,应该是其它.s文件调用的。起码你上面写的代码没调用。

最后,没看明白你要问的是什么,呵呵~~ ...
wljs012 发表于 2011-1-4 12:38

使用特权

评论回复
地板
wljs012| | 2011-1-4 14:41 | 只看该作者
__user_initial_stackheap 是在__main()中调用的,是在进入main()之前。
我说的其它.S文件,是指包含__main()函数的库文件,这个是编译环境自带的库,不体现在你上面的代码中。

使用特权

评论回复
5
mybao| | 2011-1-5 13:25 | 只看该作者
"这个函数说是自动被调用的,我们已经设置好了用户的栈,为何要加呢"

但从初始化stack来讲,是有点多余,但是加了也不会错,只是初始化了两遍。
而且heap确实需要这个函数来初始化。

使用特权

评论回复
6
ben123one|  楼主 | 2011-1-6 08:57 | 只看该作者
栈已经初始化了,再初始化下没事,我感觉貌似是
堆初始化的时候就是为了让C库里,比如malloc这种函数,知道从哪开始开辟内存吧


"这个函数说是自动被调用的,我们已经设置好了用户的栈,为何要加呢"

但从初始化stack来讲,是有点多余,但是加了也不会错,只是初始化了两遍。
而且heap确实需要这个函数来初始化。 ...
mybao 发表于 2011-1-5 13:25

使用特权

评论回复
7
ben123one|  楼主 | 2011-1-6 09:00 | 只看该作者
__user_initial_stackheap这个函数貌似是我们自己必须重写的函数,也就是在进入_main之前肯定会调用的吧?
如果我们自己不进入_main的话,进入main的话,是不是我们得自己初始化堆和栈,还有拷贝 RO RW到运行域啊,应该还有别的,比如初始化C库,不知道怎么初始化啊,呵呵,而且这些库肯定是静态的哈。有没有人不进入_main而进入main的啊

__user_initial_stackheap 是在__main()中调用的,是在进入main()之前。
我说的其它.S文件,是指包含__main()函数的库文件,这个是编译环境自带的库,不体现在你上面的代码中。 ...
wljs012 发表于 2011-1-4 14:41

使用特权

评论回复
8
wljs012| | 2011-1-6 11:27 | 只看该作者
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()当然是可以实现的,具体方法网上也有,你可以查一下。

使用特权

评论回复
9
ben123one|  楼主 | 2011-1-7 09:22 | 只看该作者
非常谢谢你,是不是如果不进入_mian的话,正常情况下是不能用C语言的吧?有说要初始化C库,初始化C库是什么意思呢,C库肯定已经是.o或者确切说是.a的静态库吧,这个库如何初始化呢

KEIL ARM的帮助文件里对__main的描述:

程序的入口点在 C 库中的__main  处,在该点,库代码执行以下操作:

将非根运行区(只读和读写)从其载入地址复制到运行地址。同如果任何区被压缩,将它们从载入地址解压到 ...
wljs012 发表于 2011-1-6 11:27

使用特权

评论回复
10
wljs012| | 2011-1-7 10:09 | 只看该作者
不进入__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。

使用特权

评论回复
评分
参与人数 1威望 +10 收起 理由
zhf0964 + 10 赞一个!
11
ben123one|  楼主 | 2011-1-8 08:53 | 只看该作者
非常谢谢你,我其实知道_main可以复制加载域到执行域,也知道会跳入main,但是就是不理解_main里初始化C库什么意思,而且还用到了堆 和 栈的地址和大小,这个堆和栈是针对  USER状态来说的吧?

不进入__main,一样能跑程序,因为__main实现的功能,我们自己也都能实现。至于怎么做,看我在最后转的帖子,看完你就了解了。
所谓C库,就是一堆写好的C代码,比如你上面说的malloc,这些代码有的是封装好的(能调 ...
wljs012 发表于 2011-1-7 10:09

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

0

主题

16

帖子

1

粉丝