+---------+ |Project 2| +---------+ +----------------+ |FileSystem calls| +----------------+ Implementing File System Calls: ------------------------------- The current implementation only has the halt system call. You will be implementing other system calls. You not need to write code for the functionality. It is already given to you. Check out these files: StubFileSystem.java, OpenFile.java and OpenFileWithPosition.java. You are going to call the methods given by the filesystem on behalf of the user (users simply call the system call). In the system call handler, all you do is invoke the appropriate APIs provided by the filesystem. Look at UserProcess.java and see how they handle the halt system call. You will be adding similar cases for other system calls. Reading syscall arguments: -------------------------- The argument to the system call are stored in processor registers. But, as you can see the processor registers are interger registers. Then how does nachos represent string arguments in the register? If you take a closer look start.s which facilitates system calls from the user program, it only stores the address (of the memory which stores the string argument) in the processor register. For testing, you can add a system call in say halt.c, compile it and then run the program with nachos and enable the debug flags. The assembly code printed by the debug statements will include a line to load the address of the system call argument into the processor register. Then, how to read the memory (which now contains the system call argument) using the address given? Look for suitable APIs in the UserProcess.java such as readVirtualMemory, readVirtualMemoryString. Reading huge files: ------------------- The way operating system reads/writes the contents of a file is, it reads/writes that contents to a buffer that it owns and uses it as a medium to transfer data to/from the filesystem to the process address space. For small files, you can allocate buffer proportional to the data to be read. But what about large files say a few MBs (just for an example)? For this, kernel will have a small buffer (of standard size) and do multiple reads from the memory. Same goes for large writes as well. You do not need to keep different buffer size for small and large read/write. File descriptors: ----------------- File descriptors are simple integers values used by a process for identifying the open files. Once you open a file, you will have a file descriptor and for any further operation on the same file (read, write, seek ...), you will be using the file descriptor. You should not supply file name for further file operations because the API of other file system calls accepts only a file descriptor. Take a look at syscall.h for the system call descriptions. To facilitate file descriptors, you need to track the association between the descriptors and the corresponding open files. See StubFilesystem, OpenFile classes. OpenFile class represents one open file. Also, once a process closes the file, you will need to free the descriptor so that the descriptor can be reused and allocated to new files when opened. +---------------------------+ |Supporting Multiprogramming| +---------------------------+ The nachos package, as of now, does not support multiprogramming. It allocates all of the available physical memory to one process. Your job is to distribute the available nachos physical memory (main memory) to the new processes as and when requested. You do not need to worry about scheduling the multiple processes since it is taken care by the scheduler. If you look at it closely, each process runs the program using a UThread which inherits KThread. So, when you have multiple processes in the system, it means you have multiple threads in the system and context switching between them is already taken care of. Look at the current UserProcess.java and see how it gets it's share of main memory. Right now the entire physical memory is given to one process. So to support multiprogramming, the available physical memory should be shared among processes. Instead of allocating entire memory at once, when a new UserProcess instance is created, you should delay allocation and allocate only after you find out how many pages are needed for the process. It is said in the project description that, the max stack size is 8 pages (the dynamic requirement). So see which part of the code calculates how much more memory is needed (static requirement; for text section, for storing globals ...). Once the process finds out the total number of pages needed, it should request the kernel that many pages. You should have a datastructure in the kernel which will maintain a list of free pages and grant a free page when a process requests. The other part is, the datestructure should facilitate freeing a page so that processes can free a page when it is no longer needed (when the process exits or terminated unexpectedly) so the freed pages can be allocated to newer processes. Note that, this datastructure which manages the list of free pages may be accessed by more than one process concurrently. For example, while one processes is being created, another process may exit. In this case, one process tries to get free pages and the other process tries to free memory. So ensure that you prevent concurrent access. What mechanism does immediately strike you to achieve this goal? Once you manage to request and get physical pages from the kernel, then comes the virtual memory management. Remember that virtual memory is contiguous and physical memory is not. So you should construct your page table with the mapping between virtual page number to physical page number. As of now, since all of the physical memory is given to one process, the virtual and physical addresses are same. One you start to have multiple processes, this mapping will change. For instance, the kernel may give the user processes the physical pages 13, 14, 15, 16, ... 23. But as we know, virtual memory always starts with page 0. So the corresponding mapping would be 0->13, 1->14, 2-15, ... 10->23. Each process maintains it's own private page table. +--------------------+ |Process System calls| +--------------------+ Exec: Creates a new process. This is how you spawn the new processes. How do you create a new process and load the appropriate object file (.coff) file to the newly created process and run it? These details are already there in the UserProcess class. Your exec system call handler will do something similar. You will need to do few more tasks in addition. Give each new process a unique process id and remember the parent process (which spawned the current process). Also, the parent process should somehow know all it's child processes. Join: The functionality is same as the KThread.join but here we join a UserProcess. A process should be able to join only it's child process(es). What mechanism can we use to make the parent process to wait for the child process to complete (something familiar!)? Exit: Termiate the calling process. Before terminating, it needs to close all the open files and also return the memory that it holds to the kernel. There could be parent process waiting for this child process to finish. So make sure, you allow the parent proess to proceed when the child exits. +--------------+ |Something more| +--------------+ Project description says, the only root process is allowed to execute halt system call and the last process, upon exit, should terminate the kernel? What is a root process? Well, the process that you first run using 'nachos -x ' is the root process. All other processes can be spawned from inside this process (your high level C program should have exec system calls). Remember that, the child process can continue to execute while the parent may finish early and exit. So when the exit system call is invoked, you should see whether the current process is the last process in the system and if so terminate the kernel.