嵌入式C语言(五)

简介: 嵌入式C语言(五)

属性声明:section

我知道不耻下问是个好事,而且身边那么多大牛,不能算不耻,但是把一些能自己学习搞定的事情,拿去打扰别人,我觉得一点也不酷。

GNU C编译器扩展关键字:attribute

__attribute__关键字用来声明一个函数、变量或类型的特殊属性

这个特殊属性是什么?

作用就在于让编译器在编译程序的时候进行特定方面的优化或者代码检查。

话不多说整个栗子:

__attribute__的使用非常简单–>直接在它们名字旁边添加下面的属性声明即可。

__attribute__((ATTRIBUTE))

告诉编译器 有ATTRIBUTE这个属性了,给我用起来

目前属性有十几种:

● section.
● aligned.
● packed.
● format.
● weak.
● alias.
● noinline.
● always_inline.
● ……

其中aligned和packed用来显式的指定一个变量的存储对齐的方式。正常的情况下,我们定义一个变量的时候,编译器会根据变量类型给这个变量分配合适大小的存储空间,按照默认的边界对齐方式分配一个地址。而使用__atttribute__这个属性声明,就相当于告诉编译器:按照我们指定的边界对齐方式去给这个变量分配存储空间。

举个栗子:

char c2 __attribute__((aligned(8)) = 4;
int global_val_attribute__((section(".data")));

**有些属性可能还有自己的参数。**如aligned(8)表示这个变量按8字节地址对齐,属性的参数也要使用小括号括起来,如果属性的参数是一个字符串,则小括号里的参数还要用双引号引起来。

(section就在屁屁后面,马上要讲的)

当对一个变量添加多个属性说明的时候,定义变量的时候,各个属性之间用都好隔开。

属性声明要紧挨着变量 – >

这个就是错误的哦

到这里我们知道了怎么去把属性用起来,让编译器听话点,下面我们来看看这些属性到底有什么。

属性声明:section

section其实如果你有过汇编的基础,会对这个有个段这个概念。当然没有也没什么影响。

section属性作用:在程序编译的时候,将一个函数或者变量放到指定的段,即放到指定的section中。

在一个可执行文件组成:代码段、数据段、BSS段构成。

代码段:编译生成的可执行指令代码

数据段+BSS段:存放全局变量、未初始化的全局变量

当然还有其他的section,比如只读数据、符号表等。

使用readelf命令可以去查看一个可执行文件的各个section的信息。

我们知道,一段源程序代码在编译生成可执行文件的过程中,函数和变量是放在不同段中的。一般默认的规则如表6-1所示。

这些都是可以通过命令看到的

readelf -s *.out-->查看符号表
readelf -S *.out -->查看section header表

最后通过符号表和section header就能看到.c文件你写的变量、函数等等在哪个段。

编译器在编译程序时,以源文件为单位,将一个个源文件编译生成一个个目标文件。

在编译过程中,编译器都会按照这个默认规则,将函数**、变量分别放在不同的section**中,最后将各个section组成一个目标文件。

编译过程结束后,链接器会将各个目标文件组装合并、重定位,生成一个可执行文件。

这里想不想让每个班先整理好,然后年级整理的时候直接组装。

而这些由编译器默认分发组装section的事情,我们可以通过section这个属性进行自定义。

通过__attribute__的section属性,显式指定一个函数或变量,在编译时放到指定的section里面。

int global_val = 8;
int uninit_val __attribute__((section(".data")));

按说这个uninit_val这个未初始化应该到BSS段,但是这里我们显示指定以后,这个玩意就到了.data 数据段

既然被你知道了这么厉害的,那我们来举个在内核中应用这个section的栗子

U-boot镜像自复制分析

我们来看看这个U-boot在启动过程中是如何将自身代码加载到RAM中的。

(这个是不是就是那个传说的动作自举?问句哈)

U-boot:加载linux内核镜像到内存,给内核传递启动参数,然后引导Linux操作系统启动。

一种bootloader,有必要的话改天整篇聊聊这个玩意诶。

U-boot一般存储在NOR Flash或NAND Flash上。无论从NOR Flash还是从NAND Flash启动,U-boot其本身在启动过程中,都会从Flash存储介质上加载自身代码到内存,然后进行重定位,跳到内存RAM中去执行。(励志啊)

(因为CPU是与内存进行交互的)

在复制自身代码的过程中,一个主要的疑问就是:

U-boot是如何识别自身代码的?

是如何知道从哪里开始复制代码的?

是如何知道复制到哪里停止的?

(这特么是一个问题?)

这个时候我们不得不说起U-boot源码中的一个零长度数组。

这两行代码定义在U-boot-2016.09中的arch/arm/lib/section.c文件中

这两行代码的作用是分别定义一个零长度数组,并指示编译器要分别放在.__image_copy_start和.__image_copy_end这两个section中。

链接器在链接各个目标文件时,会按照链接脚本里各个section的排列顺序,将各个section组装成一个可执行文件。

U-boot的链接脚本U-boot.lds在U-boot源码的根目录下面。

看看里面长啥样:

__image_copy_start:代码段.text的前面

__image_copy_end:数据段.data的后面

作为U-boot复制自身代码的起始地址和结束地址。

因为零长数组是不占位置的,所以这个就代表了U-boot镜像复制的起始与终点地址。

(好嘛这样想来前面确实是一个问题。)

无论U-boot自身镜像存储在NOR Flash,还是存储在NAND Flash上,只要知道了这两个地址,我们就可以直接调用相关代码复制。在arch/arm/lib/relocate.S中,ENTRY(relocate_code)汇编代码主要完成代码复制的功能。

在这段汇编代码中,寄存器R1、R2分别表示要复制镜像的起始地址和结束地址,R0表示要复制到RAM中的地址R4存放的是源地址和目的地址之间的偏移,在后面重定位过程中会用到这个偏移值。在汇编代码中:

通过ARM的LDR伪指令,直接获取要复制镜像的首地址,并保存在R1寄存器中。数组名本身其实就代表一个地址,通过这种方式,U-boot在嵌入式启动的初始阶段,就完成了自身代码的复制工作:从Flash复制自身镜像到内存中,然后进行重定位,最后跳到内存中执行。

嵌入式启动的时候就开始运行relocate.S这个文件,然后搬动自己。

我在阅读linux内核艺术的时候,发现这个内核对于这个内存算的很精细,同时对于内核的启动加载,整个流程是非常精细。

有时候有种那种你站在一个凉席,你边走边卷起来的感觉,反正很有意思。

目录
相关文章
|
2月前
|
存储 编解码 编译器
嵌入式C语言(四)
嵌入式C语言(四)
28 0
|
1月前
|
编译器 Linux C语言
嵌入式C语言(八)
嵌入式C语言(八)
20 0
|
1月前
|
存储 编译器 C语言
嵌入式C语言(六)
嵌入式C语言(六)
22 0
|
1月前
|
存储 编译器 程序员
嵌入式C语言(七)
嵌入式C语言(七)
19 0
|
1月前
|
编译器 C语言 芯片
嵌入式C语言(九)
嵌入式C语言(九)
18 0
|
1月前
|
缓存 小程序 编译器
嵌入式C语言(十)
嵌入式C语言(十)
25 0
|
10天前
|
安全 算法 开发工具
【C 言专栏】基于 C 语言的嵌入式系统开发
【5月更文挑战第1天】本文探讨了C语言在嵌入式系统开发中的核心作用。嵌入式系统作为专用计算机系统广泛应用于家电、汽车、医疗等领域,具备实时性、低功耗等特点。C语言因其高效性、可移植性和灵活性成为开发首选。文章介绍了开发流程,包括需求分析、硬件选型、软件设计至部署维护,并强调中断处理、内存管理等关键技术。C语言在智能家居、汽车电子和医疗设备等领域的应用实例展示了其广泛影响力。面对硬件限制、实时性要求和安全挑战,开发者需不断优化和适应新技术趋势,以推动嵌入式系统创新发展。
【C 言专栏】基于 C 语言的嵌入式系统开发
|
12天前
|
传感器 算法 C语言
C语言在嵌入式系统开发中的优化策略与代码实现
C语言在嵌入式系统开发中的优化策略与代码实现
26 1
|
1月前
|
Linux API C语言
lua 如何在嵌入式Linux中与c语言结合
lua 如何在嵌入式Linux中与c语言结合
12 1
|
算法 Linux Android开发
本CSDN博主将与北京航天航空大学出版社合作出版<嵌入式C语言技术实战开发>一书
本书作者由以下成员合作编写:     杨源鑫,主编,毕业于广州科技贸易职业学院电子应用技术专业,在校期间一并考取了华南理工大学本科数字媒体艺术专业。2015年7月工作至今,任伟易达集团嵌入式系统工程师一职,主要从事单片机,linux,Android底层开发等相关的技术。
2926 0