浅析 Keil 中的 sct 文件


一、程序的存储与运行

1、存储

程序编译后,应用程序中所有具有同一性质的数据(包括代码)被归到一个域,程序在存储或运行的时候,不同的域会呈现不同的状态,这些域的意义如下:

  • Code:即代码域,它指的是编译器生成的机器指令,这些内容被存储到 ROM 区。
  • RO-dataRead Only data,即只读数据域,它指程序中用到的只读数据,这些数据被存储在 ROM 区,因而程序不能修改其内容。
    • 例如 C 语言中 const 关键字定义的变量就是典型的 RO-data。
  • RW-data:Read Write data,即可读写数据域,它指初始化为"非0值"的可读写数据,程序刚运行时,这些数据具有非0的初始值,且运行的时候它们会常驻在RAM区,因而应用程序可以修改其内容。
    • 例如 C 语言中使用定义的全局变量,且定义时赋予"非 0 值"给该变量进行初始化。
  • ZI-dataZero Initialie data,即 0 初始化数据,它指初始化为"0 值"的可读写数据域,它与 RW-data 的区别是程序刚运行时这些数据初始值全都为 0,而后续运行过程与 RW-data 的性质一样,它们也常驻在 RAM 区,因而应用程序可以更改其内容。
    • 例如 C 语言中使用定义的全局变量,且定义时赋予"0 值"给该变量进行初始化
    • 若定义该变量时没有赋予初始值,编译器会把它当 ZI-data 来对待,初始化为 0;
  • ZI-data 的栈空间(Stack)及堆空间(Heap):
    • 在 C 语言中,函数内部定义的局部变量属于栈空间,进入函数的时候从向栈空间申请内存给局部变量,退出时释放局部变量,归还内存空间。
    • 使用 malloc 动态分配的变量属于堆空间。
    • 在程序中的栈空间和堆空间都是属于 ZI-data 区域的,这些空间都会被初始值化为 0 值。编译器给出的 ZI-data 占用的空间值中包含了堆栈的大小(经实际测试,若程序中完全没有使用 malloc 动态申请堆空间,编译器会优化,不把堆空间计算在内)。

详细内容可以参考如下文章:
STM32 map 文件浅析单片机内存区域划分

总结如下:

程序组件所属类别
机器代码指令Code
常量RO-data
初值非0的全局变量RW-data
初值为0的全局变量ZI-data
局部变量ZI-data 栈空间
使用malloc动态分配的空间ZI-data 堆空间

2、加载、运行

RW-dataZI-data 它们仅仅是初始值不一样而已,为什么编译器非要把它们区分开?原因如下:

应用程序具有静止状态和运行状态。静止态的程序被存储在非易失存储器中,如 STM32 的内部 FLASH,因而系统掉电后也能正常保存。但是当程序在运行状态的时候,程序常常需要修改一些暂存数据,由于运行速度的要求,这些数据往往存放在内存中(RAM),掉电后这些数据会丢失。因此,程序在静止与运行的时候它在存储器中的表现是不一样的,见下图。


程序在存储状态时,RO sectionRW Section 都被保存在 ROM 区。当程序开始运行时,内核直接从 ROM 中读取代码,并且在执行主体代码前,会先执行一段加载代码,它把 RW Section 数据从 ROM 复制到 RAM,并且在 RAM 加入 ZI SectionZI Section 的数据都被初始化为 0。加载完后 RAM 区准备完毕,正式开始执行主体程序。

编译生成的 RW-data 的数据属于图中的 RW SectionZI-data 的数据属于图中的 ZI Section。是否需要掉电保存,这就是把 RW-dataZI-data 区别开来的原因:
- 因为在 RAM 创建数据的时候,默认值为 0,
- 但如果有的数据要求初值非 0,那就需要使用 ROM 记录该初始值,运行时再复制到 RAM

