Compare commits

...

55 Commits

Author SHA1 Message Date
Saleh Bubshait
eed4ce5130 Merge branch 'single-exit-status' into 'master'
Use a single `exit_status` instead of two

See merge request lab2425_autumn/pintos_22!50
2024-11-15 18:08:25 +00:00
8567434231 Use a single exit_status instead of two 2024-11-15 17:28:04 +00:00
e76712d3fd Merge branch 'task2/thread-init-bug-fix' into 'master'
Fix Bug in fd_counter initialisation when USERPROG is not defined

See merge request lab2425_autumn/pintos_22!49
2024-11-15 17:17:14 +00:00
sBubshait
8f82f9d747 Fix Bug in fd_counter initialisation when USERPROG is not defined 2024-11-15 16:57:16 +00:00
Saleh Bubshait
9ee29ac40a Merge branch 'child-results-hash-table' into 'master'
Implement hash table for child process results

See merge request lab2425_autumn/pintos_22!48
2024-11-15 16:48:41 +00:00
2566948a32 Implement hash table for child process results 2024-11-15 16:45:04 +00:00
222aeff90f Merge branch 'task2/refactoring/saleh' into 'master'
Refactor System Calls and Process for Readability; Change FD to local counter and use it as hash

See merge request lab2425_autumn/pintos_22!47
2024-11-15 16:41:12 +00:00
sBubshait
ea3b3594ea Update fd_hash to use the fd itself as the hash value for performance, w/ G & E 2024-11-15 15:53:01 +00:00
sBubshait
6b1dbdd34f Update thread and syscall to use local fd counter instead global one, preventing overflow 2024-11-15 15:48:56 +00:00
sBubshait
7daf4fb079 Refactor process_exit to add more comments for readability 2024-11-15 15:35:07 +00:00
sBubshait
a7f1d519da Refactor process_wait to add more comments and improve readability 2024-11-15 15:09:24 +00:00
sBubshait
0f1bce2e88 Refactor process_init_stack to add asserts and comments 2024-11-15 14:52:21 +00:00
sBubshait
f4c900e56c Refactor process.c for comments, clarity and readability 2024-11-15 14:37:38 +00:00
sBubshait
82d45880f7 Update validate_user_pointer to start from the beginning of the page rather than the given ptr 2024-11-15 13:51:10 +00:00
sBubshait
1c757ecdfe Update syscall to add more helpful comments for clarity and readability 2024-11-15 13:51:10 +00:00
sBubshait
6a1d10a19b Refactor synch to follow PintOS curly braces indentation style in if statements 2024-11-15 13:51:10 +00:00
Demetriades, Themis
0e50603eef Merge branch 'stack-init-overflowfix' into 'master'
Remove args-ovf user program binary, replacing with source for testing

See merge request lab2425_autumn/pintos_22!44
2024-11-15 13:41:41 +00:00
Themis Demetriades
7947ac78e8 Remove args-ovf user program binary, replacing with source for testing 2024-11-15 13:40:21 +00:00
Demetriades, Themis
4b0d0a51f0 Merge branch 'stack-init-overflowfix' into 'master'
Update stack initialization code to handle all possible overflows and...

See merge request lab2425_autumn/pintos_22!43
2024-11-15 13:38:35 +00:00
Themis Demetriades
eff0837fdc Update stack initialization code to handle all possible overflows and implement a user program to test this 2024-11-15 13:35:58 +00:00
Demetriades, Themis
9b82947beb Merge branch 'stack-init-memfix' into 'master'
Fix memory leak in start_process

See merge request lab2425_autumn/pintos_22!42
2024-11-14 21:06:20 +00:00
Themis Demetriades
5e2e7199f2 Fix memory leak in start_process 2024-11-14 21:03:55 +00:00
Demetriades, Themis
bbe41e178d Merge branch 'stack-init-optimize' into 'master'
Optimize stack initialization by not using malloc, and remove use of 'goto' (for code review safety)

See merge request lab2425_autumn/pintos_22!41
2024-11-14 16:06:22 +00:00
Themis Demetriades
e4036c715f Refactor start_process to hold file system lock for less time 2024-11-14 16:05:15 +00:00
Themis Demetriades
3a46e0f73a Update user proc stack initialization comments to be more helpful 2024-11-14 15:54:34 +00:00
Themis Demetriades
8821851459 Refactor process_execute to remove use of 'goto' 2024-11-14 14:42:26 +00:00
Themis Demetriades
1a2ff35231 Refactor process_execute to pass process start data as a local reference rather than perform memory allocation on the heap 2024-11-14 14:32:48 +00:00
Saleh Bubshait
79b3b8fda7 Merge branch 'userprog-oom' into 'master'
Fix multi-oom

See merge request lab2425_autumn/pintos_22!39
2024-11-13 22:09:11 +00:00
f5e498e0a9 explicit thread_exit () when process_start () fails 2024-11-13 21:58:41 +00:00
d02b956221 Merge branch 'system-calls' into 'userprog-oom'
Add Fixes to Memory Leaks, Memory Access Validation, Synchronised Processes and Refactoring

See merge request lab2425_autumn/pintos_22!38
2024-11-13 19:20:19 +00:00
Saleh Bubshait
927c376d02 Merge branch 'task2/system-calls/saleh' into 'system-calls'
Fix Memory Leaks, Synchronisation in Processes, and Refactoring

See merge request lab2425_autumn/pintos_22!37
2024-11-13 18:51:51 +00:00
sBubshait
fa2fb4a711 Refactor system call comments for accuracy and grammar 2024-11-13 18:48:23 +00:00
sBubshait
31ea215805 Refactor validate_user_string to remove unnecessary variable to track length of str 2024-11-13 18:30:24 +00:00
e7cb16b301 Fix child_results loop accessing next after free() 2024-11-13 18:29:05 +00:00
sBubshait
9549ca28e5 Refactor syscall: Use EXIT_FAILURE instead of magic numbers & close files on failure 2024-11-13 18:18:41 +00:00
sBubshait
b1c5819469 Avoid masking the struct syscall_arguments using typedef for consistency 2024-11-13 18:06:51 +00:00
sBubshait
287130ca7b Update syscall.c to use syscall_exit on failure instead of calling thread_exit directly 2024-11-13 18:02:08 +00:00
sBubshait
4f586bb4da Fix Bug: Free all entries in the fd hashtable when the process exits, w/ E 2024-11-13 17:42:25 +00:00
sBubshait
d890c2353e Add constant MAX_SYSCALL_ARGS to avoid magic numbers [Gleb] 2024-11-13 17:16:15 +00:00
sBubshait
eb4f23c290 Add validate_user_string helper function to validate that a string is fully conatined within user vm 2024-11-13 17:11:37 +00:00
sBubshait
26de38cdba Update validate_user_pointer to check if the memory block is mapped into a physical address 2024-11-13 16:39:45 +00:00
sBubshait
6e59e8c9f3 Update validate_user_pointer to be a void function 2024-11-13 16:22:16 +00:00
sBubshait
30e49846b5 Add more tests for system calls to deal with bad buffers given to read and write 2024-11-13 16:21:12 +00:00
005791edd2 Synchronise process_execute return with child process load 2024-11-13 11:05:09 +00:00
8bcd0a467c Tidy up a69b9c808e 2024-11-12 21:35:41 +00:00
dd979f34c8 Fix syn-read, syn-write, and always free elements from donors_list 2024-11-12 21:30:23 +00:00
ca9d23edf9 Always release filesys_lock when checking if file is valid in process_execute 2024-11-12 20:07:51 +00:00
Themis Demetriades
d878dbc132 Fix bug in userprog-merge where file writes were denied in the wrong thread 2024-11-12 16:22:32 +00:00
Themis Demetriades
b0400693ae Update process_execute to acquire lock when checking if file exists 2024-11-12 16:12:24 +00:00
Themis Demetriades
3418425f20 Don't acquire filesys lock when calling exec 2024-11-12 16:08:27 +00:00
Themis Demetriades
a69b9c808e Update start_process to acquire filesys lock when loading user process file 2024-11-12 14:21:33 +00:00
Demetriades, Themis
7d9900c6d8 Merge branch 'exec-missing-validation' into 'master'
Add validation to check for missing files in exec() args

See merge request lab2425_autumn/pintos_22!36
2024-11-11 23:25:26 +00:00
EDiasAlberto
72afecfbda Add validation to check for missing files in exec() args 2024-11-11 23:10:02 +00:00
Demetriades, Themis
9e692ced9e Merge branch 'userprog-merge' into 'master'
Update variable references between conflicting merges to refer to the same...

See merge request lab2425_autumn/pintos_22!35
2024-11-11 22:59:45 +00:00
Demetriades, Themis
f194fa1fa8 Merge branch 'userprog-merge' into 'master'
Implement complete stack initialization, process_wait, and all system calls correctly except exec

See merge request lab2425_autumn/pintos_22!34
2024-11-11 22:56:28 +00:00
20 changed files with 513 additions and 226 deletions

