热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

STM32启动详细流程之__main

__main-main1.前言2.必备知识2.1.用户程序在FLASH中的组织架构2.2.用户数据在SRAM中的组织架构2.3.2.加载地址链接地址运行地址存储地址2.3.4.代

__main -> main

  • 1.前言
  • 2.必备知识
    • 2.1. 用户程序在FLASH中的组织架构
    • 2.2. 用户数据在SRAM中的组织架构
    • 2.3. 2.加载地址 链接地址 运行地址 存储地址
    • 2.3.4.代码重定向
      • 2.3.4.1.位置无关码
  • 3.__main函数
  • 4._rt_entry函数
    • 4.1.procedure
    • 4.2.Usage
  • 5.自己实现__main函数
    • 5.1.消除警告
  • 6.自己实现__rt_entry函数
  • 7.问题思考
    • 7.1.为什么我们可以自己编写__main和__rt_entry
    • 7.2.当一个用户程序运行完以后,会出现什么情况
  • 8.本系列文章总结


1.前言

上一篇博客详细地讲述了一个流程:

cpu执行第一条用户代码 -> 调用__main函数

这篇博客着重讲述了STM32启动文件中一些需要注意的细节,对于STM32启动文件的内容没有过多的讲解,因为我的第一篇博客讲述的就是STM32启动文件的解释。

而本篇博客将要详细地描述一个流程:

_ _main函数 -> __rt_entry -> main函数

这里再次声明一下:__main函数是c库中的一个函数,和用户编写的main函数是有区别的!!!

2.必备知识

必备知识中主要是用到了.map文件,双击红色箭头所指向的区域就可以打开!!!

2.1. 用户程序在FLASH中的组织架构

上面两张图截取了镜像文件在FLASH上的内存分布。

从上面两张图可以知道,在程序的最开始处,存储的是数据段,这个数据段就是中断向量表,里面存储这所有中断函数的入口地址。

紧跟着的就是代码段,代码段包含了自己编写的用户代码和库函数。

之后有跟着数据段,这个数据段有个专有的名称,叫做代码常量区,也就是你定义的const类型的全局变量(记住不是const类型的局部变量,const类型的局部变量还是存储在栈区)会存储在这个区域。

特别注意,非常重要的知识点:

在代码常量区后面还有一个区,叫做读写数据区,这个区域中的数据最终要被拷贝到SRAM中去,因为FLASH只能读不能写(事实上可以进行写操作,只不过需要密钥而已,参考手册中有说明)而SRAM中的数据是可读可写的。

但是,.map文件中并没有提到,也就是说你从.map文件中是找不到这个区的,

你能看到的最后一项就是代码常量区,因此这个地方一般情况下很难发现到,只有深入__main函数之后才可以知道。

值得注意的是:

在代码区中,不仅有Code、Data类型的数据,还有PAD!!!

PAD就是padding的意思,中文翻译过来就是填充的意思

作用:进行4字节对齐,提高cpu的取指速率

也就是说,无论是指令还是数据,在内存中都要4个字节对齐,所表现出来的特征就是:

地址的最低两位都为0,换成16进制来说,就是最后一个字母只能为0、4、8、c。

2.2. 用户数据在SRAM中的组织架构

在SRAM中,第一个区域叫做全局区,也有人叫静态区。你定义的全局变量(有初始值),静态变量都存放在这个区域当中。

这里需要说明一下一个特例:

比如你定义了一个全局变量:int a;

没有初始化的全局变量默认为0,但要注意,并不是说没有初始化的全局变量就属于.bss段(网上有很多的博客都说错了),它还是属于全局区,它的值是编译器赋值给它的!!!

(视频需要进行验证)

紧跟着的就是.bss段。

注意:.bss段不被包含在可执行文件当中

定义的未初始化全局数组,未初始化的静态全局数组等等保存在.bss段。

接下来就是堆和栈,因为堆向上生长,栈向下生长,因此堆在栈的前面。

此时,我们得到一个非常重要的结论:

栈顶指针的值 = RW-data + ZI-data

大家可以想一下,为什么。

还有,由于当一个程序生成可执行文件之后,栈顶指针的值就确定了。

那也就是说,从栈顶指针处,到SRAM最后一个存储单元都处于未使用状态,也就是说,有一部分内存我们是没有使用的,这里需要注意!!!

2.3. 2.加载地址 链接地址 运行地址 存储地址

加载地址:将指令或数据从地址A拷贝到地址B,地址A就是加载地址

链接地址:由链接脚本文件指出,链接的时候确定

运行地址:程序在内存中运行时候的地址

存储地址:指令或数据在flash中存放的存储地址,就是存储地址

这里需要说明一下:

链接地址是静态的,在程序链接的时候确定。

运行地址是动态的,因为当你使用位置无关码(后面会提到)将程序从A地址拷贝到B地址处,那么运行地址就发生了改变。

存储地址就是加载地址,没有区别!!!


2.3.4.代码重定向

程序或数据的链接地址要和运行地址一致,但往往程序或数据的存储地址(加载地址)和运行地址不一样,因此需要代码重定向。

代码重定向:使用位置无关码将用户程序或数据从存储地址拷贝到运行地址

用一句很精确的话来描述代码重定向:

使逻辑地址与实际物理地址一一对应的过程

这篇博客非常详细地描述了代码重定向的过程,读者特别需要注意的就是:

MCU和MPU代码重定向的区别!!!

跟我一起学RT-Thread之重定位

