CS560 Lab #5

  • Jim Plank, Rich Wolski

  • Lab 5 -- Simple time-shared multiprogramming

    In this lab, you will add simple, time-shared multiprogramming to JOS. 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 JOS 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 indirection and pipes won't work).

    Changes to the simluator


    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 JOS 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 JOS 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 JOS_TCGETP. Your job is to fill in the third argument which is a pointer to a (struct JOStermios). You do this by calling ioctl_console_fill(char *addr), where addr is the JOS 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 JOS 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(), which you should remember from cs360. Read the man page for more detail. As before, the stack and the heap can crash 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 the test code directory, 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 the lab 4 handout


    Implement the parts of JOS 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 JOS, 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 jsh from the test_execs directory to a.out and run it. You should be able to run any program in the test_execs 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.

    What I did

    As in the last lab, I have a detailed list of the things I did to get this lab working. This is in the file WHAT_DR_PLANK_DID. As before, it is not mandatory that you do things the same way that I did. However, it will make your life easier.