View File

@@ -4,7 +4,7 @@ SRCDIR = ..
# To add a new test, put its name on the PROGS list # To add a new test, put its name on the PROGS list
# and then add a name_SRC line that lists its source files. # and then add a name_SRC line that lists its source files.
PROGS = cat cmp cp echo halt hex-dump mcat mcp rm \ PROGS = cat cmp cp echo halt hex-dump mcat mcp rm \
bubsort insult lineup matmult recursor bubsort insult lineup matmult recursor args-ovf
# Should work from task 2 onward. # Should work from task 2 onward.
cat_SRC = cat.c cat_SRC = cat.c
@@ -18,6 +18,7 @@ lineup_SRC = lineup.c
ls_SRC = ls.c ls_SRC = ls.c
recursor_SRC = recursor.c recursor_SRC = recursor.c
rm_SRC = rm.c rm_SRC = rm.c
args-ovf_SRC = args-ovf.c
# Should work in task 3; also in task 4 if VM is included. # Should work in task 3; also in task 4 if VM is included.
bubsort_SRC = bubsort.c bubsort_SRC = bubsort.c

13
src/examples/args-ovf.c Normal file

File diff suppressed because one or more lines are too long

View File

@@ -9,14 +9,14 @@ sc-bad-arg sc-bad-num sc-boundary sc-boundary-2 halt exit create-normal \
create-empty create-null create-bad-ptr create-long create-exists \ create-empty create-null create-bad-ptr create-long create-exists \
create-bound open-normal open-missing open-boundary open-empty \ create-bound open-normal open-missing open-boundary open-empty \
open-null open-bad-ptr open-twice close-normal close-twice close-stdin \ open-null open-bad-ptr open-twice close-normal close-twice close-stdin \
close-stdout close-bad-fd read-normal read-bad-ptr read-boundary \ close-stdout close-bad-fd read-normal read-bad-ptr read-bad-buf read-boundary \
read-zero read-stdout read-bad-fd write-normal write-bad-ptr \ read-zero read-stdout read-bad-fd write-normal write-bad-ptr write-bad-buf \
write-boundary write-zero write-stdin write-bad-fd exec-once exec-arg \ write-boundary write-zero write-stdin write-bad-fd exec-once exec-arg \
exec-large-arg exec-multiple exec-missing exec-over-arg exec-over-args \ exec-large-arg exec-multiple exec-missing exec-over-arg exec-over-args \
exec-bad-ptr wait-simple wait-twice wait-killed wait-load-kill \ exec-bad-ptr wait-simple wait-twice wait-killed wait-load-kill \
wait-bad-pid wait-bad-child multi-recurse multi-child-fd rox-simple \ wait-bad-pid wait-bad-child multi-recurse multi-child-fd rox-simple \
rox-child rox-multichild bad-read bad-write bad-read2 bad-write2 \ rox-child rox-multichild bad-read bad-write bad-read2 bad-write2 \
bad-jump bad-jump2 bad-maths) bad-jump bad-jump2 bad-maths overflow-stack)
tests/userprog_PROGS = $(tests/userprog_TESTS) $(addprefix \ tests/userprog_PROGS = $(tests/userprog_TESTS) $(addprefix \
tests/userprog/,child-simple child-args child-bad child-close child-rox exec-exit) tests/userprog/,child-simple child-args child-bad child-close child-rox exec-exit)
@@ -36,6 +36,7 @@ tests/userprog/bad-read2_SRC = tests/userprog/bad-read2.c tests/main.c
tests/userprog/bad-write2_SRC = tests/userprog/bad-write2.c tests/main.c tests/userprog/bad-write2_SRC = tests/userprog/bad-write2.c tests/main.c
tests/userprog/bad-jump2_SRC = tests/userprog/bad-jump2.c tests/main.c tests/userprog/bad-jump2_SRC = tests/userprog/bad-jump2.c tests/main.c
tests/userprog/bad-maths_SRC = tests/userprog/bad-maths.c tests/main.c tests/userprog/bad-maths_SRC = tests/userprog/bad-maths.c tests/main.c
tests/userprog/overflow-stack_SRC = tests/userprog/overflow-stack.c tests/main.c
tests/userprog/sc-boundary_SRC = tests/userprog/sc-boundary.c \ tests/userprog/sc-boundary_SRC = tests/userprog/sc-boundary.c \
tests/userprog/boundary.c tests/main.c tests/userprog/boundary.c tests/main.c
tests/userprog/sc-boundary-2_SRC = tests/userprog/sc-boundary-2.c \ tests/userprog/sc-boundary-2_SRC = tests/userprog/sc-boundary-2.c \
@@ -66,6 +67,7 @@ tests/userprog/close-stdout_SRC = tests/userprog/close-stdout.c tests/main.c
tests/userprog/close-bad-fd_SRC = tests/userprog/close-bad-fd.c tests/main.c tests/userprog/close-bad-fd_SRC = tests/userprog/close-bad-fd.c tests/main.c
tests/userprog/read-normal_SRC = tests/userprog/read-normal.c tests/main.c tests/userprog/read-normal_SRC = tests/userprog/read-normal.c tests/main.c
tests/userprog/read-bad-ptr_SRC = tests/userprog/read-bad-ptr.c tests/main.c tests/userprog/read-bad-ptr_SRC = tests/userprog/read-bad-ptr.c tests/main.c
tests/userprog/read-bad-buf_SRC = tests/userprog/read-bad-buf.c tests/main.c
tests/userprog/read-boundary_SRC = tests/userprog/read-boundary.c \ tests/userprog/read-boundary_SRC = tests/userprog/read-boundary.c \
tests/userprog/boundary.c tests/main.c tests/userprog/boundary.c tests/main.c
tests/userprog/read-zero_SRC = tests/userprog/read-zero.c tests/main.c tests/userprog/read-zero_SRC = tests/userprog/read-zero.c tests/main.c
@@ -73,6 +75,7 @@ tests/userprog/read-stdout_SRC = tests/userprog/read-stdout.c tests/main.c
tests/userprog/read-bad-fd_SRC = tests/userprog/read-bad-fd.c tests/main.c tests/userprog/read-bad-fd_SRC = tests/userprog/read-bad-fd.c tests/main.c
tests/userprog/write-normal_SRC = tests/userprog/write-normal.c tests/main.c tests/userprog/write-normal_SRC = tests/userprog/write-normal.c tests/main.c
tests/userprog/write-bad-ptr_SRC = tests/userprog/write-bad-ptr.c tests/main.c tests/userprog/write-bad-ptr_SRC = tests/userprog/write-bad-ptr.c tests/main.c
tests/userprog/write-bad-buf_SRC = tests/userprog/write-bad-buf.c tests/main.c
tests/userprog/write-boundary_SRC = tests/userprog/write-boundary.c \ tests/userprog/write-boundary_SRC = tests/userprog/write-boundary.c \
tests/userprog/boundary.c tests/main.c tests/userprog/boundary.c tests/main.c
tests/userprog/write-zero_SRC = tests/userprog/write-zero.c tests/main.c tests/userprog/write-zero_SRC = tests/userprog/write-zero.c tests/main.c
@@ -122,10 +125,12 @@ tests/userprog/close-normal_PUTFILES += tests/userprog/sample.txt
tests/userprog/close-twice_PUTFILES += tests/userprog/sample.txt tests/userprog/close-twice_PUTFILES += tests/userprog/sample.txt
tests/userprog/read-normal_PUTFILES += tests/userprog/sample.txt tests/userprog/read-normal_PUTFILES += tests/userprog/sample.txt
tests/userprog/read-bad-ptr_PUTFILES += tests/userprog/sample.txt tests/userprog/read-bad-ptr_PUTFILES += tests/userprog/sample.txt
tests/userprog/read-bad-buf_PUTFILES += tests/userprog/sample.txt
tests/userprog/read-boundary_PUTFILES += tests/userprog/sample.txt tests/userprog/read-boundary_PUTFILES += tests/userprog/sample.txt
tests/userprog/read-zero_PUTFILES += tests/userprog/sample.txt tests/userprog/read-zero_PUTFILES += tests/userprog/sample.txt
tests/userprog/write-normal_PUTFILES += tests/userprog/sample.txt tests/userprog/write-normal_PUTFILES += tests/userprog/sample.txt
tests/userprog/write-bad-ptr_PUTFILES += tests/userprog/sample.txt tests/userprog/write-bad-ptr_PUTFILES += tests/userprog/sample.txt
tests/userprog/write-bad-buf_PUTFILES += tests/userprog/sample.txt
tests/userprog/write-boundary_PUTFILES += tests/userprog/sample.txt tests/userprog/write-boundary_PUTFILES += tests/userprog/sample.txt
tests/userprog/write-zero_PUTFILES += tests/userprog/sample.txt tests/userprog/write-zero_PUTFILES += tests/userprog/sample.txt
tests/userprog/multi-child-fd_PUTFILES += tests/userprog/sample.txt tests/userprog/multi-child-fd_PUTFILES += tests/userprog/sample.txt

