ASSIGNMENT 1 XV6, PROCESSES, SYSTEM CALLS, IPC Introduction
Total Page:16
File Type:pdf, Size:1020Kb
OPERATING SYSTEMS – ASSIGNMENT 1 XV6, PROCESSES, SYSTEM CALLS, IPC Introduction Throughout this course we will be using a simple, UNIX like teaching operating system called xv6: http://pdos.csail.mit.edu/6.828/2010/xv6-book/index.html The xv6 OS is simple enough to cover and understand within a few weeks yet it still contains the important concepts and organizational structure of UNIX. To run it, you will have to compile the source files and use the QEMU processor emulator (installed on all CS lab computers). Tip: xv6 was (and still is) developed as part of MIT’s 6.828 Operating Systems Engineering course. You can find a lot of useful information and getting started tips there: http://pdos.csail.mit.edu/6.828/2010/overview.html Tip: xv6 has a very useful guide. It will greatly assist you throughout the course assignments: http://pdos.csail.mit.edu/6.828/2011/xv6/book-rev6.pdf Tip: you may also find the following useful: http://pdos.csail.mit.edu/6.828/2011/xv6/xv6-rev6.pdf In this assignment we will start exploring xv6 and extend it to support various scheduling policies. Task 0: Running xv6 Begin by downloading our revision of xv6, from the OS142 svn repository: Open a shell, and traverse to a directory in your computer where you want to store the sources for the OS course. For example, in Linux: mkdir ~/os142 cd ~/os142 Execute the following command (in a single line): svn checkout http://bgu-os-142-xv6.googlecode.com/svn/trunk assignment1 This will create a new folder called assignment1 which will contain all project files. Build xv6 by calling: make Run xv6 on top of QEMU by calling: make clean qemu Warm up (“Hello XV6”) This part of the assignment is aimed at getting you started. It includes two extensions to the xv6 kernel. The main purpose of this assignment is to get familiar with xv6 kernel in general and with its processes/system calls implementation in particular. Note that in terms of writing code, the current xv6 implementation is limited: it does not support system calls you may use when writing on Linux and its standard library is quite limited. In addition, in most modern Unix based operating systems the implementation of these extensions may be slightly different (implementation in user-space instead of kernel-space), but for the sake of simplicity we will implement them in kernel-space. Extension 1: support the PATH environment variable When a single program is executed by the shell it performs two system calls: fork (in order to create another process to run the program) and exec (in order to change the new created process' image). Upon invoking the exec system call, the kernel seeks the appropriate binary file in the current working directory and executes it. If the desired file does not exist in the working directory, an error is returned to the shell who then prints an error message. For example: <bad command>: Command not found. A ‘PATH’ is an environment variable which specifies the list of directories where commonly used executables reside. If, upon typing a command, the required file is not found in the current working directory, the kernel attempts to execute the file from one of the directories specified by the PATH. An error is returned only if the required file was not found in the working directory or in any of the directories listed in the PATH. Your first task is to support a PATH environment variable. We will maintain our PATH as part of the kernel. You can assume that the length of path value will not exceed MAX_PATH_LENGTH = 256. You must set the initial value of PATH variable to contain the root directory. 1.1 Change exec behavior so that in case a required executable is not found in the current working directory, the kernel will attempt to execute the file from one of the directories specified by the PATH. An error will be returned only in case the required executable is not found in any of these directories. 1.2 Change fork implementation so it will be familiar with PATH environment variable (i.e., when the child process is created, it will copy the value of PATH variable from its parent). 1.3 Implement a system call: int set_path(char* path) This system call sets the given path value to the environment variable path. The system call returns 0 upon success and -1 upon failure. Tip: A good place to store the path variable might be proc struct (can be found at proc.h). Tip: You can find current exec implementation at sysfile.c and exec.c Tip: Adding a system call requires some delicate work and proper registration. Be sure to add changes to syscall.c, syscall.h, usys.S and sysproc.c 1.4 Implement a system call: int get_path(char* path) This system call returns the current value of the environment variable path (actually it must save the current value of the path environment variable at the address pointed by a single argument). The system call returns 0 upon success and -1 upon failure. 1.5 Extend current implementation shell with a new command set, that takes as a single argument a list of directories separated by ';' representing paths. This command should update the value of environment variable path (by using the set_path system call). Examples: set /; The Path variable will include the root directory set /;/foo/;/foo/bar/; The Path variable will include the root directory, the folder named foo, and the folder named bar (which is located in the folder foo). 1.6 Write a simple user space program (path) that will print the current value of the environment variable path. Extension 2: support right, left, up and down arrows (←, →, ↑, ↓) The ability to edit the inputs typed is a basic service provided by most shells. By pressing the right or left arrows (‘←’, ‘→’), the user can move between the typed chars and decide where to delete a char or add a new char (insert mode). By pressing the up or down arrows ('↑', '↓') the user can browse the inputs history for older or newer inputs respectively. In this task you will emulate these behaviors. You will need to support the movement of the cursor right and left, by pressing the ‘←’ and ‘→’ keys respectively, so that the line typed in the shell will change where the cursor currently is. In addition, we will maintain our command line history in the kernel. You should support a history of up to MAX_HISTORY_LENGTH = 20 records. Each record has maximum length of INPUT_BUF (see console.c). When the number of records exceeds MAX_HISTORY_LENGTH, you should reuse entries in a FIFO manner. There are no limitations of how to deal with edge cases (what happens when a user presses (up / down) more than 20 times) but you (obviously) have to make sure the kernel does not crash. Note: In most Unix based systems these behaviors are implemented at the shell level. In order to keep things simple we are implementing it at the kernel level. Tip: a good place to start working on this task is trap.c and console.c Dining philosophers problem At this problem N silent philosophers sit at a round table with bowls of spaghetti. Forks are placed between each pair of adjacent philosophers. Each philosopher must alternately think and eat. However, a philosopher can only eat spaghetti when he has both left and right forks. Each fork can be held by only one philosopher and so a philosopher can use the fork only if it's not being used by another philosopher. After he finishes eating, he needs to put down both forks so they become available to others. A philosopher can grab the fork on his right or the one on his left as they become available, but can't start eating before getting both of them. Eating is not limited by the amount of spaghetti left: assume an infinite supply. The problem is how to design a discipline of behavior such that each philosopher won't starve; i.e., can forever continue to alternate between eating and thinking assuming that any philosopher cannot know when others may want to eat or think. One of possible solutions assumes existence of an arbitrator (e.g., a waiter) that can permit/forbid philosopher to pick up forks. In order to pick up the forks, a philosopher must ask permission of the waiter. The waiter gives permission to only one philosopher at a time until he has picked up both his forks. Putting down a fork is always allowed. This approach can result in reduced parallelism: if a philosopher is eating and one of his neighbors is requesting the forks, all other philosophers must wait until this request has been fulfilled even if forks for them are still available. Write two user space programs: waiter.c – this program will imitate the behavior of an arbitrator. First it will receive as an argument (from the shell) the amount of the philosophers in the problem. Then it will create two pipes: one for writing permission to a philosopher to take forks and another to receive message from philosopher that finished eating. Next it will create the appropriate amount of processes (i.e., each process represents a single philosopher). For each created process it will redirect the standard input/output to pipes created before, and finally will execute the code of the philosopher (exec philosopher). The behavior of the waiter after creation of the philosophers will be as follows: first it will write a message to pipe that he permits to take forks, afterwards it will send next permission only after receiving a message from a philosopher that he has finished eating.