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