|
Your first midterm examination will appear here. It covers through Chapter 4.
It will be a period-long, closed book exam.
Expectations:
- See course goals and objectives
- See Objectives at the beginning of each chapter
- See Summary at the end of each chapter
- Read, understand, explain, and desk-check C programs
- See previous exams:
Questions will be similar to homework questions from the book. You will
be given a choice of questions, e.g., "Write on four out of five questions."
I try to ask questions I think you might hear in a job interview. "I
see you took a class on Operating Systems. What can you tell me about ...?"
See also Dr. Barnard's notes.
He was using a different edition of our text, so chapter and section numbers
do not match, but his chapter summaries are excellent.
Homework on your own:
Questions similar to these might appear on exams:
- Chapter 2: 2.3, 2.8, 2.14, 2.15
- Chapter 3: 3.2, 3,3, 3.4, 3.5
- Chapter 4: 4.2, 4.3, 4.4, 4.6, 4.8
Any others are fair game, too.
Question:
Are you trained somewhere that to write in paragraphs? Yes, that is often
the preferred means, but in many technical circumstances, including this
exam, outlines, enumerations, bullets, and pictures may communicate more
effectively. At the very least, you should answer by question parts:
-
-
-
Directions:
Read and follow the directions.
- Write on four of the five questions. If you write more
than four questions, only the first four will be graded. You do not get
extra credit for doing extra problems.
- Read the entire exam. If there is anything you do not understand about
a question, please ask at once.
- If you find a question ambiguous, begin your answer by stating clearly
what interpretation of the question you intend to answer.
- Begin your answer to each question at the top of a fresh sheet of
paper. [Or -5]
- Be sure I can read what you write.
- Write only on one side of a sheet.
- Be sure your name is on each sheet of paper you hand
in. [Or -5]
Score Distribution:
Problem 1 Structure of an Operating System
Describe the basic structure of an operating system.
Taken from Course Objectives
See Section 1.4, or see the book's Table of Contents:
- Provides an environment in which programs are executed
- Manages processes
- Provides interprocess communication
- Manages memory
- Manages file systems
- Manages mass storage
- Manages I/O
- Manages network communications
- Provides protection and security
You might also mention
- Layers
- Bootstrapping
- Kernel vs. user level
- User interface, although, what is the UI for an embedded OS?
- Interrupt handling
- . . .
Components might be
- Process manager
- Interprocess message manager
- Memory manager
- File manager
- Disk manager
- I/O Manager
- Protection manager
- Network manager
- Interrupt handler
- User interface
Problem 2 Tricky C
Here is a tricky C program:
#include <stdlib.h>
#include <stdio.h>
#define MAX 127
int i = 23456;
int getNumber(void)
{
printf ("In getNumber, i = %d\n", i);
return ++i;
}
void main(int argc, char** argv)
{
int *array;
int index = 0;
int current = 0;
int j = 0;
while ( EOF != (current = getNumber()) ) /* EOF has the value -1 */
{
printf ("In while, index, current = %d, %d\n", index, current);
if (! index) { array = (int *)malloc(MAX); }
array[index++] = current;
index &= MAX;
}
for (; j < index; j++)
{
printf("%10d: %10d\n", j, array[j]);
}
}
What does it print? Explain.
When I run it with a particular compiler, I get
In getNumber, i = 23456
In while, index, current = 0, 23457
In getNumber, i = 23457
In while, index, current = 1, 23458
. . .
In getNumber, i = 23583
In while, index, current = 127, 23584
In getNumber, i = 23584
In while, index, current = 0, 23585
*** [a] Error -1073741819
Dr. Brylow writes:
Looks like it ought to read and store an arbitrary list of numbers, but
it has three major problems: the malloc
size is only one fourth of what is intended, the return value from malloc()
is not checked against NULL, and most importantly, each successive
dynamically allocated block is lost because the pointers aren't being stored
anywhere else. Also, it is just badly written, with the last line depending
on a fragile property of the MAX constant being one less than a power of 2.
Having a macro named "EOF" does not imply there is a file
anywhere. That is bad, confusing programming practice. It violates the Principle
of Least Astonishment, but it runs, and you will see worse
in the wild.
Several students suggested one of the counters might overflow. Try it. What happens?
Scores on this problem suggest you may need to see tricky C programs on subsequent exams.
Be warned.
Problem 3 Processes
Suppose we wish to construct a class Process {...} to encapsulate data and
operations for a process
- List at least six attributes a class Process should
have. For each, give in a pseudo code
- Meaningful name
- Data type
- By what name does our text refer to the set of attributes of our class
Process? Explain.
- In an object-oriented design, our class Process has
a constructor
Process( ... ). List the tasks that must be accomplished
by the constructor.
- What operating system call we have studied is the place from which
the constructor Process() logically is called? Explain.
- In an object-oriented design, our class Process has
a destructor.
List the tasks that must be accomplished by the destructor.
- Who calls the Process destructor? Explain.
Hint: Some of these questions are discussed in the book
and in class, but some are not. In some cases, I am asking you to make an
educated guess and justify your guess.
- (6 points) See Section 3.1.3
- Process state: Implicit by the queue in which PCB is linked
- Program counter: Register image
- CPU registers: Register images
- CPU scheduling information
- Pointers to child processes
- Pointers to child threads
- Memory management information
- File management information
- Accounting information
- I/O management information
The Process ID is not necessarily an attribute, although it often is. The
process is known to the operating system not exactly by its ID, but by a pointer
to its PCB,
- (3 points) Process Control Block (PCB)
- (6 points) Tasks include
- Allocate memory for PCB
- Initialize most PCB attributes
- Allocate memory for code and data
- Load memory with executable
- Insert PCB into appropriate scheduling queue, probably Ready
- Call Scheduler or return to calling process
The constructor must execute in the address space of the process that calls it, not in its
own address space, because its own address space does not exist yet.
- (3 points) fork()
- (6 points) Undo the work of the constructor, including
- Release any resources, including files, locks, semaphores, messages
- Especially release process memory
- Remove PCB from any scheduling queues and waiting lists
- Release PCB memory
- Call Scheduler
- (5 points) We did not discuss that, but we DID talk about different
circumstances under which a process terminates, including
- Process finishes. It could call its own destructor
- Process finishes, usually with an operating system call to exit(), so exit() should call
the process destructor. In this (and most other) case, the Process destructor runs
in the address space of the caller, not in the address space of its own object.
- Another process (especially an operating system process) can kill(pid)
a process, so the operating system call kill() should call the process
destructor
- In a graceful shutdown, the operating system should terminate all running
processes, calling the destructor of each, probably via kill(pid)
A common thread of these problems is that in C, you are responsible for memory allocation and deallocation.
Problem 4 Threads
Suppose we wish to construct a class Thread {...} to encapsulate
data and operations for a thread
- List at least six attributes a class Thread should
have. For each, give in a pseudo code
- Meaningful name
- Data type
In addition, include a paragraph of explanation.
- In an object-oriented design, our class Thread has
a constructor
Thread( ... ). List the tasks that must be accomplished
by the constructor.
- From where is the constructor Thread() logically called?
Explain.
- In an object-oriented design, our class Thread has
a destructor. List the tasks that must be accomplished by the destructor.
Hint: Some of these questions are discussed in the book
and in class, but some are not. In some cases, I am asking you to make an
educated guess and justify your guess.
- Our book does not address this question. A thread is derived from a
process in an operating sense, although not exactly in an object-oriented
sense. Logically, the PCB
should include as an attribute pointers to its constituent threads. Attributes
for a Thread include things that differ between threads belonging to the
same process, such as
- Thread ID, although that may be maintained as a pointer to the thread
- Parent process
- Thread state: Implicit by the queue in which TCB is linked
- Program counter: Register image
- CPU registers: Register images
- CPU scheduling information
- Memory management information
- Accounting information
It is a very interesting question whether data such as control block for open
files are shared or individual for threads belonging to the same process
Book does not give a name, but I'd call it a Thread Control Block (TCB)
- Tasks include
- Allocate memory for TCB
- Initialize most TCB attributes
- Allocate memory for data
- Load memory
- Insert TCB into appropriate scheduling queue, probably Ready
- Call Scheduler or return to parent Process
- Whomever spawns a new thread. Name depends on the system. Pthreads: pthread_create();
Win32: CreateThread(); Java: constructor Thread()
- Undo the work of the constructor, including
- Release any resources, including files, locks, semaphores, messages
- Especially release thread memory
- Remove TCB from any scheduling queues and waiting lists
- Release TCB memory
- Call Scheduler or return to parent Process
- We did not discuss that, but different circumstances under which a thread
terminates are probably similar to those under which a process terminates, including
- Thread finishes. It could call its own destructor
- Process finishes. The process destructor could call the destructor for each child thread.
- Process finishes, usually with an operating system call to exit(), so exit() should call
the process destructor, which could call the destructor for each child thread.
- Another thread (or an operating system process) can kill(tid) a thread,
so the operating system call kill() should call the thread destructor
Again, explaining what memory is allocated for what purpose and the deallocation of that memory
is key.
How are threads related to processes? How are they different?
Problem 5 Interprocess communication by send() and receive()
Suppose an operating system implements interprocess communication
with blocking send and blocking receive primitives.
Case 1: Process Q
executing
send (P, message);
is blocked until process P executes
receive (Q, message);
That is, process Q does not return from its send() operating systems function
call
until process P returns from its receive() operating systems function call.
Case 2: Similarly, a process P executing
receive (Q, message);
is blocked until process Q executes
send (P, message);
- In case 1, in what schedule state should process Q be placed while
it waits for process P to execute its receive()? Explain. Hint: The expected
answer is not quite as easy as it might seem.
- Given your answer to part a), describe the a sequence of actions by the
scheduler by which process P executes its receive(), and process Q eventually
ends up in the RUN state.
- Process Q is in whichever state you said in part a)
- Process P executes its receive()
- [You fill in as many steps here as necessary]
- Process Q is running
- Does this usually work? Give a possible sequence of events
in which process Q successfully sends a message to process P, and
both are able to continue processing.
- Show a scenario (a sequence of send() and receive() by both P and Q) under
which deadlock occurs.
Hint: "Deadlock" here means that P is waiting for
Q, and Q is waiting for P, so neither is able to make any progress. We will
discuss deadlock in more detail
next week.
Problem adapted from Problem 3, Midterm Exam, Spring 2000
- "Waiting" is the easy answer. You need to say "Waiting
for P to receive()." There is a different wait queue for each resource.
-1 if you said "Wait on ..." A servant waits
on. A peer waits for. Several students
suggested Q waits in the ready queue. That is a wait state,
not a ready state
- One possible sequence is:
- Process Q is in whichever state you said in part a)
- Process P executes its receive()
- The receive() operating system call notices that Q is waiting for P to receive
- The receive() call moves Q from the WaitingForPtoReceive queue to the Ready
queue
- Sometime later, the Scheduler is invoked
- Eventually, Q is the process selected by the Scheduler from the Ready list
to be made Running
- Process Q is running
- Whether it "usually" works is debatable. Here is one sequence which works:
Process P | | Process Q |
|
pid Q = ...;
char *message = "Hello";
. . .
send(Q, message);
. . .
|
| |
pid P = ...;
char *message;
. . .
receive(P, message);
. . .
|
- Here is one sequence that deadlocks:
Process P | | Process Q |
|
pid Q = ...;
char *message = "Hello";
. . .
send(Q, message);
. . .
|
| |
pid P = ...;
char *message = "Hello";
. . .
send(P, message);
. . .
|
Yes, they are the same. Won't work, will it?
A process sending a message to itself (or receiving a message from itself)
counted, but it probably does not meet the official definition of deadlock we'll meet
next week.
- Implement a time-out. That is, both send() and receive() might wait for
a specified length of time. If the event for which the process was waiting
has not occurred, the function returns anyway. Of course, they would
not have communicated a message, but they would not be waiting forever in a deadlock.
| |