Project 0
CMSC 412, Fall 2004
Due Friday, September 10 at 6pm
The purpose of this assignment is to get you familiar with two things:
the GeekOS development environment (including the BOCHS x86 simulator)
and the Cyclone programming language.
The assignment is to write a simple kernel mode thread that does three
things:
-
Prints a message
- Reads keyboard input, echoing each key pressed to the terminal,
until the user hits control-d.
- Prints the number of lines, words, and bytes entered in total
(before pressing control-d) by the user, with each number separated by
a single space.
1 Preparation
You should use the CSIC cluster (see
http://www.csic.cs.umd.edu/linuxlab for information
on how to login). Your account information is available by logging
into the class grade web site
(grades.cs.umd.edu) using your
campus Directory ID and password.
After logging in, get the Bochs simulator environment and the
mini-kernel for GeekOS running in your account. You should download
the GeekOS kernel and unpack it in your
account. This will create a directory project0-cyclone. Next,
you need to set up your PATH to include the Bochs bin/
directory and the Cyclone bin/ directory. On Linuxlab, you
should add /usr/local/bochs-2.0.2/bin and
/afs/csic/projects/cmsc412/cyclone/bin. At this point, you
cd to the directory project0-cyclone/build and invoke
GNU make to build the kernel (GNU make is the default make
command on LinuxLab). Once the kernel builds, you can run the
simulator by typing bochs and choosing option 5
(which is the default). For this to work, you need a proper
.bochsrc in that directory; the version there should be fine
for LinuxLab. If you run into trouble, check the GeekOS documentation for more details.
2 Reading and Printing Text
Your first task is to add code to the kernel some code to create a new
kernel mode thread. The kernel mode thread should print out ``Hello
from xxx'' where xxx is your class account name. To
start a new kernel mode thread you use the
Start_Kernel_Thread function. Look at the comments in
before the definition of this function in geekos/kthread.c.
Next, implement the thread to then call the keyboard input routine
Wait_For_Key repeatedly, echoing each character entered,
until the termination character (left control-d) is
entered. You need only handle left control key when testing for
termination character. Until control-d is entered, you should also
buffer each of the characters entered. The KeyCode returned
by Wait_For_Key is 16 bits; you need to properly convert
this to an 8-bit ASCII value before storing it in a char
buffer (as well as before printing it to the screen properly).
Third, you must count the lines, words, and characters entered by the
user before pressing control-d. To do this, you must implement the
function count, which should be written in Cyclone in the
file geekos/proj0.cyc. Information about Cyclone can be
found in the Cyclone manual,
http://www.cs.umd.edu/projects/cyclone/online-manual/. Going
through the Chapter ``Cyclone for C Programmers'' should give you a
pretty good feel for the language and be sufficient for this project.
Chapter 3 on pointers is also relevant.
3 Word Counting
The count routine you must implement has the following
prototype:
void count(char * @notnull @numelts(buflen) @nozeroterm buf,
/* the buffer to count */
tag_t<`i> buflen, /* the length of the buffer */
int cnt, /* # of valid chars in the buffer */
int * @notnull w_cnt, /* # of words */
int * @notnull l_cnt, /* # of lines */
int * @notnull b_cnt, /* # of bytes (chars) */
int * @notnull was_sp) /* if the last char read was a space */
Here is an explanation of the arguments:
-
The first argument (buf) is the buffer containing the
characters to count. The type is more complicated than, but is
representationally-equivalent to, a C char * pointer (thus,
when you call this function from C, you can just pass it the
char * into which you were storing the entered characters).
All of the parts of the type beginning with @ are
qualifiers, which provide additional information about pointer
types. The first qualifier @notnull states that buf
can never be NULL. The second qualifier @numelts(buflen)
states that buf has length buflen. The final
qualifier @nozeroterm states that buf is not
zero-terminated. With these qualifiers, the Cyclone compiler will
ensure that you never call count with an incorrect pointer,
e.g., one that is NULL or has fewer than buflen characters.
However, when you call count from C, you must take care to
meet the expected preconditions yourself.
- The second argument (buflen) is the size of the first
argument, buf. Notice how this matches the
@numelts(buflen) qualifier on the type of the buf
argument. When calling this function from Cyclone, the typechecker
can thus ensure that the first two arguments are used consistently.
- The third argument (cnt) is the number of valid
characters in the buffer; it is always less than or equal to
buflen.
- The next three arguments are the word, line, and byte counts
(w_cnt, l_cnt, b_cnt, respectively).
Their types are representationally-equivalent to int * in C;
and the @notnull qualifier designates the pointers can never
be NULL, as with buf. These counts are passed as pointers
and modified by reference, which allows count to accumulate
the results from many calls.
- The last argument (was_sp) indicates whether the last
character seen, on the last call to count, was a whitespace
character. This ensures that a word split by two calls to
count is not treated as two words. The first time you call
count, the value of *was_sp should be 1.
A small bit of the count routine has been provided for you.
In particular, it calls Core::mkfat to convert buf
into a ``fat'' pointer that comes with bounds information. This
ensures that all accesses to the pointer are in bounds. (The ``thin''
pointer buf can be accessed directly too, but the compiler
will forbid some accesses that are to a human obviously correct. For
extra credit, you can try to use buf directly: what
relationship must hold between buflen and cnt to
ensure that all accesses of buf will be within bounds? How
can you communicate this relationship to the compiler?)
3.1 Testing count
The proj0.cyc file has been written with its own
main routine to allow you to test it on its own, outside of
GeekOS. Simply do
cyclone proj0.cyc -o proj0
The resulting program (proj0) takes input on standard in (stdin) and
prints its output to standard out (stdout). The output should be the
same, other than spacing, as the wc command. In particular, for any
text file foo.txt, if you were to do
wc < foo.txt > wc.out
proj0 < foo.txt > proj0.out
diff -w wc.out proj0.out
Then there should be no reported differences. Once you have debugged
the count routine, you can modify your kernel thread to call
count every time its buffer fills up and when the user hits
control-d. After the final call to count can print the
results. Use the main routine in proj0.cyc as a
guide.
4 Calling Cyclone code from C
To call a Cyclone function from C code, we must take into account that
the Cyclone function might throw an exception (like an array bounds
violation or a null pointer exception, as in Java). We have therefore
provided two macros to simplify calling Cyclone from C. These are
provided in geekos/cyclone.h. To call a Cyclone function
f(es) which returns void, use the syntax
void_cyc_call(f(es)). If the Cyclone function throws an
exception, then void_cyc_call will return its name, as a
string. Otherwise it will return NULL. For example, to call the
Cyclone function foo with argument arg, we would do:
#include <geekos/cyclone.h> // for void_cyc_call
#include <geekos/screen.h> // for Print
...
char *exn = void_cyc_call(foo(arg));
if (exn)
Print("Oops! Cyclone function threw an exception %s!\n",exn);
Note that the Cyclone compiler, when compiling Cyclone code to C code
(which is then compiled to assembly), it prepends every Cyclone
function with the prefix Cyc_. Therefore, the
void_cyc_call and cyc_call macros prepend this
prefix to the function call provided.
The cyc_call macro is similar, except that you must also
provide it with a variable to store the value returned from the
Cyclone function. For example, if the Cyclone function
foo returns an integer, then we would call it as follows:
#include <geekos/cyclone.h> // for void_cyc_call
#include <geekos/screen.h> // for Print
...
int ret = 0;
char *exn = cyc_call(ret,foo(arg));
if (exn)
Print("Oops! Cyclone function threw an exception %s!\n",exn);
At the conclusion of this code, if exn is NULL, then
ret contains the value returned from the Cyclone code,
otherwise it will contain 0.
Calling C functions from Cyclone (which you shouldn't need to do) is
straightforward, and is described in the Cyclone manual.
This document was translated from LATEX by
HEVEA.