STM32 的 RO 区域不需要加载到 SRAM,内核直接从 FLASH 读取指令运行。计算机系统的应用程序运行过程很类似,不过计算机系统的程序在存储状态时位于硬盘,执行的时候甚至会把上述的 RO 区域(代码、只读数据)加载到内存,加快运行速度,还有虚拟内存管理单元(MMU)辅助加载数据,使得可以运行比物理内存还大的应用程序。而 STM32 没有 MMU,所以无法支持 Linux 系统。

当程序存储到 STM32 芯片的内部 FLASH 时(即 ROM 区),它占用的空间是 CodeRO-dataRW-data 的总和,所以如果这些内容比STM32 芯片的 FLASH 空间大,程序就无法被正常保存了。当程序在执行的时候,需要占用内部 SRAM 空间(即 RAM 区),占用的空间包括RW-dataZI-data。应用程序在各个状态时各区域的组成见下表。

程序状态与区域组成
程序执行时的只读区域(RO)Code + RO data
程序执行时的可读写区域(RW)RW data + ZI data
程序存储时占用的ROM区Code + RO data + RW data

而这些区域的起始地址和大小,以及各个函数变量应该放在哪个存储器区域中就是由本文要讲的 sct 文件定义的。

二、sct 分散加载文件

1、简介

当工程按默认配置构建时,MDK 会根据我们选择的芯片型号,获知芯片的内部 FLASH 及内部 SRAM 存储器概况,生成一个以工程名命名的后缀为 *.sct 的分散加载文件(Linker Control Filescatter loading),链接器根据该文件的配置分配各个节区地址,生成分散加载代码,因此我们通过修改该文件可以定制具体节区的存储位置。

  • 可以设置源文件中定义的所有变量自动按地址分配到外部 SDRAM,这样就不需要再使用关键字 __attribute__ 按具体地址来指定了;
  • 利用它还可以控制代码的加载区与执行区的位置,例如可以把程序代码存储到单位容量价格便宜的 NAND-FLASH 中,但在 NAND-FLASH 中的代码是不能像内部 FLASH 的代码那样直接提供给内核运行的,这时可通过修改分散加载文件,把代码加载区设定为 NAND-FLASH 的程序位置,而程序的执行区设定为 SDRAM 中的位置,这样链接器就会生成一个配套的分散加载代码,该代码会把 NAND-FLASH 中的代码加载到 SDRAM 中,内核再从 SDRAM 中运行主体代码,大部分运行 Linux 系统的代码都是这样加载的。

2、文件格式

下面是一个由 MDK 默认生成的 sct 文件:

我使用的是 STM32F407,不同的芯片型号内存不一样

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region(加载域,基地址空间大小)
  ER_IROM1 0x08000000 0x00040000  {  ; load address = execution address(加载地址 = 执行地址)
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  RW_IRAM1 0x20000000 0x00020000  {  ; RW data(可读写数据)
   .ANY (+RW +ZI)
  }                             
}

在默认的 sct 文件配置中仅分配了 CodeRO-dataRW-dataZI-data 这些大区域的地址,链接时各个节区(函数、变量等)直接根据属性排列到具体的地址空间。

sct 文件中主要包含描述加载域及执行域的部分,一个文件中可包含有多个加载域,而一个加载域可由多个部分的执行域组成。同等级的域之间使用花括号"{}“分隔开,最外层的是加载域,第二层”{}"内的是执行域,其整体结构见下图。

2.1 加载域

sct 文件的加载域格式如下:

加载域名 (基地址 | ("+"地址偏移)) [属性列表] [最大容量]
"{"
	执行区域描述+
"}"

本例中为:
LR_IROM1 0x08000000 0x00080000  {
	...
}
  • 加载域名: 在 map 文件中的描述会使用名称 LR_IROM1 来标识空间。
  • 基地址 + 地址偏移:基地址为 STM32 内部 FLASH 的基地址 0x08000000,地址偏移可选
  • 属性列表: 属性列表说明了加载域的是否为绝对地址 N 字节对齐等属性。本例中没有描述加载域的属性。
  • 最大容量: 最大容量说明了这个加载域可使用的最大空间,该配置也是可选的,如果加上这个配置后,当链接器发现工程要分配到该区域的空间比容量还大,它会在工程构建过程给出提示。STM32 内部 FLASH 的大小 0x00080000(512KB)

