package nachos.userprog; import nachos.machine.*; import nachos.threads.*; import nachos.userprog.*; import java.io.EOFException; import java.util.*; /** * Encapsulates the state of a user process that is not contained in its * user thread (or threads). This includes its address translation state, a * file table, and information about the program being executed. * * <p> * This class is extended by other classes to support additional functionality * (such as additional syscalls). * * @see nachos.vm.VMProcess * @see nachos.network.NetProcess */ public class UserProcess { /** * Allocate a new process. */ public UserProcess() { exitcode = -255; pid = next_pid++; pids.put(pid, null); } /** * Allocate and return a new process of the correct class. The class name * is specified by the <tt>nachos.conf</tt> key * <tt>Kernel.processClassName</tt>. * * @return a new process of the correct class. */ public static UserProcess newUserProcess() { return (UserProcess)Lib.constructObject(Machine.getProcessClassName()); } /** * Execute the specified program with the specified arguments. Attempts to * load the program, and then forks a thread to run it. * * @param name the name of the file containing the executable. * @param args the arguments to pass to the executable. * @return <tt>true</tt> if the program was successfully executed. */ public boolean execute(String name, String[] args) { if (!load(name, args)) return false; running_process++; UThread t = new UThread(this); pids.put(pid, t); t.setName(name).fork(); return true; } /** * Save the state of this process in preparation for a context switch. * Called by <tt>UThread.saveState()</tt>. */ public void saveState() { } /** * Restore the state of this process after a context switch. Called by * <tt>UThread.restoreState()</tt>. */ public void restoreState() { Machine.processor().setPageTable(ptdriver.getPageTable()); } /** * Read a null-terminated string from this process's virtual memory. Read * at most <tt>maxLength + 1</tt> bytes from the specified address, search * for the null terminator, and convert it to a <tt>java.lang.String</tt>, * without including the null terminator. If no null terminator is found, * returns <tt>null</tt>. * * @param vaddr the starting virtual address of the null-terminated * string. * @param maxLength the maximum number of characters in the string, * not including the null terminator. * @return the string read, or <tt>null</tt> if no null terminator was * found. */ public String readVirtualMemoryString(int vaddr, int maxLength) { Lib.assertTrue(maxLength >= 0); byte[] bytes = new byte[maxLength+1]; int bytesRead = readVirtualMemory(vaddr, bytes); for (int length=0; length<bytesRead; length++) { if (bytes[length] == 0) return new String(bytes, 0, length); } return null; } /** * Transfer data from this process's virtual memory to all of the specified * array. Same as <tt>readVirtualMemory(vaddr, data, 0, data.length)</tt>. * * @param vaddr the first byte of virtual memory to read. * @param data the array where the data will be stored. * @return the number of bytes successfully transferred. */ public int readVirtualMemory(int vaddr, byte[] data) { return readVirtualMemory(vaddr, data, 0, data.length); } /** * Transfer data from this process's virtual memory to the specified array. * This method handles address translation details. This method must * <i>not</i> destroy the current process if an error occurs, but instead * should return the number of bytes successfully copied (or zero if no * data could be copied). * * @param vaddr the first byte of virtual memory to read. * @param data the array where the data will be stored. * @param offset the first byte to write in the array. * @param length the number of bytes to transfer from virtual memory to * the array. * @return the number of bytes successfully transferred. */ public int readVirtualMemory(int vaddr, byte[] data, int offset, int length) { Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length); byte[] memory = Machine.processor().getMemory(); vaddr = ptdriver.translate(vaddr); if (vaddr < 0 || vaddr >= memory.length) return 0; int amount = Math.min(length, memory.length-vaddr); System.arraycopy(memory, vaddr, data, offset, amount); return amount; } /** * Transfer all data from the specified array to this process's virtual * memory. * Same as <tt>writeVirtualMemory(vaddr, data, 0, data.length)</tt>. * * @param vaddr the first byte of virtual memory to write. * @param data the array containing the data to transfer. * @return the number of bytes successfully transferred. */ public int writeVirtualMemory(int vaddr, byte[] data) { return writeVirtualMemory(vaddr, data, 0, data.length); } /** * Transfer data from the specified array to this process's virtual memory. * This method handles address translation details. This method must * <i>not</i> destroy the current process if an error occurs, but instead * should return the number of bytes successfully copied (or zero if no * data could be copied). * * @param vaddr the first byte of virtual memory to write. * @param data the array containing the data to transfer. * @param offset the first byte to transfer from the array. * @param length the number of bytes to transfer from the array to * virtual memory. * @return the number of bytes successfully transferred. */ public int writeVirtualMemory(int vaddr, byte[] data, int offset, int length) { Lib.assertTrue(offset >= 0 && length >= 0 && offset+length <= data.length); byte[] memory = Machine.processor().getMemory(); vaddr = ptdriver.translate(vaddr); if (vaddr < 0 || vaddr >= memory.length) return 0; int amount = Math.min(length, memory.length-vaddr); System.arraycopy(data, offset, memory, vaddr, amount); return amount; } /** * Load the executable with the specified name into this process, and * prepare to pass it the specified arguments. Opens the executable, reads * its header information, and copies sections and arguments into this * process's virtual memory. * * @param name the name of the file containing the executable. * @param args the arguments to pass to the executable. * @return <tt>true</tt> if the executable was successfully loaded. */ private boolean load(String name, String[] args) { Lib.debug(dbgProcess, "UserProcess.load(\"" + name + "\")"); ptdriver = new PageTableDriver(this); OpenFile executable = ThreadedKernel.fileSystem.open(name, false); if (executable == null) { Lib.debug(dbgProcess, "\topen failed"); return false; } try { coff = new Coff(executable); } catch (EOFException e) { executable.close(); Lib.debug(dbgProcess, "\tcoff load failed"); return false; } // make sure the sections are contiguous and start at page 0 numPages = 0; for (int s=0; s<coff.getNumSections(); s++) { CoffSection section = coff.getSection(s); if (section.getFirstVPN() != numPages) { coff.close(); Lib.debug(dbgProcess, "\tfragmented executable"); return false; } numPages += section.getLength(); } // make sure the argv array will fit in one page byte[][] argv = new byte[args.length][]; int argsSize = 0; for (int i=0; i<args.length; i++) { argv[i] = args[i].getBytes(); // 4 bytes for argv[] pointer; then string plus one for null byte argsSize += 4 + argv[i].length + 1; } if (argsSize > pageSize) { coff.close(); Lib.debug(dbgProcess, "\targuments too long"); return false; } // program counter initially points at the program entry point initialPC = coff.getEntryPoint(); // next comes the stack; stack pointer initially points to top of it numPages += stackPages; initialSP = numPages*pageSize; // and finally reserve 1 page for arguments numPages++; if (!loadSections()) { ptdriver.destroy(); return false; } // Stack for(int i = 0; i < stackPages; i++) ptdriver.alloc(false); // store arguments in last page int vpn = ptdriver.alloc(false); int entryOffset = vpn * pageSize; int stringOffset = entryOffset + args.length * 4; this.argc = args.length; this.argv = entryOffset; for (int i=0; i<argv.length; i++) { byte[] stringOffsetBytes = Lib.bytesFromInt(stringOffset); Lib.assertTrue(writeVirtualMemory(entryOffset,stringOffsetBytes) == 4); entryOffset += 4; Lib.assertTrue(writeVirtualMemory(stringOffset, argv[i]) == argv[i].length); stringOffset += argv[i].length; Lib.assertTrue(writeVirtualMemory(stringOffset,new byte[] { 0 }) == 1); stringOffset += 1; } return true; } /** * Allocates memory for this process, and loads the COFF sections into * memory. If this returns successfully, the process will definitely be * run (this is the last step in process initialization that can fail). * * @return <tt>true</tt> if the sections were successfully loaded. */ protected boolean loadSections() { // load sections for (int s=0; s<coff.getNumSections(); s++) { CoffSection section = coff.getSection(s); Lib.debug(dbgProcess, "\tinitializing " + section.getName() + " section (" + section.getLength() + " pages)"); for (int i=0; i<section.getLength(); i++) { int vpn = ptdriver.alloc(section.isReadOnly()); if(vpn == -1) { coff.close(); Lib.debug(dbgProcess, "\tinsufficient physical memory"); return false; } section.loadPage(i, ptdriver.find(vpn)); } } return true; } /** * Release any resources allocated by <tt>loadSections()</tt>. */ protected void unloadSections() { } /** * Initialize the processor's registers in preparation for running the * program loaded into this process. Set the PC register to point at the * start function, set the stack pointer register to point at the top of * the stack, set the A0 and A1 registers to argc and argv, respectively, * and initialize all other registers to 0. */ public void initRegisters() { Processor processor = Machine.processor(); // by default, everything's 0 for (int i=0; i<processor.numUserRegisters; i++) processor.writeRegister(i, 0); // initialize PC and SP according processor.writeRegister(Processor.regPC, initialPC); processor.writeRegister(Processor.regSP, initialSP); // initialize the first two argument registers to argc and argv processor.writeRegister(Processor.regA0, argc); processor.writeRegister(Processor.regA1, argv); } /** * Handle the halt() system call. */ private int handleHalt() { if(this.pid == 0) Machine.halt(); return 0; } private int handleJoin(UThread t) { UserProcess p = t.process; if(p.parent == this) { t.join(); if(t.process.exitcode == -255) return 0; return 1; } return -1; } private int handleExit(int code) { fsdriver.destroy(); ptdriver.destroy(); this.exitcode = code; running_process--; if(running_process == 0) Kernel.kernel.terminate(); UThread.finish(); return 0; } private String[] readVirtualMemoryStringArray(int vaddr, int count) { byte[] ptr = new byte[4]; String[] ret = new String[count]; int iptr; for(int i = 0; i < count; i++) { readVirtualMemory(vaddr, ptr); iptr = ((int)ptr[3] << 24) + ((int)ptr[2] << 16) + ((int)ptr[1] << 8) + ptr[0]; Lib.debug(dbgProcess, "\tread string @ " + ptr[0] + ptr[1] + ptr[2] + ptr[3]); ret[i] = readVirtualMemoryString(iptr, 256); if(ret[i] == null) return null; Lib.debug(dbgProcess, "\tfetched: " + ret[i]); vaddr += 4; } return ret; } private int handleExec(String prog, String[] args) { UserProcess p = UserProcess.newUserProcess(); p.parent = this; child.add(p); if(!p.execute(prog, (args == null) ? (new String[] {}) : args)) { System.out.println("Executable " + prog + " cannot be loaded."); child.remove(p); return -1; } return p.pid; } private static final int syscallHalt = 0, syscallExit = 1, syscallExec = 2, syscallJoin = 3, syscallCreate = 4, syscallOpen = 5, syscallRead = 6, syscallWrite = 7, syscallClose = 8, syscallUnlink = 9; /** * Handle a syscall exception. Called by <tt>handleException()</tt>. The * <i>syscall</i> argument identifies which syscall the user executed: * * <table> * <tr><td>syscall#</td><td>syscall prototype</td></tr> * <tr><td>0</td><td><tt>void halt();</tt></td></tr> * <tr><td>1</td><td><tt>void exit(int status);</tt></td></tr> * <tr><td>2</td><td><tt>int exec(char *name, int argc, char **argv); * </tt></td></tr> * <tr><td>3</td><td><tt>int join(int pid, int *status);</tt></td></tr> * <tr><td>4</td><td><tt>int creat(char *name);</tt></td></tr> * <tr><td>5</td><td><tt>int open(char *name);</tt></td></tr> * <tr><td>6</td><td><tt>int read(int fd, char *buffer, int size); * </tt></td></tr> * <tr><td>7</td><td><tt>int write(int fd, char *buffer, int size); * </tt></td></tr> * <tr><td>8</td><td><tt>int close(int fd);</tt></td></tr> * <tr><td>9</td><td><tt>int unlink(char *name);</tt></td></tr> * </table> * * @param syscall the syscall number. * @param a0 the first syscall argument. * @param a1 the second syscall argument. * @param a2 the third syscall argument. * @param a3 the fourth syscall argument. * @return the value to be returned to the user. */ public int handleSyscall(int syscall, int a0, int a1, int a2, int a3) { String s1; String[] args; byte[] buf; int ret, m; UThread t; switch (syscall) { case syscallHalt: return handleHalt(); case syscallCreate: s1 = readVirtualMemoryString(a0, 256); if(s1 == null) return -1; return fsdriver.creat(s1); case syscallOpen: s1 = readVirtualMemoryString(a0, 256); if(s1 == null) return -1; return fsdriver.open(s1); case syscallRead: if(a2 > pageSize) return -1; buf = new byte[a2]; ret = fsdriver.read(a0, buf); if(ret == -1) return ret; writeVirtualMemory(a1, buf, 0, ret); return ret; case syscallWrite: if(a2 > pageSize) return -1; buf = new byte[a2]; readVirtualMemory(a1, buf, 0, a2); return fsdriver.write(a0, buf); case syscallClose: return fsdriver.close(a0); case syscallUnlink: s1 = readVirtualMemoryString(a0, 256); if(s1 == null) return -1; return fsdriver.unlink(s1); case syscallExec: s1 = readVirtualMemoryString(a0, 256); args = readVirtualMemoryStringArray(a2, a1); if(s1 == null) return -1; return handleExec(s1, args); case syscallJoin: if(pids.containsKey(a0)) { t = pids.get(a0); if(t == null) return -1; m = handleJoin(t); if(m != -1 && a1 > 0) { ret = t.process.exitcode; writeVirtualMemory(a1, new byte[] { (byte)(ret & 0xff), (byte)((ret >> 8) & 0xff), (byte)((ret >> 16) & 0xff), (byte)((ret >> 24) & 0xff) }); } return m; } return -1; case syscallExit: return handleExit(a0); default: Lib.debug(dbgProcess, "Unknown syscall " + syscall); handleExit(-255); Lib.assertNotReached("Unknown system call!"); } return 0; } /** * Handle a user exception. Called by * <tt>UserKernel.exceptionHandler()</tt>. The * <i>cause</i> argument identifies which exception occurred; see the * <tt>Processor.exceptionZZZ</tt> constants. * * @param cause the user exception that occurred. */ public void handleException(int cause) { Processor processor = Machine.processor(); switch (cause) { case Processor.exceptionSyscall: int result = handleSyscall(processor.readRegister(Processor.regV0), processor.readRegister(Processor.regA0), processor.readRegister(Processor.regA1), processor.readRegister(Processor.regA2), processor.readRegister(Processor.regA3) ); processor.writeRegister(Processor.regV0, result); processor.advancePC(); break; default: Lib.debug(dbgProcess, "Unexpected exception: " + Processor.exceptionNames[cause]); handleExit(-255); Lib.assertNotReached("Unexpected exception"); } } /** The program being run by this process. */ protected Coff coff; /** The number of contiguous pages occupied by the program. */ protected int numPages; /** The number of pages in the program's stack. */ protected final int stackPages = 8; private int initialPC, initialSP; private int argc, argv; private FileSystemDriver fsdriver = new FileSystemDriver(); private PageTableDriver ptdriver; public int pid, exitcode; public UserProcess parent = null; private LinkedList<UserProcess> child = new LinkedList<UserProcess>(); private static HashMap<Integer, UThread> pids = new HashMap<Integer, UThread>(); private static int next_pid = 0, running_process = 0; private static final int pageSize = Processor.pageSize; private static final char dbgProcess = 'a'; }