Project 0

CMSC 412, Fall 2005

Due Friday, September 9, at 6pm

Introduction

In this course we will implement a big project in subsequent phases. In each project we will be adding some new functionality to GeekOS. GeekOS is a tiny operating system kernel for x86 PCs. Its main purpose is to serve as a simple but realistic example of an OS kernel running on real hardware. To run our OS, we need to either use an actual machine (which must be an x86 machine) or the other (simpler) idea is to use an emulator; we will use Bochs, version 2.0.2.

The purpose of this assignment (project0) is to get you familiar with the GeekOS development environment, including the BOCHS x86 simulator. The assignment is to modify GeekOS to impose resource limits on GeekOS processes in two simple ways:

Preparation

Linuxlab

You will do all of your development for this course on the Linuxlab 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 (http://grades.cs.umd.edu) using your campus Directory ID and password. You will use that to access the cluster, and the lab. The lab is located in 3107 CSI (the Computer Science Instructional Center).

To access the cluster remotely, you have to use ssh (it doesn't accept telnet requests). The domain name is linuxlab.csic.umd.edu . So it will be something like:

ssh -l linuxlab.csic.umd.edu

Compiling your kernel

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. Next, you need to set up your PATH to include the Bochs bin/ directory as follows:

If your shell is csh (default on Linuxlab), add them with
$ setenv PATH /usr/local/bochs-2.0.2/bin:$PATH
If your shell is bash, add them with
$ export PATH=/usr/local/bochs-2.0.2/bin:$PATH
At this point, you can compile the kernel. Change the directory to project0/build/ and invoke GNU make to build the kernel (GNU make is the default make command on LinuxLab).

Running your kernel on top of Bochs

Once you have the kernel built, it's the time to run Bochs and load your kernel into it. Assuming you have set your path properly as indicated above, just go to your project's build directory and run Bochs (choose option 5, which is the default).

$ bochs

For this to work, you need a proper .bochsrc in that directory. The version there should be fine for LinuxLab.  If you are running bochs at home, you will have to edit the file so that vgaromimage and romimage refer to the bochs installation on your machine.

Running Bochs remotely

If you are working remotely from a UNIX machine, e.g. from your WAM/Glue account or from home, do as follows. If you wish to run from a Windows machine with X, Cygwin, and Secure Shell installed, please follow these instructions instead.

First download the fonts file, and uncompress/untar it in your account. Then do:

xset fp+

For example, if you have saved the files in the directory ~/fonts, and your user name in WAM is bob, do

xset fp+ ~bob/fonts

If you are forwarding your X session over ssh, you should now be good to go: just run bochs on Linuxlab, and the output will show up in a window on your local terminal. However, if you are not, you may need to invoke the command

xhost +

Note: you have to do these steps every time you log on to your account. Also, the second step is dangerous, and thus not preferred, because it enables clients from any host to connect to your X session!

Unfortunately, there is a problem with WAM's font server, which makes adding the fonts need more steps. Specifically, instead of

xset fp+

do the following if you're working from a WAM workstation: (Again, these have to be done from your WAM account, and the text in bold must be entered as is.)

For example, if your workstation's IP address is 128.8.17.52, the second command will be:

xset +fp tcp/128.8.17.52:7100

To find out what your workstation IP address is, you can do:

host $HOST

Background

Running GeekOS

Once you have started up GeekOS, you will be taken to the GeekOS shell prompt.  That is, the first program that GeekOS runs is shell.exe, which takes commands for you to run just like the UNIX shell program.  The GeekOS file system is set up so that a number of user programs are installed in the directory /c.  The source code for these programs is in the directory src/user in the distribution.  The programs b.exe, null.exe, and long.exe are three such programs.  The first program simply prints all of its arguments.  The second is an infinite loop.  The third is a long, but not infinite loop.  You can run the programs as you would expect. For example, to run the b.exe program, you could do
     $ /c/b.exe 1 2 3 4
And it would print out the four arguments that you provided. The shell also takes a number of directives.  For example, typing exit will terminate the shell (and your interaction with the OS).  Look at src/user/shell.c to understand the other features of the shell.

You can easily add your own user programs by adding a file to src/user, and modifying the Makefile in the build directory accordingly.

Now we'll talk about how GeekOS works.

User processes vs. the kernel processes

In writing an operating system, you want to make a distinction between the activities that operating systems code are allowed to do and the activities user programs are allowed to do. The goal is to protect the system from incorrect or malicious code that a user may try to run. Bad things a program could do include:

Preventing these sorts of mistakes or attacks is accomplished by controlling the parts of memory that can be accessed when user code is running and limiting the set of machine operations that the user code can execute. The x86 processor provides the operating system with facilities to support these controls. A program that is running in this sort of controlled way is said to be running in user mode.

Anatomy of a User Process

In GeekOS, there is a distinction between Kernel Threads and User Threads. As you would expect, kernel activities (that is, processes) run as kernel threads, while user programs (or rather, user processes) run in user threads. A kernel thread is represented by a Kernel_Thread structure (in include/geekos/kthread.h)


struct Kernel_Thread {
    ulong_t esp;                         /* offset 0 */
    volatile ulong_t numTicks;           /* offset 4 */
    int priority;
    DEFINE_LINK(Thread_Queue, Kernel_Thread);
    void* stackPage;
    struct User_Context* userContext;
    struct Kernel_Thread* owner;
    int refCount;
  ...
}

The kernel thread contains all of the mechanisms necessary to run and schedule a process. In order to represent user processes, GeekOS includes an extra field in the Kernel_Thread structure that points to a User_Context structure. In a thread representing a kernel process, the User_Context will be NULL. The User_Context is defined in include/geekos/user.h:


struct User_Context {
    /* We need one LDT entry each for user code and data segments. */
#define NUM_USER_LDT_ENTRIES 2

    /*
     * Each user context contains a local descriptor table with
     * just enough room for one code and one data segment
     * describing the process's memory.
     */
    struct Segment_Descriptor ldt[NUM_USER_LDT_ENTRIES];
    struct Segment_Descriptor* ldtDescriptor;

    /* The memory space used by the process. */
    char* memory;
    ulong_t size;

    /* Selector for the LDT's descriptor in the GDT */
    ushort_t ldtSelector;
   ...
};

Most of the information in the User_Context structure has to do with the memory layout for the process, and you don't need to understand that for now (not until project 2). You will have to modify User_Context to contain some bookkeeping information as part of this project.

User threads are created using the routine Start_User_Thread. This takes as its first argument the User_Context to run within that thread. This context is created by the Sys_Spawn system call. A system call is like a function call that a user program makes to the OS kernel, to ask it to perform some service. System calls are a proper topic we'll cover shortly in the course, but we'll tell you now what you need to know about them for this project.

System Calls

The operating system kernel presents an interface to user processes for the services it will perform on their behalf. These are essentially functions. However, rather than literally performing a function call to access them, user processes have to use a special mechanism, called a system call. System calls are designed to protect the kernel's memory from malicious processes. On Intel's x86 processor, a user process issues a system call via a trap, which indicates that a system call is being requested; the identity of the system call routine and/or the arguments to pass to it are stored in the processor registers. This mechanism is hidden from the typical C programmer, since it is typically used only in the standard C library routines.

In GeekOS, when a user process issues a system call, the trap causes the routine Syscall_Handler in geekos/trap.c to be invoked. This routine examines the current value in the register eax and calls the appropriate system routine to handle the syscall request. The value in eax is called the Syscall Number. The routine that handles the syscall request is passed a copy of the caller's context state, so the values in general registers (ebx, ecx, edx) can be used by the user program to pass parameters to the handler routine and can be used to return values to the user program. This state is defined in the struct Interrupt_State, defined in geekos/int.h.

The routines that implement GeekOS system calls are in geekos/syscall.c. The Sys_Spawn code is implemented here, along with code for other system calls, like Sys_Exit for the system call used by a process to terminate itself. If you are curious how system calls are invoked from user processes, take a look at src/user/shell.c in your project distribution; you'll be modifying this program in your next project.

Creating User Processes - Sys_Spawn()

In order to create a user process, the Sys_Spawn syscall is used (defined in geekos/syscall.c). The function calls Spawn() (defined in the file src/geekos/user.c) that is used to launch user programs. FYI, this Spawn() function does the following (see the comments in user.c as well)

Sys_Spawn returns the process id (pid) of the new thread.

Project Requirements

The purpose of this assignment is to modify GeekOS to impose resource limits on GeekOS processes in two simple ways:

  1. Only permit N user processes from being active at any given time (for some N).
  2. Only permit a user process to perform at most M system calls (for some M).

For the purposes of the project, you can fix N to be 10 and M to be 100. That is, just make global variables in kthread.c and initialize them to these values.

Finally, you need to create a userlevel process that spawns some other process a desired number of times.

Limiting Number of Processes

To limit the number of processes, you should maintain a counter for the current number of (active) user processes. Then you must determine whether a Spawn is allowed by checking the value of the counter. Follow the code through the Sys_Spawn system call (defined in geekos/syscall.c) and make modifications as necessary in the kernel. If the limit has been reached then the process should not be created and Sys_Spawn should fail by returning -1.

Limiting Number of System Calls

In order to limit the number of system calls, you need to modify the User_Context structure (defined in include/geekos/user.h) to include a counter for the number of system calls the user process has performed. Then you should modify the kernel to update this counter each time it performs a system call on this process's behalf.  For the system call that exceeds the limit (i.e., the N+1st system call), Sys_Exit should be called instead to kill the process.

Creating a user process to spawn a specified number of processes

Look at the code provided in src/user/* and specifically at shell.c to see how to spawn other processes using the system call interface provided. Write a user-level process called spawn.exe that takes as its argument the number P and then spawns P processes.  Spawn the provided user-level program b.exe null.exe. Your spawn.exe program should display an error message "Maximum number of processes reached" if spawn fails due to reaching the maximum number of spawned processes.

Notes

We will publish grading criteria for this project shortly.

The provided kernel is not "Cyclone-enabled" so you cannot use Cyclone for this project.  A Cyclone-ready kernel will be provided for project 1.

Web Accessibility