View File

@@ -1,5 +1,9 @@
Full robustness of argument passing code: Full robustness of argument passing and syscall handling code:
- Test user stack overflow robustness of "exec" system calls. - Test user stack overflow robustness of "exec" system calls and user code.
5 exec-over-arg 5 exec-over-arg
5 exec-over-args 5 exec-over-args
5 overflow-stack
- Test syscall user provided buffer validity checks.
5 read-bad-buf
5 write-bad-buf

View File

@@ -2,11 +2,7 @@
use strict; use strict;
use warnings; use warnings;
use tests::tests; use tests::tests;
check_expected ([<<'EOF', <<'EOF']); check_expected ([<<'EOF']);
(exec-bad-ptr) begin
(exec-bad-ptr) end
exec-bad-ptr: exit(0)
EOF
(exec-bad-ptr) begin (exec-bad-ptr) begin
exec-bad-ptr: exit(-1) exec-bad-ptr: exit(-1)
EOF EOF

View File

@@ -2,11 +2,7 @@
use strict; use strict;
use warnings; use warnings;
use tests::tests; use tests::tests;
check_expected ([<<'EOF', <<'EOF']); check_expected ([<<'EOF']);
(open-bad-ptr) begin
(open-bad-ptr) end
open-bad-ptr: exit(0)
EOF
(open-bad-ptr) begin (open-bad-ptr) begin
open-bad-ptr: exit(-1) open-bad-ptr: exit(-1)
EOF EOF

View File

@@ -0,0 +1,17 @@
/* Attempt to overflow the user stack by allocating a 4kB buffer and writing into it.
The process must be terminated with -1 exit code until stack growth has been implemented in Task 3
*/
#include <string.h>
#include <syscall.h>
#include "tests/lib.h"
#include "tests/main.h"
void
test_main (void)
{
char stack_obj[4096];
memset (stack_obj, 'a', sizeof stack_obj);
memset (stack_obj+10, '\0', 1);
msg ("buffer: %s", stack_obj);
}

View File

@@ -0,0 +1,14 @@
# -*- perl -*-
use strict;
use warnings;
use tests::tests;
check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF',<<'EOF']);
(overflow-stack) begin
overflow-stack: exit(-1)
EOF
(overflow-stack) begin
(overflow-stack) buffer: aaaaaaaaaa
(overflow-stack) end
overflow-stack: exit(0)
EOF
pass;

View File

@@ -0,0 +1,17 @@
/* Passes a buffer to the read system call that starts in valid memory, but runs into kernel space.
The process must be terminated with -1 exit code.
*/
#include <syscall.h>
#include "tests/lib.h"
#include "tests/main.h"
void
test_main (void)
{
int handle;
CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
read (handle, (char *) 0xbfffffe0, 100);
fail ("should not have survived read()");
}

View File

@@ -0,0 +1,10 @@
# -*- perl -*-
use strict;
use warnings;
use tests::tests;
check_expected (IGNORE_KERNEL_FAULTS => 1, [<<'EOF']);
(read-bad-buf) begin
(read-bad-buf) open "sample.txt"
read-bad-buf: exit(-1)
EOF
pass;

View File

@@ -2,12 +2,7 @@
use strict; use strict;
use warnings; use warnings;
use tests::tests; use tests::tests;
check_expected ([<<'EOF', <<'EOF']); check_expected ([<<'EOF']);
(read-bad-ptr) begin
(read-bad-ptr) open "sample.txt"
(read-bad-ptr) end
read-bad-ptr: exit(0)
EOF
(read-bad-ptr) begin (read-bad-ptr) begin
(read-bad-ptr) open "sample.txt" (read-bad-ptr) open "sample.txt"
read-bad-ptr: exit(-1) read-bad-ptr: exit(-1)

View File

@@ -0,0 +1,17 @@
/* Passes a buffer to the write system call that starts in valid memory, but runs into kernel space.
The process must be terminated with -1 exit code.
*/
#include <syscall.h>
#include "tests/lib.h"
#include "tests/main.h"
void
test_main (void)
{
int handle;
CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\"");
write (handle, (char *) 0xbffffff0, 32);
fail ("should have exited with -1");
}

View File

@@ -0,0 +1,10 @@
# -*- perl -*-
use strict;
use warnings;
use tests::tests;
check_expected (IGNORE_KERNEL_FAULTS => 1, [<<'EOF']);
(write-bad-buf) begin
(write-bad-buf) open "sample.txt"
write-bad-buf: exit(-1)
EOF
pass;

View File

@@ -2,12 +2,7 @@
use strict; use strict;
use warnings; use warnings;
use tests::tests; use tests::tests;
check_expected ([<<'EOF', <<'EOF']); check_expected ([<<'EOF']);
(write-bad-ptr) begin
(write-bad-ptr) open "sample.txt"
(write-bad-ptr) end
write-bad-ptr: exit(0)
EOF
(write-bad-ptr) begin (write-bad-ptr) begin
(write-bad-ptr) open "sample.txt" (write-bad-ptr) open "sample.txt"
write-bad-ptr: exit(-1) write-bad-ptr: exit(-1)

View File

