In this lecture, we will focus on the process module and add an idle process which will be running
when there is no processes running in the system. The idle process is the process where we initialize the kernel,
So in the kernel assembly file,
After it initialized the system and return from kernel main, it will be in the infinite loop
which means if there is no other process, the system is running here.
So the idle process is running in the infinite loop.
In order to accept the interrupts when the idle process is running, we have to enable interrupt
before we halt the system.
So we use the instruction sti to enable interrupt.
if an interrupt is fired, the processor will be wakened up from halt state. It’s worth mentioning that
since we haven’t initialized ss register,
it will cause exception when we return from interrupt handler. We have discussed it in the section exceptions and interrupts handling.
So here we simple set ss to null.
we zero out ax, and move ss ax
OK, at this point, we have a working idle process.
The next thing we will do is change the code in the memory module.
Remember we have modified the memory info block address in the loader file.
So in function initialize memory function.
The address of memory info block count is 20000
and the start of the memory info block array is at
20008.
Also, we set aside the high part of the 1g of memory for the file system.
So the top of the free memory region we can use is set to the base of the file system.
The following memory limits checks
like this, need to be changed.
and here
we set them all to the base of file system.
The last one is set up kvm function because memory end is not larger than the base address of the file system
if we still use it here.
the higher part of the 1g of memory region will not be mapped.
So we manually set it to the 1g of memory space.
Ok that’s it for the memory module.
What we are going to do next is we are going to change the code in the process module to run the idle process first
.
So in process.c file, we first look at initialization function,
All the code in this function is not used.
we initialize idle process and user process.
The user process is what the loader reads into the memory.
We start with idle process.
In the function, we define two variables process and process control.
Since the idle process is the first process to run. So we will save it as the current process in process control
.
First off, we find an unused process.
So we call function find unused process.
Since we are at initialization process, we assert this operation will succeed and the process
is the first item in the table.
After we find a process slot, we set the process entry. The pid of the idle process is set to 0.
The page map is initialized with current cr3 value which means we use the current stack for the idle process.
remember the value in cr3 is physical address
So here we use p2v to convert
physical address to virtual address.
Then we set the process state
to proc running
and then we save it to the process control.
As you see, we didn’t set up uvm because the idle process only has kernel stack and it’s running only in the kernel mode
.
Ok, that's it for the idle process.
next we go to user process.
So we define function initialize user process
Initializing user process is the same as we did in the previous lectures.
The variables we need include process control, process.
and ready list.
We first get the ready list from process control
and we will append it to the list for scheduling.
Next we allocate a new process
and we assume that we will find a new process.
Function alloc new process will setup kvm,
so here we only setup uvm.
The argument is page map
and the base of the user process.
Remember we read it into memory 30000
and we copy 10 sectors of data.
Since this is the first user process, we assert operation will succeed.
OK, in the end, we set the state to proc ready.
and append it to the ready list.
Alright, let's take a look at alloc new process function.
This function just finds a new process and set the process entry. Most of the work is done in set process entry function.
So we rename it to alloc new process.
and it returns the process
This function doesn't take parameters.
We define variable process
What we are going to do in this function is we are going to find a new process.
If there is no process available to us, we simply return null. So we find process by function find unused process
and we check the return value, if it is null,
It means that there is no process available to us, so we return null.
If the check passed, then we set process state,
pid number, then we allocate kernel stack. Here if the allocation of kernel stack fails,
we just return null.
Let’s move on. Initialize kernel stack with 0 and set process context,
initialize the trap frame so that we can return to the user mode.
We also set up kvm and if this operation failed, we return null to the caller.
Don’t forget to free kernel stack
and set process to null meaning that the process slot is not used.
In this function, set up uvm is not used.
at the end of the function, return the process.
So another thing I need to mention is that in our previous lectures, we assumed that the ready list
is not empty when we do the scheduling
So in function schedule, we use assert to do the check and now since we have idle process
we can allow the ready list empty
If we find that the list is empty, it means that we have no process to run.
Therefore we run the idle process.
Here we do another check,
if that is the case, we assume that the current process is not idle process.
the pid of idle process is set to zero.
In the system, we don’t append the idle process to ready list. So we just copy the address of process 0
to the current process.
If the list is not empty, it’s a normal case, we use the exsiting code.
which means we just remove the item from the ready list and then we'll switch to the process.
As we just talked about, we don’t append the idle process to the ready list,
so we need to add another check in the yield function.
If the current process is idle process and it’s about to give up cpu resource, we will not append it to the list
.
So if we find that the process is not idle process, we will append it to the ready list.
After we return from the main function, we are running as the idle process, so launch function
is not used.
Alright, that's it for the process module. In function initialize idle process, we use function read cr3.
read cr3 is implemented in the assembly code.
So we add it in the trap assembly file.
Just as we did with read cr2
we copy the value of cr3 to rax and return.
Global read cr3 so that we can reference it in the c module
In the trap.h file,
we add the declaration of read cr3
OK, now we can use function read cr3
Before we build the project, let’s first take a look at the linker script.
In this section, the kernel file is stored in the fat16 image,
So we should initialize the data in bss section before we use them.
So here we define two symbols bss start and bss end to represent the beginning and end of the bss section
.
Then we can references the symbols in the C file.
the initialization of bss section is done in main.c file.
print function is not used in this example, and we uncomment code here.
Remember launch function is not used in this section, so we got rid of it.
Now, let's reference those two symbols so we extern bss start
and bss end
Then we calculate the size of the bss section.
after we get the size of bss section, we initialize it with function memset
OK.
Now we have finished the kernel part. Let's build the kernel project.
OK, what we are going to do next is we are going to build the first user program, In the previous lectures,
user1 is used to do the cleanup for the killed process. In this section
we don’t use the program this way. At this point,
the only thing we will do is print message and counter value every 1 second
.
Second.
So we define variable i
and initialize it with 0,
then we print user process
and the value.
Here we sleep
for 1 second and then we increment
the value i.
OK, let's build the user project.
Instead of writing the binary file into the image, we mount the fat16 image and copy the kernel file
and user file to the partition.
Once we copy the binary files into the image, we unmount the partition.
And the run bochs to see the result.
OK, now, as you can see, the user process is printed on screen.