Lab 5: Process Trees

This lab is part of Assignment 3. You are to complete the exercises here before arriving in lab on Monday/Tuesday. In lab, you and your partner will demonstrate your code to your TA, and get feedback. Your revised code is due on March 14 at noon.

Labs 5 and 6, together, allow us to investigate some comuputer insecurities. In Lab 5, we will implement code for a binary tree of computer processes. In Lab 6, we will use that implementation to simulate a denial of service attack known as a fork bomb.

As always, your code must comply with the CS 190 Style Specifications, and work on the ECF machines. The marking scheme for Assignment 3 is available here.

Part Zero: Setup

Like the last assignment, you'll be submitting code via SVN.

You will need a partner for this assignment (which includes labs 5 and 6). Your partner must be from the same lab group as yourself. You may work with the same person you did for Assignment 3 provided they are in your lab group.

Like the last assignment, you and your partner will have to create a group on MarkUs so you may checkout the SVN repository for this assignment.

Part One: A Basic Binary Tree

In this section, we are modelling computer processes. These have a tree structure on your computer: when you boot your computer, the first process begins, and creates child processes, which in turn create more child processes. To make our simulation simpler, each computer process can only have two children. In reality: a process can have as many children as possible. You can have a look at the processes running on your computer using the command pstree.

Every process on a computer has two things: a process ID, known as a PID; and a certain amount of memory allocated to the process. You can read more about computer processes here. Every time a process is created, it is given the first available PID.

Download the files process.h, process_provided_parts.c, Makefile, process.c, and test_one.c. You are to implement, using no loops in any method (i.e. using recursion) -- you are encouraged to write helper methods where appropriate:

  1. make_process: this creates a new process, with its left and right child set to NULL, and pid and mem_used set to the parameters given. Assert that pid >= 0, and mem_used > 0.
  2. print_postorder: this uses helper_print_postorder to print the contents of a binary tree on one line, separated by spaces, with "Postorder: " at the beginning.
  3. helper_print_postorder: this prints a binary tree recursively using post-order traversal. Note the parameters are up to you to figure out.
  4. print_inorder: this uses helper_print_inorder to print the contents of a binary tree on one line, separated by spaces, with "Inorder: "
  5. helper_print_inorder: this prints a binary tree recursively using in-order traversal. Note the parameters are up to you to figure out.
  6. print_levelorder: this uses helper_print_levelorder to print the contents of a binary tree on one line, separated by spaces, with "Level order: "
  7. helper_print_levelorder: this prints a binary tree recursively using level-order traversal. Note the parameters are up to you to figure out.
  8. num_node: returns the number of nodes inthe tree.
  9. is_complete: tests whether the tree is a complete tree. (Recall that a complete tree is one in which every level, except possibly the last, is completely filled, and all nodes are as far left as possible. -- Wikipedia). NULL is considered to be complete.
  10. remove_all: this removes and frees all memory of a binary tree, leaving the pointer to the root node as NULL.

Use the test cases in test_one.c to implement your code incrementally. To compile, type make one. Ensure using valgrind that after remove_all is implemented, that there are no memory leaks. Your output of ./one should look like this.

Part Two: A Process Tree

For part two, you are to implement the following methods in process.c. Download the file test_two.c to test them.

The file test_two.c takes in two command-line arguments: the starting PID of your tree, and a maximum size of memory. Like in part one, no method may contain any loops: these methods are intended to be done recursively. You may write as many helper methods as you wish.

Note that PIDs are not guaranteed to be sorted.

  1. contains_pid: returns 1 iff a node with a given PID is in the tree
  2. total_mem: returns the total amount of memory stored by the different nodes
  3. can_add: tests whether a node new_node can be added to the tree: for this to be the case, no node in the tree can have the same PID, and the total memory + new_node's memory must not exceed the maximum memory. Assert that new_node is not NULL.
  4. insert_levelorder: when adds a new process to_add to the base of the tree, so that to_add is placed so the tree in the first available location using level order traversal (i.e. the tree is complete). Assert that the tree is complete after the addition, and that to_add can be added (use can_add). Hint: you may want to use a stack or queue here. Both are provided in process_provided_parts.c.
  5. create_tree: using insert_level_order, creates a tree of size n; each new process should take the next available PID (e.g. 1, 2, 3...); one parameter is the PID of the first process.
  6. is_sorted: returns 1 iff a given tree is sorted in level-order. NULL is considered to be sorted.
  7. get_min: returns the smallest process in a given tree that is larger than or equal to smallest_val. Asserts that the tree is sorted.
  8. rebuild_tree: given a tree, if it is not complete and in sorted level order, rebuilds the tree to satisfy these constraits and returns 1. If the tree already satisfies these constraints, it returns 0.
  9. kill: given a PID, remove the node with this PID from the tree and free its memory. Returns 1 iff the node was successfuly deleted. Note this has many cases! (Left leaf, right leaf, non-root parent with left child, non-root parent with left and right child, root, etc, etc) The tree, after any deletion, should be set up so that the level-order traversal prints out the PIDs in sorted order. The tree, after any deletion, should also be complete (assert this). Hint: Writing helper methods for this is a great idea.

To compile, type make two. Like in Part One, ensure that there are no memory leaks in this part.

Ensure that you commit your code by the end of the lab session.

Waiting to Talk to Your TA?

Get started on Lab 6!