Nachos Introductory Book-V2.5
Nachos Introductory Book-V2.5
Faculty of Sciences
Introductory Book
(2002)
Version 2.5
CSC2404/66204: Operating Systems
i
Contents
I Introduction 1
1 Course Specification 2
3 Study Chart 14
II Laboratory 9
ii
III Assignments 49
iii
Part I
Introduction
This part includes introductory information about this course. At the end of this part there is a
sample exam, with answers and explanations of how marks will be awarded.
1
Chapter 1
Course Specification
2
Chapter 2
2.1 Prerequisites
• Algorithms and Data Structures (CSC2401/66201) which has Advanced Procedural Pro-
gramming (CSC2400/66121) and Discrete Mathematics for Computing (MAT1101/64611)
as its prerequisites.
It is assumed that students taking this course already have the following knowledge and skills:
• digital logic, binary number systems, computer organization, CPU, registers, ALU, bus,
memory, machine instruction sets, addressing modes, I/O methods, interruption, and ma-
chine and assembly programming,
Those who do not have these knowledge and skills will find this course extremely difficult and are
strongly advised not to attempt it until ready.
We will use part of C++ as the languages for the programming assignment and lab sessions in this
course. While we do not assume your knowledge and skills in C++, your knowledge and skills in
C and abstract data types should be solid enough to enable you to pick up the C++ syntax and its
classes as abstract data types in a short period of time, say two weeks. If you are not able to do so,
you will find this course very difficult and are strongly advised not to attempt it until ready.
5
2.2 Assumption of UNIX skills
In order to be able to complete the laboratory work and programming assignments in this course,
you need to have a minimum of skills and knowledge about UNIX. LINUX is a UNIX operating
system. You need to know:
The book “Running LINUX” by Matt Welsh (published by O’Reilly Associates Inc.) is an excel-
lent introduction for everything you need to know about UNIX. I strongly recommend that every
external student have an own copy of it.
We assume that you have these UNIX skills and knowledge when we describe the laboratory ses-
sions in this book. Those who do not have these skills will find this course extremely difficult and
are strongly advised not to attempt it until ready.
If you decide to go ahead to study this course, you must order our departmental CDROM set
from USQ Bookshop now! The departmental CDROM comes in two sets, Set 1 containing course
material and additional software for Linux and Windows, and Set 2 containing a distribution of
Linux.
The CDROM Set 1 is essential for completing all the laboratory work and the two programming
assignments of this course. Set 2 is only necessary if you need a distribution CD set for installing
Linux on your computer.
An operating system is the fundamental software between computer hardware and all other software
in a computing system. It serves as a hardware resource manager which makes use of the hardware
much easier and more efficient. It also provides an interface to support any other software including
compilers, database systems, and application programs.
This course covers the design and implementation of three principle components of operating sys-
tems:
• Process Management
• Memory Management
6
This course is not about how to use operating systems as in other introductory courses of computing.
It concentrates on the concepts and techniques in design and implementation of operating systems.
In other words, this course is not a course about operating systems, but rather a course on the
operating system software itself. The knowledge and skills gained from this course are essential for
computer science professionals.
The teaching material of this course consists of two parts: the textbook and the source code of a
real operating system called NACHOS.
2.5.1 Textbook
The textbook used in this course is Silberschatz, A., Galvin, P., and Gagne, G., ”Operating System
Concepts”, 6th Edition., Addison-Wesley, Reading, Massachusetts, 2002, ISBN 0-471-41743-2.
Nachos is a working operating system written by Prof. Tom Anderson from the University of Cal-
ifornia at Berkeley for teaching operating systems. In this course we use Nachos as the system for
case study, laboratory exercises and programming assignments. While the textbook above gives
a thorough description of concepts of operating systems, the Nachos source code provides an il-
lustration of implementation of these concepts. Reading the Nachos source code is essential to
understand the concepts in the textbook. The Nachos source code is provided in the Selected Read-
ing of this course.
The most effective way to learn the design and implementation of operating systems is to examine
a real operating system at the source code level. The Nachos operating system has about 9,500
lines of C++ code with extensive comments. It is well designed and small enough to be used for
teaching. It has been used by many universities in the world for teaching operating systems.
7
2.6 Course Structure and Modules
The course consists of three parts with 10 Modules and 8 laboratory sessions altogether.
In Part I, we provide the introductory material on operating systems. This part serves as an extended
introduction to the subject. The following topics are covered in this part:
Part II and Part III are the core of the course, in which we examine the design and implementation
of three principle operating systems components.
When studying Modules 4–10, you are required to read the relevant parts of the Nachos source
code. An extensive guide for reading the Nachos source code is provided in the Study book of this
course. It is important to read both the textbook and the Nachos source code when you study these
Modules.
There are 8 laboratory sessions on Nachos. You need to complete these laboratory sessions on
your LINUX/PC system. The details of the Nachos system and LINUX/PC home laboratory are
provided in the following section.
An ancient Chinese proverb says: “I hear and I forgot, I see and I remember, I do and I understand”.
An operating system is a complicated artifact created by humans. The only way to study and learn
effectively the concepts and techniques of design and implementation of operating systems is to
8
examine and experiment with a real operating system at the source code level. However, most
operating systems are large and not suitable for teaching purposes. Prof. Andrew Tanenbaum from
Vrije University, the Netherlands, wrote a small UNIX-like operating system called MINIX for
teaching. More recently, Prof. Tom Anderson from the University of California, Berkeley wrote
the NACHOS operating system for the same purpose. This course uses the NACHOS operating
system.
The NACHOS operating system is used in this course for the following purposes:
The laboratory work and programming tasks are essential to this course. Eight laboratory sessions
are designed to enable you to complete the programming tasks.
You need to install Linux operating system on your home PC before experimenting with the Na-
chos system. You should purchase a CDROM set called “Mathematics and Computing CDROM”
published by our department from the USQ Bookshop. One of the CDROMs contains the Red-
Hat LINUX distribution 7.2 and the other all the teaching software of computing courses of this
semester including CSC2404/66204.
To install LINUX operating system, follow the instructions in the booklet that comes with the
CDROM set.
There are many documents in greater detail about Linux and Unix operating system in general.
After you install Linux operating system, read the further documents in the CDROM set.
The Department provides limited support for Linux installation for external students. Any questions
or problems related to Linux installation should be directed to
9
• phone: (07) 4631 1513 [(61-7) 4631 1513]
• email: [email protected]
There are three assignments and one final examination in this course.
• Assignment 2: Module 5
A 3-hour closed-book examination at the end of semester covers all the Modules of the course.
There is a web home page for this course which contains some last-minute information. The web
address of the page is
http://www.sci.usq.edu.au/courses/CSC2404/
Visit this home page first if you have any questions about the course.
If the home page still does not solve your problems, you can contact USQ Outreach or directly
email to [email protected] or [email protected].
10
2.11 Student Enquiries
You should carefully read the information provided in your Student Guide concerning contact de-
tails and support services.
If you have Internet access, USQAssist is the most efficient method for requesting support assis-
tance. It is a web self service facility for all students to:
By typing a keyword in the search field, you can find answers to many of the questions fre-
quently asked by students, including course troubleshooters. To access USQAssist, go to http:
//usqconnect.usq.edu.au/usqassist or click on the ‘Help’ option at USQConnect.
For all Australian Citizens and all students enrolled through the Office of Continuing and Profes-
sional Education
All administrative queries should be directed to the Distance Education Centre (DEC) Outreach
Services or your Regional Liaison Officer. Outreach Services can be contacted as follows:
Telephone: 07 46312285
Fax: 07 46361049
Web Form: http://usqconnect.usq.edu.au/usqassist
Email [email protected]
International students should contact their Local Support Office for further assistance. If there is no
Local Support Office in your country you should contact the International Office at USQ as follows:
Telephone: 61 7 46312362
Fax: 61 7 46362211
61 7 46359225
Web Form: http://usqconnect.usq.edu.au/usqassist
Email: [email protected]
11
2.11.2 USQConnect
USQConnect is a computer system which enables you to access information, services and program
resources on the Internet environment - the World Wide Web (WWW). To find out more about this
dynamic environment we recommend you look at the following URL regularly:
http://usqconnect.usq.edu.au
• access to up-to-date library catalogues, electronic journals and articles, and text databases,
• secure access to your enrolment details, course assignment and end of semester results,
• access to the USQAssist knowledge base - a list of common questions asked by students,
• Outreach Electronic Noticeboard for external students including Residential School and tele-
phone tutorial timetables, learning circles and other information,
The University of Southern Queensland is committed to continuous improvement, and seeks your
input to that process through your participation in our course evaluation process. Please complete
and return the questionnaire ‘Student Feedback on External Courses’ included later in this intro-
ductory book.
Your response will be processed so that, unless you wish otherwise, the course Examiner will not
be aware of your identity. Please help us to help our students by providing feedback on your
experiences in this course.
Please return the questionnaire before the end of this semester’s examination period.
1. Insert the completed questionnaire in an envelope, seal and address envelope as follows:
12
The Course Evaluation Co-ordinator
Information Technology Services
University of Southern Queensland
Toowoomba 4350
13
Chapter 3
Study Chart
14
Chapter 4
In this course, you need to use floppy disks to submit the source of your Nachos system for the
assignments. In this section, we are going to describe the commands you need to mount and
unmount floppy disk drive as well as to format a new floppy disk.
If you are going to use a new floppy disk or a floppy of other file systems such as MSDOS, you
need to format it before you can use it in LINUX. The command to format a floppy disk is:
# /sbin/mke2fs /dev/fd0
You need to become the superuser root to do that. Note that after you format an old disk, all the
files on it will be wiped out. Make sure that you do not need the contents of the disk before you
re-format it.
After you insert the floppy disk (and format it if it is a new floppy disk), you need to mount the
floppy disk drive to your LINUX file system. You need to become the superuser root to mount and
unmount the floppy drive.
mount: To mount the floppy disk drive, execute the following command as the superuser:
Here, /mnt/floppy is the mount point for the floppy disk. After mounting, you can access
the files on the floppy disk in directory /mnt/floppy/. Since directory /mnt/floppy/ is
owned by the superuser root, only the superuser can write files in it. Ordinary users can
only read readable files from this directory.
unmount: To unmount the floppy drive, execute the following commands (as the superuser root
again):
15
# sync
# umount /mnt/floppy
Before unmounting the floppy, you have to make sure that no users (including root) are
using /mnt/floppy/ as their current working directory; otherwise the system would show
the error that the directory is busy and does not allow you to unmount it.
After unmounting the floppy, you can remove the floppy disk from the drive.
You need to submit your Nachos system if you are required to complete a programming task in an
assignment. You will submit your Nachos code through a floppy disk in the LINUX file format.
Follow the steps below to make a floppy disk of your Nachos code:
(a) move to the directory where your nachos-3.4 directory is by using the cd command;
(b) make a tar file of your Nachos system, nachos.tar.gz, by typing the following command:
/sbin/mke2fs /dev/fd0
cp nachos.tar.gz /mnt/floppy
sync
umount /mnt/floppy
(i) remove the floppy disk and include it with your paper work for the assignment.
16
Chapter 5
FACULTY OF SCIENCES
Unit No: 66204 Unit Name: Operating Systems
Special Instructions:
This is an closed examination.
Calculators may not be used.
Programmable calculators are not allowed.
Students may keep this examination paper.
During perusal time, students are not permitted to write in the examination booklet or on
any other material submitted for assessment.
No examination booklet, used or otherwise, is to be taken from the examination room. Obey
the regulations on the Examination Booklet, and ensure that you place your name and stu-
dent number in the places provided.
Any non-USQ copyright material used herein is reproduced under the provisions of Section 200(1)(b)
of the Copyright Amendment Act 1980.
Question 1.
A modern operating system usually includes the facility for several processes to be running “simul-
taneously”, even when the computer has only one CPU. In this connection:
(a) List all the important situations in which control switches from one process to another.
(b) Describe all the processes which might be involved in the activity of transferring data from a
disk to a user process.
(c) Describe in general terms how the operating system selects which process should gain access
to the CPU next.
(d) Consider the code-fragments, one from the assembly language routine switch shown in
Figure 5.1, and another from the method Scheduler::Run, shown in Figure 5.2. Where,
precisely, in these fragments of code, does control switch between one thread and another?
(e) Explain the sequence of actions which leads to a thread being deleted.
Total: 11 marks
Answers to Question 1
Allocate 2 marks for each part of this question, together with one mark which is awarded if the
student displays a satisfactory understanding of how processes work in an operating system.
(a) Full marks will be gained for listing at least 2 of the following, or equivalent:
1. When a process requests an operating system service which requires waiting, e.g. reading
from a disk, writing to a network card, etc.;
2. When the system clock expires after the process with control has maintained control for
the entire duration of the most recent clock interval;
3. when an interrupt occurs, indicating, for example, that some other process is ready to take
control of the processor;
4. when a process invokes the semaphore wait (P()) method, and the resource is not available
(the sempaphore value is zero or lower).
(b) Full marks will be gained for listing at least 2 of the following, or equivalent:
1. The user process, which initiated the transfer;
2. the system kernel, which receives the request from the user process;
3. the driver process, which manages passing requests to the hardware and from the hardware
to the user process.
(c) The operating system maintains a list of processes which are “ready to run”, generally known
as the ready queue. Processes are removed from this queue in accordance with a discipline
which in some sense optimizes the performance of the operating system. Factors which might
be taken into account include: priority, expected duration of the next active period, memory
usage, whether the process is in memory or not (in fact, if the process is not in memory, the
kernel will have to swap it in as a stage before the process can be run), and so on.
#ifdef HOST_i386
...
.globl _SWITCH
_SWITCH:
movl %eax,_eax_save # save the value of eax
movl 4(%esp),%eax # move pointer to t1 into eax
movl %ebx,_EBX(%eax) # save registers
movl %ecx,_ECX(%eax)
movl %edx,_EDX(%eax)
movl %esi,_ESI(%eax)
movl %edi,_EDI(%eax)
movl %ebp,_EBP(%eax)
movl %esp,_ESP(%eax) # save stack pointer
movl _eax_save,%ebx # get the saved value of eax
movl %ebx,_EAX(%eax) # store it
movl 0(%esp),%ebx # get return address from stack into ebx
movl %ebx,_PC(%eax) # save it into the pc storage
ret
#endif // HOST_i386
void
Scheduler::Run (Thread *nextThread)
{
Thread *oldThread = currentThread;
SWITCH(oldThread, nextThread);
#ifdef USER_PROGRAM
if (currentThread->space != NULL) { // if there is an address space
currentThread->RestoreUserState(); // to restore, do it.
currentThread->space->RestoreState();
}
(d) control is transferred when the routine SWITCH returns control to the procedure which called
it. Any answer which suggests that control is transferred inside the routine SWITCH should
get at least half marks.
(e) The thread to be deleted declares that it should be deleted, by setting the global variable,
threadToBeDestroyed to point to itself. Next, this thread transfers control to another thread.
Finally, after control has been transferred, the new thread removes the one to be deleted,
inside the Run method.
Question 2.
(a) A fragment from the nachos code for a disk is shown in Figure 5.3. Explain the role of this
particular class in the nachos system.
(b) What methods of the disk class are used by the rest of the nachos code in the normal operation
of the disk?
(c) Explain the role (purpose) of an inode and how it is used in a file system to implement the
file construct in an operating system.
(d) What methods could be used to keep track of the free space on a disk? You may choose to
explain one method very carefully, or more than one method.
(e) What techniques are used to ensure that a file system is not permanently damaged when a
software or hardware failure causes a computer to cease normal operation during a disk IO
operation.
Total: 11 marks
Answer to Question 2
Allocate 2 marks for each of the following questions plus one mark if the student displays a rea-
sonable understanding of how file systems work.
(a) this class emulates (simulates) the physical disk of tt nachos. This is one aspect of tt nachos
which is not like a real operating system. In a real operating system, this bit would not be
simulated – it would be hardware.
(b) ReadRequest and WriteRequest are used by other parts of the nachos system to simulate
requests to the hardware to read, and to write data, and the routine HandleInterrupt is regu-
larly used when reads or writes finish. The constructor is only called once, when the system
starts, unbless there is more than one disk.
(c) An inode is used to record where on the disk the bytes of the actual file are stored. An inode
is basically a table, with each entry corresponding to one block, of about 512 bytes, (although
the precise size depends on the disk). The entry tells the user, and the computer, where each
of these collection of bytes is stored.
(d) The basic method is a table of bits, one bit per block, each of which says whether the specific
block, referred to (implicitly) by that bit is in use or not. Another method would be to have a
linked list of free blocks, or contiguous sequences of blocks.
(e) Two basic techniques are used: duplication of critcal information; and secondly, in some
situations, information is written to the disk as soon as possible, rather than caching this
information for collection later.
Question 3.
(b) How is it possible to simulate interrupts successfully in a program, such as nachos which
runs as a user process on a Unix computer.
(c) Explain how the loading of a program is simulated in the nachos system.
Total: 11 marks
Answer to Question 3
Give 4 marks for the first two parts and 3 for the last part.
(a) Basically, instructions are simulated one-by-one, advancing the clock at the end of each
instruction. Also, between each instruction, the list of pending interrupts is checked to see
if an interrupt is due, and if it is, it is declared to happen. The scheduling of interrupts is
handled by storing interrupts in this list of pending interrupts, and every time an interrupt is
scheduled, it is placed in this list in order. Interrupts are scheduled to happen by nachos code
such as the disk subsystem, which is simulating hardware, for example.
(b) Because the hardware is actually only simulated, the interrutpts are actually only simulated
also, and so it does not take system privileges to execute hachos code.
(c) The program is read from disk and stored in memory, in an array which simulates the nachos
main memory. When this occurs, the disk file is subdivided into memory blocks (that is not
the correct term). In addition, certain instructions in the program are expanded into a whole
collection of bits – for example, there are instructions which represent an array which is
initialised to zero.
Question 4.
(a) Explain how system calls are typically implemented in an operating system.
(b) Give examples of system calls which return immediately and system calls which cause con-
trol to be switched to a different process. In each case, explain why control is transferred
or not transferred, unless you feel that no explanation is necessary. Provide at least four
examples and at least one example of each sort.
Total: 11 marks
// disk.h
// Data structures to emulate a physical disk. A physical disk
// can accept (one at a time) requests to read/write a disk sector;
// when the request is satisfied, the CPU gets an interrupt, and
// the next request can be sent to the disk.
//
// Disk contents are preserved across machine crashes, but if
// a file system operation (eg, create a file) is in progress when the
// system shuts down, the file system may be corrupted.
//
// DO NOT CHANGE -- part of the machine emulation
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved. See copyright.h for copyright notice and limitation
// of liability and disclaimer of warranty provisions.
#ifndef DISK_H
#define DISK_H
#include "copyright.h"
#include "utility.h"
// The following class defines a physical disk I/O device. The disk
...
#define SectorSize 128 // number of bytes per disk sector
#define SectorsPerTrack 32 // number of sectors per disk track
#define NumTracks 32 // number of tracks per disk
#define NumSectors (SectorsPerTrack * NumTracks)
// total # of sectors per disk
class Disk {
public:
Disk(char* name, VoidFunctionPtr callWhenDone, _int callArg);
// Create a simulated disk.
// Invoke (*callWhenDone)(callArg)
// every time a request completes.
˜Disk(); // Deallocate the disk.
private:
...
};
Answer to Question 4
Give 6 marks for the first part and 5 for the last part.
(a) Normally there is an instruction of the computer which is used to call a system routine. If
this is not the case, it might be that there is an instruction which causes an exception, or a
software interrupt, which are much the same thing. In any of these cases, the interrupt to be
executed is specified as the first parameter, ie first entry on the stack. This interrupt number
is used to index into the interrupt vector, and in the case of system routines to index into the
table of system routines. This system routine is then invoked. Any remaining parameters on
the stack will be consumed.
(b) A system routine which requests a read operation, from a disk, will cause control to be
transferred, until such time as the read is complete. The same is true of network read or
write: control will be lost by the calling method. Another more benign exampe of a system
routine is a routine to gnerate the system time, or to remember the ID of another user.
Question 5.
(a) Suppose that an unknown number of threads need to make use of and alter an operating
system parameter, freebobs, denoting the number of free “bobs”. If this variable is updated
without any concern for inappropriate interaction between different processes, situations will
arise where it is set to the wrong value. Explain how one or more semaphores could be used
to overcome this difficulty.
(b) Describe the methods or procedures associated with a semaphore and the purpose of each
method.
Answer to Question 5
Give 4 marks for the first two parts and 3 for the last part.
(a) We could allocate a semaphore, eg mutex such that this semaphore must be held before the
number of freebobs can be changed. Thus, in any method or routine of the code, we would
put mutex.P() before the code which accesses the freebobs quantity and we would put put
mutex.V() afterwards.
(b) The methods, all of them, are P() and V(). The first of these will cause the calling process to
wait, if the semaphore value is zero or less. The second will cause the semaphore variable
to increase by one, and, if there is anyone wiating on the imlied list, the process at the head
of this list will be woken up.
(c) Here is an outline. It is assumed that these routines will be used only in a single-processor
machine.
void semaphore::P() {
inhibit interrupts;
if (the variable is > 0) {
decrease this variable by one;
open interrupts;
return;
} else {
place this process in the semaphore queue
and transfer control to another process
by means of SWITCH;
open interrupts;
return;
}
}
void semaphore::V() {
inhibit interrupts
if (the variable is < 1)
{
increase the variable by one;
SWITCH control to the process
at the head of the queue and
open interrupts;
} else {
increase the variable by one;
}
open interrupts;
}
Variations of the above are, of course, acceptable, so long as they provide the same function-
ality. In particular, it doesn’t greatly matter whether the counter keeps on decreasing or not
as the number of waiting processes increases.
Total: 11 marks
8
Part II
Laboratory
This part includes the laboratory exercises you are required to complete in your LINUX system.
The purpose is to prepare you for the programming tasks in the assignments and have a deep
understanding of how real operating systems work.
9
Chapter 6
6.1 Purpose
We assume that you already have installed the Red Hat LINUX 7.2 from our department CDROM
set. If you have not done so, read Section 2.8 about how to install it.
You need to install LINUX operating system on your PC before installing the Nachos system.
The instructions to install LINUX operating system on PC are described in Section 2.8 of this
introductory book.
The simplest way to install Nachos and gcc MIPS cross-compiler from the USQ Mathematics &
Computing CD is to use the command tar directly.
After you install LINUX from one of the CDs, mount another CD which contains courses soft-
ware. Move to the directory for this course. In the following, we assume the path of this directory
is /mnt/cdrom/USQ/CSC2404/ or similar. You will see the following files in the directory (or
something similar):
INSTALL gcc-2.8.1-mips.tar.gz
binutils-2.9.1.0.23-6.i386.rpm index.html
binutils-2.9.5.0.22-6.i386.rpm nachos-3.4-USQ01.tgz
10
nachos-3.4-USQ01.tgz is the file of the Nachos system package.
gcc-2.8.1-mips.tar.gz is the file of gcc MIPS cross-compiler.
Installation of Nachos
(a) cd
(c) cd CSC2404
(d) cp /cdrom/USQ/CSC2404/nachos-3.4-USQ01.tgz .
(f) rm nachos-3.4-USQ01.tgz
After these steps, you should have a directory named CSC2404 in your home directory. Directory
CSC2404 should contain a directory named nachos-3.4 which is the Nachos package.
The gcc MIPS cross-compiler is to be installed in /usr/local directory. You need to become root
(superuser) of your LINUX in order to install it.
(a) su -
(b) cd /usr/local
(c) cp /cdrom/USQ/CSC2404/gcc-2.8.1-mips.tar.gz .
(e) rm gcc-2.8.1-mips.tar.gz
After these steps, there should be a directory named mips in /usr/local, which contains gcc mips
cross-compiler.
Once you installed Nachos in your home directory, you can follow the steps below to test it:
11
• c++example: This subdirectory contains examples of simple C++ programs and a short
paper of programming language C++ (postscript file: c++.ps) written by Prof. Tom
Anderson. This article is also included in the Selected Reading of this course. The
purpose of these examples and the article is to provide a quick introduction to C++ for
programming with Nachos.
• code: This subdirectory contains the Nachos source code.
• doc: This directory is empty at the moment.
(b) Move into subdirectory code and you will see the following files and directories in it:
Here lab2/, lab3/, lab5/, lab7-8/ and ass2/ and ass3/ are your working directories for
laboratory sessions and assignments, respectively. The remaining directories are the original
Nachos directories.
The working directories for lab sessions 1, 4 and 6 are threads, filesys, and test and
userprog, respectively.
(c) Move into directory threads/ and execute command make. You should see that the Nachos
system is compiling. The last couple of lines of the output on the screen should be
If you get this far, you have successfully compiled the smallest core of the Nachos system.
You also should see symbolic link nachos in the current directly linked to
arch/unknown-i386-linux/bin/nachos.
(d) Now you can test your Nachos system by executing command nachos in the current direc-
tory. Note: it will be necessary to issue this command in the form ./nachos if your $PATH
environmental variable does not include the current directory (.). The output on the screen
should be as follows:
12
Ticks: total 130, idle 0, system 130, user 0
Disk I/O: reads 0, writes 0
Console I/O: reads 0, writes 0
Paging: faults 0
Network I/O: packets received 0, sent 0
Cleaning up...
The Nachos source code is written in C++. This course does not require prior knowledge of C++.
Of course, it is helpful if you have taken the course on object-oriented programming (66203) before.
C++ is a complicated object-oriented language. We only use the part of C++ related to abstract data
types and encapsulation. We do not use inheritance of C++ in Nachos. Dr. Tom Anderson wrote an
article for quick introduction of C++ for the purpose of using Nachos. We included this article in
the Selected Reading of this course. If you are not familiar with C++, read this article first.
There are three C++ example programs in c++example directory discussed in this article. You only
need to study program stack.cc. Read the code of stack.cc and make sure that you understand
it.
If you are not familiar with GNU debugger gdb, you may want to learn how to use it. You can
compile stack.cc by command make stack and run gdb for executable stack. You may need to
use gdb when you need to debug your programs in this course.
Let us use the c++ program in directory c++example/ to show the steps to trace programs using
gdb in emacs.
• In the c++example directory, compile the stack program by typing make stack.
– At the prompt above, type gdb command list. The first 10 lines of the main program
will be shown in the buffer as follows:
(gdb) list
120 }
13
121
122 //----------------------------------------------------------------------
123 // main
124 // Run the test code for the stack implementation.
125 //----------------------------------------------------------------------
126
127 int
128 main() {
129 Stack *stack = new Stack(10); // Constructor with an argument.
(gdb)
As you can see, the first statement of the main program is at line 129.
– Set a break point at line 129 by typing gdb command break 129 and you will see:
(gdb) break 129
Breakpoint 1 at 0x80488b6: file stack.cc, line 129.
(gdb)
– Then type gdb command run and the control will stop at the break point you just set
up. You will see:
(gdb) run
Starting program: /home/staff/ptang/units/204/98/linux/nachos-3.4/c++example/stack
//----------------------------------------------------------------------
// main
// Run the test code for the stack implementation.
//----------------------------------------------------------------------
int
main() {
=> Stack *stack = new Stack(10); // Constructor with an argument.
stack->SelfTest();
The arrow => above is the break point where the execution stops currently.
– In the original gdb buffer, continue to type gdb command next, (or just RETURN) and
you will see the arrow is moved to the next statement.
– In order to step into the method call stack->SelfTest(), type gdb command step.
You will see that the control steps into the method as follows:
14
void
Stack::SelfTest() {
=> int count = 17;
– you can print the value of any variable by gdb command print such as “count” or
“stack” in the program. Try this command to watch the values of any variables that you
think can demonstrate that the program is running correctly.
See the book “Running Linux” for more details of how to use emacs and gdb.
6.5 Clean Up
You need to clean up after you finish with Nachos in each directory. Simply execute make clean
and all the object files, dependency files, binary file nachos as well as the symbolic link nachos
in the current directory will be deleted. You can tell whether the directory is cleared by looking at
whether the symbolic link nachos exists or not.
6.6 Questions
(a) What C++ files are used to build Nachos in threads/? Where are they located?
(b) What header files do these files depend on?
(c) Explain why INCPATH in threads/Makefile.local is defined as INCPATH += -I../threads
-I../machine.
6.7 Things to do
15
Chapter 7
7.1 Purpose
• to know how to set up a separate directory to develop a new version of Nachos system.
As we mentioned in Lab 1, the Nachos system can be compiled in a number of Nachos directories,
../threads/, ../filesys/, etc.
In each of these directories, there are two makefiles, Makefile and Makefile.local. In the parent
directory ../code/, there are additional makefiles, Makefile.common and Makefile.dep, which
are shared and invoked by the makefiles in all the Nachos directories.
Thus, the structure of the makefiles is as follows:
../code/Makefile.common
/Makefile.dep
|
|
/threads/Makefile
/Makefile.local
|
|
/filesys/Makefile
/Makefile.local
|
|
..
16
7.2.1 Makefile
This is the makefile used by make program, when you build a Nachos in any Nachos directory by
typing command make or make all.
Examining this file reveals that it mainly includes two other makefiles:
include Makefile.local
include ../Makefile.common
7.2.2 Makefile.local
• CCFILES: the string to specify all the C++ files used to build the Nachos in this directory.
• INCPATH: the string to define the include path for g++ to search head files (.h files) specified
in the C++ programs.
Note that the assignment operator for INCPATH and DEFINES is +=, which means that the right-hand
side string is to be appended to the original contents of INCPATH and DEFINES.
7.2.3 Makefile.dep
The system-dependent macros defined by Makefile.dep includes: HOST, arch, CPP, CPPFLAGS,
GCCDIR, LDFLAGS and ASFLAGS. The definitions for the LINUX systems is:
17
arch = unknown-i386-linux
ifdef MAKEFILE_TEST
#GCCDIR = /usr/local/nachos/bin/decstation-ultrix-
GCCDIR = /usr/local/mips/bin/decstation-ultrix-
LDFLAGS = -T script -N
ASFLAGS = -mips2
endif
endif
Here, GCCDIR is the prefix for the gcc mips cross-compiler. Its definition shows why you need to
install it in the /usr/local/ directory. This makefile also defines other macros dependent on the
system-dependent macros above. They are:
arch_dir = arch/$(arch)
obj_dir = $(arch_dir)/objects
bin_dir = $(arch_dir)/bin
depends_dir = $(arch_dir)/depends
These macros show that in each of the system-dependent directories in arch directory, there are
three directories to accommodate object codes, binary executable and dependence files, respec-
tively. For example, in the
arch/unknown-i386-linux/ for your linux system, there are directories as follows:
[ptang@probus unknown-i386-linux]$ ls
bin/ depends/ objects/
7.2.4 Makefile.common
The file Makefile.common is the most complicated one and it defines all the rules for compiling a
completed Nachos system.
It first includes the Makefile.dep. Then it defines the vpaths for various kinds of files as follows:
It tells make where to find files if it cannot find them in the current directory. This is why you can
build a Nachos in a new directory (other than ../threads/, ../filesys/) without copying the
files which you do not need to modify.
This file then defines macros for object files ( ofiles = $(cc ofiles) $(c ofiles) $(s ofiles))
, CFLAGS, and the ultimate target
(program = $(bin dir)/nachos). These definitions show that we are going to build the binary
executable named nachos in the directory $(bin dir) which is arch/unknown-i386-linux/bin/
in your linux system. The rule to build that target is defined in the following lines:
$(bin_dir)/% :
@echo ">>> Linking" $@ "<<<"
$(LD) $ˆ $(LDFLAGS) -o $@
ln -sf $@ $(notdir $@)
18
This rule is a static pattern rule. The % in the target can match any non-empty string in the target
of other rules and these multiple rules will be combined to define the dependence. In our case, this
rule is to be combined with rule:
$(program): $(ofiles)
The rule to make object codes from the C++ source codes is:
$(obj_dir)/%.o: %.cc
@echo ">>> Compiling" $< "<<<"
$(CC) $(CFLAGS) -c -o $@ $<
It is a static rule again. The % is to match any non-empty string. For example, this rule tells how
to make arch/unknown-i386-linux/objects/main.o from main.cc. However, the object code
should also depend on many head files (.h files) included by main.cc. This dependence relation
for these head files is actually specified by another rule generated automatically.
First of all, we need to know which head files are included (directory and indirectly) by the C++
source file during the compilation. g++ can do the search automatically for you. All you have to do
is to use option -MM. Let us do some experiments. In the ../threads/ directory, do the following
(Use one line for the command. I split it here for clarity of presentation):
This is the list of all the head files on which main.o depends. If any of these head files is updated,
the main.cc should be re-compiled to make a new main.o. You can also see that the output of the
above command is actually a rule which can be included in the Makefile.common. This is exactly
what is done by the remainder of the Makefile.common.
First of all, the makefile builds a dependence file in directory
arch/unknown-i386-linux/depends/ for each source code file. The rule to do that is:
19
$(depends_dir)/%.d: %.cc
@echo ">>> Building dependency file for " $< "<<<"
@$(SHELL) -ec ’$(CC) -MM $(CFLAGS) $< \
| sed ’\’’s@$*.o[ ]*:@$(depends_dir)/$(notdir $@) $(obj_dir)/&@g’\’’ > $
@’
Here CC is g++ and CFLAGS is the same flag which would be used for the real compiling. Note
the -MM option of g++. The rest of command is just to create a dependence file in directory
arch/unknown-i386-linux/depends/ after appending the prefix arch/unknown-i386-linux/objects/
to the object file name.
For example, for main.cc, this rule will create a new file named main.d in directory arch/unknown-i386-linux/depen
whose contents are:
arch/unknown-i386-linux/depends/main.d arch/unknown-i386-linux/objects/main.o: m
ain.cc copyright.h utility.h ../machine/sysdep.h \
../threads/copyright.h system.h thread.h scheduler.h list.h \
../machine/interrupt.h ../threads/list.h ../machine/stats.h \
../machine/timer.h ../threads/utility.h
You can check if these files exist, after you make the nachos.
Then, there is an include statement in Makefile.common:
include $(dfiles)
This means that Makefile.common includes all the dependence files it created. The contents of
these files which are all makefile rules become part of this makefile. It is these rules that will be
combined with the rule of compiling to make the object codes. As a result, we have a complete list
of dependence files to make each object code.
Another important use of this technique is that we can see what head files are used in compiling a
source code by examining the corresponding dependence file. This is very helpful when you are
building a new version of Nachos in a separate directory which contains some modified source and
head files and you want to be sure that these modified files are actually used in compilation.
The current Nachos allows you to build different Nachos in directories ../threads/, ../filesys/
and ../userprog/. You will be required to extend or modify Nachos in the assignments and lab
sessions. It is always a good idea to change only the relevant files in a separate directory and build
the new Nachos there. You want to use the files which are not modified in their original directories.
Let us assume that you are required to build a new Nachos in a separate directory called ../lab2.
Suppose that you need to change class Scheduler. You do not want to change the original
scheduler.h and scheduler.cc in directory ../threads/. What you can do is to copy these
two files from ../threads/ to ../lab2/ and make changes to them in ../lab2/.
20
Suppose that you want to build the new Nachos in ../lab2/ using the new scheduler.h and
scheduler.cc there. All the other files of the new Nachos should be the original ones from
directories ../threads/ ../machine/, etc.
In order to do that, you need to copy the empty ../arch/ directory tree recursively and files
Makefile and Makefile.local from ../threads/ to ../lab2.
The last task is to modify makefiles Makefile and Makefile.local so that you can build the
new Nachos properly. Makefile in ../lab2 does not need changes, but you do need to change
Makefile.local in ../lab2/.
Makefile.local basically defines macro CCFILES and re-defines the include path macro INCPATH.
The definition of CCFILES does not need changes, because make will follow the vpaths to find the
required source files if they are not in the current directory.
(a) First Solution: You can change the re-definition of INCPATH as follows:
That is, add -I../lab2 before -I../threads so that C preprocessor (cpp) of g++ will
search ../lab2/ first when it processes include macros in the source files. However, this
simple change only does not solve all the problems. The current contents of ../lab2/ are
as follows:
[ptang@zibal lab2]$ ls
Makefile Makefile.local arch/ scheduler.cc scheduler.h
But, in this Nachos, only the new scheduler.cc uses the new scheduler.h. This can be
shown by the following script:
21
>>> Linking arch/unknown-i386-linux/bin/nachos <<<
g++ arch/unknown-i386-linux/objects/main.o ............
....................
ln -sf arch/unknown-i386-linux/bin/nachos nachos
Other classes which depend on scheduler.h use the old scheduler.h in ../threads/. This can
be shown by the following script:
This is because when g++ -MM generates dependences, it looks for the .h files in the same di-
rectory as the .cc file. For example, ../threads/main.cc indirectly includes scheduler.h
(through system.h). Therefore g++ -MM looks for the scheduler.h there first and gener-
ates the dependence which includes string ../threads/scheduler.h (check the contents
of main.d in ../lab2/arch/unknown-i386-linux/depends/).
In order to avoid this, you need to copy all the files in ../threads/ which directly and
indirectly include scheduler.h there. To find the minimum set of these files, you can use
grep command to search for the files which contain string scheduler.h as follows:
This means that the minimum set of files we need to copy from ../threads/ to ../lab2/
are
22
system.h
main.cc
synch.cc
synchtest.cc
system.cc
thread.cc
threadtest.cc
Then we make the new Nachos and the contents of ../lab2/ should be as follows:
[ptang@zibal lab2]$ ls
Makefile arch/ scheduler.cc synchtest.cc thread.cc
Makefile.local main.cc scheduler.h system.cc threadtest.cc
Makefile.local˜ nachos@ synch.cc system.h
(b) Second Solution: The second solution is much simpler than the first one. It takes advantage
of a feature of the preprocessor of g++ defined by -I- in the command. Here is the description
of this include option of g++ (obtained through man gcc).
-I-
....
23
searching the directory which was current when the
compiler was invoked. That is not exactly the same
as what the preprocessor does by default, but it is
often satisfactory.
...
This means that -I- prohibits including the .h files from the same directory of the .cc
file processed. It therefore forces the preprocessor to look for .h files according to the
path defined by -I after the -I-. Therefore, we can use the re-definition of INCPATH in
../lab2/Makefile.local as follows:
without copying any files from ../threads/ other than scheduler.cc and scheduler.h.
The contents of ../lab2/ after Nachos is made is as follows now:
[ptang@zibal lab2]$ ls
Makefile Makefile.local˜ nachos@ scheduler.h
Makefile.local arch/ scheduler.cc
[ptang@zibal lab2]$
If we touch the scheduler.h in the current directory ../lab2, it will make Nachos recom-
piled as follows:
24
7.4 Things to Do
(a) Read Section 7.2 and make sure you understand the make-file structure of Nachos.
(b) Experiment with the two solutions to build a new Nachos in a separate directory described in
Section 7.3. Make sure you understand why both solutions are correct.
25
Chapter 8
8.1 Objectives
In this laboratory session, you are required to write a test program for the producer/consumer
problem using semaphores for synchronization. After completing the session, you will
The work of this laboratory session will prepare you for the programming task in Assignment 2.
8.2 Background
8.2.1 Semaphores
Semaphores are one of the most commonly used synchronization schemes for concurrent processes
or threads. Section 7.4 of the textbook gives a full description of the concept and implementation
of semaphores. In Nachos, semaphores are implemented as class Semaphore. The implementation
of Semaphore in Nachos is different from the textbook.
26
8.2.2 The Producer/Consumer Problem
The producer/consumer problem is one of the problems encountered frequently in operating sys-
tems design. Both producer and consumer threads access the same ring buffer in the shared mem-
ory. The producers produce items and put them in the ring buffer, while the consumers take and
consume items from the buffer. A producer has to be blocked when the buffer is full and resumed
when it becomes non-full. Similarly, a consumer has to be blocked when the buffer is empty and
resumed when it becomes non-empty. Consequently, producers and consumers need a mechanism
for synchronization.
When you start nachos, the first program module executed is the main program. Every subdirectory
of Nachos can have a main.cc. Take a look at the main.cc in ../threads. You need to study
• how the thread for the main program creates another thread executing function SimpleThread(int
which). The source code of SimpleThread(int which) can be found in ../threads/threadtest.cc.
8.3 Things to Do
In this laboratory session, you are required to implement the producer/consumer algorithm in Na-
chos using semaphores for synchronization.
In your ../lab3/ directory, you can find files: main.cc, prodcons++.cc, ring.cc and ring.h.
Files ring.cc and ring.h define and implement a class Ring for the ring buffer used by producers
and consumers. These two files are complete and you do not need to change any part of them.
main.cc in this directory is modified from the version in ../threads/. It is complete and you do
not need to change it.
In the new main.cc, function ProdCons() is called instead of ThreadTest(). Function ProdCons()
is defined in file prodcons++.cc. This file is supposed to include the code to create producer and
consumer threads as well as implement the producer/consumer algorithm described in the textbook.
However, this file is not complete yet. Your task in this laboratory session is to complete this file
and make the producer/consumer algorithm work.
File prodcons++.cc contains all the data structures and the interfaces of the functions. There are
detailed comments in the file about what needs to be done in each part of the code. Because all
the interfaces of the functions are present, the file is compilable. You can execute make in the
../lab3/ now to make a new Nachos for producer/consumer problem. (But it won’t work yet
because prodcons++.cc is incomplete.)
27
(a) Read ring.h and ring.cc and make sure that you understand everything in them.
(e) Compile a new nachos by command make and test if your program is working or not.
The output files of an example run of the problem with two consumers and two producers
each of which produces four messages should be like this:
What is the criteria for testing this program? According to the concepts of producer/consumer, a
correct implementation should guarantee the following:
• all the messages produced by the producer threads are received and recorded in the output
files, and
• messages that are from the same producer and received by the same consumer should be
received in the increasing order.
You can run nachos with different random number seeds by nachos -rs seed-number and check
that all results satisfy the above criteria.
28
Chapter 9
9.1 Objectives
The purpose of this laboratory session is to study the functionality of the file system in Nachos.
The file system in Nachos is designed to be small and simple so that you can read all its source
code in a short period of time. Before starting to read the code, it is very useful to get an idea
of what functionality the Nachos file system offers. In this laboratory session, you will run the
commands of the Nachos file system and watch the effects on the simulated hard disk in Nachos.
On the completion of this laboratory session, you should know
It is very simple to compile Nachos with its file system. You simply move to the directory ../filesys
and execute command make. A new version of Nachos with its file system included will be
made in the directory. The Makefile in ../filesys/ includes both Makefile.local files from
../threads/ and ../filesys/. The Makefile.local in ../filesys/ is as follows:
ifndef MAKEFILE_FILESYS_LOCAL
define MAKEFILE_FILESYS_LOCAL
yes
endef
CCFILES +=bitmap.cc\
directory.cc\
filehdr.cc\
filesys.cc\
fstest.cc\
openfile.cc\
29
synchdisk.cc\
disk.cc
ifdef MAKEFILE_USERPROG_LOCAL
DEFINES := $(DEFINES:FILESYS_STUB=FILESYS)
else
INCPATH += -I../userprog -I../filesys
DEFINES += -DFILESYS_NEEDED -DFILESYS
endif
endif # MAKEFILE_FILESYS_LOCAL
This means that this version of Nachos uses C++ files listed above in addition to the files used to
compile the Nachos in ../threads/. Most of these additional files exist in the current directory.
Some of them are in other directories such as ../userprog/. The make utility program will find
them automatically due to the VPATH defined in ../Makefile.common.
• nachos [-d f] -f. This is used to format the simulated hard disk named DISK before any
other file system commands can start.
• nachos [-d f] -cp unix filename nachos filename. This command copies a UNIX
file named unix filename in your UNIX system to a Nachos file named nachos filename
in the Nachos file system. This is currently the only way to create a file in the Nachos file
system.
• nachos [-d f] -p nachos filename. This command displays the contents of the nachos
file named nachos filename (similar to UNIX command cat).
• nachos [-d f] -r nachos filename. This command removes the nachos file named
nachos filename (similar to UNIX command rm).
• nachos [-d f] -l. This command lists the names of all the nachos files on the screen
(similar to UNIX command ls).
• nachos [-d f] -D. This command prints all the contents of the entire file system including
the bitmap, the file headers, the directory and the files.
• nachos [-d f] -t. This command tests the performance of the file system. It is not work-
ing yet.
To understand how these commands work, you need to read ../threads/main.cc and ../filesys/fstest.cc.
30
9.4 Test Files
In the subdirectory test/ in ../filesys, there are three files to be used when testing the Nachos
file system: small, medium and big. Take a look at the contents of them.
You need to use the UNIX command od (Octal Dump) to examine the simulated hard disk when
you debug the Nachos file system.
0000000 T h i s i s t h e s p r i
0000020 n g o f o u r d i s c o n
0000040 t e n t . \n
0000046
on your screen. Each line displays 16 characters. The column on the left shows the offset in
octal of the first character of each line. For example, the offset of the first character of the
second line (“n”) is 0000020 in octal which is 16 in decimal.
9.5 Things to Do
Follow the description in Section 9.2 to compile the Nachos with its file system in ../filesys/).
(a) Execute nachos -f. Nachos should have created the simulated hard disk called DISK in your
current directory.
(b) Execute nachos -D to dump the whole file system on the simulated hard disk DISK and you
should have the following dump:
....
FileHeader contents. File size: 128. File blocks:
2
File contents:
\1f\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
31
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
Directory file header:
FileHeader contents. File size: 200. File blocks:
3 4
File contents:
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
Bitmap set:
0, 1, 2, 3, 4,
Directory contents:
Directory contents:
Cleaning up...
We have deleted the output from function ThreadTest() in the above dump. You can get rid
of them by making a copy of ../threads/main.cc in your current ../filesys directory
and comment out the line of invoking function ThreadTest().
This dump shows that the Nachos file system has been created on DISK. There are no files at
the moment in the only directory of the Nachos file system.
(c) Execute od -c DISK and you should have the following dump on the screen:
0000000 211 g E 200 \0 \0 \0 001 \0 \0 \0 002 \0 \0 \0
0000020 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0000200 \0 \0 \0 \0 \0 \0 \0 002 \0 \0 \0 003 \0 \0 \0
0000220 004 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
0000240 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0000400 \0 \0 \0 \0 037 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
0000420 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
(d) Execute nachos -cp test/small small to copy file small into the Nachos file system.
Use nachos -l, nachos -p and nachos -D to make sure you do create a new file named
small in the Nachos file system. Watch the dump of od -c DISK again to see what has been
changed.
(e) Continue the experiment with other nachos commands such as nachos -r on more files.
32
9.6 Questions
(a) According to the result of the last command nachos -D and the result of od -c DISK, how
many files are there on the hard disk DISK?
(b) What are the sector numbers of data blocks for file big?
(c) What is the sector number of the disk to store the file header for file big?
(d) The sector size of the Nachos hard disk is 128 bytes. Could you check the result of od -c
DISK to make sure that the data blocks and the file header of big are in the right places in the
disk?
33
Chapter 10
10.1 Objectives
The purpose of this lab session is to help you start to work on extending the Nachos file system, the
programming task of Assignment 3.
The Nachos file system is a simple file system with many restrictions. One of them is that the size
of the file is not extendable: once you specify the size of a file upon its creation, the size of the file
is fixed throughout its lifetime. In this laboratory session, you are going to extend the Nachos file
system to allow the size of files to be extended. In particular, we want the new Nachos file system
to have the following features:
• The size of a file can be increased if more data are written to the file.
For example, if the initial size of a file is 100 bytes and a write operation for 100 bytes data from the
position 50 (the first byte is at position 0) will extend the size of the file to 150 bytes. The situation
is illustrated in Figure 10.1, in which (a) represents the initial size (100 bytes) of the file. The light
shadow represents the current contents of the file. (b) represents the new 100 bytes of data to be
written from position 50. (c) shows the extended size of the file with dark shadow representing the
new data.
The current Nachos file system does not allow the file size to be extended like this. Your task is to
design and implement the extension of the Nachos file system to have these new features.
10.2 Analysis
• class Disk
34
0 50 99
(a)
(b)
0 50 149
(c)
• class SynchDisk
• class BitMap
• class FileHeader
• class OpenFile
• class Directory
• class FileSystem
Before we get into the implementation, let us have a discussion on the design and analysis. We are
not building a file system from scratch. Instead, we are extending an existing system. We want to
make as less changes as possible to the original system.
• What modules need to be changed and what module can be used without changes?
• In those modules that need to be changed, which functions need to be changed and how?
• In those modules that need to be changed, do you need to add new functions or variables?
• Do you need to move some variables around in the modules to be changed, or across the
modules?
You need to find answers to these questions before you do anything of implementation and coding.
10.3 Things to Do
10.3.1 Analysis
Answer the questions in Section 10.2. Write down your answers on paper. Verify your answers
carefully by reading the original source code of the Nachos file system. The more time you spend
in this step, the less troubles and difficulties you will have in later stages. If your answers are
wrong, your file system will definitely not work.
35
FileSystem
Directory
OpenFile
FileHeader
SynchDisk
BitMap
Disk
After you finish the analysis, you can start to design and implement the changes to the existing
Nachos file system. Your working directory is ../lab5/. There is one directory test in ../lab5
which contains the test files. You need to set up the arch subdirectory hierarchy and your new
makefiles in ../lab5. You also have to make a decision as to which files in ../filesys/ need to
be copied to ../lab5 in order to modify them.
The two files main.cc and fstest.cc in ../lab5/ are new and include many new file system
commands to test the new features required. We will discuss these new commands in Section 10.4.
main.cc is complete and you should not change it. fstest.cc is almost complete except that you
need to uncomment four lines in it. In both functions Append(...) and NAppend(...), you can
see the following three lines:
You need to uncomment the last two lines after you add Writeback() function to class OpenFile.
Why do you need this function for OpenFile? Think about it.
Interface: In this stage, you need to build the new interface between the modules according to
36
your plan of changing the file system. You only need to change class definitions in .h files
and function heads in .cc files. You do not change the function bodies at this stage.
Make one change at a time and compile the system to make sure that you do not bring syntax
and linking errors before making the next change.
Coding and Testing: Work out the strategies to change the functions bodies and test the resulting
codes. You may divide the whole process into a number of smaller stages and each stage has
a goal and the related test strategy.
Follow the order of stages specified and make sure the system is working before proceeding
to the next stage.
We need commands to test the new features of the Nachos file system. These new commands have
been implemented in main.cc and fstest.cc in ../lab5/ for you. They are:
• nachos [-d f] -ap unix filename nachos filename. This command appends a UNIX
file named unix filename in your UNIX system to the end of a Nachos file named nachos filename
in the Nachos file system. It is used to test whether we can extend the file size as we append
a file to the end of an existing Nachos file.
• nachos [-d f] -hap unix filename nachos filename. This command over-writes the
Nachos file (named nachos filename) from its middle with a UNIX file (named unix filename).
If the length of the UNIX file exceeds the half of that of the Nachos file, the Nachos file size
should be extended.
Read files main.cc and fstest.cc in ../lab5/ and make sure that you understand how these
new commands are implemented.
When testing the new Nachos file system, start with a fresh DISK by removing the old DISK and
executing nachos -f. Then execute the following commands in the order:
Your file system dump with nachos -D at this point should be like this:
37
Directory file header:
FileHeader contents. File size: 200. File blocks:
3 4
File contents:
\1\12\11@\5\0\0\0small\0\0\0\0G\5\8\1H\5\8\8\0\0\0empty\0\0\0\0\0\0\0\0G
\5\8\0\0\0\0\0\0\0\00\0\0\0\e0\12\11@\0\12\11@\10G\5\8X\13\0\0\d0\15\11@
\18\0\0\0\0\12\11@\c8\12\11@\80G\5\8\18\0\0\0‘\0\0\0\0\0\0\0\0\0\0\0\2\0
\0\0\a0F\5\8\0\0\0\0\0\0\0\0\18\0\0\0
\0\0\0\0\1\0\0\0\a8D\5\8\0\11\0\0\c8\15\11@H\0\0\0\f8\12\11@\f8\12\11@\0F
\5\8\0\0\0\0\0\0\0\00\0\0\0\e0\12\11@\0\12\11@\90D\5\8\c8\15\11@HH\5\8\18
\0\0\0
Bitmap set:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
Directory contents:
Name: small, Sector: 5
FileHeader contents. File size: 168. File blocks:
6 7
File contents:
small file small file small file\asmall file small file small
file\a***end of file***\asmall file small file small file\asm
all file small file small file\a***end of file***\a
Name: empty, Sector: 8
FileHeader contents. File size: 162. File blocks:
9 10
File contents:
medium file medium file medium file\amedium file medium file med
ium file\amedium file medium file medium file\amedium file medi
um file medium file\a***end of file***\a
Cleaning up...
This dump shows that the bitmap file is of size 128 bytes (one sector) and is located in sector 2. It
also shows the contents of the bitmap file. We know that the i-node of the bitmap file is located in
sector 0.
The dump also shows that the directory file has 200 bytes (two sectors) and the data blocks are
located in sectors 3 and 4. The i-node of the directory file is in section 1 (not shown in the dump)
as we know.
The file named small is shown to have 168 bytes (two sectors) and its i-node is located in sector 5.
The data block of the file is in sectors 6 and 7.
38
You also need to test the cases where part of the file is overwritten and the file is being extended.
Design your own test programs to make sure that your new file system works in all different kinds
of situations.
39
Chapter 11
11.1 Objectives
• get familiar with the code which you need to implement Nachos system calls.
11.2 Background
As we mentioned in the Study Book, Nachos uses a MIPS machine simulator to run user programs.
The binary executable files of Nachos user programs are generated by gcc MIPS cross-compiler
and then converted from the COFF format to the NOFF format by a program called coff2noff in
the ../test directory.
To run a Nachos user program in ../userprog, you use command nachos -x ../test/xxxx,
where xxxx is one of the nachos executables in ../test/.
40
11.3 Things to Do
In this task, you need to study the Makefiles in ../test/ to understand how Nachos user programs
(executables) are generated. There are five user programs already compiled and there is a symbolic
link to each of them in the directory.
#include "syscall.h"
int
main()
{
int i,j,k;
k = 3;
i = 2;
j = i-1;
k = i - j + k;
Halt();
/* not reached */
}
• To see the assembly code of this program, you can use the following to command to generate
halt.s:
/usr/local/mips/bin/decstation-ultrix-gcc -I../userprog
-I../threads -S halt.c
• Study halt.s. See how the stack frame for function main is created and deleted. See what
instructions are generated by the compiler for the statements in this C program.
41
void AddrSpace::Print() {
Add this function (or your own similar dump function) in your class AddrSpace and invoke it when
you create a new address space.
You will see that the smallest Nachos user program halt.c takes 11 pages.
Sometimes, you may want to make a user program with a larger address space. One way to do
that is to add static array in your program. For example, if you add an static integer array in user
program halt.c as follows:
#include "syscall.h"
int
main()
{
Halt();
/* not reached */
}
42
Chapter 12
12.1 Objectives
• extend the current implementation of class AddrSpace so that Nachos can run multiple user
programs.
• complete the print function for AddrSpace as mentioned in Lab 6.
This lab will get you ready to implement the nachos system calls Exec() and Exit() in Lab 8.
12.2 Background
Suppose that we want Nachos to load and run a user program as follows:
#include "syscall.h"
int
main()
{
Exec("../test/exec.noff");
Halt()
}
#include "syscall.h"
int
main()
{
Halt()
}
43
This means that Nachos has to load the user program ../test/exec.noff while the user program
../test/bar.noff is running. The current implementation of Nachos does not allow this to
happen, because it always uses the frames 0, 1, ... of the physical memory for any address space as
shown by the following code in the constructor of AddrSpace:
..
// first, set up the translation
pageTable = new TranslationEntry[numPages];
for (i = 0; i < numPages; i++) {
pageTable[i].virtualPage = i; // for now, virtual page # = phys page #
pageTable[i].physicalPage = i;
pageTable[i].valid = TRUE;
pageTable[i].use = FALSE;
pageTable[i].dirty = FALSE;
pageTable[i].readOnly = FALSE; // if the code segment was entirely on
// a separate page, we could set its
// pages to be read-only
}
..
12.3 Things to Do
(a) Extend the Nachos system so that it can run multiple user programs.
(b) Add the print function to AddrSpace class. You will need this class when you test the exten-
sion above and implement system calls Exec().
To complete the task above, you may find the bitmap class in ../userprog/ useful. Read the code
of this class in ../userprog/bitmap.h and ../userprog/bitmap.cc and try to figure out how
to use it in this lab task.
Again, before you program, you have to think about what changes you need to bring to the current
Nachos. Don’t start to program unless you finish the analysis and design of the task.
44
Chapter 13
13.1 Objectives
In this lab, you are required to implement two Nachos systems calls: Exec(), Exit().
The working directory of Labs 7 and 8 is ../lab7-8/. You need to copy files from ../userprog/
and set up your own Makefiles and the arch subdirectory hierarchy in this new directory.
If you cannot make it work, you can always use the original ../userprog/ and change files there.
You will need to write a lot of new Nachos user programs for testing your work. To make new user
programs is easy. Suppose that you have written a new test C program called “exec.c” to test your
implementation of Exec() as follows:
#include "syscall.h"
int
main()
{
SpaceId pid;
pid = Exec("../test/halt.noff");
Halt();
/* not reached */
}
45
To make the NOFF user program for it, you
...
# User programs. Add your own stuff here.
#
# Note: The convention is that there is exactly one .c file per target.
# The target is built by compiling the .c file and linking the
# corresponding .o with start.o. If you want to have more than
# one .c file per target, you will have to change stuff below.
You can also generate the assembly code of this C program by typing: make exec.s.
There are a couple of design issues you need to address before you can start to program. We list a
few major ones in this section.
To build the address space for a user program in a file, you simply open the file and then invoke the
constructor of AddrSpace as shown in function StartProcess() as follows:
void
StartProcess(char *filename)
{
OpenFile *executable = fileSystem->Open(filename);
AddrSpace *space;
if (executable == NULL) {
printf("Unable to open file %s\n", filename);
return;
}
space = new AddrSpace(executable);
...
}
Note that the filename above is an object (string) in the Nachos kernel. The filename as the
argument of Exec() is an object (string) in the user program. This can be shown by the following
assembly code of exec.c we mentioned above:
46
.file 1 "exec.c"
gcc2_compiled.:
__gnu_compiled_c:
.rdata
.align 2
$LC0:
.ascii "../test/halt.noff\000"
.text
.align 2
.globl main
.ent main
main:
.frame $fp,32,$31 # vars= 8, regs= 2/0, args= 16, extra= 0
.mask 0xc0000000,-4
.fmask 0x00000000,0
subu $sp,$sp,32
sw $31,28($sp)
sw $fp,24($sp)
move $fp,$sp
jal __main
la $4,$LC0
jal Exec
sw $2,16($fp)
jal Halt
$L1:
move $sp,$fp
lw $31,28($sp)
lw $fp,24($sp)
addu $sp,$sp,32
j $31
.end main
The question is how to copy the string of filename from the user address space to the kernel.
13.4.2 Advance PC
You should have seen that the implementations of all the systems calls start with the corresponding
assembly routines provided in start.s. For example, the routine for Exec() is
Exec:
addiu $2,$0,SC_Exec
syscall
j $31
.end Exec
The machine simulation of the execution of instruction syscall within the big switch statement
in ../machine/mipssim.cc is as follows:
case OP_SYSCALL:
RaiseException(SyscallException, 0);
return;
47
Note return instead break above. This is because the machine has to restart the same instruction
after exceptions are handled in general. But, the system call exception is a special case. It does not
need to restart the same syscall instruction after the exception is serviced.
You can either change the code above or advance the PC in your system call exception handlers. If
you chose the latter, the following function to advance the PC may be useful:
void AdvancePC() {
machine->WriteRegister(PCReg, machine->ReadRegister(PCReg) + 4);
machine->WriteRegister(NextPCReg, machine->ReadRegister(NextPCReg) + 4);
}
13.4.3 SpaceId
Exec() returns a value of type SpaceId. This value will be used as the argument of Join() to
identify the newly-created user process.
• how to record this value in the kernel so that Join() will be able find the corresponding
thread by providing this value.
The argument of Exit()is an integer as the exit state of the user process. Join() needs to return
this exit status. The user process may exit before the other (parent) user process calls Join(). This
means that the exit status needs to be saved somewhere. But, how is this done?
13.5 Things to Do
Obviously, there is one thing to do in this lab: implement Nachos system calls Exec() and Exit()
based on your work in lab 7.
13.6 Report
Although the work of Labs 7 and 8 is not part of formal assessment in this course, I do encourage
you to send me your report on it if you manage to complete it successfully. I will read your report
and give your feedback.
48
Part III
Assignments
This part includes the description of three assignments. To help you prepare the submission of
these assignments, we provide the solution to a sample assignment in Chapter 14. You need to
read this chapter before you prepare your submissions.
49
Chapter 14
14.1 Introduction
In this appendix, we provide a sample assignment and its sample submission. The purpose is to
show the format of submission and what we expect from you for the questions and programming
tasks in assignments of this course.
There are three kinds of questions and tasks in the assignments of this course:
Questions on concepts: These questions are mainly concerned with concepts, algorithms and de-
signs of operating systems covered by the text. When answering these questions, you should
demonstrate your own understanding using your own words. Quoting or copying sentences
from the text only is NOT acceptable.
Questions on Nachos: These questions are designed to test your understanding of implementa-
tion of concepts in Nachos.
They can be the questions about the current Nachos implementation or questions about fur-
ther extensions of Nachos. When answering these questions, you need to quote the relevant
code sections of Nachos to demonstrate your understanding of the Nachos system as well as
the concepts implemented. The purpose is to see if you know how to implement the concepts
in an operating system.
Programming tasks with Nachos: These tasks are the ultimate tests of your understanding of the
subject. They are also designed to make sure that you have the ability and skills to implement
operating systems concepts in a real system.
You need to submit a report for each programming task. Your report should give details of
how you accomplished the task. It should include three components as follows:
Design: You need to describe all the designs required by the tasks. They should include the
designs of
50
(a) relationship among classes
(b) classes including their data members and functions
(c) interfaces and algorithms of functions
(d) others
Implementation: You need to present relevant code sections to demonstrate the implemen-
tation of your designs.
Testing: You need to describe in detail the entire testing process of your programs. In
particular, you should
(a) describe the test strategy, test cases and programs
(b) report your test results
(c) analyze the results to show why your programs are correct or incorrect.
Questions
(a) What is the purpose of system calls? (Question 3.7 of the text)
(b) Suppose that thread A calls function Run(Thread *nextThread) of the scheduler, where
nextThread points to thread B. Within this function, the assembly function SWITCH(..) is
called as follows:
115
116 SWITCH(oldThread, nextThread);
117
Programming Tasks
(a) In this programming task, you are required to implement and experiment with the bounded-
buffer algorithm using semaphores. The program structure for both producer and consumer
processes is introduced in the section 7.1 of the text. The bounded-buffer is an array of a
certain data type. The algorithms of producer and consumer processes using semaphores for
synchronization can be found in the section 7.5.1 of the text.
Your program should work correctly according to the requirements of the bounded-buffer
problem.
51
14.4 Sample Submission
Questions
(a) What is the purpose of system calls? (Question 3.7 of the text)
Answer: System calls are the interface between user programs and the operating system
kernel. This interface is in the form of ordinary functions in system programming languages
like C or assembly languages. For example, UNIX system calls have interfaces in C and they
look like C library functions. However the purpose of system calls is completely different
from that of C library functions.
User programs invoke systems calls to get the services from the operating system kernel for
those tasks which cannot be done without the operations of the underlying operating system.
Such tasks may be read and write on an open file or create another process to run a program
concurrently. These tasks are completed by the kernel running in system mode. That way,
the access to the critical shared hardware such as the memory, disks and interrupts can be
protected from errant users because user programs can only run in the user mode.
(b) Suppose that thread A calls function Run(Thread *nextThread) of the scheduler, where
nextThread points to thread B. Within the this function, the assembly function SWITCH(..)
is called as follows:
115
116 SWITCH(oldThread, nextThread);
117
Answer:
i. First of all, this is an invocation of the assembly function SWITCH(..), rather than an
ordinary C++ function. Secondly, the return of this functions call has different seman-
tics depending on whether you look at it from the machine point of view or the view
point of the calling thread. The details of the differences will be provided in the fol-
lowing two sub-questions. Most importantly, between the time of calling this function
and the time of the return to the calling thread, the machine has at least two context
switches: one to the another thread and another to switch back to this calling thread.
ii. To answer the question, we need to trace this function call in detail. The relevant part
of the source code of function Run(..) which calls SWITCH(..) is as follows:
90 void
91 Scheduler::Run (Thread *nextThread)
92 {
93 Thread *oldThread = currentThread;
94
...
104
105 currentThread = nextThread; // switch to the next thread
52
106 currentThread->setStatus(RUNNING); // nextThread is now running
107
108 DEBUG(’t’, "Switching from thread \"%s\" to thread \"%s\"\n",
109 oldThread->getName(), nextThread->getName());
..
115
116 SWITCH(oldThread, nextThread);
117
118 DEBUG(’t’, "Now in thread \"%s\"\n", currentThread->getName());
119
..
124 if (threadToBeDestroyed != NULL) {
125 delete threadToBeDestroyed;
126 threadToBeDestroyed = NULL;
127 }
128
135 }
53
Here registers a0 and a1 hold the references to threads A and B, respectively, when
this subroutine is started. Lines 89-99 save all the registers of the CPU into the control
block of thread A. In particular, the return address of this function SWITCH(..) call
contained in register ra is the address of first instruction of line 118 in Run(). This
address is saved at line 93.
After these registers are re-loaded with the contents saved previously in the control
block of thread B at lines 101-111, the CPU has the new context of thread B. In par-
ticular, register ra now has the whatever return address was saved when thread B was
switched off previously, or the initial PC value if this is the first time for thread B to run.
In the former case, the new ra contains the same address of first instruction of line 118
in Run() (Remember that all threads share the same kernel and the scheduler is part of
the kernel). Otherwise, ra should have the starting address of subroutine ThreadRoot.
The instruction at line 113 finishes this function and the CPU starts to execute the
instruction pointed by ra. If ra points to address of first instruction of line 118 in
Run(), this SWITCH(..) function appears to have returned, but this return is the return
to thread B instead of thread A. (If thread B runs for the first time, this return causes
thread B to execute subroutine ThreadRoot.)
In summary, from the view point of the machine which can see everything, this SWITCH(..)
function call returns to thread B.
iii. However, from the view point of thread A which can only see its own context, this
function call takes much time to complete. The return to thread A itself (i.e. the normal
return as with any ordinary function calls) won’t happen until the context of thread A
including its saved ra are restored back into the CPU registers. This can only happen if
another thread (not necessarily thread B) calls function SWITCH(..) again in a similar
situation. Therefore, there exist at least two context switches in the machine between
the call and the return of this function. Of course, thread A will not see these context
switches and its only experience is that this function call seems to take a long time to
complete.
Programming Task
In this programming task, you are required to implement and experiment with the bounded-buffer
algorithm using semaphores. The program structure for both producer and consumer processes
is introduced in the section 7.1 of the text. The bounded-buffer is an array of certain data type.
The algorithms of producer and consumer processes using semaphores for synchronization can be
found in the section 6.5.1 of the text.
Your program should work correctly according to the requirement of the bounded-buffer problem.
Report:
• Objects and Data Structures Design. To experiment with the bounded-buffer problem, we
need to have
– a bounded-buffer object,
– a number of producer threads, and
– a number of consumer threads.
54
Producer and consumer threads can be created by using the standard thread creation mecha-
nism in Nachos. We need to provide separate functions for them.
The bounded-buffer object as shown in the section 7.1 of the text does not have synchroniza-
tion control. To allow multiple producer and consumer threads to access the bounded buffer
concurrently, we need to use three semaphores as follows:
A consumer then can record a line in its associate file in the following format:
for each of the messages it gets. We could use references of producer threads for their iden-
tities, but we chose to use integers for clarity. The message identity is just an integer.
• Algorithms and Function Interfaces Design. The bounded-buffer can be regarded as ab-
stract data type with two functions:
Here slot is the type of the message to be passed from producers to consumers through the
bounded buffer.
The bounded buffer should have two private variables , in and out, to hold the indexes of next
empty and full slots of the buffer, respectively. The algorithms of Put(..) and Get(..) are
from the section 7.1 of the text, except that we do not need variable counter. The algorithm
of Put is as follows:
buffer[in] = message;
in = in + 1 mod size
where “size” is the size of the buffer. The algorithm of Put is as follows:
message = buffer[out];
out = out + 1 mod size
return
55
Both functions Put(..) and Get(..) need a call-by-reference type of message argument to
copy the value of the messages in and out of the bounded buffer. In particular, for function
Get(..), we cannot just return the reference of the message to be removed, because this
message in the buffer may be overwritten immediately after Get() is finished.
We could have included the three semaphores above as a private data members of the bounded
buffer. However, the algorithm from the text suggests to declare these semaphores outside of
the bounded buffer and we follow this design here.
The algorithms for producer and consumer threads are from Figures 7.12 and 7.13 of the text,
respectively, as follows:
– Producer
repeat
...
produce an item nextp
...
P(empty);
P(mutex);
...
add nextp to buffer
...
V(mutex);
V(full);
until false;
– Consumer
repeat
P(full);
P(mutex);
...
remove an item from buffer to nextc
...
V(mutex);
V(empty);
...
consume the item in nextc
...
until false;
We have to change the infinite loop in the producer thread to a finite loop; otherwise the
record files of consumers will be infinitely long and our file system will be filled quickly.
Our consumer function also needs to create and open the file for recording the messages
received.
56
compiling Nachos in threads/ directory. We also need to add our own files. The best
way is to create a separate directory and do the work in that new directory. In that
directory (called lab2/), I have the following files:
sigma : > ls
Makefile arch/ prodcons++.cc ring.h
Makefile.local main.cc ring.cc
sigma : >
I copied the main.cc from threads/ directory, because I need to change it1 .
New files ring.h and ring.cc are used to implement the bounded-buffer class. File
prodcons++.cc is similar to threadtest.cc in threads/ directory and includes all
functions run by producer and consumer threads as well as a startup function called
ProdCons().
The makefiles are copied from threads/, but new files are added to the CC file list in
Makefile.local as follows:
CCFILES = main.cc\
list.cc\
scheduler.cc\
synch.cc\
synchlist.cc\
system.cc\
thread.cc\
utility.cc\
threadtest.cc\
synchtest.cc\
interrupt.cc\
sysdep.cc\
stats.cc\
timer.cc\
prodcons++.cc\
ring.cc
int thread_id;
int value;
};
directory. The Nachos makefiles will look for the named file in the current directory first.
57
˜Ring(); // Destructor: deallocate space allocated above.
void Get(slot *message); // Get a message from the next full slot.
private:
int size; // The size of the ring buffer.
int in, out; // Index of
slot *buffer; // A pointer to an array for the ring buffer.
};
void
Ring::Get(slot *message)
{
message->thread_id = buffer[out].thread_id;
message->value = buffer[out].value;
out = (out + 1) % size;
}
(c) Semaphores. Three semaphores are declared and defined globally. The pointers to
these semaphores are declared in file prodcons++.cc as follows:
Semaphore *nempty, *nfull; //two semaphores for empty and full slots
Semaphore *mutex; //semaphore for the mutual exclusion
These semaphores are created and initialized at the beginning the test function ProdCons()
as follows:
void
ProdCons()
{
int i;
DEBUG(’t’, "Entering ProdCons");
Here BUFF SIZE is the macro for the size of the bounded buffer.
58
(d) Consumer and Producer Threads. To enable testing of different configurations of the
problem, we define a couple of macros in prodcons++.cc as follows:
#define BUFF_SIZE 3 // the size of the round buffer
#define N_PROD 2 // the number of producers
#define N_CONS 2 // the number of consumers
#define N_MESSG 4 // the number of messages produced by each producer
The references to the producer and consume threads are kept in two arrays of Thread
pointers as follows:
Thread *producers[N_PROD]; //array of pointers to the producer
Thread *consumers[N_CONS]; // and consumer threads;
Note that each procedure thread generates only N MESSG messages. Argument which is
the integer identity of the thread we mentioned earlier.
The function for consumer threads is in the same file as follows:
Consumer(_int which)
{
char str[MAXLEN];
char fname[LINELEN];
int fd;
59
for (; ; ) {
nfull->P();
mutex->P();
ring->Get(message);
mutex->V();
nempty->V();
Note that the consumer thread first creates a UNIX file named “tmp xxx”, where xxx is
its integer identity, as the file to record the messages it obtains from the bounded buffer.
The way of consuming a message obtained is simply writing to the file a line in the
format we designed earlier.
(e) Start-up Function. We need a start-up function to
– create the bounded buffer
– create and start producer and consumer threads.
This function will be called the initial Nachos kernel thread. This function is called
ProdCons() and defined in prodcons++.cc as follows:
void
ProdCons()
{
int i;
DEBUG(’t’, "Entering ProdCons");
60
consumers[i] = new Thread(cons_names[i]);
consumers[i]->Fork(Consumer, i);
};
}
To create and start a producer or consumer thread, we follow the mechanism in Nachos
to first construct a Thread object and then call its Fork(..) providing the function we
want it to run. The integer identity is from the loop variable i.
• Testing. We start with the rules by which we can judge whether our programs behave cor-
rectly. According to the requirement of the bounded buffer problem, a correct implementa-
tion should guarantee the following:
– all the messages produced by the producer threads are received by the consumer threads
and recoded in the output files, and
– no messages are received and recorded more than once
– messages that are from the same producer and received by the same consumer should
be received in the increasing order.
We run tests on two different configurations. In the first configuration, we create 2 consumers
and 2 producers each of which generates 4 messages. The buffer size is 3. We successfully
compiled the Nachos kernel in our directory lab2/ and run nachos -rs 100 with random
seed number 100 as follows:
Cleaning up...
sigma : > ls
Makefile main.cc ring.cc tmp_1
Makefile.local nachos@ ring.h
arch/ prodcons++.cc tmp_0
sigma : >
We see that two record files are created by the two consumer threads. The contents of these
files are:
61
producer id --> 0; Message number --> 1;
producer id --> 0; Message number --> 2;
producer id --> 0; Message number --> 3;
sigma : > less tmp_1
producer id --> 1; Message number --> 3;
sigma : >
The output is correct according to the rules described. By running nachos again with rs=99,
we got the following results:
We re-compiled the Nachos kernel and run it again with rs=99. The results are:
62
Another run with rs=50 gives the following results:
While it is impossible to do exhaustive tests for all configurations and all rs values, the test
results above do show that our program appears to be correct.
63
Chapter 15
Weight: 15%
15.1 Introduction
In this assignment, you are required to answer a number of questions about the concepts of process
and thread and how they are implemented in Nachos.
15.2 Questions
i. What are processes? What are the possible causes to make a process to change
A. from state Running to state Waiting?
B. from state Running to state Ready?
C. from state Waiting to state Ready?
D. from state Ready to state Running?
64
ii. In Nachos, when the current Thread invokes currentThread->Yield(), what will
happen? Use the relevant Nachos source code to assist your answer. (Hint: Use gdb
to trace the nachos main program in the ../threads/ directory which runs the thread
test routine ThreadTest().)
69 .globl ThreadRoot
70 .ent ThreadRoot,0
71 ThreadRoot:
72 or fp,z,z # Clearing the frame pointer here
73 # makes gdb backtraces of thread stacks
74 # end here (I hope!)
75
76 jal StartupPC # call startup procedure
77 move a0, InitialArg
78 jal InitialPC # call main procedure
79 jal WhenDonePC # when we are done, call clean up proced
ure
80
81 # NEVER REACHED
82 .end ThreadRoot
65
Chapter 16
Weight: 15%
16.1 Introduction
In this assignment, you are required to complete a programming task and answer a few questions
about algorithms for monitors.
16.2 Questions
The textbook (pages 220-221) provides the algorithm using semaphores for the Hoare style monitor
with the additional semaphore next to hold the processes with higher priority than those waiting at
the entry semaphore mutex. The algorithm has three components (1) the entry and exit code for
each monitor function, (2) the code for Wait() function of condition variable and (3) the code for
Signal() function of condition variable.
(a) Write the algorithm using the same semaphores for the Mesa style monitor and explain why
your algorithm is correct.
66
(b) In the above algorithms, we use the additional semaphore next to distinguish the waiting
processes which once entered the monitor from other processes waiting at mutex and give the
former a higher priority to enter or resume its monitor function. Suppose we do not want to
distinguish them and use only one semaphore mutex to hold all the waiting processes, i.e., we
do not use next anymore. Write the algorithms for both Hoare style and Mesa style monitors
in this context. Use C code or pseudocode to describe your algorithms. Explain why your
algorithms are correct.
(c) Implement Hoare style condition variables in Nachos as a new class called condition h. In
your answer to this question you should simply quote the C code. In the next question you
will need to make use of this new condition variables class.
After completion of this programming task, you need to write a report about it. You need to submit
both the report and the Nachos source code with your code. The details of submission will be
explained later.
67
In this programming assignment, you are required to implement the producer/consumer problem
using monitors with Hoare-style condition variables for synchronization.
Monitors use condition variables for interprocess synchronization. Currently, Nachos has only the
Mesa-style condition variable implemented as class Condition in module synch.cc and synch.h.
But, you are not allowed to use this condition variable class. Instead, you need to implement the
Hoare-style condition variables and then use them to implement the producer/consumer problem
with monitors.
The monitor type is a high-level synchronization construct. Some programming languages such as
Concurrent Pascal allow programmers to declare a monitor type using the syntax shown on page
221 of the textbook. This syntax reminds us that monitor is simply an abstract data type (ADT) with
specific synchronization semantics. Object-Oriented languages such C++ support ADT through
classes. In Lab 3, we provided a class Ring used in the producer/consumer problem. While C++
does not allow you to declare an arbitrary monitor type of class, you certainly can build a particular
one by adding the synchronization mechanism into the class. This is exactly what you are required
to do in this assignment.
The details of the implementation of the monitor mechanism are explained in section 7.7 of the
textbook. The first thing you need to do is to modify the class Ring from Lab 3 to make it a monitor
type of class so that producer and consumer threads can simply call the Put(..) and Get(..)
member functions without invoking low-level synchronization primitives such as semaphores.
void
Producer(_int which)
{
int num;
slot *message = new slot(0,0);
for (num = 0; num < N_MESSG ; num++) {
// the code to prepare the message goes here.
// ..
ring->Put(message);
}
}
void
Consumer(_int which)
{
char str[MAXLEN];
char fname[LINELEN];
int fd;
slot *message = new slot(0,0);
68
sprintf(fname, "tmp_%d", which);
printf("file name is %s \n", fname);
if ( (fd = creat(fname, 0600) ) == -1)
{
perror("creat: file create failed");
exit(1);
}
for (; ; ) {
ring->Get(message);
sprintf(str,"producer id --> %d; Message number --> %d;\n",
message->thread_id,
message->value);
if ( write(fd, str, strlen(str)) == -1 ) {
perror("write: write failed");
exit(1);
}
}
}
The semaphores declared in prodcons++.cc of lab session 3 are no longer needed, because the
synchronization between the producer and consumer threads calling Put() and Get() functions is
taken care of by the condition variables in your monitor type of class Ring.
In other words, you need to implement a monitor type of class Ring such that the producer/consumer
problem with the Producer and Consumer functions as shown above is still working.
What is a monitor type of class Ring? The class Ring that you used in laboratory session 3 is not
a monitor type class, because it does not have monitor synchronization mechanism. Figure 16.1
shows a monitor ring buffer in Concurrent Pascal.
One way to implement monitor type class Ring in our system is to add monitor synchronization
control to the class. Here is the new definition of class Ring:
class Ring {
public:
Ring(int sz); // Constructor: initialize variables, allocate space.
˜Ring(); // Destructor: deallocate space allocated above.
void Get(slot *message); // Get a message from the next full slot.
private:
int size; // The size of the ring buffer.
int in, out; // Index of Put and Get
slot *buffer; // A pointer to an array for the ring buffer.
int current; // the current number of full slots in the buffer
69
Condition_H *notfull; // condition variable to wait until not full
Condition_H *notempty; // condition variable to wait until not empty
Note that the private data members of this class now include two semaphores: mutex and next, as
well as the integer next count to count the threads in the “next” queue.
There are also two condition variables: notfull and notempty for synchronization as required by
the algorithm shown in Figure 16.1.
Monitors use condition variables for synchronization between the processes accessing the shared
data in the monitors.
There are two styles of implementation of condition variables: Hoare-style and Mesa-style. The
Hoare style semantics is described as choice 1 in the discussion on page 218 of the textbook. The
Mesa-style corresponds to the second choice in that discussion.
You are not allowed to use the Mesa-style condition variables implemented in Nachos. Instead,
you are required to implement the class of condition variable in the Hoare-style. The Hoare-style
algorithm for Wait(..) and Signal(..) member functions can be found in the textbook. To avoid
the confusion between these two styles of condition variables, we use another name, Condition H,
for the Hoare-style condition variable class. You need to add the definition and implementation
of the new class Condition H into synch.h and synch.cc in your working directory for this
assignment.
The working directory of this assignment should be a separate directory called ass2/ in the code/
directory. You need to use the knowledge and skills you learned from Labs 2 and 3 to create
this working directory with appropriate makefiles, etc. You also need to copy relevant files from
../threads which you intend to modify. Of course, you need to add some files of your own.
16.4 Submission
70
(b) The report about this programming assignment. The report should tell us everything that you
think deserves the credit for your work. In general, it should include the analysis, design,
implementation and testing of your programming task. It should be self-contained and in-
clude all the necessary details to convince us that your design, implementation and testing
are correct. There is a sample report for a programming task in Chapter 14 of this Intro-
ductory Book, but you do not have to follow the format of it. You may quote some of your
source code in writing, but the source code listing itself is not acceptable as the report. Do
not submit the source code listing as the report. The testing part should also include the
evidence of the real testing processes. The best way is to copy and include the screen output
of the testing. You can use the cut-and-paste of the X window to do that. Another way is
to use a shell buffer in Emacs. Also, there is a command script available which will save
everything which takes place inside an xterm to a file.
Report writing is very important. Poor reporting makes it difficult for us to assess your
programming work. If you do not submit the report or submit a poor report, you may get
zero marks even though the program you submit works correctly, because we cannot be
convinced that the program submitted is your genuine work.
(c) a floppy disk which contains the tar file of your complete Nachos source code including the
code in your working directory ass2/ for this assignment. The details of how to submit a
floppy disk for Nachos can be found in Chapter 4.
71
Chapter 17
Weight: 15%
17.1 Introduction
This assignment comprises two parts: the work of Laboratory 5 and a programming task.
In the first part, you need to report your work of Lab 5 by answering related questions given in
Section 17.2 below. The last part is similar to the second part which focuses on a programming
task described in Section 17.3.
You need to submit your Nachos system with this assignment after you finish the work of Lab 5
and the programming task specified in 17.3.
(a) What modules of the Nachos file system need to be changed for the purpose of this laboratory
work? Describe in detail (with relevant source code) the changes you made to the modules
modified. State the reasons that you chose to make these changes.
(b) Describe your test runs and show their results. Do you think that your test runs are complete
to cover all the aspects of the new file system and if so, why?
(c) Show the dump of the Nachos file system (by nachos -D) after you execute the following
command:
i. rm DISK
ii. nachos -f
72
iii. nachos -cp test/medium strange
iv. nachos -hap test/small strange
v. nachos -ap Makefile strange
(d) Report other significant discoveries or tricks that helped you finish the work of this laboratory
session, if any.
The programming task of this assignment is based on your work in Lab 5. In Lab 5, you have
extended the Nachos file system to allow extendable files.
You may need to use UNIX od command extensively when debugging the programs for this pro-
gramming task. Let us look at this command in more detail first.
Let us move back to ../lab5/ and start with a fresh DISK by rm DISK and nachos -f. Then
execute nachos -cp test/small small.
Execute UNIX command od -c DISK. You should have the following octal dump for file DISK:
73
0001000 030 \0 \0 \0 \0 \0 \0 \0 001 \0 \0 \0 D 005 \b
0001020 \0 021 \0 \0 I 021 @ H \0 \0 \0 ( G 021 @
0001040 ( G 021 @ \0 F 005 \b \0 \0 \0 \0 \0 \0 \0 \0
0001060 0 \0 \0 \0 020 G 021 @ \0 G 021 @ 220 D 005 \b
0001100 I 021 @ H H 005 \b 030 \0 \0 \0 \0 \0 \0 \0
0001120 \0 \0 \0 \0 210 2 005 \b 2 005 \b 220 2 005 \b
0001140 P \0 \0 \0 0 G 021 @ 0 G 021 @ 207
0001160 \a \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
0001200 \0 \0 \0 \0 T \0 \0 \0 001 \0 \0 \0 006 \0 \0 \0
0001220 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0001400 \0 \0 \0 \0 s m a l l f i l e s
0001420 m a l l f i l e s m a l l
0001440 f i l e \n s m a l l f i l e
0001460 s m a l l f i l e s m a l l
0001500 f i l e \n * * * e n d o f
0001520 f i l e * * * \n F 021 @ 200 G 005 \b
0001540 030 \0 \0 \0 ‘ \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
0001560 002 \0 \0 \0 240 F 005 \b \0 \0 \0 \0 \0 \0 \0 \0
0001600 030 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
0001620 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0400000 \0 \0 \0 \0
0400004
We can verify the location of the data block of file small. According to the file system dump (by
nachos -D), the data block (the only one) of the file starts at the beginning of sector 6. Each sector
has 128 bytes. 128 equals to 2008 in octal. Since 2008 × 68 = 14008 , the starting address of sector
6 should be 14008 (in octal). Remember that we have put a magic integer number in the beginning
of the hard disk, sector 0 actually starts from address 48 . Therefore, sector 6 should start from
offset 14048 . You can verify that the data block of file small does start in the right position of
the hard disk. The size of the file is 84 = 1248 . The address of the last byte of the file should be
14048 + 1248 − 18 = 15278 . We can see that the file does end at that offset and the bytes starting
from 15308 of the sector are not used.
17.3.2 Objectives
The Nachos file system uses single-level index allocation method for data allocation of files. In
the current design, the file header (i-node) of a file can point to only NumDirect (its value is 30)
data sectors. Therefore, the maximum size of a file is NumDirect*SectorSize, which evaluates to
3720 bytes.
In Lab 5, you have made Nachos files extendable. That is, one can increase the size of a file by
writing more data to it. But, the maximum size of a file is still the same. You simply cannot put
more data than 3720 bytes to a Nachos file.
In this programming task, you are required to further change the Nachos file system to increase
the maximum file size with the two-level index allocation method similar to UNIX file system.
74
In particular, you use the last entry of array dataSectors[] to store the sector number of the
secondary indirect i-node when necessary. When the file is large, the i-node structure is as follows:
numBytes
.....
0
1
.....
NumDirect-1 numBytes
.....
0
1
.....
NumDirect-1
private:
int numBytes; // Number of bytes in the file
int numSectors; // Number of data sectors in the file
int dataSectors[NumDirect]; // Disk sector numbers for each data
// block in the file
The reason for this is that we want to fit a filehdr (i-node) into exactly one data sector. Because
there are two integer variables, numBytes and numSectors, in the file header, the above definition
for numDirect is correct.
The new definition of the data members of FileHeader should be as follows:
private:
int numBytes; // Number of bytes in the file
int numSectors; // Number of data sectors in the file
FileHeader *indirect; // pointer to the indirect header
int dataSectors[NumDirect]; // Disk sector numbers for each data
75
// block in the file
The reason is that we need a pointer to the indirect file header in the memory when processing the
operation of class FileHeader. Therefore, the definition of numDirect should change to
accordingly.
The actual size of a file should be changed dynamically: if the size is small, the file system
does not need to allocate the indirect i-node. The indirect i-node exists only if the size exceeds
((NumDirect-1) * SectorSize).
The working directory of this programming task is ../ass3/. In this directory there are only
Makefile and Makefile.local. Before you start, you need to copy all the .cc and .h files from
your ../lab5/. That is, you start from your Nachos file system completed in Lab 5.
Your task is to further modify your Nachos file system to meet the requirement described above.
17.3.4 Encapsulation
The changes that you need to make in this programming are very isolated. You do not need to
change any other classes except class FileHeader. In other words, all the changes you need are
encapsulated within the functions of class FileHeader.
17.3.5 Testing
In this section, I explain the test results of my Nachos file system completed for this programming
task on Sun Sparc platform. The purpose is help you understand how to test your new system. You
should have similar test results on your LINUX/PC platform.
In your ../ass2/, you can find a test subdirectory which contain the files shown as follows:
ptang@titus: ls -l test
total 16
-rw-r--r-- 1 ptang 337 Oct 2 17:15 big
-rw-r--r-- 1 ptang 3804 Oct 2 17:13 huge
-rw-r--r-- 1 ptang 3573 Oct 2 17:12 huge1
-rw-r--r-- 1 ptang 3678 Oct 2 17:13 huge2
-rw-r--r-- 1 ptang 169 Oct 2 17:14 medium
-rw-r--r-- 1 ptang 1 Sep 28 18:07 onebyte
-rw-r--r-- 1 ptang 90 Oct 2 17:14 small
ptang@titus:
76
In the new Nachos file system, the total size of the data blocks pointed by the direct i-node is 28
sectors or 3584 bytes. If the size of the file exceeds 3584 bytes, it should start to use indirect i-node
for the extra data blocks. In the following test, we first copy test/huge1 to Nachos file huge1.
Then we extend it by appending test/small and test/medium.
• After nachos -f and nachos -cp test/huge1 huge1, the following file system dump
shows that Nachos file huge1 takes contiguous blocks from sector 6 through sector 33 as
follows:
ptang@titus: nachos -D
Bit map file header:
FileHeader contents. File size: 128. File blocks:
2
File contents:
\ff\ff\ff\ff\0\0\0\3\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
Directory file header:
FileHeader contents. File size: 200. File blocks:
3 4
File contents:
\0\0\0\1\0\0\0\5huge1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0
Bitmap set:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33,
Directory contents:
Name: huge1, Sector: 5
FileHeader contents. File size: 3573. File blocks:
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
File contents:
This is a huge file.\aThis is a huge file.\aThis is a huge file.\aThis is a huge
file.\aThis is a huge file.\aThis is a huge file.\aTh
......
• Execute nachos -ap test/small huge1. The file system dump should show that the size
of the file is increased to 3663 bytes and the last sector of the file is sector 35:
Bitmap set:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
Directory contents:
Name: huge1, Sector: 5
FileHeader contents. File size: 3663. File blocks:
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 35
File contents:
.....
77
Note that bitmap shows that sector 34 has been allocated. It is allocated for the indirect i-node
of the file.
• Execute nachos -ap test/medium huge1. The file system system dump should show that
the file occupies 28+2=30 sectors now and the size is 3832 bytes. The following od dump
from sector 33 (starting from 10204) to sector 36 (ending at 11203) shows that the file con-
tinues on sectors 35 and 36, skipping sector 34 which is used for the indirect i-node.
0010200 a h u g e f i l e . \n T h i
0010220 s i s a h u g e f i l e
0010240 . \n T h i s i s a h u g e
0010260 f i l e . \n T h i s i s a
0010300 h u g e f i l e . \n T h i s
0010320 i s a h u g e f i l e .
0010340 \n * * * e n d o f h u g e 1
0010360 f i l e * * * \n s m a l l f
0010400 i l e \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
0010420 \0 \0 \0 # \0 \0 \0 $ \0 \0 \0 \0 \0 \0 \0 \0
0010440 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
0010600 \0 \0 \0 \0 s m a l l f i l e s
0010620 m a l l f i l e \n s m a l l
0010640 f i l e s m a l l f i l e
0010660 s m a l l f i l e \n * * * e n
0010700 d o f s m a l l f i l e *
0010720 * * \n m e d i u m f i l e m
0010740 e d i u m f i l e m e d i u
0010760 m f i l e \n m e d i u m f i
0011000 l e m e d i u m f i l e m
0011020 e d i u m f i l e \n m e d i u
0011040 m f i l e m e d i u m f i
0011060 l e m e d i u m f i l e \n m
0011100 e d i u m f i l e m e d i u
0011120 m f i l e m e d i u m f i
0011140 l e \n * * * e n d o f m e d
0011160 i u m f i l e * * * \n \0 \0 \0 \0
0011200 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0
*
The macro TransferSize defined in fstest.cc determines the number of bytes to be appended
at a time. Its current value is defined to be 10. Your program should also work correctly even when
you change its value to 100, 250, 400, 1000, 2000.
The following questions can be used to assist you to write the report for this programming assign-
ment:
78
(a) In this programming task, you only need to change class FileHeader. Describe in detail
what member functions are changed or what member functions are added. Describe in detail
the algorithm to dynamically increase the size of a file.
(c) Describe your testing with the corresponding result. Show why your tests are complete.
(d) In the indirect i-node, the last entry of array dataSectors is used for an ordinary data sector
(last sector) of the file. We could use it to point to another indirect i-node just as we did in
the director i-node. This way, we could allow the file size to grow without limit. Describe
the further changes to the system and the appropriate algorithm for this new feature.
(a) the paper work of the answers to the questions listed in Section 17.2.
(c) a floppy disk which contains the tar file nachos.tar.gz of your whole directory of nachos.
This tar file is supposed to contain all your programs in ../lab5/ and ../ass3 subdirecto-
ries as well as the original Nachos system.
79