Virtualization
Notes and Code I’ve taken from University of Wisconsin OSTEP on Operating Systems!
Virtualization: CPU Introduction
Most fundamental abstraction that the OS provides to users is the Process. A Process by defintion is a just running program, an Operating System will run our processes. The first thing we have to virtualize is the CPU. How does a CPU work? By running one process, then stopping it and running another, and so forth. But we only have one CPU. The illusion that many virtual CPUs exist, the basic technique to virtualize a CPU is the technique known as time sharing of the CPU, which allows the users to run as many concurrent processes as they would like.
To implement a virtualization of the CPU the OS will need both low-level machinery and some high-level intelligence. We will call the low-level machinery mechanism; which are low-level methods or protocols that implement a needed piece of functionality. To implement sharing of the CPU we will soon learn about context switch, which gives the OS the ability to stop one running program and start running another on a given CPU. This is a modern time-sharing mechanism. On top of mechanisms we have things we call policies, which are alogorithms for making some kind of decision within the OS. For our example above we’d have a scheduling policy that would decide which programs to run first.
Process API
Consists of *calls programs** that can mkae relarted processes.
Think the GUI of the Operating Systems, more on this subject later but here are some valuable resources that APIs can do:
Create:
Destroy:
Wait:
Miscellaneous Control:
Status:
Process Creation
First: A program code is loaded into memory, into the address space of the process
Second: The Programs run-tie stack is allocated.
Third: The Program’s heap is created.
Fourth: The OS does some other initialization task
Fifth: The program is started at main()
, the OS transfers control of the CPU to the newly-created process.
Process States
A Process can be in one of these three states:
- Running: The Process is running on a processor, it is executing instructions.
- Ready: The Process is ready to run, but for some reason the OS has chosen not to run it at the given moment.
- Blocked: Process has performed some kind of operation and it is not ready to run until some other event takes place.
A Process can be moved between the ready and running states.
Data Structures
To keep track of the state of each Process, the OS will likely keep some kind of Process Lists for all the processes that are ready and some additional information to track which process is currently running.
Another data structure included wouold be a Register Context, which would
PCB (Process Control Block) * A C-structure that contains information about each process.
// Saved registers for kernel context switches.
struct context {
uint64 ra;
uint64 sp;
// callee-saved
uint64 s0;
uint64 s1;
uint64 s2;
uint64 s3;
uint64 s4;
uint64 s5;
uint64 s6;
uint64 s7;
uint64 s8;
uint64 s9;
uint64 s10;
uint64 s11;
};
enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };
// Per-process state
struct proc {
struct spinlock lock;
// p->lock must be held when using these:
enum procstate state; // Process state
void *chan; // If non-zero, sleeping on chan
int killed; // If non-zero, have been killed
int xstate; // Exit status to be returned to parent's wait
int pid; // Process ID
// wait_lock must be held when using this:
struct proc *parent; // Parent process
// these are private to the process, so p->lock need not be held.
uint64 kstack; // Virtual address of kernel stack
uint64 sz; // Size of process memory (bytes)
pagetable_t pagetable; // User page table
struct trapframe *trapframe; // data page for trampoline.S
struct context context; // swtch() here to run process
struct file *ofile[NOFILE]; // Open files
struct inode *cwd; // Current directory
char name[16]; // Process name (debugging)
};
Here above is snippet of the proc.h
file in the XV6 Operating Systems under the kernel file. We can see there are many other processes besides the basics we discussed above.
Summary of Virtualization: CPU Introduction
- Here I will talk about a quick summary of what we discussed above
CPU- Process API
Focus on Operating System APIs and how to use them. We will discess process creation in UNIX systems.
System Call: fork()
The fork()
system call is used to create a new process. Note that is does have a strange routine, which we will see below.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{
printf("hello world (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0) {
// fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
// child (new process)
printf("hello, I am child (pid:%d)\n", (int) getpid());
} else {
// parent goes down this path (original process)
printf("hello, I am parent of %d (pid:%d)\n",
rc, (int) getpid());
}
return 0;
}
fork() call process code
Lets run this program ./p1
and the output is:
hello world (pid:7708)
hello, I am parent of 7709 (pid:7708)
hello, I am child (pid:7709)
System Call: wait()
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int
main(int argc, char *argv[])
{
printf("hello world (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0) {
// fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) {
// child (new process)
printf("hello, I am child (pid:%d)\n", (int) getpid());
sleep(1);
} else {
// parent goes down this path (original process)
int wc = wait(NULL);
printf("hello, I am parent of %d (wc:%d) (pid:%d)\n",
rc, wc, (int) getpid());
}
return 0;
}