CS 162 Project 2: User Programs
CS 162 Project 2: User Programs
CS 162 Project 2: User Programs
Contents
1 Your task 3
1.1 Task 1: Argument Passing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Task 2: Process Control Syscalls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Task 3: File Operation Syscalls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2 Deliverables 4
2.1 Design Document (Due 03/14) and Design Review . . . . . . . . . . . . . . . . . . . . . . 4
2.1.1 Design Document Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.1.2 Design Document Additional Questions . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.3 GDB Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
2.1.4 Design Review . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.1.5 Grading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2.2 Code (Due 04/02) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.2.1 Student Testing Code (Due 04/02) . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.3 Student Testing Report (Due 04/04) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.4 Final Report (Due 04/04) and Code Quality . . . . . . . . . . . . . . . . . . . . . . . . . . 8
3 Reference 9
3.1 Pintos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.1.1 Getting Started . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.1.2 Source Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3.1.3 Using the File System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.1.4 Formatting and Using the Filesystem . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.1.5 How User Programs Work . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.1.6 Virtual Memory Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.1.7 Accessing User Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
3.1.8 80x86 Calling Convention . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.1.9 Program Startup Details . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.1.10 Adding new tests to Pintos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.2 System Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.2.1 System Call Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.2.2 Process System Calls . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1
CS 162 Spring 2018 Project 2: User Programs
2
CS 162 Spring 2018 Project 2: User Programs
1 Your task
In this project, you will extend Pintos’s support for user programs. The skeleton code for Pintos is
already able to load and start user programs, but the programs cannot read command-line arguments
or make system calls.
3
CS 162 Spring 2018 Project 2: User Programs
While a user process is running, you must ensure that nobody can modify its executable on disk. The
“rox” tests ensure that you deny writes to current-running program files. The functions file_deny_write()
and file_allow_write() can assist with this feature.
Note: Your final code for Project 2 will be used as a starting point for Project 3. The tests for
Project 3 depend on some of the same syscalls that you are implementing for this project. You should
keep this in mind while designing your implementation for this project.
2 Deliverables
Your project grade will be made up of 4 components:
• 20% Design Document and Design Review
• 55% Code
• 15% Student Testing – This is a new component of your project grade (see explanation below).
• 10% Final Report and Code Quality
4
CS 162 Spring 2018 Project 2: User Programs
3. Synchronization – This section should list all resources that are shared across threads. For each
case, enumerate how the resources are accessed (e.g., from inside of the scheduler, in an interrupt
context, etc), and describe the strategy you plan to use to ensure that these resources are shared
and modified safely. For each resource, demonstrate that your design ensures correct behavior and
avoids deadlock. In general, the best synchronization strategies are simple and easily verifiable.
If your synchronization strategy is difficult to explain, this is a good indication that you should
simplify your strategy. Please discuss the time/memory costs of your synchronization approach,
and whether your strategy will significantly limit the parallelism of the kernel. When discussing the
parallelism allowed by your approach, explain how frequently threads will contend on the shared
resources, and any limits on the number of threads that can enter independent critical sections at
a single time.
4. Rationale – Tell us why your design is better than the alternatives that you considered, or
point out any shortcomings it may have. You should think about whether your design is easy to
conceptualize, how much coding it will require, the time/space complexity of your algorithms, and
how easy/difficult it would be to extend your design to accommodate additional features.
5
CS 162 Spring 2018 Project 2: User Programs
The questions you should answer in your design doc are the following.
1. Set a break point at process_execute and continue to that point. What is the name and address
of the thread running this function? What other threads are present in pintos at this time? Copy
their struct threads. (Hint: for the last part dumplist &all_list thread allelem may be
useful.)
2. What is the backtrace for the current thread? Copy the backtrace from gdb as your answer and
also copy down the line of c code corresponding to each function call.
3. Set a breakpoint at start_process and continue to that point. What is the name and address of
the thread running this function? What other threads are present in pintos at this time? Copy
their struct threads.
4. Where is the thread running start_process created? Copy down this line of code.
5. Continue one more time. The userprogram should cause a page fault and thus cause the page fault
handler to be executed. It’ll look something like
Please find out what line of our user program caused the page fault. Don’t worry if it’s just an
hex address. (Hint: btpagefault may be useful)
6. The reason why btpagefault returns an hex address is because pintos-gdb build/kernel.o
only loads in the symbols from the kernel. The instruction that caused the page fault is in our
userprogram so we have to load these symbols into gdb. To do this use
loadusersymbols build/tests/userprog/args-none
2.1.5 Grading
The design document and design review will be graded together. Your score will reflect how convincing
your design is, based on your explanation in your design document and your answers during the design
review. You must attend a design review in order to get these points. We will try to accommodate any
time conflicts, but you should let your TA know as soon as possible.
6
CS 162 Spring 2018 Project 2: User Programs
We will grade your test cases based on effort. If all of the above components are present in your
Student Testing Report and your test cases are satisfactory, you will get full credit on this part of the
project.
7
CS 162 Spring 2018 Project 2: User Programs
• The changes you made since your initial design document and why you made them (feel free to
re-iterate what you discussed with your TA in the design review)
• A reflection on the project – what exactly did each member do? What went well, and what could
be improved?
• Your Student Testing Report (see the previous section for more details)
You will also be graded on the quality of your code. This will be based on many factors:
• Does your code exhibit any major memory safety problems (especially regarding C strings), memory
leaks, poor error handling, or race conditions?
• Did you use consistent code style? Your code should blend in with the existing Pintos code. Check
your use of indentation, your spacing, and your naming conventions.
8
CS 162 Spring 2018 Project 2: User Programs
3 Reference
3.1 Pintos
User programs are written under the illusion that they have the entire machine, which means that the
operating system must manage/protect machine resources correctly to maintain this illusion for multiple
processes. In Pintos, more than one process can run at a time, but each process is single-threaded
(multithreaded processes are not supported).
$ cd ~/code/group/
$ git checkout 81c04a231523daaa082204b53bd8d3bc5f4d534f -- pintos/
$ git commit -m "Revert changes to pintos/ from Project 1"
$ git push group master
Make sure your design document and final report for Project 1 is still accessible from
the GitHub website. We will be looking for those documents, so if you do not have them, we might
mistakenly think you didn’t turn them in. Also, please do not force push and please do not delete
your commits from Project 1. You should know that orphan commits are still accessible on GitHub, and
we have a history of the commit hashes you’ve pushed to the autograder, but we will not enjoy digging
up that information if we need it.
We recommend that you use Git to tag your final Project 1 code, for your own benefit.
Once you have made some progress on your project, you can push your code to the autograder
by pushing to “group master”. You don’t have to do this right now, because you haven’t made any
progress yet.
$ cd ~/code/group/pintos/src/userprog
$ make
$ make check
The last command should run the Pintos test suite. These are the same tests that run on the
autograder. By the end of the project, your code should pass all of the tests.
9
CS 162 Spring 2018 Project 2: User Programs
lib/user/syscall.c Provides library functions for user programs to invoke system calls from a C pro-
gram. Each function uses inline assembly code to prepare the syscall arguments and invoke the
system call. We don’t expect you to understand this assembly code, but we do expect you to
understand the calling conventions used for syscalls (also in Reference).
lib/syscall-nr.h This file defines the syscall numbers for each syscall.
exception.c Handle exceptions. Currently all exceptions simply print a message and terminate the
process. Some, but not all, solutions to Project 2 involve modifying page fault() in this file.
• No internal synchronization. Concurrent accesses will interfere with one another. You should use
synchronization to ensure that only one process at a time is executing file system code.
• File size is fixed at creation time. The root directory is represented as a file, so the number of files
that may be created is also limited.
• File data is allocated as a single extent. In other words, data in a single file must occupy a
contiguous range of sectors on disk. External fragmentation can therefore become a serious problem
as a file system is used over time.
• No subdirectories.
10
CS 162 Spring 2018 Project 2: User Programs
Here’s a summary of how to create a disk with a file system partition, format the file system, copy
the echo program into the new disk, and then run echo, passing argument x. (Argument passing won’t
work until you implemented it.) It assumes that you’ve already built the examples in examples and that
the current directory is userprog/build:
pintos-mkdisk filesys.dsk --filesys-size=2
pintos -f -q
pintos -p ../../examples/echo -a echo -- -q
pintos -q run ’echo x’
The three final steps can actually be combined into a single command:
11
CS 162 Spring 2018 Project 2: User Programs
A user program can only access its own user virtual memory. An attempt to access kernel virtual
memory causes a page fault, handled by page_fault() in userprog/exception.c, and the process will be
terminated. Kernel threads can access both kernel virtual memory and, if a user process is running, the
user virtual memory of the running process. However, even in the kernel, an attempt to access memory
at an unmapped user virtual address will cause a page fault.
Typical Memory Layout Conceptually, each process is free to lay out its own user virtual memory
however it chooses. In practice, user virtual memory is laid out like this:
PHYS_BASE +----------------------------------+
| user stack |
| | |
| | |
| V |
| grows downward |
| |
| |
| |
| |
| grows upward |
| ^ |
| | |
| | |
+----------------------------------+
| uninitialized data segment (BSS) |
+----------------------------------+
| initialized data segment |
+----------------------------------+
| code segment |
0x08048000 +----------------------------------+
| |
| |
| |
| |
| |
0 +----------------------------------+
12
CS 162 Spring 2018 Project 2: User Programs
userprog/exception.c. This technique is normally faster because it takes advantage of the proces-
sor’s MMU, so it tends to be used in real kernels (including Linux).
In either case, you need to make sure not to “leak” resources. For example, suppose that your system
call has acquired a lock or allocated memory with malloc(). If you encounter an invalid user pointer
afterward, you must still be sure to release the lock or free the page of memory. If you choose to verify
user pointers before dereferencing them, this should be straightforward. It’s more difficult to handle if
an invalid pointer causes a page fault, because there’s no way to return an error code from a memory
access. Therefore, for those who want to try the latter technique, we’ll provide a little bit of helpful
code:
Each of these functions assumes that the user address has already been verified to be below PHYS BASE.
They also assume that you’ve modified page_fault() so that a page fault in the kernel merely sets %eax
to 0xffffffff and copies its former value into eip.
1. The caller pushes each of the function’s arguments on the stack one by one, normally using the
PUSH assembly language instruction. Arguments are pushed in right-to-left order.
The stack grows downward: each push decrements the stack pointer, then stores into the location
it now points to, like the C expression *--sp = value.
13
CS 162 Spring 2018 Project 2: User Programs
2. The caller pushes the address of its next instruction (the return address ) on the stack and jumps
to the first instruction of the callee. A single 80x86 instruction, CALL, does both.
3. The callee executes. When it takes control, the stack pointer points to the return address, the first
argument is just above it, the second argument is just above the first argument, and so on.
Consider a function f() that takes three int arguments. This diagram shows a sample stack frame
as seen by the callee at the beginning of step 3 above, supposing that f() is invoked as f(1, 2, 3). The
initial stack address is arbitrary:
+----------------+
0xbffffe7c | 3 |
0xbffffe78 | 2 |
0xbffffe74 | 1 |
stack pointer --> 0xbffffe70 | return address |
+----------------+
void
_start (int argc, char *argv[])
{
exit (main (argc, argv));
}
The kernel must put the arguments for the initial function on the stack before it allows the user
program to begin executing. The arguments are passed in the same way as the normal calling convention
(see section 80x86 Calling Convention).
Consider how to handle arguments for the following example command: /bin/ls -l foo bar. First,
break the command into words: /bin/ls, -l, foo, bar. Place the words at the top of the stack. Order
doesn’t matter, because they will be referenced through pointers.
Then, push the address of each string plus a null pointer sentinel, on the stack, in right-to-left order.
These are the elements of argv. The null pointer sentinel ensures that argv[argc] is a null pointer, as
required by the C standard. The order ensures that argv[0] is at the lowest virtual address. Word-
aligned accesses are faster than unaligned accesses, so for best performance round the stack pointer down
to a multiple of 4 before the first push.
Then, push argv (the address of argv[0]) and argc, in that order. Finally, push a fake “return
address”: although the entry function will never return, its stack frame must have the same structure
as any other.
The table below shows the state of the stack and the relevant registers right before the beginning of
the user program, assuming PHYS BASE is 0xc0000000:
14
CS 162 Spring 2018 Project 2: User Programs
bfffffc0 00 00 00 00 | ....|
bfffffd0 04 00 00 00 d8 ff ff bf-ed ff ff bf f5 ff ff bf |................|
bfffffe0 f8 ff ff bf fc ff ff bf-00 00 00 00 00 2f 62 69 |............./bi|
bffffff0 6e 2f 6c 73 00 2d 6c 00-66 6f 6f 00 62 61 72 00 |n/ls.-l.foo.bar.|
• User programs have access to a limited subset of the C standard library. You can find the user
library in lib/.
• User programs cannot directly access variables in the kernel.
• User programs do not have access to malloc, since brk and sbrk are not implemented. User
programs also have a limited stack size. If you need a large buffer, make it a static global variable.
• Pintos starts with 4MB of memory and the file system block device is 2MB by default. Don’t use
data structures or files that exceed these sizes.
• Your test should use msg() instead of printf() (they have the same function signature).
You can add new test cases to the userprog suite by modifying these files:
tests/userprog/Make.tests Entry point for the userprog test suite. You need to add the name of
your test to the tests/userprog_TESTS variable, in order for the test suite to find it. Additionally,
you will need to define a variable named tests/userprog/my-test-1_SRC which contains all the
files that need to be compiled into your test (see the other test definitions for examples). You can
add other source files and resources to your tests, if you wish.
15
CS 162 Spring 2018 Project 2: User Programs
tests/userprog/my-test-1.c This is the test code for your test. Your test should define a function
called test_main, which contains a user-level program. This is the main body of your test case,
which should make syscalls and print output. Use the msg() function instead of printf.
tests/userprog/my-test-1.ck Every test needs a .ck file, which is a Perl script that checks the output
of the test program. If you are not familiar with Perl, don’t worry! You can probably get through
this part with some educated guessing. Your check script should use the subroutines that are
defined in tests/tests.pm. At the end, call pass to print out the “PASS” message, which tells
Pintos test driver that your test passed.
System Call: int practice (int i) A “fake” system call that doesn’t exist in any modern operating
system. You will implement this to get familiar with the system call interface. This system call
increments the passed in integer argument by 1 and returns it to the user.
System Call: void halt (void) Terminates Pintos by calling shutdown_power_off() (declared in
devices/shutdown.h). This should be seldom used, because you lose some information about pos-
sible deadlock situations, etc.
System Call: void exit (int status) Terminates the current user program, returning status to the
kernel. If the process’s parent waits for it (see below), this is the status that will be returned.
Conventionally, a status of 0 indicates success and nonzero values indicate errors. Every user
program that finishes in the normal way calls exit - even a program that returns from main()
calls exit indirectly (see start() in lib/user/entry.c). In order to make the test suite pass, you
16
CS 162 Spring 2018 Project 2: User Programs
need to print out the exit status of each user program when it exits. The code should look like:
“printf("%s: exit(%d)\n", thread_current()->name, exit_code);”.
System Call: pid t exec (const char *cmd line) Runs the executable whose name is given in
cmd_line, passing any given arguments, and returns the new process’s program id (pid). Must
return pid -1, which otherwise should not be a valid pid, if the program cannot load or run for
any reason. Thus, the parent process cannot return from the exec until it knows whether the child
process successfully loaded its executable. You must use appropriate synchronization to ensure
this.
System Call: int wait (pid t pid) Waits for a child process pid and retrieves the child’s exit status.
If pid is still alive, waits until it terminates. Then, returns the status that pid passed to exit.
If pid did not call exit(), but was terminated by the kernel (e.g. killed due to an exception),
wait(pid) must return -1. It is perfectly legal for a parent process to wait for child processes
that have already terminated by the time the parent calls wait, but the kernel must still allow the
parent to retrieve its child’s exit status, or learn that the child was terminated by the kernel.
wait must fail and return -1 immediately if any of the following conditions is true:
• pid does not refer to a direct child of the calling process. pid is a direct child of the calling
process if and only if the calling process received pid as a return value from a successful call
to exec.
Note that children are not inherited: if A spawns child B and B spawns child process C, then
A cannot wait for C, even if B is dead. A call to wait(C) by process A must fail. Similarly,
orphaned processes are not assigned to a new parent if their parent process exits before they
do.
• The process that calls wait has already called wait on pid. That is, a process may wait for
any given child at most once.
Processes may spawn any number of children, wait for them in any order, and may even exit
without having waited for some or all of their children. Your design should consider all the ways
in which waits can occur. All of a process’s resources, including its struct thread, must be freed
whether its parent ever waits for it or not, and regardless of whether the child exits before or after
its parent.
You must ensure that Pintos does not terminate until the initial process exits. The supplied
Pintos code tries to do this by calling process_wait() (in userprog/process.c) from main() (in
threads/init.c). We suggest that you implement process_wait() according to the comment at
the top of the function and then implement the wait system call in terms of process_wait().
Warning: Implementing this system call requires considerably more work than any
of the rest.
System Call: bool create (const char *file, unsigned initial size) Creates a new file called file
initially initial size bytes in size. Returns true if successful, false otherwise. Creating a new file
does not open it: opening the new file is a separate operation which would require a open system
call.
System Call: bool remove (const char *file) Deletes the file called file. Returns true if successful,
false otherwise. A file may be removed regardless of whether it is open or closed, and removing an
open file does not close it. See Removing an Open File, for details.
17
CS 162 Spring 2018 Project 2: User Programs
System Call: int open (const char *file) Opens the file called file. Returns a nonnegative integer
handle called a “file descriptor” (fd), or -1 if the file could not be opened.
File descriptors numbered 0 and 1 are reserved for the console: fd 0 (STDIN_FILENO) is standard
input, fd 1 (STDOUT_FILENO) is standard output. The open system call will never return either of
these file descriptors, which are valid as system call arguments only as explicitly described below.
Each process has an independent set of file descriptors. File descriptors are not inherited by child
processes.
When a single file is opened more than once, whether by a single process or different processes, each
open returns a new file descriptor. Different file descriptors for a single file are closed independently
in separate calls to close and they do not share a file position.
System Call: int filesize (int fd) Returns the size, in bytes, of the file open as fd.
System Call: int read (int fd, void *buffer, unsigned size) Reads size bytes from the file open
as fd into buffer. Returns the number of bytes actually read (0 at end of file), or -1 if the file
could not be read (due to a condition other than end of file). Fd 0 reads from the keyboard using
input_getc().
System Call: int write (int fd, const void *buffer, unsigned size) Writes size bytes from buffer
to the open file fd. Returns the number of bytes actually written, which may be less than size if
some bytes could not be written.
Writing past end-of-file would normally extend the file, but file growth is not implemented by the
basic file system. The expected behavior is to write as many bytes as possible up to end-of-file and
return the actual number written, or 0 if no bytes could be written at all.
Fd 1 writes to the console. Your code to write to the console should write all of buffer in one call to
putbuf(), at least as long as size is not bigger than a few hundred bytes. (It is reasonable to break
up larger buffers.) Otherwise, lines of text output by different processes may end up interleaved
on the console, confusing both human readers and our grading scripts.
System Call: void seek (int fd, unsigned position) Changes the next byte to be read or written
in open file fd to position, expressed in bytes from the beginning of the file. (Thus, a position of 0
is the file’s start.)
A seek past the current end of a file is not an error. A later read obtains 0 bytes, indicating end
of file. A later write extends the file, filling any unwritten gap with zeros. (However, in Pintos
files have a fixed length until project 4 is complete, so writes past end of file will return an error.)
These semantics are implemented in the file system and do not require any special effort in system
call implementation.
System Call: unsigned tell (int fd) Returns the position of the next byte to be read or written in
open file fd, expressed in bytes from the beginning of the file.
System Call: void close (int fd) Closes file descriptor fd. Exiting or terminating a process implic-
itly closes all its open file descriptors, as if by calling this function for each one.
3.3 FAQ
How much code will I need to write? Here’s a summary of our reference solution, produced by
the diffstat program. the final row gives total lines inserted and deleted; a changed line counts as
both an insertion and a deletion.
The reference solution represents just one possible solution. many other solutions are also possible
and many of those differ greatly from the reference solution. some excellent solutions may not
18
CS 162 Spring 2018 Project 2: User Programs
modify all the files modified by the reference solution, and some may modify files not modified by
the reference solution.
threads/thread.c | 13
threads/thread.h | 26 +
userprog/exception.c | 8
userprog/process.c | 247 ++++++++++++++--
userprog/syscall.c | 468 ++++++++++++++++++++++++++++++-
userprog/syscall.h | 1
6 files changed, 725 insertions(+), 38 deletions(-)
The kernel always panics when I run pintos -p file -- -q. Did you format the file system
(with pintos -f)?
Is your file name too long? The file system limits file names to 14 characters. A command like
pintos -p ../../examples/echo -- -q will exceed the limit. Use
pintos -p ../../examples/echo -a echo -- -q to put the file under the name echo instead.
Is the file system full?
Does the file system already contain 16 files? The base Pintos file system has a 16-file limit.
The file system may be so fragmented that there’s not enough contiguous space for your file.
When I run pintos -p ../file --, file isn’t copied. Files are written under the name you refer
to them, by default, so in this case the file copied in would be named ../file. You probably want
to run pintos -p ../file -a file -- instead.
You can list the files in your file system with pintos -q ls.
All my user programs die with page faults. This will happen if you haven’t implemented argu-
ment passing (or haven’t done so correctly). The basic C library for user programs tries to read
argc and argv off the stack. If the stack isn’t properly set up, this causes a page fault.
All my user programs die with system call! You’ll have to implement system calls before you see
anything else. Every reasonable program tries to make at least one system call (exit()) and most
programs make more than that. Notably, printf() invokes the write system call. The default
system call handler just prints system call! and terminates the program. Until then, you can
use hex_dump() to convince yourself that argument passing is implemented correctly (see section
Program Startup Details).
How can I disassemble user programs? The objdump (80x86) or i386-elf-objdump (SPARC) util-
ity can disassemble entire user programs or object files. Invoke it as objdump -d file. You can use
GDB’s disassemble command to disassemble individual functions.
19
CS 162 Spring 2018 Project 2: User Programs
How do I compile new user programs? Modify src/examples/Makefile, then run make.
Can I run user programs under a debugger? Yes, with some limitations. See the section of this
spec on GDB macros.
What’s the difference between tid t and pid t? A tid_t identifies a kernel thread, which may
have a user process running in it (if created with process_execute()) or not (if created with
thread_create()). It is a data type used only in the kernel.
A pid_t identifies a user process. It is used by user processes and the kernel in the exec and wait
system calls.
You can choose whatever suitable types you like for tid_t and pid_t. By default, they’re both
int. You can make them a one-to-one mapping, so that the same values in both identify the same
process, or you can use a more complex mapping. It’s up to you.
Is PHYS BASE fixed? No. You should be able to support PHYS_BASE values that are any multiple
of 0x10000000 from 0x80000000 to 0xf0000000, simply via recompilation.
How do I handle multiple spaces in an argument list? Multiple spaces should be treated as one
space. You do not need to support quotes or any special characters other than space.
Can I enforce a maximum size on the arguments list? You can set a reasonable limit on the
size of the arguments.
What happens when an open file is removed? You should implement the standard Unix seman-
tics for files. That is, when a file is removed any process which has a file descriptor for that file
may continue to use that descriptor. This means that they can read and write from the file. The
file will not have a name, and no other processes will be able to open it, but it will continue to
exist until all file descriptors referring to the file are closed or the machine shuts down.
How can I run user programs that need more than 4 kB stack space? You may modify the
stack setup code to allocate more than one page of stack space for each process. This is not required
in this project.
20
CS 162 Spring 2018 Project 2: User Programs
What should happen if an exec fails midway through loading? exec should return -1 if the
child process fails to load for any reason. This includes the case where the load fails part of
the way through the process (e.g. where it runs out of memory in the multi-oom test). Therefore,
the parent process cannot return from the exec system call until it is established whether the load
was successful or not. The child must communicate this information to its parent using appropriate
synchronization, such as a semaphore, to ensure that the information is communicated without
race conditions.
21