Bootloaders in embedded systems can be slightly different from general purpose computers in that you can configure the processor (in a space apart from the code memory, often "fuses") to begin execution from a different memory address. What this permits is injection of code (the bootloader) that can watch for a special signal to do something different (typically for reprogramming the device). If the signal is not seen, the bootloader typically redirects to the traditional boot address and things continue on as normal.
What the adjustable boot location allows is a single, fixed binary to work with and without a bootloader, so long as when the main program is compiled it doesn't overlap with the memory used for the bootloader. So when programming the device, there are two rough options:
- Program the device with just the main program and leave the bootloader fuse alone. On reset the processor begins execution of the main program at the normal location.
- Program the device with the (unmodified) main program and the bootloader, and set the bootloader fuse as appropriate. On reset the processor begins execution at the bootloader, which can do something (e.g. reprogramming) or nothing (pass control to the normal location).
If the processor doesn't have a bootloader fuse and you want a bootloader, it's a bit more complicated, but still possible (e.g. ATtiny bootloaders).
In any event, embedded systems don't need a bootloader like computers. When the processor powers up or otherwise resets, a reset interrupt is triggered, which (should) reinitialize the memory and peripherals to the state specified in the datasheet. On an AVR, the program counter is just set to 0, so the processor grabs whatever's there and goes. If you're using any interrupts, typically the first bunch of memory addresses are all
JMP xxxx, which serves as the vector table, but if you're interrupt-less, your program logic can just start then and there.