vinny wrote:
I would like to know how I would go about loading a program and setting its offset to where it assumes its free memory starts at 0 or wherever they automatically assume their memory starts
The design phase of this is almost easy: you choose where the program will be loaded. A good design is to have the kernel at one end of available memory and the program at the other. This puts all the free space into one contiguous chunk between them. 8-bit computers used this scheme, having a system variable -- a predefined address holding a value -- to mark the top of memory available to the program. They could have as many as 3 system variables which might be called RAMTOP for the top of physical RAM, MEMTOP for the highest point below the (shared) screen memory, and APPMHI for the point at which allocation would overwrite drivers and stuff. Or, screen memory might be below driver memory if it changes size with different graphics modes. This is a very powerful scheme, allowing all sorts of stuff to be loaded into the driver area.
How to get programs to know where they'll be is a separate question. In assembly lanuage without a linker, you set the load and start addresses in the source code so that the assembler knows what addresses to generate. You may want an include file to do this. The include file can also give names to the locations of system variables.
With a linker, (whether assembling or compiling,) I'm not so sure since I don't use linkers. (Yet!
) Ideally, the linker would be told where your program will be loaded and its entry point. If your linker is confusing, you might find it easier to link an assembly-language function to the start of your program. In another thread,
Octocontrabass went into detail on how to do this. He was describing a bootloader loading a kernel, but it's very much the same as a single-tasking OS loading a program.
vinny wrote:
— returning back to the kernel when the program is finished.
This is basically just another system call. You jump (or INT or TRAP or whatever) to a routine which cleans up and restores the shell.
vinny wrote:
Would I have to keep the kernel loaded in RAM at all times? Or would I leave a bit of “kernel loading” code somewhere to jump to when the program is done.
This gets easier if you think about what your kernel is made up of. You can probably divide it into two parts, drivers and shell. The drivers should, of course, stay in memory to be used by the program. The bulk of the shell can be removed while programs are running, but you might want to keep part of the shell in memory depending on how much work the kernel delegates to the shell. Without an MMU, you have essentially infinite freedom in what part does what.