storing structs in ROM on ARM device
I have some constant data that I want to store in ROM since there is a fair amount of it and I'm working with a memory-constrained ARM7 embedded device. I'm trying to do this using structures that look something like this:
struct objde开发者_开发知识库f
{
int x;
int y;
bool (*function_ptr)(int);
some_other_struct * const struct_array; // array of similar structures
const void* vp; // previously ommittted to shorten code
}
which I then create and initialize as globals:
const objdef def_instance = { 2, 3, function, array, NULL };
However, this eats up quite a bit of RAM despite the const
at the beginning. More specifically, it significantly increases the amount of RW data and eventually causes the device to lock up if enough instances are created.
I'm using uVision and the ARM compiler, along with the RTX real-time kernel.
Does anybody know why this doesn't work or know a better way to store structured heterogenous data in ROM?
Update
Thank you all for your answers and my apologies for not getting back to you guys earlier. So here is the score so far and some additional observations on my part.
Sadly, __attribute__
has zero effect on RAM vs ROM and the same goes for static const
. I haven't had time to try the assembly route yet.
My coworkers and I have discovered some more unusual behavior, though.
First, I must note that for the sake of simplicity I did not mention that my objdef
structure contains a const void*
field. The field is sometimes assigned a value from a string table defined as
char const * const string_table [ROWS][COLS] =
{
{ "row1_1", "row1_2", "row1_3" },
{ "row2_1", "row2_2", "row2_3" },
...
}
const objdef def_instance = { 2, 3, function, array, NULL };//->ROM
const objdef def_instance = { 2, 3, function, array, string_table[0][0] };//->RAM
string_table
is in ROM as expected. And here's the kicker: instances of objdef
get put in ROM until one of the values in string_table
is assigned to that const void*
field. After that the struct instance is moved to RAM.
But when string_table
is changed to
char const string_table [ROWS][COLS][MAX_CHARS] =
{
{ "row1_1", "row1_2", "row1_3" },
{ "row2_1", "row2_2", "row2_3" },
...
}
const objdef def_instance = { 2, 3,function, array, NULL };//->ROM
const objdef def_instance = { 2, 3, function, array, string_table[0][0] };//->ROM
those instances of objdef
are placed in ROM despite that const void*
assigment. I have no idea why this should matter.
I'm beginning to suspect that Dan is right and that our configuration is messed up somewhere.
I assume you have a scatterfile that separates your RAM and ROM sections. What you want to do is to specify your structure with an attribute for what section it will be placed, or to place this in its own object file and then specify that in the section you want it to be in the scatterfile.
__attribute__((section("ROM"))) const objdef def_instance = { 2, 3, function, array };
The C "const" keyword doesn't really cause the compiler to put something in the text or const section. It only allows the compiler to warn you of attempts to modify it. It's perfectly valid to get a pointer to a const object, cast it to a non-const, and write to it, and the compiler needs to support that.
Your thinking is correct and reasonable. I've used Keil / uVision (this was v3, maybe 3 years ago?) and it always worked how you expected it to, i.e. it put const data in flash/ROM.
I'd suspect your linker configuration / script. I'll try to go back to my old work & see how I had it configured. I didn't have to add #pragma
or __attribute__
directives, I just had it place .const
& .text
in flash/ROM. I set up the linker configuration / memory map quite a while ago, so unfortunately, my recall isn't very fresh.
(I'm a bit confused by people who are talking about casting & const pointers, etc... You didn't ask anything about that & you seem to understand how "const" works. You want to place the initialized data in flash/ROM to save RAM (not ROM->RAM copy at startup), not to mention a slight speedup at boot time, right? You're not asking if it's possible to change it or whatever...)
EDIT / UPDATE:
I just noticed the last field in your (const) struct is a some_other_struct * const
(constant pointer to a some_other_struct). You might want to try making it a (constant) pointer to a constant some_other_struct [some_other_struct const * const
] (assuming what it points to is indeed constant). In that case it might just work. I don't remember the specifics (see a theme here?), but this is starting to seem familiar. Even if your pointer target isn't a const item, and you can't eventually do this, try changing the struct definition & initializing it w/ a pointer to const and just see if that drops it into ROM. Even though you have it as a const
pointer and it can't change once the structure is built, I seem to remember something where if the target isn't also const, the linker doesn't think it can be fully initialized at link time & defers the initialization to when the C runtime startup code is executed, incl. the ROM to RAM copy of initialized RW memory.
You could always try using assembly language.
Put in the information using DATA
statements and publish (make public) the starting addresses of the data.
In my experience, large Read-Only data was declared in a source file as static const
. A simple global function inside the source file would return the address of the data.
If you are doing stuff on ARM you are probably using the ELF binary format. ELF files contain an number of sections but constant data should find its way into .rodata or .text sections of the ELF binary. You should be able to check this with the GNU utility readelf
or the RVCT utility fromelf
.
Now assuming you symbols find themselves in the correct part of the elf file, you now need to find out how the RTX loader does its job. There is also no reason why the instances cannot share the same read only memory but this will depend on the loader. If the executable is stored in the rom, it may be run in-place but may still be loaded into RAM. This also depends on the loader.
A complete example would have been best. If I take something like this:
typedef struct { char a; char b; } some_other_struct; struct objdef { int x; int y; const some_other_struct * struct_array; }; typedef struct { int x; int y; const some_other_struct * struct_array; } tobjdef; const some_other_struct def_other = {4,5}; const struct objdef def_instance = { 2, 3, &def_other}; const tobjdef tdef_instance = { 2, 3, &def_other}; unsigned int read_write=7;
And compile it with the latest codesourcery lite
arm-none-linux-gnueabi-gcc -S struct.c
I get
.arch armv5te .fpu softvfp .eabi_attribute 20, 1 .eabi_attribute 21, 1 .eabi_attribute 23, 3 .eabi_attribute 24, 1 .eabi_attribute 25, 1 .eabi_attribute 26, 2 .eabi_attribute 30, 6 .eabi_attribute 18, 4 .file "struct.c" .global def_other .section .rodata .align 2 .type def_other, %object .size def_other, 2 def_other: .byte 4 .byte 5 .global def_instance .align 2 .type def_instance, %object .size def_instance, 12 def_instance: .word 2 .word 3 .word def_other .global tdef_instance .align 2 .type tdef_instance, %object .size tdef_instance, 12 tdef_instance: .word 2 .word 3 .word def_other .global read_write .data .align 2 .type read_write, %object .size read_write, 4 read_write: .word 7 .ident "GCC: (Sourcery G++ Lite 2010.09-50) 4.5.1" .section .note.GNU-stack,"",%progbits
With the section marked as .rodata, which I would assume is desired. Then it is up to the linker script to make sure that ro data is put in rom. And note the read_write variable is after switching from .rodata to .data which is read/write.
So to make this a complete binary and see if it gets placed in rom or ram (.text or .data) then
start.s
.globl _start _start: b reset b hang b hang b hang b hang b hang b hang b hang reset: hang: b hang
Then
# arm-none-linux-gnueabi-gcc -c -o struct.o struct.c # arm-none-linux-gnueabi-as -o start.o start.s # arm-none-linux-gnueabi-ld -Ttext=0 -Tdata=0x1000 start.o struct.o -o struct.elf # arm-none-linux-gnueabi-objdump -D struct.elf > struct.list
And we get
Disassembly of section .text: 00000000 <_start>: 0: ea000006 b 20 <reset> 4: ea000008 b 2c <hang> 8: ea000007 b 2c <hang> c: ea000006 b 2c <hang> 10: ea000005 b 2c <hang> 14: ea000004 b 2c <hang> 18: ea000003 b 2c <hang> 1c: ea000002 b 2c <hang> 00000020 <reset>: 20: e59f0008 ldr r0, [pc, #8] ; 30 <hang+0x4> 24: e5901000 ldr r1, [r0] 28: e5801000 str r1, [r0] 0000002c <hang>: 2c: eafffffe b 2c <hang> 30: 00001000 andeq r1, r0, r0 Disassembly of section .data: 00001000 <read_write>: 1000: 00000007 andeq r0, r0, r7 Disassembly of section .rodata: 00000034 <def_other>: 34: 00000504 andeq r0, r0, r4, lsl #10 00000038 <def_instance>: 38: 00000002 andeq r0, r0, r2 3c: 00000003 andeq r0, r0, r3 40: 00000034 andeq r0, r0, r4, lsr r0 00000044 <tdef_instance>: 44: 00000002 andeq r0, r0, r2 48: 00000003 andeq r0, r0, r3 4c: 00000034 andeq r0, r0, r4, lsr r0
And that achieved the desired result. The read_write variable is in ram, the structs are in the rom. Need to make sure both the const declarations are in the right places, the compiler gives no warnings about say putting a const on some pointer to another structure that it may not determine at compile time as being a const, and even with all of that getting the linker script (if you use one) to work as desired can take some effort. for example this one seems to work:
MEMORY { bob(RX) : ORIGIN = 0x0000000, LENGTH = 0x8000 ted(WAIL) : ORIGIN = 0x2000000, LENGTH = 0x8000 } SECTIONS { .text : { *(.text*) } > bob .data : { *(.data*) } > ted }
精彩评论