2.2 执行域

执行域名 (基地址 | "+"地址偏移) [属性列表] [最大容量 ]
"{"
	输入节区描述
"}"

本例中为:
ER_IROM1 0x08000000 0x00040000  {  ; load address = execution address(加载地址 = 执行地址)
   ...
}
RW_IRAM1 0x20000000 0x00020000  {  ; RW data(可读写数据)
	...
}    

执行域的格式与加载域是类似的,区别只是输入节区的描述有所不同。

本例中包含了 ER_IROM1RW_IRAM1 两个执行域,它们分别对应描述了 STM32 的内部 FLASH 及内部 SRAM 的基地址及空间大小。而它们内部的“输入节区描述”说明了哪些节区要存储到这些空间,链接器会根据它来处理编排这些节区。

2.3 输入节区描述

模块选择样式 “(“输入节区样式”,” “+“输入节区属性”)”
模块选择样式 “(“输入节区样式”,” “+“节区特性”)”

模块选择样式 “(“输入符号样式”,” “+“输入节区属性”)”
模块选择样式 “(“输入符号样式”,” “+“节区特性”)”

本例中为:
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
.ANY (+XO)
.ANY (+RW +ZI)
  • 模块选择样式: 模块选择样式可用于选择 o 及 lib 目标文件作为输入节区,它可以直接使用目标文件名或“*”通配符,也可以使用“.ANY”。
    • 使用语句“.o”可以选择所有 o 文件,使用“.lib”可以选择所有 lib 文件,使用“*”或“.ANY”可以选择所有的 o 文件及 lib 文件。
    • 其中“.ANY”选择语句的优先级是最低的,所有其它选择语句选择完剩下的数据才会被“.ANY”语句选中。
  • 输入节区样式: 通过输入节区样式可以选择要控制的节区。“(RESET, +First)” 语句的 RESET 就是输入节区样式,它选择 RESET 的节区,并使用后面介绍的节区特性控制字“+First”表示它要存储到本区域的第一个地址。
  • (InRoot$$Sections)” 是一个链接器支持的特殊选择符号,它可以选择所有标准库里要求存储到 root 区域的节区。
  • 输入符号样式: 可以选择要控制的符号,符号样式需要使用“:gdef:”来修饰。例如可以使用“*(:gdef:Value_Test)”来控制选择符号“Value_Test”。
  • 输入节区属性: 通过在模块选择样式后面加入输入节区属性,可以选择样式中不同的内容,每个节区属性描述符前要写一个“+”号,使用空格或“,”号分隔开,可以使用的节区属性描述符见下表。
节区属性描述符说明
RO-CODE、CODE只读代码段
RO-DATA、CONST只读数据段
RO及TEXT包括 RO-CODE 和 RO-DATA
RW-DATA可读写数据段
RW-CODE可读写代码段
RW、DATA包括 RW-DATA 和 RW-CODE
ZI及BSS初始化为 0 的可读写数据段
XO只可执行的区域
ENTRY节区的入口点

例如,示例文件中使用".ANY(+RO)“选择剩余所有节区 RO 属性的内容都分配到执行域 ER_IROM1 中,使用”.ANY(+RW +ZI)"选择剩余所有节区 RW 及 ZI 属性的内容都分配到执行域 RW_IRAM1中。

  • 节区特性:节区特性可以使用"+FIRST"或"+LAST"选项配置它要存储到的位置,FIRST 存储到区域的头部,LAST 存储到尾部。通常重要的节区会放在头部,而 CheckSum(校验和)之类的数据会放在尾部。
    • 例如示例文件中使用"(RESET,+First)"选择了 RESET 节区,并要求把它放置到本区域第一个位置,而 RESET 是工程启动代码中定义的向量表,该向量表中定义的堆栈顶和复位向量指针必须要存储在内部 FLASH 的前两个地址,这样 STM32 才能正常启动(详见 STM32 芯片启动过程),所以必须使用 FIRST 控制它们存储到首地址。

