OK... loading modular drivers into your kernel can be the same thing as loading DLLs into your user apps, just at a different address. I'll describe PE because I know that format better, although the principles will be almost the same for ELF.
The file on disk (executable, DLL or driver) is more-or-less the same as the memory image. It consists of a set of headers and some sections; sections are just chunks of code, data, etc.. One section (called .text) contains code, another (.data) contains initialised global data, and another (.bss) contains uninitialsied global data.
For example
Code: Select all
int this_goes_in_bss;
int this_goes_in_data;
int this_goes_in_text()
{
return 42;
}
There are two general ways of loading a module: explictly and demand paging. With explicit loading, the loader will read the headers and each section from disk into memory and start running the code. With demand loading, the loader will set up a structure in memory so that it knows that this module is loaded, and it starts running the code; nothing is actually loaded yet. Inevitably, the code will page fault, because there is nothing there; at this point, the loader checks its list of modules, realises that there should be something mapped to that page, loads that page from disk, and continues.
The code in this case is the machine opcodes that the compiler emits; the data is the various global variables the program uses, appended to each other into one block (local variables are stored on the stack).
ELF and PE both support dynamic linking; that is, you can load a module and ask it for a specific function. PE supports this with an export table, which consists of a list of names and addresses. When asked for a particular function, the loader just walks the list of names and returns the appropriate address. I'm sure ELF does it similarly, but I'll have to leave it to someone else to fill in the details here, since I've never written an ELF loader.