2.3.4.1.位置无关码

当程序或数据的链接地址和运行地址不一样的时候,此时只有位置无关码才能够正确被执行

位置无关码:依赖于程序当前运行的PC值,进行相对的跳转,导致的结果就是,无论代码在哪,总能达到指令正常运行的目的,因此是位置无关的。

位置有关码:不依赖当前PC值,是绝对跳转,只有程序运行在链接地址处时,才能达到指令的正常目的,因此是位置有关系的。

3.__main函数

作用:Initialization of the execution environment and execution of the application

You can customize execution intialization by defining your own __main that branches to __rt_entry.

The entry point of a program is at __main in the C library where library code:

  1. Copies non-root (RO(不会拷贝,官方提供和实际实践有出入) and RW) execution regions from their load addresses to their execution addresses. Also, if any data sections are compressed, they are decompressed from the load address to the execution address.
  2. Zeroes ZI regions.
  3. Branches to __rt_entry.

If you do not want the library to perform these actions, you can define your own __main that branches to __rt_entry.(我们后面会自己实现__main函数)

注意:__main函数不会将RO段数据拷贝到执行地址处,虽然官方说明了


4._rt_entry函数

4.1.procedure

The library function __rt_entry() runs the program as follows:

  1. Sets up the stack and the heap by one of a number of means that include calling __user_setup_stackheap(), calling __rt_stackheap_init(), or loading the absolute addresses of scatter-loaded regions.

  2. Calls __rt_lib_init() to initialize referenced library functions, initialize the locale and, if necessary, set up argc and argv for main().This function is called immediately after__rt_stackheap_init() and is passed an initial chunk of memory to use as a heap. This function is the standard ARM C library initialization function and it must not be reimplemented.

  3. Calls main(), the user-level root of the application.

    From main(), your program might call, among other things, library functions.

  4. Calls exit() with the value returned by main().

entry的是ARM汇编语法中程序的入口地址,GNU Assember语法中start是程序的入口地址

__rt_lib库函数是没有源文件,都已经编译完成了。

The symbol __rt_entry is the starting point for a program using the ARM C library.

Control passes to __rt_entry after all scatter-loaded regions have been relocated to their execution addresses.

4.2.Usage

The default implementation of __rt_entry:

  1. Sets up the heap and stack.
  2. Initializes the C library by calling __rt_lib_init.(ARMc库里面全面都是.b .l形式的库,没有源码)
  3. Calls main().
  4. Shuts down the C library, by calling __rt_lib_shutdown.
  5. Exits.

__rt_entry must end with a call to one of the following functions:

  • exit()

    Calls atexit()-registered functions and shuts down the library.

  • __rt_exit()

    Shuts down the library but does not call atexit() functions.

  • _sys_exit()

    Exits directly to the execution environment. It does not shut down the library and does not call atexit() functions.


5.自己实现__main函数

视频链接如下:
STM32启动流程之__main

需要源码的和我说!!!

5.1.消除警告

提示:程序的首地址并不和程序的入口地址等效

注意:ARM汇编语法entry是一个程序的入口地址,GNU汇编语法start是一个程序的入口地址

我们已自己实现__main 函数,ENTRY 已没有实质作用, 但为了避免 KEIL 警告,这里加上。

6.自己实现__rt_entry函数

你觉得你行吗?你知道要多少行代码吗,并且,没必要!!!

7.问题思考

7.1.为什么我们可以自己编写__main和__rt_entry

因为库函数里面的 ___main函数 和 ____rt_entry函数是弱函数

弱函数定义时需要写红色箭头所指向的关键字。

7.2.当一个用户程序运行完以后,会出现什么情况

结论:具体情况我也不知,有大神知道可以告诉我吗

半主机模式介绍:

STM32半主机模式

8.本系列文章总结

这篇文章是本系列博客文章的结尾,主要就是描述了:

_ _main函数 -> __rt_entry函数 -> main函数

本系列文章流程:

可执行程序 -> cpu执行第一条用户代码的流程 -> _ _main函数 -> __rt_entry函数 -> main函数

编写用户代码到如何生成可执行文件并没有解释,如果需要,可以安排!!!

详细地阐述了可执行文件是如何被加载到FLASH上,以及编写的用户程序(main函数)被调用之前经历了哪些步骤。

如果你对这些步骤了然于胸的时候,那么恭喜你,你已经很强了,大部分人是学不到这么深的,就算工作了很多年!!!

希望本系列的博文能够对你有所帮助!!!

最后,希望大家能够学有所成,未来可期!


推荐阅读
  • vue使用
    关键词: ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了C++中省略号类型和参数个数不确定函数参数的使用方法,并提供了一个范例。通过宏定义的方式,可以方便地处理不定参数的情况。文章中给出了具体的代码实现,并对代码进行了解释和说明。这对于需要处理不定参数的情况的程序员来说,是一个很有用的参考资料。 ... [详细]
  • 成功安装Sabayon Linux在thinkpad X60上的经验分享
    本文分享了作者在国庆期间在thinkpad X60上成功安装Sabayon Linux的经验。通过修改CHOST和执行emerge命令,作者顺利完成了安装过程。Sabayon Linux是一个基于Gentoo Linux的发行版,可以将电脑快速转变为一个功能强大的系统。除了作为一个live DVD使用外,Sabayon Linux还可以被安装在硬盘上,方便用户使用。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
author-avatar
忧伤玫瑰coco_873
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有