CS170 KOS

Lab 3 -- Simple time-shared multiprogramming

In this lab, you will add simple, time-shared multiprogramming to KOS. Notice the use of the word "add" in the previous sentence. If your version of Lab 2 isn't working well, you may find yourself tuning that up as well. Operating systems development works in this way. Each piece you develop supports the next piece. If you find yourself spending time on Lab 2 parts to make Lab 3 parts work, you are enjoying a common reality.

Specifically, you will divide the user's memory into eight equal parts, and you will allow up to eight user processes to be in memory at any one time. There will be a timer that you can use to perform time-slicing.

Also, you will be able to compile and execute C programs under KOS that make use of malloc() and the standard I/O library.

When you are done, you will have implemented the following system calls:

Moreover, you'll be able to execute a shell and use it to run user programs (although file redirection and pipes won't work).

Changes to the simulator


Your Job in this Lab

Basically, we want to do four things in this lab:
  1. Allow the user to compile and run programs that use the standard I/O library and malloc().
  2. Implement the parts of KOS that enable a simple shell to work: fork(), execve() and wait().
  3. Implement process id's.
  4. Make time-slicing work.
I will describe each of these in turn.

Allow the user to compile and run programs that use the standard I/O library and malloc().

As before, set up KOS so that it loads the program a.out as its user process when it starts up. What we first want to do is allow these a.out programs to use the standard I/O library and malloc(). To do this, we need to do some busy work and implement some system calls that normally we would not care about. The first few are: getpagesize(), getdtablesize(), and a simple close(). Getpagesize() should return the value of the PageSize variable in simulator.h. Getdtablesize() should return 64. Close() should be implemented so that it returns an error (that's not really how to implement close(), but it will suffice for this lab).

We also have to implement one case of ioctl() and one case of fstat(). Read the man page on ioctl(). I don't expect you to understand much about ioctl() except for its syntax. You are going to need to implement ioctl() when the first argument is 1 and the second argument is KOS_TCGETP. Your job is to fill in the third argument which is a pointer to a (struct KOStermios). You do this by calling ioctl_console_fill(char *addr), where addr is the KOS address of the third argument. Then return zero. This is probably confusing, but it must be done.

Fstat() is called by the standard I/O library in order to find out how it should buffer I/O on file descriptors 0, 1 and 2. You will service these system calls by calling stat_buf_fill(char *addr, int blk_size), where addr is the KOS address of the stat struct that the user passed to fstat(), and blk_size is the amount of buffering that the file descriptor should allow. For file descriptor zero, this should be one. For file descriptors one and two, try 256.

Last, you must implement sbrk(). Read the nearest man page for more detail. As before, the stack and the heap can collide if the user messes up. However, sbrk() should not be allowed to increase past User_limit.

When you have implemented all of these system calls, the a.out programs should be allowed to use the standard I/O library (most notably printf() and malloc()). Look at the programs in

/cs/faculty/rich/cs170/test_execs

, and try ones like hw2.c that use printf(). You can write, compile and run your own programs too -- just follow the compilation steps in Makefile.xcomp and the instructions from lab 2 .


Implement the parts of KOS that enable a simple shell to work: fork(), execve() and wait()

This means you must do multiprogramming. In the last lab, you allowed one user program to execute and gave it all of memory. This time, you should divide memory into 8 equal parts and when you create a new process, it will use one of those parts. Context switching will now involve saving/restoring the registers, User_base and User_limit.

You'll need to modify the exit() system call so that instead of halting KOS, it simply terminates a process. Fork() and execve() are rather straightforward. You should ignore the third argument to execve() -- we won't have any environment variables. You also must implement wait() and process id's, which should work as in Unix. In particular, processes have parents; otherwise they are orphans. If a process exits and its parent hasn't called wait() it becomes a zombie -- it releases its memory but maintains a PCB until its parent either dies or calls wait(). Orphans must do the correct thing when they die.

To test this, copy or link the

/cs/faculty/rich/cs170/test_execs/jsh

program to a.out and run it. Alternatively, cross-compile your own version of Jshell by modifying Makefile.xcomp and link that to a.out. You should be able to run any program in the test_code directory from the shell. You should also be able to run programs in the background. Jsh does allow you to do file indirection and pipes, but you do not have those implemented, so they will not work. That is ok.


Implement process id's

Finally, you need to implement getpid() and getppid() to work with your process id's. This is straightforward. I have no process with process id 0. Orphans return pid 0 as their parent.

Make time-slicing work.

This is straightforward -- set the timer for some number of ticks, and then reschedule the CPU when you get the interrupt.

As in the last lab, there is a cook book of things that you might do to get this lab working. As before, it is not mandatory that you do things the same way that I did. However, it may make your life easier.


A Word About System Calls

In this lab, you need to implement working or almost working versions of several system calls. If you do not know what a system call is, don't panic. I'll tell you. When you want the operating system to do something for you, you make a system call. The arguments for the system call are documented. For example, try the command
man getpid
on your favorite Linux system in the CSIL. What you get are the header files (for the types) and the prototype for the getpid() system call. When you call getpid() on a Linux system, the program traps into the kernel and the kernel figures out how to service the call, and return the necessary values -- just like in KOS. You are implementing the KOS kernel so you have to implement the system calls that KOS supports. The ones that are specified in the lab are necessary to run a small shell called jsh. The lab gives you some details on what they should do, but for many of them, you can simply look at the man page on the CSIL systems to see what they do, what their arguments are, and what their return values are. In other words, your system calls really implement the same thing that the "real" system calls do, so the man pages are relevant.

There are some exceptions, however, which the lab points out. These have to do with the way things like the standard I/O libraries work. In these cases, the lab will tell you what values to return regardless of what the man page says.

Thus, if you find yourself a little confused over the system calls, speak to the TAs, but also consult your local man page for details.