Purpose of linker files in placing code in internal flash
This boils down to what the binary representation of a program ought to be. You might for instance think that your program is a series of bytes, and that all you have to do is to transfer those bytes into the internal flash. However, there's a problem with that, namely addressing of data.

If, in your program, you rely on a piece of data, e.g.:

static int my_variable = 1;


Then any code that wants to use this variable must know the actual address of the variable. If you put the executable binary in location 0x1000_0000, and the offset within the binary is 0xFF0, then the system needs to know that it will end up at 0x1000_0FF0. 

The program that has the task of grouping all code and data snippets into one binary file is called the linker. The purpose of the linker script is to aid the linker to figure out where these addresses are going to end up at execution time. But of course, there are additional things that the linker-script does for us:


It can divide the binary into different sections, and handle those independently. This is useful, when you want certain code or data to reside somewhere special.
It understands sections such as the "bss". This is a special memory region where the linker will assume is all zeros at initialization. The code below [1], will most likely end up being in the .bss section.
It can also understand copied (duplicated) sections, where it will assume that it will be copied somewhere else. For instance, non-zero data is usually copied from flash memory to RAM at initialization time. Code [2] below shows some data that might be copied to RAM, hence needing a different memory address at compile time.
The linker script can also define variables at specific locations. This is helpful in order to figure out the sizes of the different sections.


[1] .bss data:

static int my_table[4] = { 0, 0, 0, 0 };


[2] .data:

static float pi = 3.14159287;