This is something thats really bugged me since I've graduated. We programmed PIC16f877a micrcontrollers in class but never got into the how it works, just the make it work. So let's say I write a program that makes LEDS flash in a specific order and send this program to the microcontroller through this special adapter to the right pins. What do you know, the LED's begin to flash as instructed. Now, I disconnect the power and remove the PIC from the breadboard. I put the PIC back in its correct spot, plug in the power, and the LED's still flash as previously programmed without having to reprogram it. How is this happening? How does this microcontroller essentially "remember" what I told it to do?
The PIC includes a type of nonvolatile memory called "Flash EEPROM". When you "send your program to the microcontroller", the special adapter is in fact programming your code into this memory. The physical memory is a special kind of transistor with a floating gate that can store a charge more or less indefinitely, even with no power applied. In most devices, "charged" = 0, "no charge" = 1.
Whenever the PIC is reset or powered-up, it begins executing your code, fetching it directly from this memory.
On the PIC, this memory is integrated into the same chip that holds the CPU and the rest of the logic (peripherals, etc.). The same kind of memory is used to hold the BIOS in your desktop/laptop PC, but it's a separate chip from the CPU. The first few screens you see when you switch on your PC come from the BIOS, which then loads your operating system from hard drive or SSD and runs that.
There are quite a few parts to make the magic of programming a microcontroller work.
Your development toolchain (compiler, assembler, linker etc) translates your program from source code to a series of machine instructions which will be stored on the microcontroller.
The micro controller normally stores the program in flash memory (UV erasable and one-time programmable microcontrollers also exist). This is a special type of memory that is designed to retain data even when the power is turned off. These rely on a structure known as a "floating gate mosfet". Essentially each mosfet in the memory array has a tiny capacitor in series with it's gate and the charge on this capacitor changes the behavior of the transistor.
The floating gate is surrounded by insulating material and therefore under normal conditions charge can neither enter or leave the floating gate. This allows the floating gate to retain charge and hence information even when the circuit is powered off.
However when sufficiently high voltages are applied, electrons can cross the insulating layer. This allows charge to be added to or removed from the floating gate. Generally the mechanism for charging the gates (known as "programming") can be done on an individual bit basis, but the method for removing charge (known as "erasing) can only be done for a whole block of memory at a time.
When you connect your programming adapter and tell the software to program the micro-controller, it puts the microcontroller into a special "program mode". The details of this vary between different microcontrollers, but on your PIC16f877a it is normally done by applying 13V to the Vpp/MCLR line. On older flash chips Vpp would actually supply the power needed for programming, but on your PIC it is just a signal line with whatever voltages are needed to program the flash array generated internally. Once in program mode the processor stops executing normal instructions and instead responds to special commands sent over the programming interface. On PICs this is a synchronous serial interface with a unidirectional clock line PGC and a bidirectional data line PGD.
Once in programming mode mode, the programming software will send a series of serial commands through the programming adapter to write your program to flash. Two different approches are possible, in one approach "erase and program" commands are used to erase and reprogram one block at a time. In the other approach the whole chip is erased with a "bulk erase" command, then "program only" commands can be used to program the new data. In both cases before issuing an "erase and program" or "program only" command the data must first be loaded into staging registers. You can find the gory details in http://ww1.microchip.com/downloads/en/devicedoc/39589b.pdf
First things first...do you understand how a one-time programmable fusible memory works? Like if I had a billion little wires and each one I choose to connect to either +V for a logic HI (ONE )and GND for a logic LO (ZERO), do you understand how that stores a program in itself of ones and zeroes? You could take a voltmeter and for each wire you could measure whether it is connected to +V or GND to figure out whether the bit at that location is a HI or LO. A microcontroller does the same thing.
Everything else is a fancy, more flexible version of this concept: How does ROM work?
This tends to lead to the question: "How does a microcontroller take the program of ones and zeroes and do stuff with it What happens at hardware level when we feed a code?
Program code is stored in microcontrollers ROM memory (in this case flash). Stored data in such memory is retained after power-off. Your microcontroller has 14KB of flash memory, there code resides after programming.
This memory is based on MOSFET structures, which retains data after power is disconnected. For more information on how flash memory works I suggest you to read wiki article.
Depending on the vintage of the microcontroller, it's equipped with either UV erasable EPROM in a ceramic package with glass window, 'one time programmable' EPROM in plastic packaging (for production) or Flash memory (typical of most current devices). Some can even be bought with mask programmed ROM for high qty manufacture.
Some programmers support various microcontrollers or the device manufacturer may have some dedicated EVB or whatever. Serial programming via SPI, USB, serial port (old school) or parallel port is also supported for some chips.
I rather like the 'in circuit programming' myself.