Loading a Process
To load and start a process can be done in a number of ways.
In this post Brendan describes a way where the created process executes a loader function which loads the executable into it's address space. This loader function is only active when the process gets it's timeslice. This way, the process spawning a new process doesn't get blocked.
- Create a new page directory (mapped into kernel space);
- Copy kernel page tables into the new page directory;
- Setup scheduler data (thread priority, etc...) and set the initial EIP to a "start process" kernel function;
- (Optionally) wait or block until the new thread sets some sort of status to return;
- Return to caller.
After this I've got a new thread/process running in kernel space. When the scheduler gives it a time slice, the kernel sets up "user space" (loads an executable file, etc), and then "returns" to CPL=3. All of this happens from within the new address space though, so there's no hassles with paging, etc.
For error handling, for my OS everything is asynchronous - the spawning thread doesn't block waiting for status from the spawned thread. Just before the kernel "returns" to the new process/thread it sends a status/error message to the first thread. This means the "spawnThread()" function might return "OK" and you might be sent an error (e.g. "failed to load executable file") afterwards.
For synchronous OSs, the "spawnProcess()" function would block until full status can be returned. In theory this is easier for applications programmers to use but worse for performance. For example, a user interface might lock up for several seconds because the thread decided to spawn a process (e.g. where the executable file for the new process happens to be a large file on a slow file system). To avoid this problem application programmers might want to spawn a new thread that spawns the new process; but this makes it harder to use than an asynchronous function, has higher overhead, and most application programmers won't do it (it's either too much hassle, or they simply don't realise the problem is there).
A Process Manager is used to load the executable in Messiah Andrw's post.
The process manager runs in it's own thread. Usually this thread is sleeping, but if a job gets added to the process manager's queue ("Spawn this process" - that's all it's used for right now), the process manager's thread is sleeping. The process that sent the request usually sleeps until the process manager has completed it's job - but it's possible in a multi threaded application to do other stuff while the process is loading.
When my scheduler switches to one of these special kernel threads (the process manager, the vfs manager, etc) it actually switches to the memory context of the process it is working in. That way, really large requests (e.g. load 20MB from a file) is actually processing in a separate kernel thread with the data being loaded into the process's address space, allowing the process and all other processes to continue doing what ever it wants to do.
This does have a (fairly insignificant) performance penalty. For example, to load a program into memory the system has to do something along the following lines:
- Parent program requests program to be loaded.
- - JUMP TO KERNEL LAND -
- Kernel adds request to process manager's queue. Wake process manager.
- - JUMP TO USER LAND -
- Parent program requests to go to sleep.
- - JUMP INTO KERNEL LAND -
- Remove program from 'awake' list.
- - WAIT FOR CONTEXT SWITCH INTO PROCESS MANAGER -
- Set up new process's memory. Request file from VFS manager. Wake VFS manager. Send process manager to sleep.
begin of loop:
- - WAIT FOR CONTEXT SWITCH INTO VFS MANAGER -
- Request part of the file from the disk driver. Wake disk driver. Send VFS to sleep. (Requests are broken up into 128kb chunks, so multiple read/writes can occur at the same time).
- - WAIT FOR CONTEXT SWITCH INTO DISK DRIVER -
- Load part of file from disk. Wake VFS.
if not fully loaded go back to beginning of loop (VFS will only request parts of the file, e.g. the ELF header first, then the program headers, then the data, etc).
- Wake process manager.
- - WAIT FOR CONTEXT SWITCH INTO PROCESS MANAGER -
- Add new process to scheduling algorithm. Wake parent process. If nothing else to queue, send process manager to sleep.
As you can see, a lot of work is involved. These special threads aren't really part of the kernel and they don't exist in their own address , they sort of just go up to other processes and say "what's up, can I hang with you?"
When your OS supports swapping a page in from any file (instead of only the swap file), then you may create an address space where the pages are marked as not present and mapped to the executable file on disk. This way, when the new process starts executing, your memory manager will swap the required pages in from the executable automatically. It only loads those pages that are required at that point, thus it requires less memory at startup.
Some operating systems use a long-term scheduler or admission scheduler which controls when a specific process is started. It is mainly used for real-time operating systems to control the system's load. The idea is similar to the process manager approach. If you ever intent to support real-time scheduling, this is the way to go. Even when your OS is first just a simple desktop OS, because then you can code the admission scheduler to automatically load any executable in it's queue.