Project Overview

This project is composed of three parts, which you must complete in order. Part 0 is the easiest part and simply required you to get your development environment setup correctly. Part 1, which is slightly more difficult, required you to trace a fork system call ("syscall") using GDB and answer some questions. Part 2, which is the most challenging, will have you implement the ps shell command along with a syscall that is required for the command to work.

In this third part, you will write some kernel code as well as a user program. The user program you must write is the ps shell command. In order to write this user program, you will also have to write a new syscall for the kernel called getprocs. This will give you a first taste of operating system development.

Educational Objectives

This project aims to achieve several educational objectives:

Part 2: Implementing the ps command

70 points

Part 2 requires you to implement the ps shell command.  It will require only about 60 lines of code, but the biggest challenge is figuring out where to write the code.  You will have to edit about a dozen different files.  When you are done with this project, you will be able to get info about the currently running processes on xv6 by typing:

$ ps

To get an idea of what the ps command is supposed to do, open a regular Linux shell (e.g. on a lab machine) and type ps. You will see some info about the currently running processes. To see even more info, run ps again with the -u flag. To see even more processes (from all users on the machine) add the -a flag.  You can read more about the ps command on wikipedia or by doing a google search.

Part 2a: Writing the getprocs syscall

Before you can implement the ps command, you will need to write a new syscall to get information about the current processes from the kernel. Here is the function signature for the syscall you will write:

int getprocs(struct ProcessInfo processInfoTable[]);

You'll have to dig through the codebase to find where exactly this function signature should be added.

This syscall, once implemented, will return the number of current processes in the kernel--that is, the number of entries in the kernel's process table that are in any state other than UNUSED. If the syscall cannot be completed for any reason, it shall return -1.

getprocs will also fill-in the processInfoTable that is passed-in.  We'll worry about that in part 2b, below.  For now, we just need to add a "ProcessInfo" struct definition so that your code will compile. Add a new file ProcessInfo.h with this definition of struct ProcessInfo:

#ifndef _PROCESSINFO_H_
#define _PROCESSINFO_H_
#include "types.h"

struct ProcessInfo {
  char name[16]; // name of process
  int pid; // process id
  int ppid; // parent pid
  uint sz; // size in bytes
  int state; // state
};

#endif //_PROCESSINFO_H_

Now you are ready to begin implementing your new syscall. Again, you'll have to search the codebase to see how and where other syscalls are implemented, then mimic their implementations.

HINT: There is a simple syscall named "getpid" which gets the process id of the current process.  Look for all occurences of "getpid" in the source code to see all the places where code is needed to define this syscall.

In order to help you search the code, you'll probably want to use a text editor that allows you to search multiple files at once so that you can find every occurrence of your search term. This will help you figure out all the files that you have to touch in order to implement a new syscall.  Otherwise, use the "grep" command to search in multiple files.  For example:

grep -RI getpid *

You can also use the search bar at the top of the github page to search through all the xv6 source code.

Once you have your syscall implemented, see if your code compiles by simply typing "make" in the xv6 directory. Once your code is compiling, you can try to run xv6 by typing "make qemu-nox".

Finally you can do a sanity check to see if your syscall is working by writing a simple user program that uses it. Create a new file ps.c that invokes your new syscall and outputs the result using printf. In order to run this new user program within xv6, you'll have to make a small addition to the Makefile.

Part 2b: Writing the ps user program

In the final part of this project, you'll expand your ps.c program to actually print out information about each process similar to the Linux ps command. Your command won't print out the header line, but for each process it will print out a line containing the following info:

process id, parent process id, state, size, name

Here are some additional requirements:

Here is an example of what your output will approximately look like when you run your ps command from the xv6 shell prompt:

$ ps
1  -1  SLEEPING  8192  init
2  1  SLEEPING  12288  sh
3  2  RUNNING  8192  ps

In order to accomplish this, you'll have to expand your getprocs syscall to actually populate the processInfoTable[] with information that it gathers from the kernel's ptable (process table). You'll be working in pretty much all the same files as in Part 2a, just adding functionality.

Once you are done with your modifications to your getprocs syscall and your ps.c program, you can test everything by running xv6 and typing ps at the prompt. For more interesting outputs you can run a shell within a shell (within a shell...) by typing sh at the xv6 prompt. And you can kill processes by typing kill followed by the pid.

HINT1: One tricky part is figuring out how a syscall gets the arguments that were passed to it (e.g. how your kernel code will get a hold of the pointer to the processInfoTable).  This is complicated because system calls are not called like regular C functions (they are called through interrupts).  Fortunately, xv6 has already defined some functions to help with getting syscall parameters. There are three such functions in kernel/syscall.c, called argint, argptr, and argstr.

HINT2: Recall from part 0 that we looked at the "fork" function in proc.c.  If you dig into that code you'll see how xv6 manipulates information about processes.

HINT3: The "printf" command defined in printf.c is actually requires you to specify the output file descriptor as the first parameter.  So, it's actually like the "fprintf" function in the standard C library.  Look at the implementation of cat.c to see how printf is used.

HINT4: xv6 actually has a built-in debugging feature very similar to the "ps" command you are implementing.  If you press control+p in xv6 the kernel will print some information about all the running processes.  Please compare the "control+p" output to your own results.

Submission Instructions

You will submit your solution through github classroom by simply committing your changes and pushing to your private repository.  If you created new files for your solution then you will have to tell git to add these files to the project.  Run "git status" and look for untracked files.  Then run "git add <my_new_file>" to add that file to the project.  For example, you will have to run "git add ProcessInfo.h".  Review your changes with "git diff" and finally run "git commit -a" to commit your changes.  Now push to your private github repository in github classroom by running "git push".

You should go to the github classroom website to verify that all your new code appears when you view the list of commits.  You should also test it by cloning the repository again (to a different folder) and testing that it works.

NOTE: If you submit your code before the deadline and then realize you need to change something, you can just push an update with the fix.  You might find it useful to push prior to the deadline when you want to share your progress with your partner.

In Canvas, list:

Resources