0% found this document useful (0 votes)
36 views32 pages

Lecture 1

Operating System

Uploaded by

Krishnendu Rarhi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
36 views32 pages

Lecture 1

Operating System

Uploaded by

Krishnendu Rarhi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 32

Dr.

Krishnendu Rarhi ©
Dr. Krishnendu Rarhi ©
Dr. Krishnendu Rarhi ©
Dr. Krishnendu Rarhi ©
Dr. Krishnendu Rarhi ©
Dr. Krishnendu Rarhi ©
Introduction to Operating System
So, what happens when a program runs?
• Well, a running program does one very simple thing: it executes
instructions.
• Many millions (and these days, even billions) of times every
second, the processor fetches an instruction from
memory, decodes it (i.e., figures out which instruction this is),
and executes it (i.e., it does the thing that it is supposed to do,
like add two numbers together, access memory, check a
condition, jump to a function, and so forth).
• After it is done with this instruction, the processor moves on to
the next instruction, and so on, and on, until the program finally
completes.
Dr. Krishnendu Rarhi ©
Of course, modern processors do many bizarre and
frightening things underneath the hood to make
programs run faster, e.g., executing multiple
instructions at once, and even issuing​and
completing them out of order! But that is not our
concern here; we are just concerned with the Another early name for
the OS was the
simple model most programs assume: that supervisor or even the
master control
instructions seemingly execute one at a time, in an program. Apparently,
the latter sounded a
orderly and sequential fashion. little overzealous (see
the movie Tron for
details) and thus,

There is a body of software, in fact, that is thankfully, “operating


system” caught on

responsible for making it easy to run programs instead.

(even allowing you to seemingly run many at the


same time), allowing programs to share a memory,
enabling programs to interact with devices, and
other fun stuff like that. That body of software is
called the operating system (OS), as it is in
charge of making sure the system operates
correctly and efficiently in an easy-to-use manner.
Dr. Krishnendu Rarhi ©
Virtualization
• The OS takes a physical resource (such as the processor, or
memory, or a disk) and transforms it into a more general, powerful,
and easy-to-use virtual form of itself. Thus, we sometimes refer to
the operating system as a virtual machine.
• In order to allow users to tell the OS what to do and thus make use of
the features of the virtual machine (such as running a program, or
allocating memory, or accessing a file), the OS also provides some
interfaces (APIs) that you can call.
• A typical OS, in fact, exports a few hundred system calls that are
available to applications. Because the OS provides these calls to run
programs, access memory and devices, and other related actions, we
also sometimes say that the OS provides a standard library to
applications.
Dr. Krishnendu Rarhi ©
Virtualization
• Since virtualization allows many programs to run (thus
sharing the CPU), and many programs to concurrently
access their own instructions and data (thus sharing
memory), and many programs to access devices (thus
sharing disks and so forth), the OS is sometimes known as
a resource manager.
• Each of the CPU, memory, and disk is a resource of the
system; it is thus the operating system’s role
to manage those resources, doing so efficiently or fairly or
indeed with many other possible goals in mind. To
understand the role of the OS a little bit better, let’s take a
look at some examples in the next lesson.
Dr. Krishnendu Rarhi ©
THE CRUX OF THE PROBLEM: HOW TO VIRTUALIZE RESOURCES

One central question we will answer in this course


is quite simple: how does the operating system
virtualize resources? This is the crux of our
problem. Why the OS does this is not the main
question, as the answer should be obvious: it
makes the system easier to use. Thus, we focus on
the how: what mechanisms and policies are
implemented by the OS to attain virtualization?
How does the OS do so efficiently? What hardware
support is needed?

Dr. Krishnendu Rarhi ©


