0
\$\begingroup\$

Hope all is well.

Lately I've been studying linker scripts and I believe I got a gist of it enough to hold a conversation, however I can't find the answers I am looking for to some of the questions I had and was hoping if someone can educate me on this.

Case Scenario 1.

In a material I was watching he put an array called "buf_flash" into a section called ".myBufSectionFLASH" which is then loaded into flash at the address of 0x8001000. This array is simply populated with the values '0, 1, .. 9'. He verifies the contents within the address of 0x8001000 and the values are there.

Question 1.

I am confused as to how does the linker know how to interface with the internal flash? AFAIK If I were to write to flash there must be some procedures such as unlocking flash, erasing the page, keeping track of pages and so on and so forth.

Case Scenario 2.

In this article in the ".data section" he loads the .data section from flash into ram, then proceeds to explain "RAM isn’t persisted while power is off, those sections need to be loaded from flash. At boot, the Reset_Handler copies the data from flash to RAM before the main function is called."

Question 2.

Why is the .data section loaded from flash then into ram. Is this not inefficient? As you have to occupy space in both memory spaces? Can one just load the .data section into ram initially?

Every article I read kept mentioning about persistent storage. What I am confused about is that if I have a global variable called int foo = 3; Wouldn't my program initialize this variable to 3 every time, why does it matter if it powers off and the value is wiped, it will just get initalize to three again on boot up? If non-volatile is needed just write to flash in application code?

Thank you for reading, learning forward to any feedback.

\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

If you consider the model of a conventional computer (with disk storage, files etc) loading a program, there are classically three segments:

  • Initialised data (int x = 9;)
  • Uninitiased data (int y;)
  • Program (int main() {})

The classical mechanism puts a copy of the initialised data in the file, and a copy of (machine code) of the program. When the program is being put into memory, the operating system copies the initialised data and program (from the file system, ie, hard disk) into their suitable portions of memory, and (usually) clears the uninitialised data (effectively setting them all to zeroes.) Then it jumps to the start of the program.

In an embedded system -- perhaps with no operating system at all -- how should we do this?

How does the program get into memory in an embedded system? Normally, the program is in ROM or flash or similar: something non-volatile and not ordinarily changed during the running of programs. The CPU can run the program directly from this place. What about the initialised data? It must be in RAM. So how does it get there every time the program starts?

The answer is we can put a copy in the program segment. Effectively this is adding x = 9; as a preamble to the real program. Often this is done with a block copy from static data in the program segment. Sometimes it's done elsewhere if there is some kind of elsewhere: and one of those places might be flash. But it could be anywhere that's nonvolatile. (This is up to the designers of the system, usually part of the design of tools.)

The copy isn't wasted, it's just a little surprising that it's required. Some compilers are good at finding out that x might never change and can optimise it in some way as a constant.

In the particular case of embedded systems with flash, there's often an extra segment for flash, as is apparently the case in your example.

  • Answer 1: The linker doesn't know how to program flash. It's whatever is responsible for getting the program onto your embedded system that knows, ie, the downloader (jtag or avrdude or whatever method.)
  • Answer 2: How does one load it into ram "initially" without having a copy of it? The downloader could put it into RAM, but what happens when the embdedded system is on its own? So it puts it somewhere appropriate (according to the system at hand: program segment, a ROM constants segment, a nonvolatile segment in flash, and copies it to RAM on reset.)
\$\endgroup\$
4
  • \$\begingroup\$ Thank you for the detailed response, a couple of questions, when you say "ie, the downloader" are you referring to something like a JTag? I see, if I can, may I get more of an explanation as to why .data needs to be in flash first than into ram? I still cant make the connection as to why a copy needs to be made in flash first. What would happen if I were to altar my linker script from >RAM AT > FLASH to >RAM. I guess what I am asking for is more of a lower level explanation. None the less your response helped me understand a lot more. \$\endgroup\$
    – Leoc
    Commented Oct 21, 2022 at 14:16
  • \$\begingroup\$ I updated my answer a bit. Yes, the downloader is using jtag or avrdude or similar. My answer is intended to be general: not for any specific system. \$\endgroup\$
    – jonathanjo
    Commented Oct 21, 2022 at 15:12
  • \$\begingroup\$ Thank you for the further insight. Just to get a more sense of the idea, for example global int foo = 3; I believe the compiler knows it needs to reserve a int worth of space and does it also know the value "3" as well? What happens when one does the initial load to ram using the ">ram". What would happen? Would the value inside that address made from the linker script for foo be invalid? Garbage value? \$\endgroup\$
    – Leoc
    Commented Oct 21, 2022 at 17:25
  • \$\begingroup\$ The linker file doesn't actually put anything anywhere. It creates a file which describes where everything goes. The downloader (or EPROM programmer) puts it in ROM, flash, wherever. The startup copies from the ROM/flash into RAM. \$\endgroup\$
    – jonathanjo
    Commented Oct 21, 2022 at 17:32
2
\$\begingroup\$

The linker for a bare-metal embedded executable ("bare-metal" meaning loaded directly into a processor's startup address without a surrounding operating system) is responsible not only for arranging things in memory space, it also provides special pointers to the code which are used by startup code, written in assembly, to set up a basic C or C++ runtime.

All sections defined in the linker script have a virtual address (where the user code can expect it to live) and a load address (where it lives on nonvolatile memory.) Normally these addresses are the same, but when they're not, the section needs to be "relocated" by the startup code. RAM gets cleared on power cycle, so something needs to do the relocating and populate RAM.

The startup script (which is often named something like startup_[name_of_processor].s) is what makes that happen. It needs to know both the virtual address and load address of all sections which are to be relocated to RAM. By example, the .data section usually falls into this category, and includes e.g. global variables with nonzero initialization values. The linker provides the addresses using named symbols, usually something like "_sdata" for the virtual address of the block, "_edata" at the end so it knows how big the section is, and "_sidata" for the load address. The startup script will have an assembly loop which copies the byte at _sidata to _sdata, increment both by 1, continue until reaching _edata. On some processors, the code executes from RAM as well, and the startup script will copy the .text section to RAM.

The names of these pointers are arbitrary, there are some conventions but ultimately all that matters is that the name in the linker script matches the name in the startup script. The linker then knows how to do substitution.

Other responsibilities of the startup script include setting the processor's stack pointer register to an appropriate value (provided by the linker script), and if creating a C++ runtime, running static constructors, usually provided in sections called .preinit_array and .init_array.

In other words, somebody creating the toolchain for your processor wrote assembly code which does the "magic" to allow C and/or C++ code to run on the processor, and that magic relies on named pointers defined in the linker script. You can't fully understand the linker script for any given processor without also reading that processor's startup script.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.