Assignment title: Information


Design (20 points) and Implementation (60 points) - (80 points total) Before you begin this assignment, tag your repository as asst2-begin. System calls and exceptions Implement system calls and exception handling. The full range of system calls that we think you might want over the course of the semester is listed in kern/include/kern/callno.h. For this assignment you should implement: • getpid • fork • execv • waitpid, • _exit It's crucial that your syscalls handle all error conditions gracefully (i.e., without crashing OS/161.) You should consult the OS/161 man pages included in the distribution( in ~os161/os161-1.11/man/syscall directory) and understand fully the system calls that you must implement. You must return the error codes as described in those man pages. Additionally, your syscalls must return the correct value (in case of success) or error code (in case of failure) as specified in the man pages. Some of the grading scripts rely on the return of appropriate error codes; adherence to the guidelines is as important as the correctness of the implementation. The file include/unistd.h contains the user-level interface definition of the system calls that you will be writing for OS/161 (including ones that are relevant for another potential assignment). This interface is different from that of the kernel functions that you will define to implement these calls. You need to design this interface and put it in kern/include/syscall.h. As you may have discovered in OS 161/Asst0, the integer codes for the calls are defined in kern/include/kern/callno.h. You need to think about a variety of issues associated with implementing system calls. Perhaps the most obvious one is: can two different user-level processes (or user-level threads, if you choose to implement them) find themselves running a system call at the same time? Be sure to argue for or against this, and explain your final decision in the design document. For any given process, the first file descriptors (0, 1, and 2) are considered to be standard input (stdin), standard output (stdout), and standard error (stderr). These file descriptors should start out attached to the console device ("con:"). Although these system calls may seem to be tied to the filesystem, in fact, these system calls are really about manipulation of file descriptors, or process-specific filesystem state. A large part of this assignment is designing and implementing asystem to track this state. Some of this information (such as the current working directory) is specific only to the process, but others (such as file offset) is specific to the process and file descriptor. Don't rush this design. Think carefully about the state you need to maintain, how to organize it, and when and how it has to change. Note that there is a system call __getcwd() and then a library routine getcwd(). Once you've written the system call, the library routine should function correctly. getpid() A pid, or process ID, is a unique number that identifies a process. The implementation of getpid() is not terribly challenging, but pid allocation and reclamation are the important concepts that you must implement. It is not OK for your system to crash because over the lifetime of its execution you've used up all the pids. Design your pid system; implement all the tasks associated with pid maintenance, and only then, implement getpid(). fork(), execv(), waitpid(), _exit() These system calls are probably the most challenging part of the assignment, but also the most rewarding. They enable multiprogramming and make OS/161 a much more useful entity. fork() is the mechanism for creating new processes. It should make a copy of the invoking process and make sure that the parent and child processes each observe the correct return value (that is, 0 for the child and the newly created pid for the parent). You will want to think carefully through the design of fork() and consider it together with execv() to make sure that each system call is performing the correct functionality. execv(), although "only" a system call, is really the heart of this assignment. It is responsible for taking newly created processes and make them execute something useful (i.e., something different than what the parent is executing). Essentially, it must replace the existing address space with a brand new one for the new executable (created by calling as_create in the current dumbvm system) and then run it. While this is similar to starting a process straight out of the kernel (as runprogram() does), it's not quite that simple. Remember that this call is coming out of userspace, into the kernel, and then returning back to userspace. You must manage the memory that travels across these boundaries very carefully. (Also, notice that runprogram() doesn't take an argument vector -- but this must of course be handled correctly in execv()). Although it may seem simple at first, waitpid() requires a fair bit of design. Read the specification in the OS 161 waitpid() man page carefully to understand the semantics, and consider these semantics from the ground up in your design. Youmay also wish to consult the general UNIX man page, though keep in mind that you are not required to implement all the things UNIX waitpid() supports, nor is the UNIX parent/child model of waiting the only valid or viable possibility. The implementation of _exit() is intimately connected to the implementation of waitpid(). They are essentially two halves of the same mechanism. Most of the time, the code for _exit() will be simple and the code for waitpid() more complicated, but it's perfectly viable to design it the other way around as well. If you find both are becoming extremely complicated, it may be a sign that you should rethink your design. kill_curthread() As part of this project, you may need to write the code for kill_curthread(). Feel free to write it in as simple a manner as possible. Just keep in mind that essentially nothing about the current thread's userspace state can be trusted if it has suffered a fatal exception -- it must be taken off the processor in as judicious a manner as possible, but without returning execution to the user level. A note on errors and error handling of system calls: The man pages in the OS/161 distribution contain a description of the error return values that you must return. If there are conditions that can happen that are not listed in the man page, return the most appropriate error code from kern/errno.h. If none seem particularly appropriate, consider adding a new one. If you're adding an error code for a condition for which Unix has a standard error code symbol, use the same symbol if possible. If not, feel free to make up your own, but note that error codes should always begin with E, should not be EOF, etc. Consult Unix man pages to learn about Unix error codes; on Linux systems "man errno" will do the trick. Note that if you add an error code to kern/include/kern/errno.h, you need to add a corresponding error message to the file lib/libc/strerror.c. Design Considerations Here are some additional questions and thoughts to aid in writing your design document. They are not, by any means, meant to be a comprehensive list of all the issues you will want to consider. You do not need to explicit answer or discuss these questions in your design document, but you should at least think about them. Your system must allow user programs to receive arguments from the command line. For example, you should be able to run the following program: char *filename = "/bin/cp"; char *args[4];pid_t pid; args[0] = "cp"; args[1] = "file1"; args[2] = "file2"; args[3] = NULL; pid = fork(); if (pid == 0) execv(filename, argv); which will load the executable file cp, install it as a new process, and execute it. The new process will then find file1 on the disk and copy it to file2. Some questions to think about: Passing arguments from one user program, through the kernel, into another user program, is a bit of a chore. What form does this take in C? This is rather tricky, and there are many ways to be led astray. You will probably need several iterations to get this right. What primitive operations exist to support the transfer of data to and from kernel space? Do you want to implement more on top of these? How will you determine: (a) the stack pointer initial value; (b) the initial register contents; (c) the return value; (d) whether you can exec the program at all? You will need to "bullet-proof" the OS/161 kernel from user program errors. There should be nothing a user program can do to crash the operating system (with the exception of explicitly asking the system to halt). What new data structures will you need to manage multiple processes? What relationships do these new structures have with the rest of the system? Testing your system call implementations To test your system call implementations, the best way would be to write small programs that make use of those system calls (fork, exec, waitpid, etc.), compile with cs161-gcc compiler, and run it under os161. Specifically, when you build and invoke your kernel, on the sys161 menu, choose first the "Operations Menu", and then use the "[p]" option to invoke the executable. For example, typing: p testbin/forktestwould invoke the forktest executable which is already provided in the "testbin" directory. The "testbin" directory has other executables; and some of them may be useful for debugging. By reading the first few lines of C programs there, you may be able to see whether the test program is suitable for this project or not. But more importantly, do not restrict your tests with the programs in the testbin directory. In fact it may be wiser to first use very small programs (like the program with fork() and exec() whose code is given on the previous page). For example you can store that program in a separate directory under testbin and compile (by preparing and running a Makefile similar to that available for "forktest" in testbin). Those makefiles call cs161-gcc compiler with proper flags. And then you can again use p testbin/myprogram if the name of your new executable is myprogram. In your submission, include in your asst2 directory the scripts for the tests that you designed so that we can evaluate them as well. In this project, it is NOT necessary to implement the file-system related system calls (such as read() and write()). You can certainly do that if you wish; but it is not required. However, you will still need a "shortcut" in order to execute library calls like putchar(), printf() to print to the screen; because those calls issue a call to "write()". If you don't want to implement "write()" separately, you can do the following: Go to the kern/arch/mips/mips directory, and open for editing the file syscall.s. Within the mips_syscall() function, you will see a switch statement. To handle the calls to "write()", add the following to the switch statement: case SYS_write: for (i = 0; i < (size_t) tf->tf_a2; ++i) { kprintf("%c", ((char *) tf->tf_a1)[i]); } Save syscall.s, build your kernel again, and this should be sufficient for simply printing to the screen. In fact, even the "forktest" program will need this "patch" in addition to the correct implementation of fork(), and exec(), for proper printing. But as we mentioned, it is best to test your implementations first with very short programs, before moving to "forktest" which is more complicated. You may also find some of the programs from the bin directory useful, such as false,and true.Scheduling Right now, the OS/161 scheduler implements a simple Round-Robin queue. As we learned in class, this is typically not the best method for achieving optimal processor performance. For this assignment, you will extend this scheduling algorithm, by adding another queue, converting it into a multi-level (2) feedback queuing system. Both queues will use Round-Robin, and the quantum of the second queue should be two times the quantum of the first. You want the scheduler to be configurable by making it possible to specify the quantum of the first queue. It is OK to have to recompile to change these settings, as with the HZ parameter of the default scheduler. And it is OK to require a recompile to switch schedulers. But it shouldn't require editing more than a couple #defines or the kernel config file to make these changes. In any event, OS/161 should display at boot time which scheduler is in use. Test your scheduler by running several of the test programs from testbin (e.g., add.c, hog.c, farm.c, sink.c, kitchen.c, ps.c) using the default time slice and scheduling algorithm. Experiment with it. Write down what you expect to happen. Then compare what actually happened to what you predicted and explain the difference. It is critical that you plan, run, and report the experiments for the scheduling component in your report, to get credit for this part. Suggestions In this project, you can continue to work with your partner from Assignment 3. If you worked alone in Assignment 3, you can pair up with another CS 571 student who also worked alone in Assignment 3. You will need to (again) put significant time in reading the existing OS 161 kernel code. Make sure to allocate sufficient time to debugging and extensive testing; consider also testing your partner's code extensively! On the assignment due date: 1. Commit all your final changes to the system. Make sure your partner has committed everything as well. Make sure you have the most up-to-date version of everything. Re-run make on both the kernel and userland to make sure all the last-minute changes get incorporated. 2. Run the tests and generate scripts. If anything breaks, repeat step 1. 3. Tag your CVS repository; prepare the diff and submission source tree. 4. Make your submission to the Blackboard by following the guidelines below.