总的来说,我们的 sct 示例文件配置如下:

  • 程序的加载域为内部 FLASH 的 0x08000000,最大空间为 0x00100000;程
  • 序的执行基地址与加载基地址相同,其中 RESET 节区定义的向量表要存储在内部 FLASH 的首地址,且所有 o 文件及 lib 文件的 RO 属性内容都存储在内部 FLASH 中;
  • 程序执行时 RW 及 ZI 区域都存储在以 0x20000000 为基地址,大小为 0x00020000 的空间。

链接器根据 sct 文件链接,链接后各个节区、符号的具体地址信息可以在 map 文件中查看。

3、配置 sct 文件

通过 Use Memory Layout from Target Dialog 选项可以选择是使用 MDK 生成还是使用用户自定义的 sct 文件。

取消选择后,即可自己设置 sct 文件,点击下面的 Edit 即可编辑 sct 文件:

可以看到,其地址、大小和如下设置是对应的:

现在尝试分配一个变量到 RAM 中:

uint32_t gTest __attribute__((section(".my_data")));

int main(void)
{
	...
	gTest = 10;
	printf("Value gTest == %d is in: %p\r\n", gTest, &gTest);

	...
}

sct 文件修改如下:

LR_IROM1 0x08000000 0x00080000  {    ; load region size_region
  ER_IROM1 0x08000000 0x00040000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
   .ANY (+XO)
  }
  MY_DATA 0x20000000 0x00005000   {
    .ANY(my_section)
  }
  RW_IRAM1 0x20010000 0x00010000  {  ; RW data
   .ANY (+RW +ZI)
  }                              
}

最终打印出来的结果为:

map 文件:

数据治理是确保数据准确性、可靠性、安全性、可用性和完整性的体系和框架。它定义了组织内部如何使用、存储、保护和共享数据的规则和流程。数据治理的重要性随着数字化转型的加速而日益凸显,它能够提高决策效率、增强业务竞争力、降低风险,并促进业务创新。有效的数据治理体系可以确保数据在采集、存储、处理、共享和保护等环节的合规性和有效性。 数据质量管理是数据治理的关键环节,它涉及数据质量评估、数据清洗、标准化和监控。高质量的数据能够提升业务决策的准确性,优化业务流程,并挖掘潜在的商业价值。随着大数据和人工智能技术的发展,数据质量管理在确保数据准确性和可靠性方面的作用愈发重要。企业需要建立完善的数据质量管理和校验机制,并通过数据清洗和标准化提高数据质量。 数据安全与隐私保护是数据治理的另一个重要领域。随着数据量的快速增长和互联网技术的迅速发展,数据安全与隐私保护面临前所未有的挑战。企业需要加强数据安全与隐私保护的法律法规和技术手段,采用数据加密、脱敏和备份恢复等技术手段,以及加强培训和教育,提高安全意识和技能水平。 数据流程管理与监控是确保数据质量、提高数据利用率、保护数据安全的重要环节。有效的数据流程管理可以确保数据流程的合规性和高效性,而实时监控则有助于及时发现并解决潜在问题。企业需要设计合理的数据流程架构,制定详细的数据管理流程规范,并运用数据审计和可视化技术手段进行监控。 数据资产管理是将数据视为组织的重要资产,通过有效的管理和利用,为组织带来经济价值。数据资产管理涵盖数据的整个生命周期,包括数据的创建、存储、处理、共享、使用和保护。它面临的挑战包括数据量的快速增长、数据类型的多样化和数据更新的迅速性。组织需要建立完善的数据管理体系,提高数据处理和分析能力,以应对这些挑战。同时,数据资产的分类与评估、共享与使用规范也是数据资产管理的重要组成部分,需要制定合理的标准和规范,确保数据共享的安全性和隐私保护,以及建立合理的利益分配和权益保障机制。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值