mirror of
https://github.com/CoderSherlock/CoderSherlock.github.io.git
synced 2026-06-12 23:58:11 -07:00
Update post of os lab of scheduler.
This commit is contained in:
@@ -31,8 +31,8 @@ Remember, it's for helping in learning. DON'T COPY & PASTE CODE!
|
||||
|
||||
### First user process in xv6
|
||||
#### Kernel works
|
||||
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).
|
||||
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 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*.
|
||||
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();
|
||||
}
|
||||
~~~
|
||||
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()***.
|
||||
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.
|
||||
That's why its makes the creation of the first user process becomes so unique.
|
||||
Forking including clone the whole user virtual memory layout. However, the first process has no parent to fork from.
|
||||
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**.
|
||||
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()***.
|
||||
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**.
|
||||
|
||||
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),
|
||||
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.
|
||||
|
||||
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
|
||||
void
|
||||
userinit(void)
|
||||
@@ -275,4 +275,91 @@ main(void)
|
||||
[^ldman]: [ld\(1\) - Linux man page](https://linux.die.net/man/1/ld)
|
||||
[^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 can’t have one algorithm to fit all scenarios.
|
||||
|
||||
Xv6 by default has a round-robin scheduler.
|
||||
It’s 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);
|
||||
|
||||
}
|
||||
}
|
||||
~~~
|
||||
|
||||
It’s 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.
|
||||
It’s time to switch from the running scheduler to the selected process. Wait for a second, there are two questions we haven’t 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
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user