Update post of os lab of scheduler.

This commit is contained in:
2022-04-03 00:06:08 -04:00
parent c7cff6bced
commit 4707d969fe
2 changed files with 193 additions and 21 deletions
+96 -9
View File
@@ -31,8 +31,8 @@ Remember, it's for helping in learning. DON'T COPY & PASTE CODE!
### First user process in xv6 ### First user process in xv6
#### Kernel works #### Kernel works
In xv6, as the same as conventional linux OS, the very first user level process is **init**. In xv6, as the same as conventional linux OS, the very first user-level process is **init**.
Before **init**'s running, all the OS bootstraps are happened in a high privileged mode(kernel level). Before **init**'s running, all the OS bootstraps happen in a highly privileged mode(kernel level).
Xv6's kernel has the entry point as the main function located in the file *main.c*. Xv6's kernel has the entry point as the main function located in the file *main.c*.
The main function invokes 17 functions to set up kernel page tables, interrupt handlers, I/O devices and etc. The main function invokes 17 functions to set up kernel page tables, interrupt handlers, I/O devices and etc.
@@ -65,26 +65,26 @@ main(void)
mpmain(); mpmain();
} }
~~~ ~~~
It's tricky since that **init** is a user process, but kernel can't call any user level system calls to create it. It's tricky since that **init** is a user process, but kernel can't call any user-level system calls to create it.
Why? 1. Kernel has all privileges to create a user process. So it doesn't need to call system calls such as ***fork()***. Why? 1. Kernel has all privileges to create a user process. So it doesn't need to call system calls such as ***fork()***.
And 2. All other user processes can be created by forking from its parent. And 2. All other user processes can be created by forking from its parent.
Forking including clone the whole user virtual memory layout. However, first process have no parent to fork from. Forking including clone the whole user virtual memory layout. However, the first process has no parent to fork from.
That's why its makes the creation of the first user process becomes so unique. That's why it makes the creation of the first user process becomes so unique.
In *proc.c*, ***userinit()*** define there gives us the whole procedure of creating **init**. In *proc.c*, ***userinit()*** define there gives us the whole procedure of creating **init**.
Similar to the ***fork()***, but more simple. Similar to the ***fork()***, but more simple.
Process control block(structures for storing the process status) was created at the very first by calling ***allocproc()***. Process control block(structures for storing the process status) was created at the very first by calling ***allocproc()***.
After then, by invoking ***setupkvm()***(defined in *vm.c*), kernel memory map was setup for the process. After then, by invoking ***setupkvm()***(defined in *vm.c*), kernel memory map was setup for the process.
During setting up kernel memory map, a page size virtual memory will assigned to the process as ready. During setting up kernel memory map, a page size virtual memory will be assigned to the process as ready.
And later, this page size memory will be used to store instructions of **init**. And later, this page size memory will be used to store instructions of **init**.
Followed by setup kernel stack for the **init** process, calling ***inituvm()*** will load **init**'s text into the page that just being allocated. Followed by setup kernel stack for the **init** process, calling ***inituvm()*** will load **init**'s text into the page that is just being allocated.
***inituvm()*** takes 3 arguments: a pointer to the process's page directory (p->pgdir), ***inituvm()*** takes 3 arguments: a pointer to the process's page directory (p->pgdir),
a char-type pointer declared from external which point to **init**'s text segment(_binary_initcode_start), and a char-type pointer declared from external which point to **init**'s text segment(_binary_initcode_start), and
a char-type pointer which point to an external integer as the size of the **init**'s text segment(_binary_initcode_size). a char-type pointer which points to an external integer as the size of the **init**'s text segment(_binary_initcode_size).
Simply put, it will load instructions of **init** into the memory. Simply put, it will load instructions of **init** into the memory.
So now, the problem becomes when and where did instructions for **init** has compiled into the kernel? So now, the problem becomes when and where did instructions for **init** have compiled into the kernel?
~~~c ~~~c
void void
userinit(void) userinit(void)
@@ -276,3 +276,90 @@ main(void)
[^objcopyman]: [3 objcopy - binutils mannual](https://sourceware.org/binutils/docs/binutils/objcopy.html) [^objcopyman]: [3 objcopy - binutils mannual](https://sourceware.org/binutils/docs/binutils/objcopy.html)
### Xv6's round robin schduler ### Xv6's round robin schduler
The Scheduler is the core of an operating system.
With the scheduling of processes, the kernel can achieve near-real-time execution of multiple workloads.
The scheduling problem is also an active aspect of computer science research.
You cant have one algorithm to fit all scenarios.
Xv6 by default has a round-robin scheduler.
Its controlled using two-level for-loops, where the top-level for-loop is an endless loop that will keep the scheduler busy running.
The second-level nested for-loop will iterate a data structure named Ptable where all control information for processes is stored.
Information including pid, process name, etc. is stored in a structure called proc. Ptable is an array of processes.
Every runnable process in the Ptable will run strictly 1 time tick until the for-loop reached the last process in the Ptable.
Then it will loop back to the top-level for-loop for the next iteration of processes.
~~~c
// In file proc.c
struct {
struct spinlock lock;
struct proc proc[NPROC];
} ptable;
// In file proc.h
struct proc {
uint sz; // Size of process memory (bytes)
pde_t* pgdir; // Page table
char *kstack; // Bottom of kernel stack for this process
enum procstate state; // Process state
int pid; // Process ID
struct proc *parent; // Parent process
struct trapframe *tf; // Trap frame for current syscall
struct context *context; // swtch() here to run process
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
};
~~~
~~~c
// In file proc.c
void
scheduler(void)
{
struct proc *p;
for(;;){
// Enable interrupts on this processor.
sti();
// Loop over process table looking for process to run.
acquire(&ptable.lock);
for(p = ptable.proc; p < &ptable.proc[NPROC]; p++){
if(p->state != RUNNABLE)
continue;
// Switch to chosen process. It is the process's job
// to release ptable.lock and then reacquire it
// before jumping back to us.
proc = p;
switchuvm(p);
p->state = RUNNING;
swtch(&cpu->scheduler, proc->context);
switchkvm();
// Process is done running for now.
// It should have changed its p->state before coming back.
proc = 0;
}
release(&ptable.lock);
}
}
~~~
Its not hard to understand why this logic makes a round-robin manner.
This is very important to understand how to pick a process to run because scheduling is about always picking the appropriate process to achieve higher performance.
You can always come up with some new ideas for designing a good scheduler policy.
Understanding how to switch from one process to another is equivalently important.
Once the process for the next time tick is selected.
Its time to switch from the running scheduler to the selected process. Wait for a second, there are two questions we havent answered.
1. What is the running scheduler?
2. How did the last running process stop running and give the CPU back to the scheduler?
+96 -11
View File
File diff suppressed because one or more lines are too long