@@ -119,14 +119,14 @@ sema_up (struct semaphore *sema)
old_level = intr_disable (); old_level = intr_disable ();
if (!list_empty (&sema->waiters)) if (!list_empty (&sema->waiters))
{ {
/* Enforces wake-up of the highest priority thread waiting for the /* Enforces wake-up of the highest priority thread waiting for the
semaphore. */ semaphore. */
struct list_elem *e = list_max (&sema->waiters, priority_less, NULL); struct list_elem *e = list_max (&sema->waiters, priority_less, NULL);
list_remove (e); list_remove (e);
thread_unblock (list_entry (e, struct thread, elem)); thread_unblock (list_entry (e, struct thread, elem));
thread_unblocked = true; thread_unblocked = true;
} }
sema->value++; sema->value++;
intr_set_level (old_level); intr_set_level (old_level);
@@ -134,12 +134,12 @@ sema_up (struct semaphore *sema)
priority that the current running thread, including the case when called priority that the current running thread, including the case when called
within an interrupt handler. */ within an interrupt handler. */
if (thread_unblocked) if (thread_unblocked)
{ {
if (intr_context ()) if (intr_context ())
intr_yield_on_return (); intr_yield_on_return ();
else else
thread_yield (); thread_yield ();
} }
} }
static void sema_test_helper (void *sema_); static void sema_test_helper (void *sema_);
@@ -212,6 +212,7 @@ donate_priority (struct thread *donee) {
ASSERT (intr_get_level () == INTR_OFF); ASSERT (intr_get_level () == INTR_OFF);
struct thread *donor = thread_current (); struct thread *donor = thread_current ();
list_remove (&donor->donor_elem);
list_push_back (&donee->donors_list, &donor->donor_elem); list_push_back (&donee->donors_list, &donor->donor_elem);
while (donee != NULL) while (donee != NULL)
@@ -260,6 +261,7 @@ lock_acquire (struct lock *lock)
ASSERT (!lock_held_by_current_thread (lock)); ASSERT (!lock_held_by_current_thread (lock));
struct thread *t = thread_current (); struct thread *t = thread_current ();
ASSERT (t->waiting_lock == NULL);
enum intr_level old_level = intr_disable (); enum intr_level old_level = intr_disable ();
if (lock->holder != NULL) if (lock->holder != NULL)
@@ -341,7 +343,6 @@ lock_release (struct lock *lock)
released, transfer the remaining orphaned donors to its donor list. */ released, transfer the remaining orphaned donors to its donor list. */
if (max_donor != NULL) if (max_donor != NULL)
{ {
list_remove (&max_donor->donor_elem);
while (!list_empty (&orphan_list)) while (!list_empty (&orphan_list))
list_push_back (&max_donor->donors_list, list_pop_front (&orphan_list)); list_push_back (&max_donor->donors_list, list_pop_front (&orphan_list));
} }

View File

@@ -84,6 +84,10 @@ void thread_schedule_tail (struct thread *prev);
static tid_t allocate_tid (void); static tid_t allocate_tid (void);
static bool donor_priority_less (const struct list_elem *a_, static bool donor_priority_less (const struct list_elem *a_,
const struct list_elem *b_, void *aux UNUSED); const struct list_elem *b_, void *aux UNUSED);
static unsigned process_result_hash (const struct hash_elem *e,
void *aux UNUSED);
static bool process_result_less (const struct hash_elem *a,
const struct hash_elem *b, void *aux UNUSED);
/* Initializes the threading system by transforming the code /* Initializes the threading system by transforming the code
that's currently running into a thread. This can't work in that's currently running into a thread. This can't work in
@@ -122,6 +126,13 @@ thread_init (void)
void void
thread_start (void) thread_start (void)
{ {
/* Malloc has been initalised, we can allocate the child results table
for the main thread. */
struct thread *t = thread_current ();
if (!hash_init (&t->child_results, process_result_hash, process_result_less,
t))
PANIC ("Failed to initialise child results table for main thread.");
/* Create the idle thread. */ /* Create the idle thread. */
struct semaphore idle_started; struct semaphore idle_started;
sema_init (&idle_started, 0); sema_init (&idle_started, 0);
@@ -243,9 +254,19 @@ thread_create (const char *name, int priority,
tid = t->tid = allocate_tid (); tid = t->tid = allocate_tid ();
init_process_result (t); init_process_result (t);
#ifdef USERPROG #ifdef USERPROG
hash_init (&t->open_files, fd_hash, fd_less, NULL); /* Initialize the thread's file descriptor table. */
#endif t->fd_counter = MINIMUM_USER_FD;
if (!hash_init (&t->open_files, fd_hash, fd_less, NULL)
|| !hash_init (&t->child_results, process_result_hash,
process_result_less, t))
{
palloc_free_page (t);
free (t->result);
return TID_ERROR;
}
#endif
/* Prepare thread for first run by initializing its stack. /* Prepare thread for first run by initializing its stack.
Do this atomically so intermediate values for the 'stack' Do this atomically so intermediate values for the 'stack'
@@ -269,9 +290,7 @@ thread_create (const char *name, int priority,
intr_set_level (old_level); intr_set_level (old_level);
/* No need to synchronise child_results since it is only ever accessed by one hash_insert (&parent_thread->child_results, &t->result->elem);
thread. By the nature of increasing TIDs, this list is ordered. */
list_push_back (&parent_thread->child_results, &t->result->elem);
/* Add to run queue. */ /* Add to run queue. */
thread_unblock (t); thread_unblock (t);
@@ -373,7 +392,9 @@ thread_exit (void)
and schedule another process. That process will destroy us and schedule another process. That process will destroy us
when it calls thread_schedule_tail(). */ when it calls thread_schedule_tail(). */
intr_disable (); intr_disable ();
list_remove (&thread_current()->allelem); struct thread *t = thread_current ();
list_remove (&t->allelem);
list_remove (&t->donor_elem);
thread_current ()->status = THREAD_DYING; thread_current ()->status = THREAD_DYING;
schedule (); schedule ();
NOT_REACHED (); NOT_REACHED ();
@@ -652,7 +673,7 @@ init_process_result (struct thread *t)
{ {
struct process_result *result = malloc (sizeof (struct process_result)); struct process_result *result = malloc (sizeof (struct process_result));
result->tid = t->tid; result->tid = t->tid;
result->exit_status = t->exit_status; result->exit_status = -1;
lock_init (&result->lock); lock_init (&result->lock);
sema_init (&result->sema, 0); sema_init (&result->sema, 0);
t->result = result; t->result = result;
@@ -679,15 +700,13 @@ init_thread (struct thread *t, const char *name, int nice, int priority,
t->base_priority t->base_priority
= thread_mlfqs ? calculate_bsd_priority (recent_cpu, nice) : priority; = thread_mlfqs ? calculate_bsd_priority (recent_cpu, nice) : priority;
list_init (&t->donors_list); list_init (&t->donors_list);
list_push_back (&t->donors_list, &t->donor_elem);
t->waiting_lock = NULL; t->waiting_lock = NULL;
t->nice = nice; t->nice = nice;
t->recent_cpu = recent_cpu; t->recent_cpu = recent_cpu;
t->priority = t->base_priority; t->priority = t->base_priority;
t->exit_status = -1;
list_init (&t->child_results);
old_level = intr_disable (); old_level = intr_disable ();
list_push_back (&all_list, &t->allelem); list_push_back (&all_list, &t->allelem);
intr_set_level (old_level); intr_set_level (old_level);
@@ -818,6 +837,29 @@ allocate_tid (void)
return tid; return tid;
} }
/* Hashing function needed for child_results table.
Returns hash of process_result's TID. */
static unsigned
process_result_hash (const struct hash_elem *e, void *aux UNUSED)
{
const struct process_result *result
= hash_entry (e, struct process_result, elem);
return hash_int (result->tid);
}
/* Comparator function needed for child_results table.
Returns less than comparison on process_results' TIDs. */
static bool
process_result_less (const struct hash_elem *a_, const struct hash_elem *b_,
void *aux UNUSED)
{
const struct process_result *a
= hash_entry (a_, struct process_result, elem);
const struct process_result *b
= hash_entry (b_, struct process_result, elem);
return a->tid < b->tid;
}
/* Offset of `stack' member within `struct thread'. /* Offset of `stack' member within `struct thread'.
Used by switch.S, which can't figure it out on its own. */ Used by switch.S, which can't figure it out on its own. */
uint32_t thread_stack_ofs = offsetof (struct thread, stack); uint32_t thread_stack_ofs = offsetof (struct thread, stack);

View File

@@ -32,6 +32,9 @@ typedef int tid_t;
#define NICE_DEFAULT 0 /* Default niceness. */ #define NICE_DEFAULT 0 /* Default niceness. */
#define NICE_MAX 20 /* Highest niceness. */ #define NICE_MAX 20 /* Highest niceness. */
/* File Descriptors. */
#define MINIMUM_USER_FD 2 /* Minimum file descriptor for user programs. */
/* A process result, synchronised between parent and child. */ /* A process result, synchronised between parent and child. */
struct process_result struct process_result
{ {
@@ -41,7 +44,7 @@ struct process_result
struct lock lock; /* Lock the exit_status and sema. */ struct lock lock; /* Lock the exit_status and sema. */
struct semaphore sema; /* Semaphore to signal the parent that the exit_status struct semaphore sema; /* Semaphore to signal the parent that the exit_status
has been set. */ has been set. */
struct list_elem elem; /* List element for the parent's children list. */ struct hash_elem elem; /* Hash element for the parent's children map. */
}; };
/* A kernel thread or user process. /* A kernel thread or user process.
@@ -125,19 +128,19 @@ struct thread
/* Process wait properties. */ /* Process wait properties. */
struct process_result *result; /* Result of the process. */ struct process_result *result; /* Result of the process. */
struct list child_results; /* List of children's of this thread struct hash child_results; /* Map of children's of this thread
process results. */ TID to process result. */
struct file *exec_file; /* Thread's currently running file */ struct file *exec_file; /* Thread's currently running file */
/* Shared between thread.c and synch.c. */ /* Shared between thread.c and synch.c. */
struct list_elem elem; /* List element. */ struct list_elem elem; /* List element. */
int exit_status; /* Exit Status: 0 = successful exit. */
#ifdef USERPROG #ifdef USERPROG
/* Owned by userprog/process.c. */ /* Owned by userprog/process.c. */
uint32_t *pagedir; /* Page directory. */ uint32_t *pagedir; /* Page directory. */
struct hash open_files; /* Hash Table of FD -> Struct File */ unsigned int fd_counter; /* File descriptor counter for thread's
open files. */
struct hash open_files; /* Hash Table of FD -> Struct File. */
#endif #endif
/* Owned by thread.c. */ /* Owned by thread.c. */

View File

@@ -1,5 +1,6 @@
#include "userprog/process.h" #include "userprog/process.h"
#include <debug.h> #include <debug.h>
#include <hash.h>
#include <inttypes.h> #include <inttypes.h>
#include <list.h> #include <list.h>
#include <round.h> #include <round.h>
@@ -8,6 +9,7 @@
#include <string.h> #include <string.h>
#include "userprog/gdt.h" #include "userprog/gdt.h"
#include "userprog/pagedir.h" #include "userprog/pagedir.h"
#include "userprog/syscall.h"
#include "userprog/tss.h" #include "userprog/tss.h"
#include "filesys/directory.h" #include "filesys/directory.h"
#include "filesys/file.h" #include "filesys/file.h"
@@ -27,6 +29,10 @@
(for the purposes of alignment). */ (for the purposes of alignment). */
#define WORD_SIZE 4 #define WORD_SIZE 4
/* Defines non-negative integer division wherein the result is always rounded
up. */
#define DIV_CEIL(x, y) ((x + (y - 1)) / y)
/* Keeps track of the position of pointers to user program arguments /* Keeps track of the position of pointers to user program arguments
within a linked list. */ within a linked list. */
struct arg_elem struct arg_elem
@@ -39,16 +45,18 @@ struct arg_elem
that executes process_start for the purpose of starting a user process. */ that executes process_start for the purpose of starting a user process. */
struct process_start_data struct process_start_data
{ {
char *cmd; /* Pointer to a copy of the command used to execute the process.
Allocated a page that must be freed by process_start. */
char *cmd_saveptr; /* Value pointed to by 'saveptr' argument used by char *cmd_saveptr; /* Value pointed to by 'saveptr' argument used by
successive calls to strtok_r to split 'cmd' into successive calls to strtok_r to split 'cmd' into
tokens while maintaining state. */ tokens while maintaining state. */
char file_name[FNAME_MAX_LEN + 1]; /* Name of the file of the process to char file_name[FNAME_MAX_LEN + 1]; /* Name of the file of the process to
be started. */ be started. */
bool success; /* Indicates whether the process was successfully loaded. */
struct semaphore loaded; /* Semaphore used to signal that the process has
finished attempting to load. */
}; };
static thread_func start_process NO_RETURN; static thread_func start_process NO_RETURN;
static void destruct_process_result (struct hash_elem *e, void *aux UNUSED);
static bool load (const char *cmdline, void (**eip) (void), void **esp); static bool load (const char *cmdline, void (**eip) (void), void **esp);
/* Starts a new thread running a user program executed via /* Starts a new thread running a user program executed via
@@ -60,12 +68,7 @@ process_execute (const char *cmd)
{ {
char *cmd_copy; char *cmd_copy;
tid_t tid; tid_t tid;
struct process_start_data data;
struct process_start_data *data = malloc (sizeof (struct process_start_data));
if (data == NULL)
{
return TID_ERROR;
}
/* Make a copy of command. /* Make a copy of command.
Otherwise there's a race between the caller and load(). */ Otherwise there's a race between the caller and load(). */
@@ -79,17 +82,34 @@ process_execute (const char *cmd)
/* Retrieve first argument of command, which is the file name /* Retrieve first argument of command, which is the file name
of the process. */ of the process. */
char *file_name = strtok_r (cmd_copy, " ", &data->cmd_saveptr); char *file_name = strtok_r (cmd_copy, " ", &data.cmd_saveptr);
/* Validates that the current file to be executed can be opened/exists. */
lock_acquire (&filesys_lock);
struct file *file = filesys_open (file_name);
lock_release (&filesys_lock);
if (file == NULL)
return TID_ERROR;
/* Create a new thread to execute the command, by initializing /* Create a new thread to execute the command, by initializing
it running the function 'start_process' with the appropriate it running the function 'start_process' with the appropriate
arguments. For details of arguments, see 'start_process'. */ arguments. For details of arguments, see 'start_process'. */
data->cmd = cmd_copy; strlcpy (data.file_name, file_name, FNAME_MAX_LEN + 1);
strlcpy (data->file_name, file_name, FNAME_MAX_LEN + 1); sema_init (&data.loaded, 0);
data.success = false;
tid = thread_create (file_name, PRI_DEFAULT, start_process, data); tid = thread_create (file_name, PRI_DEFAULT, start_process, &data);
if (tid == TID_ERROR)
palloc_free_page (cmd_copy); /* Wait until process file has finished attempting to load via the child
thread before reporting success of starting execution. */
if (tid != TID_ERROR)
{
sema_down (&data.loaded);
if (!data.success)
tid = TID_ERROR;
}
palloc_free_page (cmd_copy);
return tid; return tid;
} }
@@ -101,14 +121,15 @@ static void *push_to_stack (void **esp, void *data, size_t data_size);
/* Make the current thread execute 'cmd', passing in a copy of the /* Make the current thread execute 'cmd', passing in a copy of the
command string used for processing, the saveptr used by strtok_r command string used for processing, the saveptr used by strtok_r
(in order to further tokenize the same command and retrieve its (in order to further tokenize the same command and retrieve its
arguments), as well as the name of the file being executed. This arguments), the name of the file being executed, and a semaphore that
involves loading the specified file and starting it running. */ calls sema_up to indicate that the 'success' variable passed to it
has been updated to indicate whether the process file loading succeeded.
This involves loading the specified file and calling its main () function
with the specified command arguments. */
static void static void
start_process (void *proc_start_data) start_process (void *proc_start_data)
{ {
struct intr_frame if_; struct intr_frame if_;
bool success;
struct process_start_data *data = proc_start_data; struct process_start_data *data = proc_start_data;
/* Initialize interrupt frame and load executable. */ /* Initialize interrupt frame and load executable. */
@@ -116,33 +137,46 @@ start_process (void *proc_start_data)
if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG; if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG;
if_.cs = SEL_UCSEG; if_.cs = SEL_UCSEG;
if_.eflags = FLAG_IF | FLAG_MBS; if_.eflags = FLAG_IF | FLAG_MBS;
success = load (data->file_name, &if_.eip, &if_.esp);
/* If load failed, quit. */ /* Acquire the file system lock to prevent race conditions. */
if (!success) lock_acquire (&filesys_lock);
{
palloc_free_page (data->cmd);
goto fail;
}
/* Initialize user process stack and free page used to store the
command that executed the process. */
success = process_init_stack (data->cmd_saveptr, &if_.esp, data->file_name);
palloc_free_page (data->cmd);
/* If stack initialization failed, free resources and quit. */
if (!success)
{
process_exit ();
goto fail;
}
/* NOTE: Currently, the file being executed is closed in load () and then
reopened here. Because load is an exported public function, this
might be necessary. */
struct file *exec_file = filesys_open (data->file_name); struct file *exec_file = filesys_open (data->file_name);
thread_current ()->exec_file = exec_file; if (exec_file == NULL)
{
/* If the executable file cannot be opened, free resources and quit. */
lock_release (&filesys_lock);
sema_up (&data->loaded);
thread_exit ();
}
/* Deny write to the executable file to prevent writing to it and release the
file system lock. */
file_deny_write (exec_file); file_deny_write (exec_file);
lock_release (&filesys_lock);
thread_current ()->exec_file = exec_file;
/* Load the ELF executable file, and store the success of the operation in
the 'success' variable in data. */
data->success = load (data->file_name, &if_.eip, &if_.esp);
/* If load was sucessful, initialize user process stack and free page used
to store the command that executed the process. */
if (data->success)
{
data->success =
process_init_stack (data->cmd_saveptr, &if_.esp, data->file_name);
}
/* Signal that the process has finished attempting to load. */
bool success = data->success;
sema_up (&data->loaded);
/* If the load was unsuccessful or if it was but the stack initialization
failed, exit the thread. */
if (!success)
thread_exit ();
/* Start the user process by simulating a return from an /* Start the user process by simulating a return from an
interrupt, implemented by intr_exit (in interrupt, implemented by intr_exit (in
@@ -152,11 +186,6 @@ start_process (void *proc_start_data)
and jump to it. */ and jump to it. */
asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (&if_) : "memory"); asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (&if_) : "memory");
NOT_REACHED (); NOT_REACHED ();
/* If starting the process failed, free its common resources and exit. */
fail:
free (data);
thread_exit ();
} }
/* Helper function that initializes the stack of a newly created /* Helper function that initializes the stack of a newly created
@@ -164,6 +193,10 @@ start_process (void *proc_start_data)
static bool static bool
process_init_stack (char *cmd_saveptr, void **esp, char *file_name) process_init_stack (char *cmd_saveptr, void **esp, char *file_name)
{ {
ASSERT (cmd_saveptr != NULL);
ASSERT (esp != NULL);
ASSERT (file_name != NULL);
/* Load command line argument *data* to user process stack. /* Load command line argument *data* to user process stack.
This can't cause overflow due to enforcing that the size of This can't cause overflow due to enforcing that the size of
command line input must fit in a page. Also keep track command line input must fit in a page. Also keep track
@@ -175,8 +208,12 @@ process_init_stack (char *cmd_saveptr, void **esp, char *file_name)
int arg_count = 0; int arg_count = 0;
while (arg != NULL) while (arg != NULL)
{ {
/* filename has already been validated to be a safe-to-access string,
so we can safely use strlen here. Filename has already been
split from the command line arguments. */
push_to_stack (esp, arg, (strlen (arg) + 1) * sizeof (char)); push_to_stack (esp, arg, (strlen (arg) + 1) * sizeof (char));
/* Try to allocate memory for the argument pointer. */
struct arg_elem *arg_elem = malloc (sizeof (struct arg_elem)); struct arg_elem *arg_elem = malloc (sizeof (struct arg_elem));
if (arg_elem == NULL) if (arg_elem == NULL)
{ {
@@ -185,9 +222,11 @@ process_init_stack (char *cmd_saveptr, void **esp, char *file_name)
return false; return false;
} }
/* Store the argument pointer in the linked list. */
arg_elem->arg = *esp; arg_elem->arg = *esp;
list_push_front (&arg_list, &arg_elem->elem); list_push_front (&arg_list, &arg_elem->elem);
/* Increment the argument count and get the next argument. */
arg_count++; arg_count++;
arg = strtok_r (NULL, " ", &cmd_saveptr); arg = strtok_r (NULL, " ", &cmd_saveptr);
} }
@@ -203,13 +242,22 @@ process_init_stack (char *cmd_saveptr, void **esp, char *file_name)
+ return_addr_size; + return_addr_size;
/* If pushing the rest of the data required for the stack would cause /* If pushing the rest of the data required for the stack would cause
overflow, allocate an extra page that is contiguous within the overflow, allocate as many extra pages as needed to the user process
virtual address space (below the current address range). */ contiguously in the virtual address space below the initial page. */
if (PHYS_BASE - *esp + remaining_size > PGSIZE) int overflow_bytes = (PHYS_BASE - *esp) + remaining_size - PGSIZE;
if (overflow_bytes > 0)
{ {
uint8_t *kpage = palloc_get_page (PAL_USER | PAL_ZERO); /* Calculate the number of pages needed to allocate. */
if (!install_page (((uint8_t *) PHYS_BASE) - PGSIZE * 2, kpage, true)) int pages_needed = DIV_CEIL (overflow_bytes, PGSIZE);
return false;
/* Allocate the pages and map them to the user process. */
for (int i = 1; i < pages_needed + 1; i++)
{
uint8_t *kpage = palloc_get_page (PAL_USER | PAL_ZERO);
if (!install_page (((uint8_t *) PHYS_BASE) - PGSIZE * (i + 1),
kpage, true))
return false;
}
} }
/* Align stack pointer to word size before pushing argv elements for /* Align stack pointer to word size before pushing argv elements for
@@ -267,39 +315,35 @@ push_to_stack (void **esp, void *data, size_t data_size)
* This function will be implemented in task 2. * This function will be implemented in task 2.
* For now, it does nothing. */ * For now, it does nothing. */
int int
process_wait (tid_t child_tid UNUSED) process_wait (tid_t child_tid)
{ {
struct process_result *child_result = NULL; struct thread *t = thread_current ();
struct list_elem *e; struct process_result fake_result;
struct thread *cur = thread_current (); fake_result.tid = child_tid;
for (e = list_begin (&cur->child_results); struct hash_elem *e = hash_find (&t->child_results, &fake_result.elem);
e != list_end (&cur->child_results); e = list_next (e)) if (e == NULL)
{
struct process_result *result
= list_entry (e, struct process_result, elem);
if (result->tid == child_tid)
{
child_result = result;
break;
}
/* List is ordered, allowing us to break early. */
else if (result->tid > child_tid)
break;
}
if (child_result == NULL)
return -1; return -1;
struct process_result *child_result
= hash_entry (e, struct process_result, elem);
/* Wait for child to die. */ /* Wait for child to die. */
sema_down (&child_result->sema); sema_down (&child_result->sema);
/* We need lock release in process_exit, so we need to acquire (and possibly /* We need lock release in process_exit, so we need to acquire (and possibly
wait) for it here to ensure we don't free the lock memory before it is wait) for it here to ensure we don't free the lock memory before it is
released in process_exit. */ released in process_exit. */
lock_acquire (&child_result->lock); lock_acquire (&child_result->lock);
/* To prevent waiting for child twice, remove it from the list. /* To prevent waiting for child twice, remove it from the table.
No need to use lock since this is the only thread with access to No need to use lock since this is the only thread with access to
the struct process_result now. */ the struct process_result now. */
list_remove (&child_result->elem); hash_delete (&t->child_results, &child_result->elem);
/* Get the exit status of the child */
int exit_status = child_result->exit_status; int exit_status = child_result->exit_status;
/* Release the lock */
lock_release (&child_result->lock); lock_release (&child_result->lock);
/* Result no-longer used by parent, nor child. Deallocate it. */
free (child_result); free (child_result);
return exit_status; return exit_status;
} }
@@ -311,50 +355,27 @@ process_exit (void)
struct thread *cur = thread_current (); struct thread *cur = thread_current ();
uint32_t *pd; uint32_t *pd;
printf ("%s: exit(%d)\n", cur->name, cur->exit_status); /* Clean up all open files */
file_close (cur->exec_file); hash_destroy (&cur->open_files, fd_cleanup);
/* Close the executable file, implicitly allowing it to be written to. */
if (cur->exec_file != NULL)
{
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock);
file_close (cur->exec_file);
lock_release (&filesys_lock);
}
/* Update process result. */
if (cur->result != NULL) if (cur->result != NULL)
{ {
lock_acquire (&cur->result->lock); printf ("%s: exit(%d)\n", cur->name, cur->result->exit_status);
cur->result->exit_status = cur->exit_status; /* Update own process result. */
/* Parent has died, child has to free the struct process_result * */ destruct_process_result (&cur->result->elem, cur);
if (sema_try_down (&cur->result->sema))
{
lock_release (&cur->result->lock);
free (cur->result);
}
/* Parent is still alive and will be the one to free the
struct process_result *, and may be waiting so call sema_up */
else
{
sema_up (&cur->result->sema);
lock_release (&cur->result->lock);
}
} }
/* Free child process results or signal parent's death. */ /* Free child process results or signal parent's death. */
struct list_elem *e; hash_destroy (&cur->child_results, destruct_process_result);
for (e = list_begin (&cur->child_results);
e != list_end (&cur->child_results); e = list_next (e))
{
struct process_result *result
= list_entry (e, struct process_result, elem);
lock_acquire (&result->lock);
/* Child has died (and was not waited for). Free the result. */
if (sema_try_down (&result->sema))
{
lock_release (&result->lock);
free (result);
}
/* Child is still alive, signal via sema that parent has died. */
else
{
sema_up (&result->sema);
lock_release (&result->lock);
}
}
/* Destroy the current process's page directory and switch back /* Destroy the current process's page directory and switch back
to the kernel-only page directory. */ to the kernel-only page directory. */
@@ -374,6 +395,28 @@ process_exit (void)
} }
} }
/* Destruct a process_result, with multi-thread awareness.
If the other thread is running, simply signals death. Otherwise
frees the result. */
static void
destruct_process_result (struct hash_elem *e, void *aux UNUSED)
{
struct process_result *result = hash_entry (e, struct process_result, elem);
lock_acquire (&result->lock);
/* Other thread has died (and was not waited for). Free the result. */
if (sema_try_down (&result->sema))
{
lock_release (&result->lock);
free (result);
}
/* Other thread is still alive, signal via sema that parent has died. */
else
{
sema_up (&result->sema);
lock_release (&result->lock);
}
}
/* Sets up the CPU for running user code in the current /* Sets up the CPU for running user code in the current
thread. thread.
This function is called on every context switch. */ This function is called on every context switch. */
@@ -472,6 +515,7 @@ load (const char *file_name, void (**eip) (void), void **esp)
off_t file_ofs; off_t file_ofs;
bool success = false; bool success = false;
int i; int i;
lock_acquire (&filesys_lock);
/* Allocate and activate page directory. */ /* Allocate and activate page directory. */
t->pagedir = pagedir_create (); t->pagedir = pagedir_create ();
@@ -571,6 +615,7 @@ load (const char *file_name, void (**eip) (void), void **esp)
done: done:
/* We arrive here whether the load is successful or not. */ /* We arrive here whether the load is successful or not. */
file_close (file); file_close (file);
lock_release (&filesys_lock);
return success; return success;
} }

