nm和map文件

在Linux中,我们可以使用nm命令来查看程序的符号表,包括全局变量。这个命令可以帮助我们查看全局变量的地址。然而,全局变量的地址是否相邻取决于编译器的实现和优化。

下面是一个简单的例子:首先,我们创建一个C程序,定义两个全局变量:

int global_var1;
int global_var2;

int main() {
    return 0;
}

然后,我们编译这个程序并生成map文件:

gcc -o test test.c -Wl,-Map,test.map

最后,我们使用nm命令查看全局变量的地址:

nm test | grep 'global_var'

这将显示全局变量的地址。你可以比较这两个地址来看它们是否相邻。但是请注意,即使这两个变量在源代码中是相邻的,它们在内存中的地址也可能不是相邻的,这取决于编译器如何分配内存。此外,全局变量的大小也会影响它们的地址。例如,如果一个变量是int类型,另一个变量是double类型,那么它们的地址就不可能是相邻的,因为double类型的变量需要更多的内存空间。所以,即使两个全局变量在源代码中是相邻的,它们在内存中的地址也可能不是相邻的。这就是为什么我们需要使用nm命令来查看实际的内存地址。


我定义了一个static的变量,如:static unsigned int global_var8;通过nm test | grep 'global_var'查找的时候能看到,但是在map文件里找不到,是什么问题呢?

在Linux中,static关键字会使变量的作用域限制在声明它的文件内。因此,static变量不会出现在链接器的符号表中,也就不会出现在map文件中。这是因为链接器只需要处理全局符号,即那些可能需要在一个文件中定义,在其他文件中引用的符号。

然而,你可以使用nm命令查看包含static变量的目标文件(.o文件),这样就可以看到static变量了。但是请注意,这些地址是在链接前分配的,所以它们在最终的可执行文件中可能会有所不同。

此外,如果你的static变量没有被使用,编译器可能会优化掉它,因此即使在目标文件中也看不到它¹。你可以尝试关闭优化选项,看看是否能在目标文件中看到你的static变量。


global_var10是一个在函数xiaomi_func内部定义的静态变量。这意味着这个变量只在这个函数内部可见,而在函数外部是看不到的。然而,由于它是静态的,所以它在程序的整个生命周期内都存在。

当你使用nm命令查看符号时,你会看到global_var10.0。这是因为编译器对局部静态变量进行了名字修饰(name mangling)。这是一种编译器用来确保每个符号名称在内部都是唯一的技术。编译器会在变量名后面添加一个点和一个序列号。这就是你看到的.0

所以,global_var10.0实际上就是你在xiaomi_func函数内部定义的静态变量global_var10


在嵌入式系统中,我们可以使用链接脚本来配置特定的段(section),并使用特定的属性来将变量设置为归属于特定的段。以下是一个简单的示例,展示了如何在GCC编译器中进行操作:

// 链接脚本
SECTIONS
{
    . = 0x20000000;
    .data : { *(.data) }
    .my_section : { *(.my_section) }
    .bss : { *(.bss) }
}

// C代码
int var1 __attribute__((section(".data"))) = 0;        // 将var1放在.data段
int var2 __attribute__((section(".my_section"))) = 0;  // 将var2放在.my_section段

在这个例子中,我们首先定义了一个链接脚本,它指定了内存的起始地址(0x20000000),然后定义了三个段:.data.my_section.bss。然后,在C代码中,我们使用__attribute__((section(".section_name")))来指定变量应该放在哪个段中。

请注意,这只是一个基本的示例,实际的链接脚本可能会更复杂,并且可能需要包含更多的信息,例如段的大小,对齐要求等。此外,不同的编译器可能会有不同的语法,所以请确保查阅您所使用的编译器的文档。


在Keil中配置段,通常需要通过链接脚本(Linker Script)来完成。链接脚本可以定义内存区域的布局,包括代码段(.text)、数据段(.data)、未初始化数据段(.bss)等。以下是一个简单的示例:

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

在这个例子中,我们定义了两个内存区域:一个是Flash(从0x08000000开始,大小为0x00040000),另一个是RAM(从0x20000000开始,大小为0x00010000)。在Flash区域中,我们存储了代码和只读数据,在RAM区域中,我们存储了读写数据和未初始化数据。

然后,你可以使用__attribute__((section(".section_name")))来将特定的变量或函数放入特定的段中。例如:

int var __attribute__((section("ER_IROM1"))) = 0;

这将会把变量var放入ER_IROM1段中。

请注意,这只是一个基本的示例,实际的链接脚本可能会更复杂,并且可能需要包含更多的信息,例如段的大小,对齐要求等。此外,不同的编译器可能会有不同的语法,所以请确保查阅您所使用的编译器的文档。


在嵌入式系统中,可以将data段和bss段配置到SRAM或PSRAM中。这通常涉及到链接脚本的修改,以便在链接过程中将特定的段定位到特定的内存区域。

以下是一个简单的示例,展示了如何在链接脚本中配置段的位置。这个示例假设你正在使用GNU链接器,并且你的链接脚本可能看起来像这样:

MEMORY
{
  sram (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
  psram (rwx) : ORIGIN = 0x60000000, LENGTH = 4M
}

SECTIONS
{
  .data : {
    *(.data)
  } >sram

  .bss : {
    *(.bss)
  } >psram
}

在这个示例中,我们首先定义了两个内存区域,一个是SRAM,起始地址为0x20000000,长度为64KB;另一个是PSRAM,起始地址为0x60000000,长度为4MB。

然后,在SECTIONS部分,我们指定.data段应该链接到SRAM中,.bss段应该链接到PSRAM中。*(.data)*(.bss)是通配符,匹配所有的.data.bss输入段。

这只是一个基本的示例,实际的链接脚本可能会更复杂,包括处理初始化数据、复制到SRAM的数据等。

请注意,这需要对链接脚本有深入的理解,并且可能需要根据你的具体硬件和编译器进行调整。如果你不熟悉这个过程,可能需要寻求专业的嵌入式系统开发人员的帮助。在进行这些更改时,请务必谨慎,因为错误的配置可能会导致程序无法正确运行。


SRAM和PSRAM都需要源源不断的供电来保持其存储的数据。然而,它们在功耗上有所不同。

  • SRAM(静态随机存取存储器)不需要进行刷新操作来保持数据,因此其功耗相对较小。然而,SRAM的存储单元由6个晶体管构成,这使得其在相同的存储容量下,需要更多的晶体管,从而产生较多的热量。

  • PSRAM(伪静态随机存取存储器)具有与SRAM相同的接口协议,但其内核架构却与DRAM(动态随机存取存储器)相同。这意味着PSRAM不需要复杂的内存控制器定期刷新数据,但其内核是DRAM架构。因此,PSRAM的功耗可能会介于SRAM和DRAM之间。

总的来说,SRAM和PSRAM的具体功耗可能会因其具体的使用情况和设计而有所不同。如果你正在考虑在嵌入式系统中使用这些存储器,可能需要根据你的具体需求(例如,对速度、功耗、成本和存储容量的需求)来选择最适合的存储器类型。

你可能感兴趣的:(我的博客,符号表,map文件)