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:
  1. Prints a message
  2. Reads keyboard input, echoing each key pressed to the terminal, until the user hits control-d.
  3. 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: 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.