View File

@@ -13,8 +13,8 @@
#include <stdio.h> #include <stdio.h>
#include <syscall-nr.h> #include <syscall-nr.h>
static struct lock filesys_lock; #define MAX_SYSCALL_ARGS 3
static unsigned fd_counter = MIN_USER_FD; #define EXIT_FAILURE -1
struct open_file struct open_file
{ {
@@ -46,18 +46,19 @@ static unsigned syscall_tell (int fd);
static void syscall_close (int fd); static void syscall_close (int fd);
static struct open_file *fd_get_file (int fd); static struct open_file *fd_get_file (int fd);
static void *validate_user_pointer (const void *ptr, size_t size); static void validate_user_pointer (const void *start, size_t size);
static void validate_user_string (const char *str);
/* A struct defining a syscall_function pointer along with its arity. */ /* A struct defining a syscall_function pointer along with its arity. */
typedef struct struct syscall_arguments
{ {
syscall_function function; /* Function pointer. */ syscall_function function; /* Function pointer. */
int arity; /* Number of arguments of the function. */ int arity; /* Number of arguments of the function. */
} syscall_arguments; };
/* A look-up table mapping numbers to system call functions with their number of /* A look-up table mapping numbers to system call functions with their number of
arguments. */ arguments. */
static const syscall_arguments syscall_lookup[] = static const struct syscall_arguments syscall_lookup[] =
{ {
[SYS_HALT] = {(syscall_function) syscall_halt, 0}, [SYS_HALT] = {(syscall_function) syscall_halt, 0},
[SYS_EXIT] = {(syscall_function) syscall_exit, 1}, [SYS_EXIT] = {(syscall_function) syscall_exit, 1},
@@ -77,8 +78,7 @@ static const syscall_arguments syscall_lookup[] =
/* The number of syscall functions (i.e, number of elements) within the /* The number of syscall functions (i.e, number of elements) within the
syscall_lookup table. */ syscall_lookup table. */
static const int LOOKUP_SIZE static const int LOOKUP_SIZE
= sizeof (syscall_lookup) / sizeof (syscall_arguments); = sizeof (syscall_lookup) / sizeof (struct syscall_arguments);
/* Initialises the syscall handling system, as well as a global lock to /* Initialises the syscall handling system, as well as a global lock to
synchronise all file access between processes. */ synchronise all file access between processes. */
@@ -89,28 +89,29 @@ syscall_init (void)
lock_init (&filesys_lock); lock_init (&filesys_lock);
} }
/* Function that takes a interrupt frame containing a syscall and its args. /* Function that takes an interrupt frame containing a syscall and its args.
Validates the arguments and pointers before calling the relevant Validates the arguments and pointers before calling the relevant
high-level system call function, storing its output (if any) in f->eax */ high-level system call function, storing its output (if any) in f->eax */
static void static void
syscall_handler (struct intr_frame *f) syscall_handler (struct intr_frame *f)
{ {
/* First, read the system call number from the stack. */ /* First, read the system call number from the stack. */
validate_user_pointer (f->esp, 1); validate_user_pointer (f->esp, sizeof (uintptr_t));
unsigned syscall_number = *(int *) f->esp; uintptr_t syscall_number = *(int *) f->esp;
/* Ensures the number corresponds to a system call that can be handled. */ /* Ensures the number corresponds to a system call that can be handled. */
if (syscall_number >= LOOKUP_SIZE) if (syscall_number >= LOOKUP_SIZE)
thread_exit (); syscall_exit (EXIT_FAILURE);
syscall_arguments syscall = syscall_lookup[syscall_number]; struct syscall_arguments syscall = syscall_lookup[syscall_number];
/* Next, read and copy the arguments from the stack pointer. */ /* Next, read and copy the arguments from the stack pointer. */
validate_user_pointer (f->esp + sizeof (uintptr_t), validate_user_pointer (f->esp + sizeof (uintptr_t),
syscall.arity * sizeof (uintptr_t)); syscall.arity * sizeof (uintptr_t));
uintptr_t args[3] = {0};
for (int i=0; i < syscall.arity; i++) uintptr_t args[MAX_SYSCALL_ARGS] = {0};
args[i] = *(uintptr_t *) (f->esp + sizeof (uintptr_t) * (i + 1)); for (int i = 0; i < syscall.arity && i < MAX_SYSCALL_ARGS; i++)
args[i] = *(uintptr_t *) (f->esp + sizeof (uintptr_t) * (i + 1));
/* Call the function that handles this system call with the arguments. When /* Call the function that handles this system call with the arguments. When
there is a return value it is stored in f->eax. */ there is a return value it is stored in f->eax. */
@@ -130,24 +131,19 @@ syscall_exit (int status)
{ {
/* Sets exit_status of the thread to status. thread_exit () will call /* Sets exit_status of the thread to status. thread_exit () will call
process_exit () if user programs are allowed. */ process_exit () if user programs are allowed. */
thread_current ()->exit_status = status; thread_current ()->result->exit_status = status;
thread_exit (); thread_exit ();
} }
/* Executes a given command with the relevant args, by calling process_execute. /* Executes a given command with the relevant args, by calling process_execute.
Acquires the filesystem lock as process_execute accesses the file system. Returns PID for the process that is running the CMD_LINE. */
Returns PID for the process that is running the CMD_LINE
*/
static pid_t static pid_t
syscall_exec (const char *cmd_line) syscall_exec (const char *cmd_line)
{ {
validate_user_pointer (cmd_line, 1); /* Validate the user string before executing the process. */
validate_user_string (cmd_line);
lock_acquire (&filesys_lock); return process_execute (cmd_line); /* Returns the PID of the new process */
pid_t pid = process_execute(cmd_line);
lock_release (&filesys_lock);
return pid;
} }
/* Handles the syscall of wait. Effectively a wrapper for process_wait as the /* Handles the syscall of wait. Effectively a wrapper for process_wait as the
@@ -155,21 +151,24 @@ syscall_exec (const char *cmd_line)
static int static int
syscall_wait (pid_t pid) syscall_wait (pid_t pid)
{ {
return process_wait (pid); return process_wait (pid); /* Returns the exit status of the waited process */
} }
/* Handles the syscall for file creation. First validates the user file /* Handles the syscall for file creation. First validates the user file
pointer. Acquires the file system lock to prevent synchronisation issues, pointer. Acquires the file system lock to prevent synchronisation issues,
and then uses FILESYS_CREATE to create the file, returning the same status */ and then uses FILESYS_CREATE to create the file, returning the same status */
static bool static bool
syscall_create (const char *file UNUSED, unsigned initial_size UNUSED) syscall_create (const char *file, unsigned initial_size)
{ {
validate_user_pointer (file, 1); /* Validate the user string before creating the file. */
validate_user_string (file);
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock); lock_acquire (&filesys_lock);
bool status = filesys_create (file, initial_size); bool status = filesys_create (file, initial_size);
lock_release (&filesys_lock); lock_release (&filesys_lock);
/* Return the status of the file creation. */
return status; return status;
} }
@@ -179,12 +178,15 @@ syscall_create (const char *file UNUSED, unsigned initial_size UNUSED)
static bool static bool
syscall_remove (const char *file) syscall_remove (const char *file)
{ {
validate_user_pointer (file, 1); /* Validate the user string before removing the file. */
validate_user_string (file);
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock); lock_acquire (&filesys_lock);
bool status = filesys_remove (file); bool status = filesys_remove (file);
lock_release (&filesys_lock); lock_release (&filesys_lock);
/* Return the status of the file removal. */
return status; return status;
} }
@@ -195,23 +197,32 @@ syscall_remove (const char *file)
static int static int
syscall_open (const char *file) syscall_open (const char *file)
{ {
validate_user_pointer (file, 1); /* Validate the user string before opening the file. */
validate_user_string (file);
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock); lock_acquire (&filesys_lock);
struct file *ptr = filesys_open (file); struct file *ptr = filesys_open (file);
lock_release (&filesys_lock); lock_release (&filesys_lock);
/* If the file could not be opened, return failure. */
if (ptr == NULL) if (ptr == NULL)
return -1; return EXIT_FAILURE;
/* Allocate space for a struct representing a mapping from an FD to a struct /* Allocate space for a struct representing a mapping from an FD to a struct
file. */ file. */
struct open_file *file_info struct open_file *file_info
= (struct open_file*) malloc (sizeof (struct open_file)); = (struct open_file*) malloc (sizeof (struct open_file));
if (file_info == NULL) if (file_info == NULL)
return -1; {
/* If we could not allocate memory for the file_info struct, close the
file and return failure. */
file_close (ptr);
return EXIT_FAILURE;
}
/* Populate the above struct, with a unique FD and the current open file */ /* Populate the above struct, with a unique FD and the current open file */
file_info->fd = fd_counter++; file_info->fd = thread_current ()->fd_counter++;
file_info->file = ptr; file_info->file = ptr;
/* Add the new FD->file mapping to the hashtable for the current thread */ /* Add the new FD->file mapping to the hashtable for the current thread */
@@ -227,14 +238,17 @@ syscall_open (const char *file)
static int static int
syscall_filesize (int fd) syscall_filesize (int fd)
{ {
/* Try to get the file from the FD. If it does not exist, return failure. */
struct open_file *file_info = fd_get_file (fd); struct open_file *file_info = fd_get_file (fd);
if (file_info == NULL) if (file_info == NULL)
return -1; return EXIT_FAILURE;
/* Acquire the file system lock to prevent any race conditions. */
lock_acquire (&filesys_lock); lock_acquire (&filesys_lock);
int bytes = file_length (file_info->file); int bytes = file_length (file_info->file);
lock_release (&filesys_lock); lock_release (&filesys_lock);
/* Return the number of bytes in the file. */
return bytes; return bytes;
} }
@@ -247,30 +261,37 @@ syscall_read (int fd, void *buffer, unsigned size)
{ {
/* Only console (fd = 0) or other files, not including STDOUT, (fd > 1) are /* Only console (fd = 0) or other files, not including STDOUT, (fd > 1) are
allowed. */ allowed. */
if (fd < 0 || fd == STDOUT_FILENO) if (fd < STDIN_FILENO || fd == STDOUT_FILENO)
return -1; return EXIT_FAILURE;
/* Validate the user buffer for the provided size before reading. */
validate_user_pointer (buffer, size); validate_user_pointer (buffer, size);
if (fd == STDIN_FILENO) if (fd == STDIN_FILENO)
{ {
/* Reading from the console. */ /* Reading from the console. */
char *write_buffer = buffer; char *write_buffer = buffer;
for (int i = 0; i < size; i++) for (unsigned i = 0; i < size; i++)
write_buffer[i] = input_getc (); write_buffer[i] = input_getc ();
/* In case of console, read is always (eventually) successful. So return
the size for the number of bytes read. */
return size; return size;
} }
else else
{ {
/* Reading from a file. */ /* Reading from a file. */
/* Find the file from the FD. If it does not exist, return failure. */
struct open_file *file_info = fd_get_file (fd); struct open_file *file_info = fd_get_file (fd);
if (file_info == NULL) if (file_info == NULL)
return -1; return EXIT_FAILURE;
/* Acquire the file system lock to prevent race-conditions. */
lock_acquire (&filesys_lock); lock_acquire (&filesys_lock);
int bytes_written = file_read (file_info->file, buffer, size); int bytes_written = file_read (file_info->file, buffer, size);
lock_release (&filesys_lock); lock_release (&filesys_lock);
/* Return the number of bytes read. */
return bytes_written; return bytes_written;
} }
} }
@@ -287,25 +308,32 @@ syscall_write (int fd, const void *buffer, unsigned size)
if (fd <= 0) if (fd <= 0)
return 0; return 0;
/* Validate the user buffer for the provided size before writing. */
validate_user_pointer (buffer, size); validate_user_pointer (buffer, size);
if (fd == STDOUT_FILENO) if (fd == STDOUT_FILENO)
{ {
/* Writing to the console. */ /* Writing to the console. */
putbuf (buffer, size); putbuf (buffer, size);
/* In case of console, write is always successful. So return the size for
the number of bytes written. */
return size; return size;
} }
else else
{ {
/* Writing to a file. */ /* Writing to a file. */
/* Find the file from the FD. If it does not exist, return failure. */
struct open_file *file_info = fd_get_file (fd); struct open_file *file_info = fd_get_file (fd);
if (file_info == NULL) if (file_info == NULL)
return 0; return 0;
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock); lock_acquire (&filesys_lock);
int bytes = file_write (file_info->file, buffer, size); int bytes = file_write (file_info->file, buffer, size);
lock_release (&filesys_lock); lock_release (&filesys_lock);
/* Return the number of bytes written. */
return bytes; return bytes;
} }
} }
@@ -317,9 +345,11 @@ syscall_write (int fd, const void *buffer, unsigned size)
static void static void
syscall_seek (int fd, unsigned position) syscall_seek (int fd, unsigned position)
{ {
/* Find the file from the FD. If it does not exist, do nothing. */
struct open_file *file_info = fd_get_file (fd); struct open_file *file_info = fd_get_file (fd);
if (file_info != NULL) if (file_info != NULL)
{ {
/* File exists: Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock); lock_acquire (&filesys_lock);
file_seek (file_info->file, position); file_seek (file_info->file, position);
lock_release (&filesys_lock); lock_release (&filesys_lock);
@@ -332,14 +362,17 @@ syscall_seek (int fd, unsigned position)
static unsigned static unsigned
syscall_tell (int fd) syscall_tell (int fd)
{ {
/* Find the file from the FD. If it does not exist, return 0. */
struct open_file *file_info = fd_get_file (fd); struct open_file *file_info = fd_get_file (fd);
if (file_info == NULL) if (file_info == NULL)
return 0; return 0;
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock); lock_acquire (&filesys_lock);
unsigned pos = file_tell (file_info->file); unsigned pos = file_tell (file_info->file);
lock_release (&filesys_lock); lock_release (&filesys_lock);
/* Return the current position in the file. */
return pos; return pos;
} }
@@ -349,14 +382,21 @@ syscall_tell (int fd)
static void static void
syscall_close (int fd) syscall_close (int fd)
{ {
/* Find the file from the FD. If it does not exist, do nothing. */
struct open_file *file_info = fd_get_file (fd); struct open_file *file_info = fd_get_file (fd);
if (file_info != NULL) if (file_info != NULL)
{ {
/* File exists */
/* First, remove the file from the hash table of open files. */
hash_delete (&thread_current ()->open_files, &file_info->elem); hash_delete (&thread_current ()->open_files, &file_info->elem);
/* Then, close the file, acquiring the file system lock to prevent race
conditions. */
lock_acquire (&filesys_lock); lock_acquire (&filesys_lock);
file_close (file_info->file); file_close (file_info->file);
lock_release (&filesys_lock); lock_release (&filesys_lock);
/* Free the memory allocated for the file_info struct. */
free (file_info); free (file_info);
} }
} }
@@ -366,7 +406,12 @@ syscall_close (int fd)
unsigned unsigned
fd_hash (const struct hash_elem *element, void *aux UNUSED) fd_hash (const struct hash_elem *element, void *aux UNUSED)
{ {
return hash_int (hash_entry (element, struct open_file, elem)->fd); /* We use the FD as the hash value. This is because the FD is incremented
sequentially and is therefore unique for each file. It positively affects
the performance of the hash table: 1. It is unique so no need to call
expensive hash functions. 2. It being sequential means that the hash table
is more likely to be weight balanced. */
return hash_entry (element, struct open_file, elem)->fd;
} }
/* Comparator function for the open_file table. Compares two entries based on /* Comparator function for the open_file table. Compares two entries based on
@@ -381,6 +426,20 @@ fd_less (const struct hash_elem *a_, const struct hash_elem *b_,
return a->fd < b->fd; return a->fd < b->fd;
} }
/* Function to clean up an open file entry. Closes the file and frees the
associated memory. */
void
fd_cleanup (struct hash_elem *e, void *aux UNUSED)
{
struct open_file *file_info = hash_entry (e, struct open_file, elem);
lock_acquire (&filesys_lock);
file_close (file_info->file);
lock_release (&filesys_lock);
free (file_info);
}
/* Gets a file from its descriptor (FD number). If there is no file with the fd /* Gets a file from its descriptor (FD number). If there is no file with the fd
FD it returns NULL. */ FD it returns NULL. */
static struct open_file * static struct open_file *
@@ -400,18 +459,63 @@ fd_get_file (int fd)
return hash_entry (e, struct open_file, elem); return hash_entry (e, struct open_file, elem);
} }
/* Validates if a block of memory starting at PTR and of size SIZE bytes is /* Validates if a block of memory starting at START and of size SIZE bytes is
fully contained within user virtual memory. Kills the thread (by calling fully contained within user virtual memory. Kills the thread (by exiting with
thread_exit) if the memory is invalid. Otherwise, returns the PTR given. failure) if the memory is invalid. Otherwise, returns (nothing) normally.
If the size is 0, the function does no checks and returns PTR.*/ If the size is 0, the function does no checks and returns the given ptr. */
static void * static void
validate_user_pointer (const void *ptr, size_t size) validate_user_pointer (const void *start, size_t size)
{ {
if (size > 0 && (ptr == NULL || /* If the size is 0, we do not need to check anything. */
!is_user_vaddr (ptr) || if (size == 0)
!is_user_vaddr (ptr + size - 1) || return;
pagedir_get_page (thread_current()->pagedir, ptr) == NULL))
thread_exit ();
return (void *) ptr; const void *end = start + size - 1;
/* Check if the start and end pointers are valid user virtual addresses. */
if (start == NULL || !is_user_vaddr (start) || !is_user_vaddr (end))
syscall_exit (EXIT_FAILURE);
/* We now need to check if the entire memory block is mapped to physical
memory by the page table. */
for (const void *ptr = pg_round_down (start); ptr <= end; ptr += PGSIZE)
if (pagedir_get_page (thread_current ()->pagedir, ptr) == NULL)
syscall_exit (EXIT_FAILURE);
}
/* Validates if a string is fully contained within user virtual memory. Kills
the thread (by exiting with failure) if the memory is invalid. Otherwise,
returns (nothing) normally. */
static void
validate_user_string (const char *str)
{
/* Check if the string pointer is a valid user virtual address. */
if (str == NULL || !is_user_vaddr (str))
syscall_exit (EXIT_FAILURE);
/* Calculate the offset of the string within the (first) page. */
size_t offset = (uintptr_t) str % PGSIZE;
/* We move page by page, checking if the page is mapped to physical memory. */
for (;;)
{
void *page = pg_round_down (str);
/* If we reach addresses that are not mapped to physical memory before the
end of the string, the thread is terminated. */
if (!is_user_vaddr(page) ||
pagedir_get_page (thread_current ()->pagedir, page) == NULL)
syscall_exit (EXIT_FAILURE);
while (offset < PGSIZE)
{
if (*str == '\0')
return; /* We reached the end of the string without issues. */
str++;
offset++;
}
offset = 0; /* Next page will start at the beginning. */
}
} }

View File

@@ -2,14 +2,16 @@
#define USERPROG_SYSCALL_H #define USERPROG_SYSCALL_H
#include <hash.h> #include <hash.h>
#include "threads/synch.h"
#define MIN_USER_FD 2
typedef int pid_t; typedef int pid_t;
struct lock filesys_lock;
void syscall_init (void); void syscall_init (void);
unsigned fd_hash (const struct hash_elem *element, void *aux); unsigned fd_hash (const struct hash_elem *element, void *aux);
bool fd_less (const struct hash_elem *a, const struct hash_elem *b, void *aux); bool fd_less (const struct hash_elem *a, const struct hash_elem *b, void *aux);
void fd_cleanup (struct hash_elem *e, void *aux);
#endif /* userprog/syscall.h */ #endif /* userprog/syscall.h */