HW3: System Calls

This homework teaches you to implement a new system call for the xv6 OS, and helps you to solidify your understanding of how the process address space is constructed. Since you program the xv6 operating system, you should use the same setup as for the HW2: Xv6 boot.

Your task is to add a new system call to xv6. The main point of the exercise is for you to see some of the different pieces of the system call machinery.

Your new system call will dump memory of a process on the console. Specifically, your new system call will have the following interface:

 int dump(int pid, void *addr, void *buffer, int size); 
Where pid is a process identifier of the process which memory you're dumping, addr is the address inside the process memory from where the memory is dumped, buffer is the user allocated buffer where content of the user memory is saved, and size is the size of the memory region to dump.

You will have to dump the entire memory of the process on the console, i.e., starting from address 0 to proc->sz. Since there is no system call to find proc->sz of another process (the process can find its own memory size by invoking the sbrk() system call and passing 0 as an argument) you can iterate through the memory of the process page by page until you see the first unmapped page (your dump system call should return an error for that). Your dump on the console can look like this:

0x7bcc:	0x00007db7	0x00000000	0x00000000	0x00000000
0x7bdc:	0x00000000	0x00000000	0x00000000	0x00000000
0x7bec:	0x00000000	0x00000000	0x00000000	0x00000000
0x7bfc:	0x00007c4d	0x8ec031fa	0x8ec08ed8	0xa864e4d0
0x7c0c:	0xb0fa7502	0xe464e6d1	0x7502a864	0xe6dfb0fa
0x7c1c:	0x16010f60	0x200f7c78	0xc88366c0	0xc0220f01
In order to test your system call you should create a user-level program dump that calls your new system call. In order to make your new dump program available to run from the xv6 shell, look at how other programs are implemented, e.g., ls and wc and make appropriate modifications to the Makefile such that the new application gets compiled, linked, and added to the xv6 filesystem.

When you're done, you should be able to invoke "dump" from the shell:

...
init: starting sh
$ ls   
.              1 1 512
..             1 1 512
...
zombie         2 17 12200
dump           2 18 12260
console        3 19 0
$ dump
0x000: 0x00007db7      0x00000000      0x00000000      0x00000000
0x0010 0x00000000      0x00000000      0x00000000      0x00000000
...
You can follow the following example template for dump.c:
#include "types.h"
#include "stat.h"
#include "user.h"
#include "syscall.h"

#define PGSIZE 4096

void dump() {

  /* Fork a new process to play with */
  /* We don't have a good way to list all pids in the system
     so forking a new process works for testing */ 
  int pid = fork();

  if (pid == 0) {
    /* child spins and yields */
    while (1) {
       sleep(5);
    };
  }

  /* parent dumps memory of the child */
}

int main(int argc, char *argv[])
{
  dump();
  exit();
}

In order to make your new dump program available to run from the xv6 shell, add _dump to the UPROGS definition in Makefile.

Your strategy for making the dump system call should be to clone all of the pieces of code that are specific to some existing system call, for example the "uptime" system call or "read". You should grep for uptime in all the source files, using grep -n uptime *.[chS].

Some hints

It's convenient to implement the body of the dump() system call in the proc.c file. xv6 keeps all processes as an array inside the ptable data structure defined in proc.c. To find the process you're working with you can simply loop through the entire array and find the process which has the matching pid. You can find an example for how it's done inside the kill() function (proc.c).

In order to access memory of another process you will have to walk the page table directory of that process with walkpgdir(). However, walkpgdir() is defined as a static function in vm.c. Hence, to use this function in proc.c where your system call is implemented you will have to export walkpgdir() by removing the word static from the definition, i.e.,

//static 
pte_t *
walkpgdir(pde_t *pgdir, const void *va, int alloc)
{
   pde_t *pde;
And also adding the following definition to defs.h:
pte_t*          walkpgdir(pde_t *pgdir, const void *va, int alloc);
Finally, to make sure that pte_t type is defined, add the following line to types.h:
typedef uint pte_t;

Extra credit (5%): annotate text, guard, stack, and heap

Change your dump.c program to annotate which memory is which, i.e., text, guard, stack, and heap. You should be clever about detecting the section boundaries, i.e., look at the value of the stack pointer, rounding it to the page boundary, making a conclusion that above this page is heap, below is the guard page, etc.

Extra credit (5%): implement ps

Implement the ps tool that lists all processes running on the system. For that you should implement yet another system call getprocinfo() that returns information for a process. Specifically, your new system call will have the following interface:
 int getprocinfo(int pid, struct uproc *up); 
Where pid is the process id of the target process, and struct uproc is a structure that describes the process, i.e., contains the following information about the process: process name, process id, parent process id, size of process memory, process state, whether process is waiting on a channel, and whether it's been killed.

You will have to define the struct uproc and implement the ps utility by querying the system about all processes in the system. You should create a user-level program that calls your new getprocinfo() system call.

When you're done, typing ps to an xv6 shell prompt should print all processes running in the system and information about them.

Finally, change your dump program to take the process id as an argument instead of forking a child. Now you can use ps to enumerate all processes in the system, and then dump memory for one of them. Your new dumppid tool (dumppid.c) accepts a command line parameter, a process id which memory is dumped. E.g., typing dumppid 2 on the shell command prompt should dump the memory of the process with id 2.

Submit

Submit your answers on Canvas HW3 Syscalls as a compressed tar file of your xv6 source tree (after running make clean). You can use the following command to create a compressed tar file (if you submit extra credit assignments, put a short hw3.txt readme file describing what you've done inside your archive)

vagrant@odin$ cd /vagrant/cs238p
vagrant@odin$ make clean
vagrant@odin$ tar -czvf hw3.tgz xv6-public
Updated: March, 2018