mirror of
https://github.com/CoderSherlock/CoderSherlock.github.io.git
synced 2026-06-13 08:08:10 -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
|
### 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 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