Another feature a kernel needs after paging is multitasking. Firstly there is a lot of confusion over the difference between multithreading, multitasking, and multiprocessing - so we'll go over that first, then learn about context switching and timers.
So to start, most computers have multiple cores, each core can run multiple processes, each process can have multiple threads. Multithreading is when you have parts inside the same program(task) that want to execute in parallel. Remember that this all runs on a single core - it can only do one thing at once. So what the computer does is swap between the threads so fast that it seems the computer is doing multiple things at once. Each thread has a certain time it can run before it is preempted
. Remember since threads are in the same process they share files, network, and memory. Next is multiprocessing. A process is just the instance of a program which can be made from many threads. The kernel handles this in almost exactly the same way as how it handles multiple threads. Tasks are preempted and swapped so fast it seems that the computer is doing multiple things at once. Essentially your computer gives a slice of it's time to each process - note that programs which need the most time from the CPU receive it so if you're editing a video, chrome might feel a bit slow. Multiprocessing is where it gets interesting - we have more than one CPU core which means we can actually do multiple things at the same time.
We will be implementing pre-emptive multitasking. Preemption is just interrupting a executing task with the idea of resuming it later on.
We need some way to represent and store our processes. Note that each process needs it's own stack for it to operate and store data. I made a struct containing a pointer to a process' stack (1 page big), it's name, and status. The stack needs to have some initial values on it as when we switch stacks we simply pop certain registers. Remember popping a register sets it equal to it's past value. When an interrupt is called, the SS register, ESP register, EFLAGS, CS register, and EIP register are pushed onto the stack. So for our process', we need to push these values - the EIP register should be set the address of the function of your process.
Pre-emptive multitasking can be done with a timer. The timer sends out an interrupt every interval (which you set). Once the interrupt is called, you must preserve the state of the current process, choose what process should be run next, and switch the ESP, to that of the next process. To preserve state, you just push all general registers. Remember that context switch
? Well that is simply just storing/saving the state of an active process, so we can resume it later, and execute a new process. I have a linked list of tasks, so I simply fetch the next task. After you've selected your next task, you set the ESP register to the ESP of that process' stack. From here, you pop off the general registers, and iret. The iret will pop the SS register, ESP register, EFLAGS, CS register, and EIP register. By popping the EIP register we can get to the address of the code of your process! After the code for a process has been executed, you'll want an exit function which calls the timer interrupt, and deletes your task. I just remove a finished task from my linked list. Also once all the tasks are finished, I retrieve the ESP of the main process, and swap it to that.
That is multitasking, check out my implementation here: https://github.com/sid-shakthivel/SidOS!