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';
}