Virtualizing The CPU
You can see our first program below. It doesn’t do
//cpu.c much. In fact, all it does is call Spin(),​a function
#include <stdio.h> that repeatedly checks the time and returns once
#include <stdlib.h> it has run for a second. Then, it prints out the
string that the user passed in on the command
#include "common.h" line, and repeats, forever.
int main(int argc, char *argv[]) {
Let’s say we save this file as cpu.c and decide to
if (argc != 2) { compile and run it on a system with a single
fprintf(stderr, "usage: cpu processor (or CPU as we will sometimes call it).
Here is what we will see:
<string>\n");
exit(1);
} prompt> gcc -o cpu cpu.c -Wall
char *str = argv[1]; prompt> ./cpu “A"
while (1) A
the system begins running the program, which
{ A repeatedly checks the time until a second has

printf("%s\n", str); A elapsed. Once a second has passed, the code prints
the input string passed in by the user (in this
Spin(1); A example, the letter “A”), and continues. Note the
program will run forever; by pressing “Control-c”
} ˆC (which on UNIX-based systems will terminate the

return 0; prompt> program running in the foreground), we can halt the


program.
} Dr. Krishnendu Rarhi ©
Virtualizing The CPU
//cpu.c prompt> ./cpu A & ./cpu B & ./cpu C & ./cpu D &
#include <stdio.h> [1] 7353
#include <stdlib.h> [2] 7354
#include "common.h" [3] 7355
int main(int argc, char *argv[]) { [4] 7356
if (argc != 2) { A
fprintf(stderr, "usage: cpu B It turns out that the operating system, with
<string>\n"); D some help from the hardware, is in charge of
exit(1); C this illusion, i.e., the illusion that the system
} A has a very large number of virtual CPUs.
char *str = argv[1]; B Turning a single CPU (or a small set of them)
while (1) D into a seemingly infinite number of CPUs and
{ thus allowing many programs to seemingly
C
printf("%s\n", str); run at once is what we call virtualizing the
A ... CPU, the focus of the first major part of this
Spin(1);
course.
}
return 0; If two programs want to run at a particular time,
} which should run?Dr. Krishnendu Rarhi ©
Virtualizing Memory
• The model of physical memory presented by modern machines is very simple.
Memory is just an array of bytes; to read memory, one must specify an address to be
able to access the data stored there; to write (or update) memory, one must also
• specify
Memorythe data to be
is accessed allwritten to the
the time whengiven address.
a program is running. A program keeps all of its
data structures in memory and accesses them through various instructions, like loads
and stores or other explicit instructions that access memory in doing their work. Don’t
forget that each instruction of the program is in memory too; thus memory is accessed
on each instruction fetch.

Dr. Krishnendu Rarhi ©


Virtualizing Memory The program does a couple of things. First, it
allocates some memory (line a1). Then, it
prints out the address of the memory (a2),
//mem.c and then puts the number zero (0 is passed
#include <unistd.h> to the program as argv[1]) into the first slot
#include <stdio.h> of the newly-allocated memory (a3). Finally,
#include <stdlib.h> it loops, delaying for a second and
#include "common.h" incrementing the value stored at the address
int main(int argc, char *argv[]) {
held in p. With every print statement, it also
if (argc != 2) { prints out what is called the process
fprintf(stderr, "usage: mem <value>\n"); identifier (the PID) of the running program.
exit(1); This PID is unique per running process.
}
int *p; prompt> ./mem 0
p = malloc(sizeof(int)); (2134) address pointed to by p:
//a1
assert(p != NULL); 0x200000
printf("(%d) addr pointed to by p: %p\n", (int) getpid(), (2134) p: 1
p); //a2 (2134) p: 2
*p = atoi(argv[1]); // assign value to addr stored in p.
//a3
(2134) p: 3
while (1) { (2134) p: 4
Spin(1); (2134) p: 5
//a4 ˆC
*p = *p + 1; This first result shown above is not too
printf("(%d) value of p: %d\n", getpid(), *p);
interesting. The newly-allocated memory is
}
return 0; at address 0x200000. As the program runs, it
} slowly updates the value and prints out the
result.

Dr. Krishnendu Rarhi ©


Now, we run multiple instances of this same
program again to see what happens. We see

Virtualizing Memory from the example that each running program


has allocated memory at the same address
(0x200000) and yet, each seems to be
updating the value
//mem.c at 0x200000 independently! It is as if each
#include <unistd.h>
#include <stdio.h>
running program has its own private
#include <stdlib.h> memory, instead of sharing the same
#include "common.h" physical memory with other running
programs.
int main(int argc, char *argv[]) { prompt> ./mem 0 & ./mem 0 &
if (argc != 2) { [1] 24113
fprintf(stderr, "usage: mem <value>\n"); [2] 24114
exit(1); (24113) address pointed to by p:
} 0x200000
int *p; (24114) address pointed to by p:
p = malloc(sizeof(int)); 0x200000
//a1 (24113) p: 1
assert(p != NULL); (24114) p: 1
printf("(%d) addr pointed to by p: %p\n", (int) getpid(), (24114) p: 2
p); //a2 (24113) p: 2
*p = atoi(argv[1]); // assign value to addr stored in p. (24113) p: 3
//a3 (24114) p: 3
while (1) { (24113) p: 4
Spin(1); (24114) p: 4
Each
... process accesses its own private virtual
//a4
address space (sometimes just called
*p = *p + 1;
printf("(%d) value of p: %d\n", getpid(), *p); its address space), which the OS somehow
} maps onto the physical memory of the machine.
return 0; A memory reference within one running program
} does not affect the address space of other
processes (or the OS itself); as far as the running
program is concerned, it has physical memory all
Dr. Krishnendu Rarhi © to itself. The reality, however, is that physical
memory is a shared resource, managed by the
Concurrency
The problems of concurrency arose first within the
operating system itself; as you can see in the
examples on virtualization, the OS is juggling many
things at once, first running one process, then
another, and so forth. As it turns out, doing so
leads to some deep and interesting problems.

Unfortunately, the problems of concurrency are no


longer limited just to the OS itself. Indeed,
modern multi-threaded programs exhibit the
same problems.

Dr. Krishnendu Rarhi ©


Concurrency You can think of a thread as a function running
#include <stdio.h>
#include <stdlib.h> within the same memory space as other functions,
#include "common_1.h" with more than one of them active at a time. In this
#include "common_threads.h" example, each thread starts running in a routine
called worker(), in which it simply increments a
volatile int counter = 0;
int loops;
counter in a loop for loops number of times.

void *worker(void *arg) {


int i;
for (i = 0; i < loops; i++) {
counter++;
}
return NULL;
}

int main(int argc, char *argv[]) {


if (argc != 2) {
fprintf(stderr, "usage: threads <loops>\
n"); The main program creates
exit(1); two threads using Pthread_
} create().
loops = atoi(argv[1]);
pthread_t p1, p2; The actual call should be to lower-
printf("Initial value : %d\n", counter); case pthread_create(); the upper-case
Pthread_create(&p1, NULL, worker, NULL); version is our own wrapper that
Pthread_create(&p2, NULL, worker, NULL); calls pthread_create() and makes sure that
Pthread_join(p1, NULL);
the return code indicates that the call
Pthread_join(p2, NULL);
printf("Final value : %d\n", counter); succeeded. See the code for details.
return 0; Dr. Krishnendu Rarhi ©
}
Concurrency Below is a transcript of what happens when we run this program with
the input value for the variable loops set to 1000. The value
#include <stdio.h> of loops determines how many times each of the two workers will
#include <stdlib.h> increment the shared counter in a loop. When the program is run with
#include "common_1.h" the value of loops set to 1000, what do you expect the final value
#include "common_threads.h"
of counter to be?
volatile int counter = 0;
int loops; prompt> gcc -o thread threads.c -Wall -pthread
prompt> ./thread 1000
void *worker(void *arg) { Initial value : 0
int i; Final value : 2000
for (i = 0; i < loops; i++) {
counter++; As you probably guessed, when the two threads are finished, the final value of the counter is
} 2000, as each thread incremented the counter 1000 times. Indeed, when the input value of
return NULL; loops is set to N, we would expect the final output of the program to be 2N. But life is not so
} simple, as it turns out. Let’s run the same program, but with higher values for loops, and see
what happens:
int main(int argc, char *argv[]) {
if (argc != 2) {
prompt> ./thread 100000
fprintf(stderr, "usage: threads <loops>\
n"); Initial value : 0
exit(1); Final value : 143012 // huh??
} prompt> ./thread 100000
loops = atoi(argv[1]); Initial value : 0
pthread_t p1, p2; Final value : 137298 // what the??
printf("Initial value : %d\n", counter);
Pthread_create(&p1, NULL, worker, NULL);
Pthread_create(&p2, NULL, worker, NULL);
Pthread_join(p1, NULL);
Pthread_join(p2, NULL);
printf("Final value : %d\n", counter);
return 0; Dr. Krishnendu Rarhi ©
}
Concurrency
prompt> ./thread 100000
Initial value : 0
Final value : 143012 // huh??
prompt> ./thread 100000
#include <stdio.h>
Initial value : 0
#include <stdlib.h>
#include "common_1.h"
Final value : 137298 // what the??
#include "common_threads.h" In this run, when we gave an input value of 100,000, instead of
getting a final value of 200,000, we instead first get 143,012.
volatile int counter = 0; Then, when we run the program a second time, we not only again
int loops;
get the wrong value, but also a different value than the last time.
void *worker(void *arg) { In fact, if you run the program over and over with high values
int i; of loops, you may find that sometimes you even get the right
for (i = 0; i < loops; i++) { answer!
counter++;
As it turns out, the reason for these odd and unusual
}
return NULL; outcomes relate to how instructions are executed, which is
} one at a time. Unfortunately, a key part of the program
above, where the shared counter is incremented, takes three
int main(int argc, char *argv[]) {
if (argc != 2) {
instructions: one to load the value of the counter from
fprintf(stderr, "usage: threads <loops>\ memory into a register, one to increment it, and one to store
n"); it back into memory. Because these three instructions do not
exit(1); execute atomically (all at once), strange things can happen.
} THE CRUX OF THE PROBLEM: HOW TO BUILD CORRECT
loops = atoi(argv[1]); CONCURRENT PROGRAMS
pthread_t p1, p2;
printf("Initial value : %d\n", counter);
When there are many concurrently executing threads within
Pthread_create(&p1, NULL, worker, NULL); the same memory space, how can we build a correctly
Pthread_create(&p2, NULL, worker, NULL); working program? What primitives are needed from the OS?
Pthread_join(p1, NULL); What mechanisms should be provided by the hardware? How
Pthread_join(p2, NULL);
printf("Final value : %d\n", counter); can we use them to solve the problems of concurrency?
return 0; Dr. Krishnendu Rarhi ©
}
Persistence
• In system memory, data can be easily lost, as devices such as DRAM
store values in a volatile manner; when the power goes away or the
system crashes, any data in memory is lost. Thus, we need hardware
and software to be able to store data persistently; such storage is
thus critical to any system as users care a great deal about their data.
• The hardware comes in the form of some kind
of input/output or I/O device; in modern systems, a hard drive is a
common repository for long-lived information, although solid-state
drives (SSDs) are making headway in this arena as well.
• The software in the operating system that usually manages the disk is
called the file system; it is thus responsible for storing any files the
user creates in a reliable and efficient manner on the disks of the
system.
Dr. Krishnendu Rarhi ©
Persistence
Unlike the abstractions provided by the OS for the CPU and memory, the OS does not create a private,
virtualized disk for each application.
Rather, it is assumed that oftentimes, users will want to share information that is in files.
For example, when writing a C program, you might first use an editor (e.g., Emacs) to create and edit the C file
(emacs -nw main.c).
Once done, you might use the compiler to turn the source code into an executable (e.g., gcc -o main main.c).
When you’re finished, you might run the new executable (e.g., ./main).
Thus, you can see how files are shared across different processes.
First, Emacs creates a file that serves as input to the compiler; the compiler uses that input file to create a new
executable file (in many
steps — take a compiler course for details); finally, the new executable is then run.
And thus, a new program is born!

Dr. Krishnendu Rarhi ©


Persistence
To accomplish this task, the program makes three calls into the
//io.c operating system. The first, a call to open(), opens the file and
#include <stdio.h> creates it; the second, write(), writes some data to the file; the
#include <unistd.h> third, close(), simply closes the file thus indicating the program
#include <assert.h> won’t be writing any more data to it. These system calls are
#include <fcntl.h> routed to the part of the operating system called the file
#include <sys/stat.h> system, which then handles the requests and returns some kind
#include <sys/types.h> of error code to the user.
#include <string.h>

int main(int argc, char *argv[]) {


int fd = open("/tmp/file", O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR |
S_IWUSR);
assert(fd >= 0);
char buffer[20];
sprintf(buffer, "hello world\n");
int rc = write(fd, buffer, strlen(buffer));
assert(rc == (strlen(buffer)));
fsync(fd);
close(fd);
return 0;
}

Dr. Krishnendu Rarhi ©


Persistence
• You might be wondering what the OS does in order to actually write
to disk.
• The file system has to do a fair bit of work: first figuring out where on
disk this new data will reside, and then keeping track of it in various
structures the file system maintains.
• Doing so requires issuing I/O requests to the underlying storage
device, to either read existing structures or update (write) them. As
anyone who has written a device driver knows, getting a device to
do something on your behalf is an intricate and detailed process. It
requires a deep knowledge of the low-level device interface and its
exact semantics. Fortunately, the OS provides a standard and simple
way to access devices through its system calls. Thus, the OS is
sometimes seen as a standard library.
Dr. Krishnendu Rarhi ©
Persistence
• Of course, there are many more details on how devices are accessed and
how file systems manage data persistently atop said devices. For
performance reasons, most file systems first delay such writes for a
while, hoping to batch them into larger groups. To handle the problems
of system crashes during writes, most file systems incorporate some kind
of intricate write protocol, such as journaling or copy-on-write, carefully
ordering writes to disk to ensure that if a failure occurs during the write
sequence, the system can recover to reasonable state afterward. To
make different common operations efficient, file systems employ many
different data structures and access methods, from simple lists to
complex b-trees. If all of this doesn’t make sense yet, good!

Dr. Krishnendu Rarhi ©


Persistence

THE CRUX OF THE PROBLEM: HOW TO STORE DATA


PERSISTENTLY
The file system is the part of the OS in charge of managing
persistent data. What techniques are needed to do so
correctly? What mechanisms and policies are required to do so
with high performance? How is reliability achieved, in the face
of failures in hardware and software?

Dr. Krishnendu Rarhi ©


Design Goals
Requirements
So, now you have some idea of what an OS actually does:
it takes physical resources, such as a CPU, memory, or
disk, and virtualizes them. It handles tough and tricky
issues related to concurrency. And it stores
files persistently, thus making them safe over the long-
term. Given that we want to build such a system, we
want to have some goals in mind to help focus our design
and implementation and make trade-offs as necessary;
finding the right set of trade-offs is a key to building
systems.
Dr. Krishnendu Rarhi ©
Design Goals
Abstractions
One of the most basic goals is to build up
some abstractions in order to make the system convenient
and easy to use. Abstractions are fundamental to everything
we do in computer science. Abstraction makes it possible to
write a large program by dividing it into small and
understandable pieces, to write such a program in a high-level
language like C without thinking about assembly, to write code
in assembly without thinking about logic gates, and to build a
processor out of gates without thinking too much about
transistors. Abstraction is so fundamental that sometimes we
forget its importance, but we won’t here.
Dr. Krishnendu Rarhi ©
Design Goals
High performance: minimum cost
One goal in designing and implementing an operating system
is to provide high performance; another way to say this is our
goal is to minimize the overheads of the OS. Virtualization
and making the system easy to use are well worth it, but not at
any cost; thus, we must strive to provide virtualization and
other OS features without excessive overheads.
These overheads arise in a number of forms: extra time (more
instructions) and extra space (in memory or on disk). We’ll seek
solutions that minimize one or the other or both, if possible.

Dr. Krishnendu Rarhi ©


Design Goals
Protection
Another goal will be to provide protection between
applications, as well as between the OS and applications.
Because we wish to allow many programs to run at the same
time, we want to make sure that the malicious or accidental
bad behavior of one does not harm others; we certainly don’t
want an application to be able to harm the OS itself (as that
would affect all programs running on the system). Protection is
at the heart of one of the main principles underlying an
operating system, which is that of isolation; isolating
processes from one another is the key to protection and thus
underlies much of what an OS must do.
Dr. Krishnendu Rarhi ©
Design Goals
Reliability
The operating system must also run non-stop; when it
fails, all applications running on the system fail as well.
Because of this dependence, operating systems often
strive to provide a high degree of reliability. As
operating systems grow ever more complex (sometimes
containing millions of lines of code), building a reliable
operating system is quite a challenge — and indeed,
much of the on-going research in the field focuses on this
exact problem.

Dr. Krishnendu Rarhi ©


Design Goals
Other goals
Other goals make sense: energy-efficiency is important
in our increasingly green world; security (an extension
of protection, really) against malicious applications is
critical, especially in these highly-networked
times; mobility is increasingly important as OSes are run
on smaller and smaller devices. Depending on how the
system is used, the OS will have different goals and thus
likely be implemented in at least slightly different ways.

Dr. Krishnendu Rarhi ©

You might also like