Compare commits

..

152 Commits

Author SHA1 Message Date
Themis Demetriades
65da1659e5 feat: merged shared-read-only-executables with the rest of VM 2024-12-06 04:15:13 +00:00
Themis Demetriades
3897e83963 fix: use correct page_get function within page eviction 2024-12-06 01:43:41 +00:00
Demetriades, Themis
96b350d623 Merge branch 'vm/mmap-write-back-on-eviction' into 'vm/virtual-memory/themis-synch'
Write back mmap file pages to file upon eviction

See merge request lab2425_autumn/pintos_22!59
2024-12-06 01:01:50 +00:00
Themis Demetriades
31403ac7cb fix: obtain correct page table entry when performing eviction 2024-12-06 00:56:03 +00:00
1da0c7d48c fix: properly assign frame owners and deallocate in all required places 2024-12-06 00:29:57 +00:00
Demetriades, Themis
8220b931a9 Merge branch 'vm/virtual-memory/frame-synch/saleh' into 'vm/virtual-memory/themis-synch'
Merge frame pinning to themis-synch

See merge request lab2425_autumn/pintos_22!60
2024-12-06 00:21:02 +00:00
sBubshait
1efa1fef9a Merge frame pinning into themis-synch 2024-12-05 23:56:25 +00:00
sBubshait
fc088a19ac Merge remote-tracking branch 'origin/vm/frame-pinning' into vm/virtual-memory/frame-synch/saleh
# Conflicts:
#	src/userprog/syscall.c
2024-12-05 23:48:52 +00:00
sBubshait
a34bbbed08 Update frame: When evicting an mmapped file page, write it back to the file if it is dirty 2024-12-05 22:53:48 +00:00
833c1b0520 fix: only swap out shared pages once 2024-12-05 22:37:14 +00:00
9aa9cdb91e feat: implement proper destruction of pages, including for shared pages 2024-12-05 22:23:50 +00:00
Themis Demetriades
2811ea0eb3 fix: SPT never removes entries until process termination or special case 2024-12-05 22:05:02 +00:00
dd46200256 feat: initial shared file page management and initialization 2024-12-05 21:46:49 +00:00
sBubshait
6da855fe47 Implement validation of pointers and strings in syscalls with pinning and unpinning to protect against eviction 2024-12-05 21:12:31 +00:00
4dd6b6e928 fix: do not leak when inserting the same page twice, just update 2024-12-05 19:38:27 +00:00
0f1f7b9a6f refactor: extract init_pages 2024-12-05 19:35:39 +00:00
sBubshait
e03273756d Update frame table to add a pinned flag and protect those from being evicted 2024-12-05 17:52:01 +00:00
Themis Demetriades
7860f3863f fix: add check to mmap to ensure file isn't mapped over stack segment (ed1223) 2024-12-05 17:11:02 +00:00
Themis Demetriades
d03e253046 feat: implement synchronisation to protecting access to PTEs of SPTs during eviction 2024-12-05 16:51:15 +00:00
EDiasAlberto
5cf79b5389 fix: add check to mmap to ensure file isn't mapped over stack segment 2024-12-05 16:05:08 +00:00
EDiasAlberto
e779e8ac7c fix: modify stack growth to use frame allocation to allow for page swapping 2024-12-05 04:39:50 +00:00
sBubshait
16db01d3d8 Refactor: Check if page is in a swap in fetch_page instead of the page fault handler 2024-12-05 03:17:18 +00:00
sBubshait
c12cd95093 Fix issues with merging, duplicate references and definition of VM 2024-12-05 02:27:48 +00:00
sBubshait
f13fd435cd Merge remote-tracking branch 'origin/vm/page-swap-synch' into vm/virtual-memory/saleh
# Conflicts:
#	.gitlab-ci.yml
#	src/Makefile.build
#	src/threads/thread.c
#	src/userprog/exception.c
#	src/userprog/process.c
#	src/vm/frame.c
#	src/vm/page.c
#	src/vm/page.h
#	src/vm/stackgrowth.c
#	src/vm/stackgrowth.h
2024-12-05 02:21:53 +00:00
EDiasAlberto
ac31fb1e1e feat: set accessed bit to allocated frames in page_load and get_usr_kpage 2024-12-05 01:41:23 +00:00
sBubshait
1a8eb1bbe5 Merge branch 'vm/memory-mapped-files' into vm/virtual-memory/saleh 2024-12-05 01:24:50 +00:00
sBubshait
52ec8fe779 Fix Bug: Grow stack if necessary in case of a page fault in the kernel context 2024-12-05 01:15:46 +00:00
sBubshait
f171a05108 Merge branch 'vm/stack-growth/saleh' into vm/virtual-memory/saleh
# Conflicts:
#	src/userprog/exception.c
#	src/userprog/process.c
#	src/userprog/syscall.c
#	src/vm/frame.c
#	src/vm/page.c
#	src/vm/page.h
2024-12-05 00:51:03 +00:00
Demetriades, Themis
f06c91cf0d ci: include linear page tests in VM test pipeline 2024-12-05 00:29:49 +00:00
sBubshait
5265fed288 Refactor stack growth to be helper functions in exception for easier merging 2024-12-05 00:27:40 +00:00
Themis Demetriades
19d5b02341 fix: remove use of USERPROG compiler flag specific code when the flag is disabled 2024-12-04 23:48:51 +00:00
Themis Demetriades
0288e13206 fix: don't discriminate between user and kernel page fault contexts for stack growth, lazy loading, and swapping 2024-12-04 23:46:31 +00:00
Themis Demetriades
60faf995ea fix: lazy load executable files of user processes even when accessed in a kernel context 2024-12-04 22:21:31 +00:00
sBubshait
61f6374006 Update gitlab CI to only run the tests associated with this feature (mmapped-files). 2024-12-04 22:06:09 +00:00
sBubshait
26a2d40325 Implement implicitly unmapping all mmapped files when a process exits. Refactor to reduce duplication 2024-12-04 22:00:59 +00:00
sBubshait
806d6bc19e Refactor: Move destroying mmap data into process_exit instead of thread 2024-12-04 21:59:38 +00:00
Themis Demetriades
723055f485 fix: only use lazy loading if VM flag is enabled 2024-12-04 21:33:21 +00:00
Themis Demetriades
1e236a5c47 Merge branch 'vm/lazy-loading' into vm/page-swap-synch 2024-12-04 19:11:37 +00:00
sBubshait
ecbb4e74a5 Implement the unmap system call, writing back to the file if a page is dirty before removing from SPT 2024-12-04 19:07:28 +00:00
sBubshait
02b79d1934 Update mmap to add temporarily page_set_swap until swap is implemented 2024-12-04 18:13:07 +00:00
sBubshait
857cae3578 Update mmap to add a get helper function to find a mmap entry from its mapping 2024-12-04 18:08:05 +00:00
sBubshait
941e1e067a Update SPT page entry to change type from EXECUTABLE to PAGE_FILE to capture mmaps in addition to executables 2024-12-04 17:51:30 +00:00
sBubshait
ad6e4b4059 Implement syscall_mmap to validate and then map all file data into a user address in memory 2024-12-04 17:42:53 +00:00
sBubshait
72fa0c1bbb Fix Bug: Initialise the mmap table for the newly created thread rather than the current thread 2024-12-04 17:41:14 +00:00
Themis Demetriades
4bf6914cfa feat: incorporate lazy-loading data & helpers into supplemental page table 2024-12-04 16:45:36 +00:00
Themis Demetriades
fb73d694bf fix: frame allocation now invalidates the victim process page directory, not the caller's 2024-12-04 16:41:13 +00:00
sBubshait
67f16cb2a6 Update syscall.c to allow mmap and unmap system calls through helper handler functions for each 2024-12-04 15:31:53 +00:00
sBubshait
6b0f708d8f Update mmap to add an insert helper function to allocate and add new mmap entries to the hash table 2024-12-04 15:26:00 +00:00
sBubshait
6e838aa06a Fix Bug in thread.c: Only initialise and destroy mmap files table if VM is defined 2024-12-04 15:24:11 +00:00
sBubshait
a2f46f3b72 Add a mmap destroy function to cleanup all mmap hash table entries upon thread exit 2024-12-04 15:14:02 +00:00
sBubshait
1ce09a49a1 Add helper functions to initialise the memory-mapped files table and counter 2024-12-04 15:08:43 +00:00
Themis Demetriades
1b73e415d7 fix: invalidate PTEs of evicted pages before eviction occurs to prevent modificationof pages mid-eviction 2024-12-04 15:02:49 +00:00
sBubshait
b3042b5aa6 Update thread structure to add mmap files table and a counter for mappings of the thread 2024-12-04 14:56:52 +00:00
sBubshait
85aabd86cd Update gitlab ci file to include mmap tests in the automated testing pipeline 2024-12-04 14:55:28 +00:00
sBubshait
acc768e177 Add mmap module in vm defining mmap_entry structure and some helper functions 2024-12-04 13:01:01 +00:00
Themis Demetriades
47a7dfae04 refactor: add comments describing each type of page fault dealt by the page fault handler 2024-12-03 21:47:59 +00:00
EDiasAlberto
9a3c8a1c38 fix: grow stack upon page fault in kernel context to support syscall stack growth 2024-12-03 20:56:10 +00:00
Themis Demetriades
08eafcf7ef feat: implement page swapping 2024-12-03 16:53:47 +00:00
Themis Demetriades
df7d847978 fix: remove stack fault checks for page faults outside user non-present addresses 2024-12-02 21:07:17 +00:00
Demetriades, Themis
fbcd3c9f19 ci: include dynamic stack growth tests in VM test pipeline 2024-12-02 20:57:05 +00:00
Themis Demetriades
6190d1bee6 fix: disable dynamic stack growth when VM flag is disabled 2024-12-02 20:44:54 +00:00
Themis Demetriades
6adf2e743b refactor: dynamic stack growth functions to follow code style 2024-12-02 19:50:40 +00:00
Themis Demetriades
05a48cf9c6 refactor: page fault exception handler follows code style 2024-12-01 23:36:55 +00:00
Themis Demetriades
bb16abdc0d refactor: supplemental page table helper functions follow code style 2024-12-01 23:30:50 +00:00
Demetriades, Themis
8e278b349a Merge branch 'page-swap-helpers' into 'virtual-memory'
Implement helper functions for managing the supplemental page table

See merge request lab2425_autumn/pintos_22!55
2024-12-01 21:47:30 +00:00
Demetriades, Themis
9d35beb2e4 Merge branch 'virtual-memory' into 'page-swap-helpers'
# Conflicts:
#   src/vm/frame.c
#   src/vm/page.c
2024-12-01 21:44:17 +00:00
Themis Demetriades
7ce512305e fix: remove DVM flag when compiling outside of vm directory 2024-12-01 00:41:09 +00:00
Demetriades, Themis
775b73a3e9 Merge branch 'ethan-stack-growth' into 'virtual-memory'
Implement dynamic stack growth

See merge request lab2425_autumn/pintos_22!54
2024-11-30 23:21:33 +00:00
Demetriades, Themis
d8edc6d3fe Merge branch 'virtual-memory' into 'ethan-stack-growth'
# Conflicts:
#   src/Makefile.build
2024-11-30 23:21:16 +00:00
Demetriades, Themis
5682974f9d Merge branch 'vm/supplemental-page-table' into 'master'
Implement frame table & page eviction algorithm

See merge request lab2425_autumn/pintos_22!53
2024-11-30 23:01:04 +00:00
Themis Demetriades
6f85d7642d feat: implement clock (second-chance) page eviction algorithm 2024-11-30 22:40:13 +00:00
EDiasAlberto
94adc11f03 Feat: implement page_get_swap and page_set_swap functions 2024-11-30 03:21:34 +00:00
EDiasAlberto
40c553d68b Merge stack growth functions 2024-11-30 01:54:28 +00:00
EDiasAlberto
13de832586 Refactor stack growth code to remove messy conditions 2024-11-29 23:52:05 +00:00
EDiasAlberto
5c661c2e24 Feat: pointer validation checks string across multiple pages and handle kernel page faults 2024-11-29 23:49:49 +00:00
EDiasAlberto
5f40d83e66 Implement MMU-based user memory validation 2024-11-29 23:03:31 +00:00
Themis Demetriades
149bb42889 feat: implement clock (second-chance) page eviction algorithm 2024-11-29 19:30:47 +00:00
sBubshait
8047c65227 Update syscall handling of invalid pointers by preloading pages using the helper try_fetch_page 2024-11-29 18:55:28 +00:00
sBubshait
d047a3c242 Update Userprog Makefile to include vm files 2024-11-29 18:49:26 +00:00
sBubshait
c8b93c57ee Fix: Update writability of the last page after loading as a workaround to calculating the offset, w/ G 2024-11-29 15:45:44 +00:00
sBubshait
92d0b68243 Fix: Avoid closing the file after loading segments to be able to lazily load from file without opening, w/ G 2024-11-29 14:32:55 +00:00
sBubshait
801fd7d310 Implement page fault for lazy loading executables, w/ G 2024-11-28 20:03:50 +00:00
sBubshait
df20e0fdfe Update load_segment to load only the metadata of the pages to the SPT, w/ G 2024-11-28 18:35:49 +00:00
sBubshait
97e6e0fb1e Add SPT Helper Functions: Insert, Get, Cleanup w/ G 2024-11-28 18:18:52 +00:00
sBubshait
14fc96febf Implement Supplemental Page Table (SPT) per thread along with structure for its entries, w/ G 2024-11-28 17:54:15 +00:00
EDiasAlberto
4f84a83611 Refactor: abstract new page allocation to one general function and make helper functions static 2024-11-27 19:41:22 +00:00
EDiasAlberto
c74a8c55aa Implement stack growth for system calls and add stack pointer tracking to thread 2024-11-27 19:21:43 +00:00
EDiasAlberto
c670c29e47 update stack growth header to fit virtual memory naming format 2024-11-27 18:57:20 +00:00
Themis Demetriades
1e6b90da0d Implement skeleton for swapping frames into disk 2024-11-26 18:59:46 +00:00
Themis Demetriades
ea2725f606 feat: implement frame table without thread safety 2024-11-26 15:17:11 +00:00
EDiasAlberto
af7f2ba873 Fix: Magic number in stackgrowth.c 2024-11-26 04:54:00 +00:00
EDiasAlberto
3ef5264b6e feat: allow stack to grow for process up to 8MB in size 2024-11-26 04:43:25 +00:00
Demetriades, Themis
605050e38d Merge branch 'code-review-2-changes' into 'master'
fix: code review 2 changes

See merge request lab2425_autumn/pintos_22!52
2024-11-24 16:52:48 +00:00
6225a2eb8b fix: ignore failing tests for now 2024-11-24 16:22:13 +00:00
aedb72246b fix: do not acquire filesys_lock for tell and seek 2024-11-24 15:41:18 +00:00
e1f0258f8e fix: handle malloc result in init_process_result 2024-11-24 15:09:32 +00:00
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
59e7a64f8e Only check user pages rather than all bytes in-between, for known-size pointers 2024-11-12 15:48:22 +00:00
cf4bf90cbb Implement user pointer checking for C strings 2024-11-12 15:34:45 +00:00
9a6abab95e Check access to user memory using page fault method (via get_user and put_user). 2024-11-12 15:00:16 +00:00
44f6a85163 Add get_user and put_user provided by spec. 2024-11-12 14:50:53 +00:00
83e044cf68 Let kernel handle its own page faults 2024-11-12 14:50:53 +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
166 changed files with 17151 additions and 297 deletions

View File

@@ -16,18 +16,13 @@ stages:
script:
- cd src/$DIR
- make check | tee build.log
- grep -q "FAIL tests/$DIR" build.log && exit 1 || exit 0
- grep -vE "^FAIL $IGNORE\$" build.log | grep -q "FAIL tests/$DIR" && exit 1 || exit 0
test_devices:
extends: .pintos_tests
variables:
DIR: devices
test_filesys:
extends: .pintos_tests
variables:
DIR: filesys
test_threads:
extends: .pintos_tests
variables:
@@ -37,8 +32,10 @@ test_userprog:
extends: .pintos_tests
variables:
DIR: userprog
IGNORE: (tests/userprog/no-vm/multi-oom)
test_vm:
extends: .pintos_tests
variables:
DIR: vm
IGNORE: (tests/vm/pt-grow-stack|tests/vm/pt-grow-pusha|tests/vm/pt-big-stk-obj|tests/vm/pt-overflowstk|tests/vm/pt-write-code2|tests/vm/pt-grow-stk-sc|tests/vm/page-linear|tests/vm/page-parallel|tests/vm/page-merge-seq|tests/vm/page-merge-par|tests/vm/page-merge-stk|tests/vm/page-merge-mm|tests/vm/mmap-over-stk)

View File

@@ -62,6 +62,9 @@ userprog_SRC += userprog/gdt.c # GDT initialization.
userprog_SRC += userprog/tss.c # TSS management.
# Virtual memory code.
vm_SRC += vm/frame.c # Frame table manager.
vm_SRC += vm/page.c # Page table manager.
vm_SRC += vm/mmap.c # Memory-mapped files.
vm_SRC += devices/swap.c # Swap block manager.
#vm_SRC = vm/file.c # Some other file.

View File

@@ -4,7 +4,7 @@ SRCDIR = ..
# To add a new test, put its name on the PROGS list
# and then add a name_SRC line that lists its source files.
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.
cat_SRC = cat.c
@@ -18,6 +18,7 @@ lineup_SRC = lineup.c
ls_SRC = ls.c
recursor_SRC = recursor.c
rm_SRC = rm.c
args-ovf_SRC = args-ovf.c
# Should work in task 3; also in task 4 if VM is included.
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

@@ -1,6 +1,7 @@
#include "filesys/inode.h"
#include <list.h>
#include <debug.h>
#include <stdio.h>
#include <round.h>
#include <string.h>
#include "filesys/filesys.h"

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-bound open-normal open-missing open-boundary open-empty \
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 \
read-zero read-stdout read-bad-fd write-normal write-bad-ptr \
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 write-bad-buf \
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-bad-ptr wait-simple wait-twice wait-killed wait-load-kill \
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 \
bad-jump bad-jump2 bad-maths)
bad-jump bad-jump2 bad-maths overflow-stack)
tests/userprog_PROGS = $(tests/userprog_TESTS) $(addprefix \
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-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/overflow-stack_SRC = tests/userprog/overflow-stack.c tests/main.c
tests/userprog/sc-boundary_SRC = tests/userprog/sc-boundary.c \
tests/userprog/boundary.c tests/main.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/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-buf_SRC = tests/userprog/read-bad-buf.c tests/main.c
tests/userprog/read-boundary_SRC = tests/userprog/read-boundary.c \
tests/userprog/boundary.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/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-buf_SRC = tests/userprog/write-bad-buf.c tests/main.c
tests/userprog/write-boundary_SRC = tests/userprog/write-boundary.c \
tests/userprog/boundary.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/read-normal_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-zero_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-buf_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/multi-child-fd_PUTFILES += tests/userprog/sample.txt

View File

@@ -1,5 +1,9 @@
Full robustness of argument passing code:
- Test user stack overflow robustness of "exec" system calls.
Full robustness of argument passing and syscall handling code:
- Test user stack overflow robustness of "exec" system calls and user code.
5 exec-over-arg
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 warnings;
use tests::tests;
check_expected ([<<'EOF', <<'EOF']);
(exec-bad-ptr) begin
(exec-bad-ptr) end
exec-bad-ptr: exit(0)
EOF
check_expected ([<<'EOF']);
(exec-bad-ptr) begin
exec-bad-ptr: exit(-1)
EOF

View File

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

View File

@@ -32,6 +32,8 @@
#include "tests/threads/tests.h"
#endif
#ifdef VM
#include "vm/frame.h"
#include "vm/page.h"
#include "devices/swap.h"
#endif
#ifdef FILESYS
@@ -101,6 +103,10 @@ main (void)
palloc_init (user_page_limit);
malloc_init ();
paging_init ();
#ifdef VM
frame_init ();
shared_file_pages_init ();
#endif
/* Segmentation. */
#ifdef USERPROG

View File

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

View File

@@ -18,6 +18,10 @@
#ifdef USERPROG
#include "userprog/process.h"
#include "userprog/syscall.h"
#include "vm/page.h"
#endif
#ifdef VM
#include "vm/mmap.h"
#endif
/* Random value for struct thread's `magic' member.
@@ -71,7 +75,7 @@ static void kernel_thread (thread_func *, void *aux);
static void idle (void *aux UNUSED);
static struct thread *running_thread (void);
static struct thread *next_thread_to_run (void);
static void init_process_result (struct thread *t);
static bool init_process_result (struct thread *t);
static void init_thread (struct thread *, const char *name, int nice,
int priority, fp32_t recent_cpu);
static bool is_thread (struct thread *) UNUSED;
@@ -84,6 +88,10 @@ void thread_schedule_tail (struct thread *prev);
static tid_t allocate_tid (void);
static bool donor_priority_less (const struct list_elem *a_,
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
that's currently running into a thread. This can't work in
@@ -122,6 +130,13 @@ thread_init (void)
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. */
struct semaphore idle_started;
sema_init (&idle_started, 0);
@@ -241,11 +256,43 @@ thread_create (const char *name, int priority,
struct thread *parent_thread = thread_current ();
init_thread (t, name, parent_thread->nice, priority, parent_thread->recent_cpu);
tid = t->tid = allocate_tid ();
init_process_result (t);
if (!init_process_result (t))
{
palloc_free_page (t);
return TID_ERROR;
}
#ifdef USERPROG
hash_init (&t->open_files, fd_hash, fd_less, NULL);
#endif
#ifdef USERPROG
/* Initialize the thread's file descriptor table. */
t->fd_counter = MINIMUM_USER_FD;
bool success = hash_init (&t->open_files, fd_hash, fd_less, NULL);
if (success)
{
success = hash_init (&t->child_results, process_result_hash,
process_result_less, t);
if (!success)
hash_destroy (&t->open_files, NULL);
#ifdef VM
else
{
success = init_pages (&t->pages);
if (!success)
hash_destroy (&t->child_results, NULL);
}
#endif
}
if (!success)
{
palloc_free_page (t);
free (t->result);
return TID_ERROR;
}
#endif
#ifdef VM
mmap_init (t);
#endif
/* Prepare thread for first run by initializing its stack.
Do this atomically so intermediate values for the 'stack'
@@ -269,9 +316,7 @@ thread_create (const char *name, int priority,
intr_set_level (old_level);
/* No need to synchronise child_results since it is only ever accessed by one
thread. By the nature of increasing TIDs, this list is ordered. */
list_push_back (&parent_thread->child_results, &t->result->elem);
hash_insert (&parent_thread->child_results, &t->result->elem);
/* Add to run queue. */
thread_unblock (t);
@@ -373,7 +418,9 @@ thread_exit (void)
and schedule another process. That process will destroy us
when it calls thread_schedule_tail(). */
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;
schedule ();
NOT_REACHED ();
@@ -647,15 +694,18 @@ is_thread (struct thread *t)
}
/* Allocate and initialise a process result for given thread. */
static void
static bool
init_process_result (struct thread *t)
{
struct process_result *result = malloc (sizeof (struct process_result));
if (result == NULL)
return false;
result->tid = t->tid;
result->exit_status = t->exit_status;
result->exit_status = -1;
lock_init (&result->lock);
sema_init (&result->sema, 0);
t->result = result;
return true;
}
/* Does basic initialization of T as a blocked thread named
@@ -679,15 +729,13 @@ init_thread (struct thread *t, const char *name, int nice, int priority,
t->base_priority
= thread_mlfqs ? calculate_bsd_priority (recent_cpu, nice) : priority;
list_init (&t->donors_list);
list_push_back (&t->donors_list, &t->donor_elem);
t->waiting_lock = NULL;
t->nice = nice;
t->recent_cpu = recent_cpu;
t->priority = t->base_priority;
t->exit_status = -1;
list_init (&t->child_results);
old_level = intr_disable ();
list_push_back (&all_list, &t->allelem);
intr_set_level (old_level);
@@ -818,6 +866,29 @@ allocate_tid (void)
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'.
Used by switch.S, which can't figure it out on its own. */
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_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. */
struct process_result
{
@@ -41,7 +44,7 @@ struct process_result
struct lock lock; /* Lock the exit_status and sema. */
struct semaphore sema; /* Semaphore to signal the parent that the exit_status
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.
@@ -125,21 +128,29 @@ struct thread
/* Process wait properties. */
struct process_result *result; /* Result of the process. */
struct list child_results; /* List of children's of this thread
process results. */
struct hash child_results; /* Map of children's of this thread
TID to process result. */
struct file *exec_file; /* Thread's currently running file */
/* Shared between thread.c and synch.c. */
struct list_elem elem; /* List element. */
int exit_status; /* Exit Status: 0 = successful exit. */
struct hash pages; /* Table of open user pages. */
/* Memory mapped files for user virtual memory. */
struct hash mmap_files; /* List of memory mapped files. */
unsigned int mmap_counter; /* Counter for memory mapped files. */
#ifdef USERPROG
/* Owned by userprog/process.c. */
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
void *curr_esp;
/* Owned by thread.c. */
unsigned magic; /* Detects stack overflow. */
};

View File

@@ -1,7 +1,7 @@
# -*- makefile -*-
kernel.bin: DEFINES = -DUSERPROG -DFILESYS
KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys
kernel.bin: DEFINES = -DUSERPROG -DFILESYS -DVM
KERNEL_SUBDIRS = threads devices lib lib/kernel userprog filesys vm
TEST_SUBDIRS = tests/userprog tests/userprog/no-vm tests/filesys/base
GRADING_FILE = $(SRCDIR)/tests/userprog/Grading
SIMULATOR = --qemu

View File

@@ -1,9 +1,20 @@
#include "userprog/exception.h"
#include <inttypes.h>
#include <stdio.h>
#include "stdbool.h"
#include "userprog/gdt.h"
#include "threads/interrupt.h"
#include "threads/thread.h"
#ifdef VM
#include "vm/frame.h"
#include "vm/page.h"
#include "devices/swap.h"
#include "threads/vaddr.h"
#include "userprog/pagedir.h"
#endif
#define MAX_STACK_SIZE (8 * 1024 * 1024) // 8MB
#define MAX_STACK_OFFSET 32 // 32 bytes offset below stack pointer (ESP)
/* Number of page faults processed. */
static long long page_fault_cnt;
@@ -11,6 +22,10 @@ static long long page_fault_cnt;
static void kill (struct intr_frame *);
static void page_fault (struct intr_frame *);
static bool is_valid_stack_access (const void *fault_addr, const void *esp);
static bool grow_stack (void *upage);
bool fetch_page (void *upage, bool write);
/* Registers handlers for interrupts that can be caused by user
programs.
@@ -145,6 +160,34 @@ page_fault (struct intr_frame *f)
write = (f->error_code & PF_W) != 0;
user = (f->error_code & PF_U) != 0;
/* Select the appropriate stack pointer based on the context of the fault. */
void *esp = user ? f->esp : thread_current()->curr_esp;
/* If the fault address is in a user page that is not present, then it might
be just that the stack needs to grow or that it needs to be lazily loaded.
So we attempt to grow the stack. If this does not work, we check our SPT to
see if the page is expected to have data loaded in memory. */
void *upage = pg_round_down (fault_addr);
if (not_present && is_user_vaddr (upage) && upage != NULL)
{
if (fetch_page (upage, write))
return;
if (is_valid_stack_access (fault_addr, esp))
if (grow_stack (upage))
return;
}
/* If the page fault occurred in kernel mode, then we intentionally indicate
a fault (for get_user() etc). */
if (!user)
{
f->eip = (void *)f->eax;
f->eax = 0xffffffff;
return;
}
/* To implement virtual memory, delete the rest of the function
body, and replace it with code that brings in the page to
which fault_addr refers. */
@@ -156,3 +199,107 @@ page_fault (struct intr_frame *f)
kill (f);
}
/* Validates whether the fault address is a valid stack access. Access is a
valid stack access under the following two conditions:
1. The fault address must be within MAX_STACK_OFFSET (32) bytes below
the current stack pointer. (Accounts for both PUSH and PUSHA instructions)
2. Growing this stack to this address does not cause it to exceed the
MAX_STACK_SIZE (8MB) limit.
Returns true if both conditions are met, false otherwise.
Pre: fault_addr is a valid user virtual address (so also not NULL). */
static bool
is_valid_stack_access (const void *fault_addr, const void *esp)
{
uint32_t new_stack_size = PHYS_BASE - pg_round_down (fault_addr);
uint32_t *lowest_valid_push_addr = (uint32_t *)esp - MAX_STACK_OFFSET;
bool is_within_push_range = (uint32_t *)fault_addr >= lowest_valid_push_addr;
return is_within_push_range && new_stack_size <= MAX_STACK_SIZE;
}
/* Attempts to grow the stack by allocating and mapping a new page.
This involves:
1. Allocating a zeroed page from the user pool
2. Installing it into the page table with write permissions
Returns true if the stack was successfully grown, false if either
allocation or installation fails.
Pre: upage is a valid page-aligned address (so also not NULL). */
static bool
grow_stack (void *upage)
{
/* Allocate new page for stack */
void *new_page = frame_alloc (PAL_ZERO, upage, thread_current ());
if (new_page == NULL)
return false;
/* Install the page into user page table */
if (!pagedir_set_page (thread_current ()->pagedir, upage, new_page, true))
{
frame_free (new_page);
return false;
}
return true;
}
bool
fetch_page (void *upage, bool write)
{
/* Check if the page is in the supplemental page table. That is, it is a page
that is expected to be in memory. */
struct page_entry *page = page_get (thread_current (), upage);
if (page == NULL)
return false;
/* Check if the non-present user page is in the swap partition.
If so, swap it back into main memory, updating the PTE for
the faulted virtual address to point to the newly allocated
frame. */
struct thread *t = thread_current ();
if (page_in_swap (t, upage))
{
/* NOTE: This code should be refactored and moved into helper functions
within 'page.c'.*/
void *kpage = frame_alloc (0, upage, t);
lock_acquire (&page->lock);
size_t swap_slot = page_get_swap (t, upage);
swap_in (kpage, swap_slot);
lock_release (&page->lock);
bool writeable = pagedir_is_writable (t->pagedir, upage);
/* TODO: When this returns false we should quit the page fault,
but currently we continue and check the stack conditions in the
page fault handler. */
return pagedir_set_page (t->pagedir, upage, kpage, writeable);
}
/* An attempt to write to a non-writeable should fail. */
if (write && !page->writable)
return false;
/* Load the page into memory based on the type of data it is expecting. */
bool success = false;
switch (page->type) {
case PAGE_MMAP:
case PAGE_FILE:
case PAGE_SHARED:
success = page_load_file (page);
break;
default:
return false;
}
if (success && page->writable &&
!pagedir_is_writable(thread_current()->pagedir, upage))
pagedir_set_writable(thread_current()->pagedir, upage, true);
return success;
}

View File

@@ -1,6 +1,8 @@
#ifndef USERPROG_EXCEPTION_H
#define USERPROG_EXCEPTION_H
#include <stdbool.h>
/* Page fault error code bits that describe the cause of the exception. */
#define PF_P 0x1 /* 0: not-present page. 1: access rights violation. */
#define PF_W 0x2 /* 0: read, 1: write. */
@@ -8,5 +10,7 @@
void exception_init (void);
void exception_print_stats (void);
bool
try_fetch_page (void *upage, bool write);
#endif /* userprog/exception.h */

View File

@@ -2,12 +2,14 @@
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "devices/swap.h"
#include "threads/init.h"
#include "threads/pte.h"
#include "threads/palloc.h"
#include "vm/frame.h"
#include "vm/page.h"
static uint32_t *active_pd (void);
static void invalidate_pagedir (uint32_t *);
/* Creates a new page directory that has mappings for kernel
virtual addresses, but none for user virtual addresses.
@@ -40,8 +42,14 @@ pagedir_destroy (uint32_t *pd)
uint32_t *pte;
for (pte = pt; pte < pt + PGSIZE / sizeof *pte; pte++)
if (*pte & PTE_P)
palloc_free_page (pte_get_page (*pte));
{
if (page_is_shared_pte (pte))
continue;
else if (page_in_swap_pte (pte))
swap_drop (page_get_swap_pte (pte));
else if (*pte & PTE_P)
frame_free (pte_get_page (*pte));
}
palloc_free_page (pt);
}
palloc_free_page (pd);
@@ -53,7 +61,7 @@ pagedir_destroy (uint32_t *pd)
on CREATE. If CREATE is true, then a new page table is
created and a pointer into it is returned. Otherwise, a null
pointer is returned. */
static uint32_t *
uint32_t *
lookup_page (uint32_t *pd, const void *vaddr, bool create)
{
uint32_t *pt, *pde;
@@ -278,7 +286,7 @@ active_pd (void)
This function invalidates the TLB if PD is the active page
directory. (If PD is not active then its entries are not in
the TLB, so there is no need to invalidate anything.) */
static void
void
invalidate_pagedir (uint32_t *pd)
{
if (active_pd () == pd)

View File

@@ -6,6 +6,7 @@
uint32_t *pagedir_create (void);
void pagedir_destroy (uint32_t *pd);
uint32_t *lookup_page (uint32_t *pd, const void *vaddr, bool create);
bool pagedir_set_page (uint32_t *pd, void *upage, void *kpage, bool rw);
void *pagedir_get_page (uint32_t *pd, const void *upage);
void pagedir_clear_page (uint32_t *pd, void *upage);
@@ -16,5 +17,6 @@ void pagedir_set_accessed (uint32_t *pd, const void *upage, bool accessed);
bool pagedir_is_writable (uint32_t *pd, const void *upage);
void pagedir_set_writable (uint32_t *pd, const void *upage, bool writable);
void pagedir_activate (uint32_t *pd);
void invalidate_pagedir (uint32_t *pd);
#endif /* userprog/pagedir.h */

View File

@@ -1,5 +1,6 @@
#include "userprog/process.h"
#include <debug.h>
#include <hash.h>
#include <inttypes.h>
#include <list.h>
#include <round.h>
@@ -23,11 +24,20 @@
#include "threads/vaddr.h"
#include "threads/synch.h"
#include "devices/timer.h"
#include "vm/page.h"
#include "vm/mmap.h"
#ifdef VM
#include "vm/frame.h"
#endif
/* Defines the native number of bytes processed by the processor
(for the purposes of alignment). */
#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
within a linked list. */
struct arg_elem
@@ -40,16 +50,18 @@ struct arg_elem
that executes process_start for the purpose of starting a user process. */
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
successive calls to strtok_r to split 'cmd' into
tokens while maintaining state. */
char file_name[FNAME_MAX_LEN + 1]; /* Name of the file of the process to
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 void destruct_process_result (struct hash_elem *e, void *aux UNUSED);
static bool load (const char *cmdline, void (**eip) (void), void **esp);
/* Starts a new thread running a user program executed via
@@ -61,12 +73,7 @@ process_execute (const char *cmd)
{
char *cmd_copy;
tid_t tid;
struct process_start_data *data = malloc (sizeof (struct process_start_data));
if (data == NULL)
{
return TID_ERROR;
}
struct process_start_data data;
/* Make a copy of command.
Otherwise there's a race between the caller and load(). */
@@ -80,25 +87,41 @@ process_execute (const char *cmd)
/* Retrieve first argument of command, which is the file name
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 is a valid file */
if (filesys_open (file_name) == NULL)
/* 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
it running the function 'start_process' with the appropriate
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);
if (tid == TID_ERROR)
palloc_free_page (cmd_copy);
tid = thread_create (file_name, PRI_DEFAULT, start_process, &data);
/* 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;
}
static bool install_page (void *upage, void *kpage, bool writable);
static void *get_usr_kpage (enum palloc_flags flags, void *upage);
static void free_usr_kpage (void *kpage);
bool install_page (void *upage, void *kpage, bool writable);
static bool process_init_stack (char *cmd_saveptr, void **esp, char *file_name);
static void *push_to_stack (void **esp, void *data, size_t data_size);
#define push_var_to_stack(esp, var) (push_to_stack (esp, &var, sizeof (var)))
@@ -106,14 +129,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
command string used for processing, the saveptr used by strtok_r
(in order to further tokenize the same command and retrieve its
arguments), as well as the name of the file being executed. This
involves loading the specified file and starting it running. */
arguments), the name of the file being executed, and a semaphore that
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
start_process (void *proc_start_data)
{
struct intr_frame if_;
bool success;
struct process_start_data *data = proc_start_data;
/* Initialize interrupt frame and load executable. */
@@ -121,33 +145,46 @@ start_process (void *proc_start_data)
if_.gs = if_.fs = if_.es = if_.ds = if_.ss = SEL_UDSEG;
if_.cs = SEL_UCSEG;
if_.eflags = FLAG_IF | FLAG_MBS;
success = load (data->file_name, &if_.eip, &if_.esp);
/* If load failed, quit. */
if (!success)
{
palloc_free_page (data->cmd);
goto fail;
}
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock);
/* 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);
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);
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
interrupt, implemented by intr_exit (in
@@ -157,11 +194,6 @@ start_process (void *proc_start_data)
and jump to it. */
asm volatile ("movl %0, %%esp; jmp intr_exit" : : "g" (&if_) : "memory");
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
@@ -169,6 +201,10 @@ start_process (void *proc_start_data)
static bool
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.
This can't cause overflow due to enforcing that the size of
command line input must fit in a page. Also keep track
@@ -180,8 +216,12 @@ process_init_stack (char *cmd_saveptr, void **esp, char *file_name)
int arg_count = 0;
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));
/* Try to allocate memory for the argument pointer. */
struct arg_elem *arg_elem = malloc (sizeof (struct arg_elem));
if (arg_elem == NULL)
{
@@ -190,9 +230,11 @@ process_init_stack (char *cmd_saveptr, void **esp, char *file_name)
return false;
}
/* Store the argument pointer in the linked list. */
arg_elem->arg = *esp;
list_push_front (&arg_list, &arg_elem->elem);
/* Increment the argument count and get the next argument. */
arg_count++;
arg = strtok_r (NULL, " ", &cmd_saveptr);
}
@@ -208,13 +250,23 @@ process_init_stack (char *cmd_saveptr, void **esp, char *file_name)
+ return_addr_size;
/* If pushing the rest of the data required for the stack would cause
overflow, allocate an extra page that is contiguous within the
virtual address space (below the current address range). */
if (PHYS_BASE - *esp + remaining_size > PGSIZE)
overflow, allocate as many extra pages as needed to the user process
contiguously in the virtual address space below the initial page. */
int overflow_bytes = (PHYS_BASE - *esp) + remaining_size - PGSIZE;
if (overflow_bytes > 0)
{
uint8_t *kpage = palloc_get_page (PAL_USER | PAL_ZERO);
if (!install_page (((uint8_t *) PHYS_BASE) - PGSIZE * 2, kpage, true))
return false;
/* Calculate the number of pages needed to allocate. */
int pages_needed = DIV_CEIL (overflow_bytes, PGSIZE);
/* Allocate the pages and map them to the user process. */
void *upage;
uint8_t *kpage;
for (int i = 1; i < pages_needed + 1; i++)
{
upage = ((uint8_t *) PHYS_BASE) - PGSIZE * (i + 1);
kpage = get_usr_kpage (PAL_ZERO, upage);
if (!install_page (upage, kpage, true)) return false;
}
}
/* Align stack pointer to word size before pushing argv elements for
@@ -272,39 +324,35 @@ push_to_stack (void **esp, void *data, size_t data_size)
* This function will be implemented in task 2.
* For now, it does nothing. */
int
process_wait (tid_t child_tid UNUSED)
process_wait (tid_t child_tid)
{
struct process_result *child_result = NULL;
struct list_elem *e;
struct thread *cur = thread_current ();
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);
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)
struct thread *t = thread_current ();
struct process_result fake_result;
fake_result.tid = child_tid;
struct hash_elem *e = hash_find (&t->child_results, &fake_result.elem);
if (e == NULL)
return -1;
struct process_result *child_result
= hash_entry (e, struct process_result, elem);
/* Wait for child to die. */
sema_down (&child_result->sema);
/* 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
released in process_exit. */
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
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;
/* Release the lock */
lock_release (&child_result->lock);
/* Result no-longer used by parent, nor child. Deallocate it. */
free (child_result);
return exit_status;
}
@@ -316,50 +364,33 @@ process_exit (void)
struct thread *cur = thread_current ();
uint32_t *pd;
printf ("%s: exit(%d)\n", cur->name, cur->exit_status);
file_close (cur->exec_file);
/* Unmap all memory mapped files */
mmap_destroy ();
/* Clean up all open files */
hash_destroy (&cur->open_files, fd_cleanup);
/* Clean up the thread's supplemental page table. */
hash_destroy (&cur->pages, page_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)
{
lock_acquire (&cur->result->lock);
cur->result->exit_status = cur->exit_status;
/* Parent has died, child has to free the struct process_result * */
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);
}
printf ("%s: exit(%d)\n", cur->name, cur->result->exit_status);
/* Update own process result. */
destruct_process_result (&cur->result->elem, cur);
}
/* Free child process results or signal parent's death. */
struct list_elem *e;
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);
}
}
hash_destroy (&cur->child_results, destruct_process_result);
/* Destroy the current process's page directory and switch back
to the kernel-only page directory. */
@@ -379,6 +410,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
thread.
This function is called on every context switch. */
@@ -477,6 +530,7 @@ load (const char *file_name, void (**eip) (void), void **esp)
off_t file_ofs;
bool success = false;
int i;
lock_acquire (&filesys_lock);
/* Allocate and activate page directory. */
t->pagedir = pagedir_create ();
@@ -575,7 +629,10 @@ load (const char *file_name, void (**eip) (void), void **esp)
done:
/* We arrive here whether the load is successful or not. */
#ifndef VM
file_close (file);
#endif
lock_release (&filesys_lock);
return success;
}
@@ -642,58 +699,29 @@ validate_segment (const struct Elf32_Phdr *phdr, struct file *file)
or disk read error occurs. */
static bool
load_segment (struct file *file, off_t ofs, uint8_t *upage,
uint32_t read_bytes, uint32_t zero_bytes, bool writable)
uint32_t read_bytes, uint32_t zero_bytes, bool writable)
{
ASSERT ((read_bytes + zero_bytes) % PGSIZE == 0);
ASSERT (pg_ofs (upage) == 0);
ASSERT (ofs % PGSIZE == 0);
file_seek (file, ofs);
while (read_bytes > 0 || zero_bytes > 0)
while (read_bytes > 0 || zero_bytes > 0)
{
/* Calculate how to fill this page.
We will read PAGE_READ_BYTES bytes from FILE
and zero the final PAGE_ZERO_BYTES bytes. */
size_t page_read_bytes = read_bytes < PGSIZE ? read_bytes : PGSIZE;
size_t page_zero_bytes = PGSIZE - page_read_bytes;
/* Check if virtual page already allocated */
struct thread *t = thread_current ();
uint8_t *kpage = pagedir_get_page (t->pagedir, upage);
if (kpage == NULL){
/* Get a new page of memory. */
kpage = palloc_get_page (PAL_USER);
if (kpage == NULL){
return false;
}
/* Add the page to the process's address space. */
if (!install_page (upage, kpage, writable))
{
palloc_free_page (kpage);
return false;
}
} else {
/* Check if writable flag for the page should be updated */
if(writable && !pagedir_is_writable(t->pagedir, upage)){
pagedir_set_writable(t->pagedir, upage, writable);
}
}
/* Load data into the page. */
if (file_read (file, kpage, page_read_bytes) != (int) page_read_bytes){
return false;
}
memset (kpage + page_read_bytes, 0, page_zero_bytes);
/* Add the page metadata to the SPT to be lazy loaded later on */
if (page_insert_file (file, ofs, upage, page_read_bytes, page_zero_bytes,
writable, PAGE_FILE) == NULL)
return false;
/* Advance. */
read_bytes -= page_read_bytes;
zero_bytes -= page_zero_bytes;
ofs += PGSIZE;
upage += PGSIZE;
}
return true;
@@ -706,19 +734,54 @@ setup_stack (void **esp)
{
uint8_t *kpage;
bool success = false;
kpage = palloc_get_page (PAL_USER | PAL_ZERO);
void *upage = ((uint8_t *) PHYS_BASE) - PGSIZE;
kpage = get_usr_kpage (PAL_ZERO, upage);
if (kpage != NULL)
{
success = install_page (((uint8_t *) PHYS_BASE) - PGSIZE, kpage, true);
success = install_page (upage, kpage, true);
if (success)
*esp = PHYS_BASE;
else
palloc_free_page (kpage);
free_usr_kpage (kpage);
}
return success;
}
/* Claims a page from the user pool for ownership by the current thread
and returns its kernel address, updating the frame table if VM
is enabled. Requires the intended virtual address for where the page
will be installed. */
static void *
get_usr_kpage (enum palloc_flags flags, void *upage)
{
void *page;
#ifdef VM
struct thread *t = thread_current ();
if (pagedir_get_page (t->pagedir, upage) != NULL)
return NULL;
else
page = frame_alloc (flags, upage, t);
pagedir_set_accessed (t->pagedir, upage, true);
#else
page = palloc_get_page (flags | PAL_USER);
#endif
return page;
}
/* Frees a page belonging to a user process given its kernel address,
updating the frame table if VM is enabled. */
static void
free_usr_kpage (void *kpage)
{
#ifdef VM
frame_free (kpage);
#else
palloc_free_page (kpage);
#endif
}
/* Adds a mapping from user virtual address UPAGE to kernel
virtual address KPAGE to the page table.
If WRITABLE is true, the user process may modify the page;
@@ -728,7 +791,7 @@ setup_stack (void **esp)
with palloc_get_page().
Returns true on success, false if UPAGE is already mapped or
if memory allocation fails. */
static bool
bool
install_page (void *upage, void *kpage, bool writable)
{
struct thread *t = thread_current ();

View File

@@ -8,4 +8,6 @@ int process_wait (tid_t);
void process_exit (void);
void process_activate (void);
bool install_page (void *upage, void *kpage, bool writable);
#endif /* userprog/process.h */

View File

@@ -10,10 +10,16 @@
#include "threads/synch.h"
#include "userprog/process.h"
#include "userprog/pagedir.h"
#include "vm/frame.h"
#include "vm/page.h"
#include "vm/mmap.h"
#include <stdio.h>
#include <stdbool.h>
#include <syscall-nr.h>
static unsigned fd_counter = MIN_USER_FD;
#define MAX_SYSCALL_ARGS 3
#define EXIT_FAILURE -1
#define MMAP_FAILURE -1
struct open_file
{
@@ -43,20 +49,31 @@ static int syscall_write (int fd, const void *buffer, unsigned size);
static void syscall_seek (int fd, unsigned position);
static unsigned syscall_tell (int fd);
static void syscall_close (int fd);
static mapid_t syscall_mmap (int fd, void *addr);
static void syscall_munmap (mapid_t mapping);
static struct open_file *fd_get_file (int fd);
static void *validate_user_pointer (const void *ptr, size_t size);
static void validate_user_ptr (const void *start, size_t size,
bool write);
static void validate_and_pin_user_ptr (const void *start, size_t size,
bool write);
static void validate_and_pin_user_str (const char *ptr);
static void unpin_user_ptr (const void *start, size_t size);
static void unpin_user_str (const char *ptr);
static int get_user (const uint8_t *);
static bool put_user (uint8_t *, uint8_t);
/* A struct defining a syscall_function pointer along with its arity. */
typedef struct
struct syscall_arguments
{
syscall_function function; /* Function pointer. */
int arity; /* Number of arguments of the function. */
} syscall_arguments;
};
/* A look-up table mapping numbers to system call functions with their number of
arguments. */
static const syscall_arguments syscall_lookup[] =
static const struct syscall_arguments syscall_lookup[] =
{
[SYS_HALT] = {(syscall_function) syscall_halt, 0},
[SYS_EXIT] = {(syscall_function) syscall_exit, 1},
@@ -71,13 +88,14 @@ static const syscall_arguments syscall_lookup[] =
[SYS_SEEK] = {(syscall_function) syscall_seek, 2},
[SYS_TELL] = {(syscall_function) syscall_tell, 1},
[SYS_CLOSE] = {(syscall_function) syscall_close, 1},
[SYS_MMAP] = {(syscall_function) syscall_mmap, 2},
[SYS_MUNMAP] = {(syscall_function) syscall_munmap, 1}
};
/* The number of syscall functions (i.e, number of elements) within the
syscall_lookup table. */
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
synchronise all file access between processes. */
@@ -88,28 +106,29 @@ syscall_init (void)
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
high-level system call function, storing its output (if any) in f->eax */
static void
syscall_handler (struct intr_frame *f)
{
/* First, read the system call number from the stack. */
validate_user_pointer (f->esp, 1);
unsigned syscall_number = *(int *) f->esp;
validate_user_ptr (f->esp, sizeof (uintptr_t), false);
uintptr_t syscall_number = *(int *)f->esp;
thread_current ()->curr_esp = f->esp;
/* Ensures the number corresponds to a system call that can be handled. */
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. */
validate_user_pointer (f->esp + sizeof (uintptr_t),
syscall.arity * sizeof (uintptr_t));
uintptr_t args[3] = {0};
for (int i=0; i < syscall.arity; i++)
args[i] = *(uintptr_t *) (f->esp + sizeof (uintptr_t) * (i + 1));
validate_user_ptr (f->esp + sizeof (uintptr_t),
syscall.arity * sizeof (uintptr_t), false);
uintptr_t args[MAX_SYSCALL_ARGS] = { 0 };
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
there is a return value it is stored in f->eax. */
@@ -129,20 +148,18 @@ syscall_exit (int status)
{
/* Sets exit_status of the thread to status. thread_exit () will call
process_exit () if user programs are allowed. */
thread_current ()->exit_status = status;
thread_current ()->result->exit_status = status;
thread_exit ();
}
/* 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
syscall_exec (const char *cmd_line)
{
validate_user_pointer (cmd_line, 1);
pid_t pid = process_execute(cmd_line);
validate_and_pin_user_str (cmd_line);
pid_t pid = process_execute (cmd_line);
unpin_user_str (cmd_line);
return pid;
}
@@ -152,21 +169,25 @@ syscall_exec (const char *cmd_line)
static int
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
pointer. Acquires the file system lock to prevent synchronisation issues,
and then uses FILESYS_CREATE to create the file, returning the same status */
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_and_pin_user_str (file);
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock);
bool status = filesys_create (file, initial_size);
lock_release (&filesys_lock);
unpin_user_str (file);
/* Return the status of the file creation. */
return status;
}
@@ -176,12 +197,16 @@ syscall_create (const char *file UNUSED, unsigned initial_size UNUSED)
static bool
syscall_remove (const char *file)
{
validate_user_pointer (file, 1);
validate_and_pin_user_str (file);
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock);
bool status = filesys_remove (file);
lock_release (&filesys_lock);
unpin_user_str (file);
/* Return the status of the file removal. */
return status;
}
@@ -192,23 +217,33 @@ syscall_remove (const char *file)
static int
syscall_open (const char *file)
{
validate_user_pointer (file, 1);
validate_and_pin_user_str (file);
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock);
struct file *ptr = filesys_open (file);
lock_release (&filesys_lock);
unpin_user_str (file);
/* If the file could not be opened, return failure. */
if (ptr == NULL)
return -1;
return EXIT_FAILURE;
/* Allocate space for a struct representing a mapping from an FD to a struct
file. */
struct open_file *file_info
= (struct open_file*) malloc (sizeof (struct open_file));
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 */
file_info->fd = fd_counter++;
file_info->fd = thread_current ()->fd_counter++;
file_info->file = ptr;
/* Add the new FD->file mapping to the hashtable for the current thread */
@@ -224,14 +259,17 @@ syscall_open (const char *file)
static int
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);
if (file_info == NULL)
return -1;
return EXIT_FAILURE;
/* Acquire the file system lock to prevent any race conditions. */
lock_acquire (&filesys_lock);
int bytes = file_length (file_info->file);
lock_release (&filesys_lock);
/* Return the number of bytes in the file. */
return bytes;
}
@@ -244,31 +282,44 @@ syscall_read (int fd, void *buffer, unsigned size)
{
/* Only console (fd = 0) or other files, not including STDOUT, (fd > 1) are
allowed. */
if (fd < 0 || fd == STDOUT_FILENO)
return -1;
validate_user_pointer (buffer, size);
if (fd < STDIN_FILENO || fd == STDOUT_FILENO)
return EXIT_FAILURE;
if (fd == STDIN_FILENO)
{
/* Validate the user buffer. */
validate_user_ptr (buffer, size, true);
/* Reading from the console. */
char *write_buffer = buffer;
for (int i = 0; i < size; i++)
for (unsigned i = 0; i < size; i++)
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;
}
else
{
/* 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);
if (file_info == NULL)
return -1;
return EXIT_FAILURE;
/* Validate the user buffer, and pin the pages to prevent eviction. */
validate_and_pin_user_ptr (buffer, size, true);
/* Acquire the file system lock to prevent race-conditions. */
lock_acquire (&filesys_lock);
int bytes_written = file_read (file_info->file, buffer, size);
int bytes_read = file_read (file_info->file, buffer, size);
lock_release (&filesys_lock);
return bytes_written;
/* Unpin the pages to allow eviction. */
unpin_user_ptr (buffer, size);
/* Return the number of bytes read. */
return bytes_read;
}
}
@@ -284,26 +335,39 @@ syscall_write (int fd, const void *buffer, unsigned size)
if (fd <= 0)
return 0;
validate_user_pointer (buffer, size);
if (fd == STDOUT_FILENO)
{
/* Validate the user buffer. */
validate_user_ptr (buffer, size, false);
/* Writing to the console. */
putbuf (buffer, size);
/* In case of console, write is always successful. So return the size for
the number of bytes written. */
return size;
}
else
{
/* 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);
if (file_info == NULL)
return 0;
/* Validate the user buffer, and pin the pages to prevent eviction. */
validate_and_pin_user_ptr (buffer, size, false);
/* Acquire the file system lock to prevent race conditions. */
lock_acquire (&filesys_lock);
int bytes = file_write (file_info->file, buffer, size);
int bytes_written = file_write (file_info->file, buffer, size);
lock_release (&filesys_lock);
return bytes;
/* Unpin the pages to allow eviction. */
unpin_user_ptr (buffer, size);
/* Return the number of bytes written. */
return bytes_written;
}
}
@@ -314,13 +378,10 @@ syscall_write (int fd, const void *buffer, unsigned size)
static void
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);
if (file_info != NULL)
{
lock_acquire (&filesys_lock);
file_seek (file_info->file, position);
lock_release (&filesys_lock);
}
}
/* Handles the syscall for returning the next byte in a file referenced by
@@ -329,14 +390,14 @@ syscall_seek (int fd, unsigned position)
static unsigned
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);
if (file_info == NULL)
return 0;
lock_acquire (&filesys_lock);
unsigned pos = file_tell (file_info->file);
lock_release (&filesys_lock);
/* Return the current position in the file. */
return pos;
}
@@ -346,24 +407,113 @@ syscall_tell (int fd)
static void
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);
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);
/* Then, close the file, acquiring the file system lock to prevent race
conditions. */
lock_acquire (&filesys_lock);
file_close (file_info->file);
lock_release (&filesys_lock);
/* Free the memory allocated for the file_info struct. */
free (file_info);
}
}
/* Handles the syscall for memory mapping a file. */
static mapid_t
syscall_mmap (int fd, void *addr)
{
/* Ensure the FD is for a file in the filesystem (not STDIN or STDOUT). */
if (fd == STDOUT_FILENO || fd == STDIN_FILENO)
return MMAP_FAILURE;
/* Validate that there is a file associated with the given FD. */
struct open_file *file_info = fd_get_file (fd);
if (file_info == NULL)
return MMAP_FAILURE;
/* Ensure that the address is page-aligned and it's neither NULL nor zero. */
if (addr == 0 || addr == NULL || pg_ofs (addr) != 0)
return MMAP_FAILURE;
/* Reopen the file to obtain a separate and independent reference to the file
for the mapping. */
struct file *file = file_reopen (file_info->file);
if (file == NULL)
return MMAP_FAILURE;
/* Get the size of the file. Mmap fails if the file is empty. */
off_t file_size = file_length (file);
if (file_size == 0)
return MMAP_FAILURE;
/* ensures the page for mmap does not overlap with the stack */
if (addr >= (thread_current ()->curr_esp - PGSIZE))
return MMAP_FAILURE;
/* Check and ensure that there is enough space in the user virtual memory to
hold the entire file. */
for (off_t ofs = 0; ofs < file_size; ofs += PGSIZE)
{
if (page_get (thread_current (), addr + ofs) != NULL)
return MMAP_FAILURE;
}
/* Map the file data into the user virtual memory starting from addr. */
for (off_t ofs = 0; ofs < file_size; ofs += PGSIZE)
{
off_t read_bytes = file_size - ofs < PGSIZE ? file_size - ofs : PGSIZE;
off_t zero_bytes = PGSIZE - read_bytes;
if (page_insert_file (file, ofs, addr + ofs, read_bytes, zero_bytes, true,
PAGE_FILE) == NULL)
return MMAP_FAILURE;
}
/* Create a new mapping for the file. */
struct mmap_entry *mmap = mmap_insert (file, addr);
if (mmap == NULL)
return MMAP_FAILURE;
return mmap->mapping;
}
/* Handles the syscall for unmapping a memory mapped file.
Pre: mapping is a valid mapping identifier returned by mmap syscall. */
static void
syscall_munmap (mapid_t mapping)
{
/* Get the mmap entry from the mapping identifier. */
struct mmap_entry *mmap = mmap_get (mapping);
/* Delete the mmap entry from the hash table. */
hash_delete (&thread_current ()->mmap_files, &mmap->elem);
/* Unmap the mmap entry: free the pages and write back to the file if
necessary. NOTE. freeing and cleaning up is also handled by mmap_unmap. */
mmap_unmap (mmap);
}
/* Hashing function needed for the open_file table. Returns a hash for an entry,
based on its FD. */
unsigned
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
@@ -378,6 +528,20 @@ fd_less (const struct hash_elem *a_, const struct hash_elem *b_,
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
FD it returns NULL. */
static struct open_file *
@@ -397,18 +561,193 @@ fd_get_file (int fd)
return hash_entry (e, struct open_file, elem);
}
/* Validates if a block of memory starting at PTR and of size SIZE bytes is
fully contained within user virtual memory. Kills the thread (by calling
thread_exit) if the memory is invalid. Otherwise, returns the PTR given.
If the size is 0, the function does no checks and returns PTR.*/
static void *
validate_user_pointer (const void *ptr, size_t size)
/* Helper function that validates a block of memory and optionally pins frames.
thread_exit() if the memory is invalid. Used only by the two helper functions
validate_user_ptr and validate_and_pin_user_ptr. See the comments for those
functions for more details on each. */
static void
validate_user_ptr_helper (const void *start, size_t size, bool write, bool pin)
{
if (size > 0 && (ptr == NULL ||
!is_user_vaddr (ptr) ||
!is_user_vaddr (ptr + size - 1) ||
pagedir_get_page (thread_current()->pagedir, ptr) == NULL))
thread_exit ();
if (size == 0)
return;
return (void *) ptr;
/* ptr < ptr + size - 1, so sufficient to check that (ptr + size -1) is a
valid user virtual memory address. */
void *end = start + size - 1;
if (!is_user_vaddr (end))
syscall_exit (EXIT_FAILURE);
for (const void *ptr = pg_round_down (start); ptr <= end; ptr += PGSIZE)
{
int result;
/* Check read access to pointer. */
if ((result = get_user (ptr)) == -1)
syscall_exit (EXIT_FAILURE);
/* Check write access to pointer (if required). */
if (write && !put_user ((uint8_t *)ptr, result))
syscall_exit (EXIT_FAILURE);
/* If pin is set, pin the frame to prevent eviction. */
if (pin)
{
void *kpage = pagedir_get_page(thread_current()->pagedir, ptr);
if (kpage == NULL)
{
// If it was evicted, try to load it back in.
ptr -= PGSIZE;
continue;
}
frame_pin(kpage);
}
}
}
/* Validates if a block of memory starting at PTR and of size SIZE bytes is
fully contained within valid user virtual memory. thread_exit () if the
memory is invalid.
If the size is 0, the function does no checks and returns PTR. */
static void
validate_user_ptr (const void *start, size_t size, bool write)
{
validate_user_ptr_helper (start, size, write, false);
}
/* Validates if a block of memory starting at PTR and of size SIZE bytes is
fully contained within valid user virtual memory. thread_exit () if the
memory is invalid. The function also checks if the memory is writable if
WRITE flag is set.
The function attempts to preload the pages in case they are not in memory
yet (e.g., in a swap, lazy loading). If this is successful, the frame pages
are pinned to prevent eviction prior to access.
As such, a call to this function MUST be followed by a call to
unpin_user_ptr (START, SIZE) to unpin the pages and allow eviction.
If the size is 0, the function does no checks and returns PTR. */
static void
validate_and_pin_user_ptr (const void *start, size_t size, bool write)
{
validate_user_ptr_helper (start, size, write, true);
}
/* Unpins all the pages containing a block of memory starting at START and of
size SIZE bytes.
Pre: The pages were previously pinned by validate_and_pin_user_ptr (START,
SIZE). */
static void
unpin_user_ptr (const void *start, size_t size)
{
void *end = start + size - 1;
/* We don't need to do any checks as this function is always called after
validate_and_pin_user_ptr. */
/* Go through all pages in the block range, unpinning the frames. */
for (void *ptr = pg_round_down (start); ptr <= end; ptr += PGSIZE)
{
void *kpage = pagedir_get_page (thread_current ()->pagedir, ptr);
ASSERT (kpage != NULL);
frame_unpin (kpage);
}
}
/* Validates of a C-string starting at ptr is fully contained within valid
user virtual memory. thread_exit () if the memory is invalid. */
static void
validate_and_pin_user_str (const char *ptr)
{
size_t offset = (uintptr_t) ptr % PGSIZE;
for (;;)
{
if (!is_user_vaddr (ptr))
syscall_exit (EXIT_FAILURE);
if (get_user ((const uint8_t *)ptr) == -1)
syscall_exit (EXIT_FAILURE);
/* Pin the frame to prevent eviction. */
void *page = pg_round_down (ptr);
void *kpage = pagedir_get_page (thread_current ()->pagedir, page);
if (kpage == NULL)
{
// If it was evicted, attempt to reload.
ptr -= PGSIZE;
continue;
}
frame_pin (kpage);
while (offset < PGSIZE)
{
if (*ptr == '\0')
return; /* We reached the end of the string without issues. */
ptr++;
offset++;
}
offset = 0;
}
}
/* Unpins all the pages containing a C-string starting at PTR.
Pre: The pages were previously pinned by validate_and_pin_user_str (PTR).
PTR points to a valid C string that ends with '\0'. */
static void
unpin_user_str (const char *ptr)
{
size_t offset = (uintptr_t)ptr % PGSIZE;
const char *str_ptr = ptr;
for (;;)
{
void *page = pg_round_down(str_ptr);
void *kpage = pagedir_get_page(thread_current()->pagedir, page);
ASSERT(kpage != NULL);
frame_unpin (kpage);
/* Scan until end of string or page */
while (offset < PGSIZE)
{
if (*str_ptr == '\0')
return; /* Found end of string */
str_ptr++;
offset++;
}
offset = 0;
}
}
/* PROVIDED BY SPEC.
Reads a byte at user virtual address UADDR.
UADDR must be below PHYS_BASE.
Returns the byte value if successful, -1 if a segfault occurred. */
static int
get_user (const uint8_t *uaddr)
{
int result;
asm ("movl $1f, %0; movzbl %1, %0; 1:" : "=&a"(result) : "m"(*uaddr));
return result;
}
/* PROVIDED BY SPEC.
Writes BYTE to user address UDST.
UDST must be below PHYS_BASE.
Returns true if successful, false if a segfault occurred. */
static bool
put_user (uint8_t *udst, uint8_t byte)
{
int error_code;
asm ("movl $1f, %0; movb %b2, %1; 1:"
: "=&a"(error_code), "=m"(*udst)
: "q"(byte));
return error_code != -1;
}

View File

@@ -4,8 +4,6 @@
#include <hash.h>
#include "threads/synch.h"
#define MIN_USER_FD 2
typedef int pid_t;
struct lock filesys_lock;
@@ -14,5 +12,6 @@ void syscall_init (void);
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);
void fd_cleanup (struct hash_elem *e, void *aux);
#endif /* userprog/syscall.h */

401
src/vm/frame.c Normal file
View File

@@ -0,0 +1,401 @@
#include <debug.h>
#include <hash.h>
#include <list.h>
#include <string.h>
#include "frame.h"
#include "page.h"
#include "filesys/file.h"
#include "threads/malloc.h"
#include "threads/vaddr.h"
#include "userprog/pagedir.h"
#include "userprog/syscall.h"
#include "threads/synch.h"
/* Hash table that maps every active frame's kernel virtual address
to its corresponding 'frame_metadata'.*/
struct hash frame_table;
/* Linked list used to represent the circular queue in the 'clock'
algorithm for page eviction. Iterating from the element that is
currently pointed at by 'next_victim' yields an ordering of the entries
from oldest to newest (in terms of when they were added or checked
for having been referenced by a process). */
struct list lru_list;
/* The next element in lru_list to be considered for eviction (oldest added
or referenced page in the circular queue). If this page has has an
'accessed' bit of 0 when considering eviction, then it will be the next
victim. Otherwise, the next element in the queue is similarly considered. */
struct list_elem *next_victim = NULL;
/* Synchronisation variables. */
/* Protects access to 'lru_list'. */
struct lock lru_lock;
struct frame_metadata
{
void *frame; /* The kernel virtual address holding the frame. */
void *upage; /* The user virtual address pointing to the frame. */
struct list owners; /* List of threads that own the frame. */
bool pinned; /* Indicates wheter the frame should be
considered as an eviction candidate.*/
struct hash_elem hash_elem; /* Tracks the position of the frame metadata
within 'frame_table', whose key is the
kernel virtual address of the frame. */
struct list_elem list_elem; /* Tracks the position of the frame metadata
in either the 'active' or 'inactive' list,
so a victim can be chosen for eviction. */
};
hash_hash_func frame_metadata_hash;
hash_less_func frame_metadata_less;
static struct list_elem *lru_next (struct list_elem *e);
static struct list_elem *lru_prev (struct list_elem *e);
static struct frame_metadata *frame_metadata_get (void *frame);
static struct frame_metadata *get_victim (void);
static void free_owners (struct list *owners);
static struct frame_metadata *frame_metadata_find (void *frame);
/* Initialize the frame system by initializing the frame (hash) table with
the frame_metadata hashing and comparison functions, as well as initializing
'lru_list' and its associated synchronisation primitives. */
void
frame_init (void)
{
hash_init (&frame_table, frame_metadata_hash, frame_metadata_less, NULL);
list_init (&lru_list);
lock_init (&lru_lock);
}
/* TODO: Consider synchronisation more closely (i.e. just for hash
table). */
/* Attempt to allocate a frame for a user process, either by direct
allocation of a user page if there is sufficient RAM, or by
evicting a currently active page if memory allocated for user
processes is fulled and storing it in swap. If swap is full in
the former case, panic the kernel. */
void *
frame_alloc (enum palloc_flags flags, void *upage, struct thread *owner)
{
struct frame_metadata *frame_metadata;
flags |= PAL_USER;
lock_acquire (&lru_lock);
void *frame = palloc_get_page (flags);
/* If a frame couldn't be allocated we must be out of main memory. Thus,
obtain a victim page to replace with our page, and swap the victim
into disk. */
if (frame == NULL)
{
/* 1. Obtain victim. */
if (next_victim == NULL)
PANIC ("Couldn't allocate a single page to main memory!\n");
struct frame_metadata *victim = get_victim ();
ASSERT (victim != NULL); /* get_victim () should never return null. */
/* 2. Handle victim page writing based on its type. */
struct page_entry *victim_page = page_get (thread_current (), victim->upage);
if (victim_page != NULL && victim_page->type == PAGE_MMAP)
{
/* If it was a memory-mapped file page, we just write it back
to the file if it was dirty. */
if (pagedir_is_dirty(owner->pagedir, victim->upage))
{
lock_acquire (&filesys_lock);
file_write_at (victim_page->file, victim->upage,
victim_page->read_bytes, victim_page->offset);
lock_release (&filesys_lock);
}
}
else
{
/* Otherwise, insert the page into swap. */
page_insert_swapped (victim->upage, victim->frame, &victim->owners);
}
/* Free victim's owners. */
free_owners (&victim->owners);
/* If zero flag is set, zero out the victim page. */
if (flags & PAL_ZERO)
memset (victim->frame, 0, PGSIZE);
/* 3. Indicate that the new frame's metadata will be stored
inside the same structure that stored the victim's metadata.
As both the new frame and the victim frame share the same kernel
virtual address, the hash map need not be updated, and neither
the list_elem value as both share the same lru_list position. */
frame_metadata = victim;
}
/* If sufficient main memory allows the frame to be directly allocated,
we must update the frame table with a new entry, and grow lru_list. */
else
{
/* Must own lru_lock here, as otherwise there is a race condition
with next_victim either being NULL or uninitialized. */
frame_metadata = malloc (sizeof (struct frame_metadata));
if (frame_metadata == NULL)
PANIC ("Couldn't allocate memory for frame metadata!\n");
frame_metadata->frame = frame;
/* Newly allocated frames are pushed to the back of the circular queue
represented by lru_list. Must explicitly handle the case where the
circular queue is empty (when next_victim == NULL). */
if (next_victim == NULL)
{
list_push_back (&lru_list, &frame_metadata->list_elem);
next_victim = &frame_metadata->list_elem;
}
else
{
struct list_elem *lru_tail = lru_prev (next_victim);
list_insert (lru_tail, &frame_metadata->list_elem);
}
hash_insert (&frame_table, &frame_metadata->hash_elem);
}
struct frame_owner *frame_owner = malloc (sizeof (struct frame_owner));
if (frame_owner == NULL)
PANIC ("Couldn't allocate memory for frame owner!\n");
frame_owner->owner = owner;
list_init (&frame_metadata->owners);
list_push_back (&frame_metadata->owners, &frame_owner->elem);
frame_metadata->upage = upage;
frame_metadata->pinned = false;
lock_release (&lru_lock);
return frame_metadata->frame;
}
void
frame_pin (void *frame)
{
struct frame_metadata *frame_metadata = frame_metadata_get (frame);
if (frame_metadata == NULL)
PANIC ("Attempted to pin a frame at an unallocated kernel address '%p'\n",
frame);
frame_metadata->pinned = true;
}
void
frame_unpin (void *frame)
{
struct frame_metadata *frame_metadata = frame_metadata_get (frame);
if (frame_metadata == NULL)
PANIC ("Attempted to unpin a frame at an unallocated kernel address '%p'\n",
frame);
frame_metadata->pinned = false;
}
/* Attempt to deallocate a frame for a user process by removing it from the
frame table as well as lru_list, and freeing the underlying page
memory & metadata struct. Panics if the frame isn't active in memory. */
void
frame_free (void *frame)
{
struct frame_metadata *frame_metadata = frame_metadata_find (frame);
if (frame_metadata == NULL)
PANIC ("Attempted to free a frame at kernel address %p, "
"but this address is not allocated!\n",
frame);
free_owners (&frame_metadata->owners);
lock_acquire (&lru_lock);
hash_delete (&frame_table, &frame_metadata->hash_elem);
list_remove (&frame_metadata->list_elem);
/* If we're freeing the frame marked as the next victim, update
next_victim to either be the next least recently used page, or NULL
if no pages are loaded in main memory. */
if (&frame_metadata->list_elem == next_victim)
{
if (list_empty (&lru_list))
next_victim = NULL;
else
next_victim = lru_next (next_victim);
}
lock_release (&lru_lock);
free (frame_metadata);
palloc_free_page (frame);
}
/* Add a thread to a frame's frame_metadata owners list. */
bool
frame_owner_insert (void *frame, struct thread *owner)
{
struct frame_metadata *frame_metadata = frame_metadata_find (frame);
if (frame_metadata == NULL)
return false;
struct frame_owner *frame_owner = malloc (sizeof (struct frame_owner));
if (frame_owner == NULL)
return false;
frame_owner->owner = owner;
list_push_back (&frame_metadata->owners, &frame_owner->elem);
return true;
}
/* Remove and deallocate a frame owner from the frame_metadata owners list.
*/
void
frame_owner_remove (void *frame, struct thread *owner)
{
struct frame_metadata *frame_metadata = frame_metadata_find (frame);
if (frame_metadata == NULL)
PANIC ("Attempted to remove an owner from a frame at kernel "
"address %p, but this address is not allocated!\n",
frame);
struct list_elem *oe;
for (oe = list_begin (&frame_metadata->owners);
oe != list_end (&frame_metadata->owners);)
{
struct frame_owner *frame_owner
= list_entry (oe, struct frame_owner, elem);
oe = list_next (oe);
if (frame_owner->owner == owner)
{
list_remove (&frame_owner->elem);
free (frame_owner);
return;
}
}
NOT_REACHED ();
}
/* Find a frame_metadata entry in the frame table. */
static struct frame_metadata *
frame_metadata_find (void *frame)
{
struct frame_metadata key_metadata;
key_metadata.frame = frame;
struct hash_elem *e = hash_find (&frame_table, &key_metadata.hash_elem);
if (e == NULL)
return NULL;
return hash_entry (e, struct frame_metadata, hash_elem);
}
/* TODO: Account for page aliases when checking accessed bit. */
/* A pre-condition for calling this function is that the calling thread
owns lru_lock and that lru_list is non-empty. */
static struct frame_metadata *
get_victim (void)
{
struct list_elem *ve = next_victim;
struct frame_metadata *frame_metadata;
bool found = false;
while (!found)
{
frame_metadata = list_entry (ve, struct frame_metadata, list_elem);
ve = lru_next (ve);
struct list_elem *oe;
/* Skip pinned frames */
if (frame_metadata->pinned)
continue;
/* Returns once a frame that was not accessed by any owner is found. */
found = true;
for (oe = list_begin (&frame_metadata->owners);
oe != list_end (&frame_metadata->owners); oe = list_next (oe))
{
struct frame_owner *frame_owner
= list_entry (oe, struct frame_owner, elem);
uint32_t *pd = frame_owner->owner->pagedir;
void *upage = frame_metadata->upage;
if (pagedir_is_accessed (pd, upage))
{
found = false;
pagedir_set_accessed (pd, upage, false);
}
}
}
next_victim = ve;
return frame_metadata;
}
static void
free_owners (struct list *owners)
{
struct list_elem *oe;
for (oe = list_begin (owners); oe != list_end (owners);)
{
struct frame_owner *frame_owner
= list_entry (oe, struct frame_owner, elem);
oe = list_remove (oe);
free (frame_owner);
}
}
/* Hash function for frame metadata, used for storing entries in the
frame table. */
unsigned
frame_metadata_hash (const struct hash_elem *e, void *aux UNUSED)
{
struct frame_metadata *frame_metadata =
hash_entry (e, struct frame_metadata, hash_elem);
return hash_bytes (&frame_metadata->frame, sizeof (frame_metadata->frame));
}
/* 'less_func' comparison function for frame metadata, used for comparing
the keys of the frame table. Returns true iff the kernel virtual address
of the first frame is less than that of the second frame. */
bool
frame_metadata_less (const struct hash_elem *a_, const struct hash_elem *b_,
void *aux UNUSED)
{
struct frame_metadata *a =
hash_entry (a_, struct frame_metadata, hash_elem);
struct frame_metadata *b =
hash_entry (b_, struct frame_metadata, hash_elem);
return a->frame < b->frame;
}
static struct frame_metadata *
frame_metadata_get (void *frame)
{
struct frame_metadata key_metadata;
key_metadata.frame = frame;
struct hash_elem *e = hash_find (&frame_table, &key_metadata.hash_elem);
if (e == NULL) return NULL;
return hash_entry (e, struct frame_metadata, hash_elem);
}
/* Returns the next recently used element after the one provided, which
is achieved by iterating through lru_list like a circular queue
(wrapping around the list at the tail). */
static struct list_elem *
lru_next (struct list_elem *e)
{
if (!list_empty (&lru_list) && e == list_back (&lru_list))
return list_front (&lru_list);
return list_next (e);
}
/* Returns the previous recently used element after the one provided, which
is achieved by iterating through lru_list like a circular queue
(wrapping around the list at the head). */
static struct list_elem *
lru_prev (struct list_elem *e)
{
if (!list_empty (&lru_list) && e == list_front (&lru_list))
return list_back (&lru_list);
return list_prev (e);
}

22
src/vm/frame.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef VM_FRAME_H
#define VM_FRAME_H
#include "threads/thread.h"
#include "threads/palloc.h"
struct frame_owner
{
struct thread *owner; /* The thread that owns the frame. */
struct list_elem elem; /* List element for the list of owners. */
};
void frame_init (void);
void *frame_alloc (enum palloc_flags, void *, struct thread *);
void frame_pin (void *frame);
void frame_unpin (void *frame);
void frame_free (void *frame);
bool frame_owner_insert (void *frame, struct thread *owner);
void frame_owner_remove (void *frame, struct thread *owner);
#endif /* vm/frame.h */

129
src/vm/mmap.c Normal file
View File

@@ -0,0 +1,129 @@
#include "mmap.h"
#include "page.h"
#include "threads/thread.h"
#include "threads/vaddr.h"
#include "threads/malloc.h"
#include "userprog/syscall.h"
#include "userprog/pagedir.h"
#include <stdio.h>
static unsigned mmap_hash (const struct hash_elem *e, void *aux);
static bool mmap_less (const struct hash_elem *a_, const struct hash_elem *b_,
void *aux);
static void mmap_cleanup(struct hash_elem *e, void *aux);
/* Initializes the mmap table for the given thread, setting the mmap counter to
0 and initializing the hash table. */
bool
mmap_init (struct thread *t)
{
t->mmap_counter = 0;
return hash_init (&t->mmap_files, mmap_hash, mmap_less, NULL);
}
struct mmap_entry *
mmap_get (mapid_t mapping)
{
struct mmap_entry fake_mmap_entry;
fake_mmap_entry.mapping = mapping;
struct hash_elem *e
= hash_find (&thread_current ()->mmap_files, &fake_mmap_entry.elem);
if (e == NULL)
return NULL;
return hash_entry (e, struct mmap_entry, elem);
}
/* Inserts a new mmap entry into the mmap table for the current thread. Upage
is the start address of the file data in the user VM. */
struct mmap_entry *
mmap_insert (struct file *file, void *upage)
{
if (file == NULL || upage == NULL)
return NULL;
struct mmap_entry *mmap = malloc (sizeof (struct mmap_entry));
if (mmap == NULL)
return NULL;
mmap->mapping = thread_current ()->mmap_counter++;
mmap->file = file;
mmap->upage = upage;
hash_insert (&thread_current ()->mmap_files, &mmap->elem);
return mmap;
}
/* Unmaps the given mmap entry from the current thread's mmap table. */
void
mmap_unmap (struct mmap_entry *mmap)
{
if (mmap == NULL)
return;
/* Free all the pages associated with the mapping, writing back to the file
if necessary. */
off_t length = file_length (mmap->file);
for (off_t ofs = 0; ofs < length; ofs += PGSIZE)
{
void *upage = mmap->upage + ofs;
/* Get the SPT page entry for this page. */
struct page_entry *page = page_get(thread_current (), upage);
if (page == NULL)
continue;
/* Write the page back to the file if it is dirty. */
if (pagedir_is_dirty (thread_current ()->pagedir, upage))
{
lock_acquire (&filesys_lock);
file_write_at (mmap->file, upage, page->read_bytes, ofs);
lock_release (&filesys_lock);
}
/* Remove the page from the supplemental page table. */
hash_delete (&thread_current ()->pages, &page->elem);
}
file_close (mmap->file);
free (mmap);
}
/* Destroys the mmap table for the current thread. Frees all the memory
allocated for the mmap entries. */
void
mmap_destroy (void)
{
hash_destroy (&thread_current ()->mmap_files, mmap_cleanup);
}
/* A hash function for the mmap table. Returns a hash for an entry, based on its
mapping. */
static unsigned
mmap_hash (const struct hash_elem *e, void *aux UNUSED)
{
return hash_entry (e, struct mmap_entry, elem)->mapping;
}
/* A comparator function for the mmap table. Compares two entries based on their
mappings. */
static bool
mmap_less (const struct hash_elem *a_, const struct hash_elem *b_,
void *aux UNUSED)
{
const struct mmap_entry *a = hash_entry (a_, struct mmap_entry, elem);
const struct mmap_entry *b = hash_entry (b_, struct mmap_entry, elem);
return a->mapping < b->mapping;
}
/* Cleans up the mmap table for the current thread. Implicitly unmaps the mmap
entry, freeing pages and writing back to the file if necessary. */
static void
mmap_cleanup (struct hash_elem *e, void *aux UNUSED)
{
struct mmap_entry *mmap = hash_entry (e, struct mmap_entry, elem);
mmap_unmap (mmap);
}

27
src/vm/mmap.h Normal file
View File

@@ -0,0 +1,27 @@
#ifndef VM_MMAP_H
#define VM_MMAP_H
#include <hash.h>
#include "threads/thread.h"
#include "filesys/file.h"
/* A mapping identifier type. */
typedef unsigned mapid_t;
/* A structure to represent a memory mapped file. */
struct mmap_entry {
mapid_t mapping; /* The mapping identifier of the mapped file. */
struct file *file; /* A pointer to the file that is being mapped. */
void *upage; /* The start address of the file data in the user VM. */
struct hash_elem elem; /* An elem for the hash table. */
};
bool mmap_init (struct thread *t);
struct mmap_entry *mmap_get (mapid_t mapping);
struct mmap_entry *mmap_insert (struct file *file, void *upage);
void mmap_unmap (struct mmap_entry *mmap);
void mmap_umap_all (void);
void mmap_destroy (void);
#endif /* vm/mmap.h */

450
src/vm/page.c Normal file
View File

@@ -0,0 +1,450 @@
#include "page.h"
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include "filesys/file.h"
#include "threads/pte.h"
#include "threads/malloc.h"
#include "threads/palloc.h"
#include "threads/synch.h"
#include "devices/swap.h"
#include "userprog/process.h"
#include "userprog/pagedir.h"
#include "vm/frame.h"
#define SWAP_FLAG_BIT 9
#define SHARED_FLAG_BIT 10
#define ADDR_START_BIT 12
struct hash shared_file_pages;
struct lock shared_file_pages_lock;
static unsigned page_hash (const struct hash_elem *e, void *aux UNUSED);
static bool page_less (const struct hash_elem *a_, const struct hash_elem *b_,
void *aux UNUSED);
static void page_flag_shared (struct thread *owner, void *upage, bool shared);
static unsigned shared_file_page_hash (const struct hash_elem *e,
void *aux UNUSED);
static bool shared_file_page_less (const struct hash_elem *a_,
const struct hash_elem *b_,
void *aux UNUSED);
static struct shared_file_page *shared_file_page_get (struct file *file,
void *upage);
/* Initialise a supplementary page table. */
bool
init_pages (struct hash *pages)
{
ASSERT (pages != NULL);
return hash_init (pages, page_hash, page_less, NULL);
}
/* Hashing function needed for the SPT table. Returns a hash for an entry,
based on its upage. */
static unsigned
page_hash (const struct hash_elem *e, void *aux UNUSED)
{
struct page_entry *page = hash_entry (e, struct page_entry, elem);
return hash_ptr (page->upage);
}
/* Comparator function for the SPT table. Compares two entries based on their
upages. */
static bool
page_less (const struct hash_elem *a_, const struct hash_elem *b_,
void *aux UNUSED)
{
const struct page_entry *a = hash_entry (a_, struct page_entry, elem);
const struct page_entry *b = hash_entry (b_, struct page_entry, elem);
return a->upage < b->upage;
}
static void page_flag_swap (uint32_t *pte, bool set);
static void page_set_swap (struct thread *owner, uint32_t *pte,
size_t swap_slot);
// TODO: Deal with NULL malloc returns
/* Swap out 'owner' process's 'upage' stored at 'kpage'. Then, allocate and
insert a new page entry into the user process thread's SPT representing
this swapped out page. */
bool
page_insert_swapped (void *upage, void *kpage, struct list *owners)
{
struct file *exec_file = NULL;
struct list_elem *e;
for (e = list_begin (owners); e != list_end (owners); e = list_next (e))
{
struct thread *owner = list_entry (e, struct frame_owner, elem)->owner;
uint32_t *pte = lookup_page (owner->pagedir, upage, false);
if (exec_file != NULL || page_is_shared_pte (pte))
{
ASSERT (page_is_shared_pte (pte));
pagedir_clear_page (owner->pagedir, upage);
exec_file = owner->exec_file;
ASSERT (exec_file != NULL);
continue;
}
ASSERT (list_size (owners) == 1);
/* 1. Initialize swapped page entry. */
struct page_entry *page = page_get (owner, upage);
if (page == NULL)
{
page = malloc (sizeof (struct page_entry));
if (page == NULL)
return NULL;
page->upage = upage;
lock_init (&page->lock);
hash_insert (&owner->pages, &page->elem);
}
/* Mark page as 'swapped' and flag the page directory as having
been modified *before* eviction begins to prevent the owner of the
victim page from accessing/modifying it mid-eviction. */
/* TODO: We need to stop the process from destroying pagedir mid-eviction,
as this could render the page table entry invalid. */
page_flag_swap (pte, true);
lock_acquire (&page->lock);
pagedir_clear_page (owner->pagedir, upage);
size_t swap_slot = swap_out (kpage);
page_set_swap (owner, pte, swap_slot);
lock_release (&page->lock);
}
if (exec_file != NULL)
{
lock_acquire (&shared_file_pages_lock);
struct shared_file_page *sfp = shared_file_page_get (exec_file, upage);
sfp->frame = NULL;
sfp->swap_slot = swap_out (kpage);
lock_release (&shared_file_pages_lock);
}
return true;
}
/* Allocate and insert a new page entry into the user process thread's
SPT representing a file page. */
struct page_entry *
page_insert_file (struct file *file, off_t ofs, void *upage,
uint32_t read_bytes, uint32_t zero_bytes, bool writable,
enum page_type type)
{
/* If page exists, just update it. */
struct page_entry *existing = page_get (thread_current (), upage);
if (existing != NULL)
{
ASSERT (existing->read_bytes == read_bytes);
ASSERT (existing->zero_bytes == zero_bytes);
existing->writable = existing->writable || writable;
return existing;
}
struct page_entry *page = malloc(sizeof (struct page_entry));
if (page == NULL)
return NULL;
page->type = type;
page->file = file;
page->offset = ofs;
page->upage = upage;
page->read_bytes = read_bytes;
page->zero_bytes = zero_bytes;
page->writable = writable;
lock_init (&page->lock);
hash_insert (&thread_current ()->pages, &page->elem);
return page;
}
/* Gets a page_entry from the starting address of the page. Returns NULL if no
such page_entry exists in the hash map.*/
struct page_entry *
page_get (struct thread *thread, void *upage)
{
struct page_entry fake_page_entry;
fake_page_entry.upage = upage;
struct hash_elem *e
= hash_find (&thread->pages, &fake_page_entry.elem);
if (e == NULL)
return NULL;
return hash_entry (e, struct page_entry, elem);
}
bool
page_load_file (struct page_entry *page)
{
/* Allocate a frame for the page. If a frame allocation fails, then
frame_alloc should try to evict a page. If it is still NULL, the OS
panics as this should not happen if eviction is working correctly. */
struct thread *t = thread_current ();
bool shareable = !page->writable && file_compare (page->file, t->exec_file);
if (shareable)
{
lock_acquire (&shared_file_pages_lock);
struct shared_file_page *sfp
= shared_file_page_get (page->file, page->upage);
if (sfp != NULL)
{
/* Frame exists, just install it. */
if (sfp->frame != NULL)
{
if (!install_page (page->upage, sfp->frame, page->writable))
{
lock_release (&shared_file_pages_lock);
return false;
}
/* First time adding the shared page, so add thread as owner. */
if (page->type != PAGE_SHARED)
{
frame_owner_insert (sfp->frame, t);
}
}
/* Shared page is in swap. Load it. */
else
{
void *frame = frame_alloc (PAL_USER, page->upage, t);
if (frame == NULL)
PANIC (
"Could not allocate a frame to load page into memory.");
swap_in (frame, sfp->swap_slot);
if (!install_page (page->upage, frame, false))
{
frame_free (frame);
lock_release (&shared_file_pages_lock);
return false;
}
}
page_flag_shared (t, page->upage, true);
if (page->type != PAGE_SHARED)
{
sfp->ref_count++;
page->type = PAGE_SHARED;
}
lock_release (&shared_file_pages_lock);
return true;
}
}
void *frame = frame_alloc (PAL_USER, page->upage, t);
pagedir_set_accessed (t->pagedir, page->upage, true);
if (frame == NULL)
PANIC ("Could not allocate a frame to load page into memory.");
/* Map the page to the frame. */
if (!install_page (page->upage, frame, page->writable))
{
if (shareable)
lock_release (&shared_file_pages_lock);
frame_free (frame);
return false;
}
/* Move the file pointer to the correct location in the file. Then, read the
data from the file into the frame. Checks that we were able to read the
expected number of bytes. */
file_seek (page->file, page->offset);
if (file_read (page->file, frame, page->read_bytes) != (int) page->read_bytes)
{
if (shareable)
lock_release (&shared_file_pages_lock);
frame_free (frame);
return false;
}
/* Zero out the remaining bytes in the frame. */
memset (frame + page->read_bytes, 0, page->zero_bytes);
/* If file page is read-only, make it shared. */
if (shareable)
{
struct shared_file_page *sfp = malloc (sizeof (struct shared_file_page));
if (sfp == NULL)
{
lock_release (&shared_file_pages_lock);
frame_free (frame);
return false;
}
sfp->file = page->file;
sfp->upage = page->upage;
sfp->frame = frame;
sfp->swap_slot = 0;
sfp->ref_count = 1;
hash_insert (&shared_file_pages, &sfp->elem);
page_flag_shared (t, page->upage, true);
page->type = PAGE_SHARED;
lock_release (&shared_file_pages_lock);
}
/* Mark the page as loaded successfully. */
return true;
}
/* Function to clean up a page_entry. Given the elem of that page_entry, frees
the page_entry itself. */
void
page_cleanup (struct hash_elem *e, void *aux UNUSED)
{
struct page_entry *page = hash_entry (e, struct page_entry, elem);
if (page->type == PAGE_SHARED)
{
lock_acquire (&shared_file_pages_lock);
struct shared_file_page *sfp
= shared_file_page_get (page->file, page->upage);
ASSERT (sfp != NULL);
if (sfp->frame != NULL)
frame_owner_remove (sfp->frame, thread_current ());
sfp->ref_count--;
if (sfp->ref_count == 0)
{
hash_delete (&shared_file_pages, &sfp->elem);
if (sfp->frame != NULL)
frame_free (sfp->frame);
else
swap_drop (sfp->swap_slot);
free (sfp);
}
lock_release (&shared_file_pages_lock);
}
free (page);
}
/* Flags the provided page table entry as representing a swapped out page. */
void
page_flag_swap (uint32_t *pte, bool set)
{
if (set)
*pte |= (1 << SWAP_FLAG_BIT);
else
*pte &= ~(1 << SWAP_FLAG_BIT);
}
/* Sets the address bits of the page table entry to the provided swap slot
value. To be used for later retrieval of the swap slot when page faulting. */
static void
page_set_swap (struct thread *owner, uint32_t *pte, size_t swap_slot)
{
/* Store the provided swap slot in the address bits of the page table
entry, truncating excess bits. */
*pte |= (1 << SWAP_FLAG_BIT);
uint32_t swap_slot_bits = (swap_slot << ADDR_START_BIT) & PTE_ADDR;
*pte = (*pte & PTE_FLAGS) | swap_slot_bits;
invalidate_pagedir (owner->pagedir);
}
/* Returns true iff the page with user address 'upage' owned by 'owner'
is flagged to be in the swap disk via the owner's page table. */
bool
page_in_swap (struct thread *owner, void *upage)
{
uint32_t *pte = lookup_page (owner->pagedir, upage, false);
return page_in_swap_pte (pte);
}
/* Returns true iff the page table entry is marked to be in the swap disk. */
bool
page_in_swap_pte (uint32_t *pte)
{
return pte != NULL && (*pte & (1 << SWAP_FLAG_BIT)) != 0;
}
/* Given that the page with user address 'upage' owned by 'owner' is flagged
to be in the swap disk via the owner's page table, returns its stored
swap slot and marks the PTE as not being in swap. */
size_t
page_get_swap (struct thread *owner, void *upage)
{
uint32_t *pte = lookup_page (owner->pagedir, upage, false);
ASSERT (pte != NULL);
ASSERT ((*pte & PTE_P) == 0);
/* Masks the address bits and returns truncated value. */
page_flag_swap (pte, false);
return ((*pte & PTE_ADDR) >> ADDR_START_BIT);
}
/* Returns the swap slot stored in a PTE. */
size_t
page_get_swap_pte (uint32_t *pte)
{
ASSERT (pte != NULL);
ASSERT ((*pte & PTE_P) == 0);
return ((*pte & PTE_ADDR) >> ADDR_START_BIT);
}
/* Flags the provided page table entry as representing a shared page. */
static void
page_flag_shared (struct thread *owner, void *upage, bool shared)
{
uint32_t *pte = lookup_page (owner->pagedir, upage, false);
ASSERT (pte != NULL);
if (shared)
*pte |= (1 << SHARED_FLAG_BIT);
else
*pte &= ~(1 << SHARED_FLAG_BIT);
}
/* Returns true iff the page table entry is marked to be shared. */
bool
page_is_shared_pte (uint32_t *pte)
{
return pte != NULL && (*pte & (1 << SHARED_FLAG_BIT)) != 0;
}
/* Initializes the shared file pages hash table. */
void
shared_file_pages_init ()
{
if (!hash_init (&shared_file_pages, shared_file_page_hash,
shared_file_page_less, NULL))
PANIC ("Failed to initialize shared file pages hash table.");
lock_init (&shared_file_pages_lock);
}
/* Hash function for shared file pages, used for storing entries in the
shared file pages table. */
static unsigned
shared_file_page_hash (const struct hash_elem *e, void *aux UNUSED)
{
struct shared_file_page *sfp = hash_entry (e, struct shared_file_page, elem);
void *inode = file_get_inode (sfp->file);
void *upage = sfp->upage;
void *bytes[2] = { inode, upage };
return hash_bytes (bytes, sizeof (bytes));
}
/* 'less_func' comparison function for shared file pages, used for comparing
the keys of the shared file pages table. */
static bool
shared_file_page_less (const struct hash_elem *a_, const struct hash_elem *b_,
void *aux UNUSED)
{
const struct shared_file_page *a
= hash_entry (a_, struct shared_file_page, elem);
const struct shared_file_page *b
= hash_entry (b_, struct shared_file_page, elem);
return !file_compare (a->file, b->file) || a->upage < b->upage;
}
static struct shared_file_page *
shared_file_page_get (struct file *file, void *upage)
{
struct shared_file_page fake_sfp;
fake_sfp.file = file;
fake_sfp.upage = upage;
struct hash_elem *e = hash_find (&shared_file_pages, &fake_sfp.elem);
if (e == NULL)
return NULL;
return hash_entry (e, struct shared_file_page, elem);
}

64
src/vm/page.h Normal file
View File

@@ -0,0 +1,64 @@
#ifndef VM_PAGE_H
#define VM_PAGE_H
#include "threads/thread.h"
#include "threads/synch.h"
#include "filesys/off_t.h"
enum page_type
{
PAGE_FILE,
PAGE_MMAP,
PAGE_EMPTY,
PAGE_SHARED
};
struct page_entry
{
enum page_type type; /* Type of Data that should go into the page */
void *upage; /* Start Address of the User Page (Key of hash table). */
/* Data for swapped pages */
struct lock lock; /* Enforces mutual exclusion in accessing the page
referenced by the entry between its owning process
and any thread performing page eviction. */
/* File Data */
struct file *file; /* Pointer to the file for executables. */
off_t offset; /* Offset of the page content within the file. */
uint32_t read_bytes; /* Number of bytes to read within the page. */
uint32_t zero_bytes; /* Number of bytes to zero within the page. */
bool writable; /* Flag for whether this page is writable or not. */
struct hash_elem elem; /* An elem for the hash table. */
};
struct shared_file_page
{
struct file *file;
void *upage;
void *frame;
size_t swap_slot;
int ref_count;
struct hash_elem elem;
};
bool init_pages (struct hash *pages);
bool page_insert_swapped (void *upage, void *kpage, struct list *owners);
struct page_entry *page_insert_file (struct file *file, off_t ofs, void *upage,
uint32_t read_bytes, uint32_t zero_bytes,
bool writable, enum page_type);
struct page_entry *page_get (struct thread *thread, void *upage);
bool page_load_file (struct page_entry *page);
void page_cleanup (struct hash_elem *e, void *aux);
bool page_in_swap (struct thread *, void *);
bool page_in_swap_pte (uint32_t *pte);
size_t page_get_swap (struct thread *owner, void *upage);
size_t page_get_swap_pte (uint32_t *pte);
bool page_is_shared_pte (uint32_t *pte);
void shared_file_pages_init (void);
#endif

4
tests/devices/src/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
cscope.files
cscope.out
TAGS
tags

95
tests/devices/src/LICENSE Normal file
View File

@@ -0,0 +1,95 @@
PintOS, including its documentation, is subject to the following
license:
Copyright (C) 2004, 2005, 2006 Board of Trustees, Leland Stanford
Jr. University. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
A few individual files in PintOS were originally derived from other
projects, but they have been extensively modified for use in PintOS.
The original code falls under the original license, and modifications
for PintOS are additionally covered by the PintOS license above.
In particular, code derived from Nachos is subject to the following
license:
/* Copyright (c) 1992-1996 The Regents of the University of California.
All rights reserved.
Permission to use, copy, modify, and distribute this software
and its documentation for any purpose, without fee, and
without written agreement is hereby granted, provided that the
above copyright notice and the following two paragraphs appear
in all copies of this software.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO
ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE
AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA
HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS"
BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR
MODIFICATIONS.
*/
Also, code derived from MIT's 6.828 course code is subject to the
following license:
/*
* Copyright (C) 1997 Massachusetts Institute of Technology
*
* This software is being provided by the copyright holders under the
* following license. By obtaining, using and/or copying this software,
* you agree that you have read, understood, and will comply with the
* following terms and conditions:
*
* Permission to use, copy, modify, distribute, and sell this software
* and its documentation for any purpose and without fee or royalty is
* hereby granted, provided that the full text of this NOTICE appears on
* ALL copies of the software and documentation or portions thereof,
* including modifications, that you make.
*
* THIS SOFTWARE IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO
* REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF EXAMPLE,
* BUT NOT LIMITATION, COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR
* WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR
* THAT THE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY
* THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. COPYRIGHT
* HOLDERS WILL BEAR NO LIABILITY FOR ANY USE OF THIS SOFTWARE OR
* DOCUMENTATION.
*
* The name and trademarks of copyright holders may NOT be used in
* advertising or publicity pertaining to the software without specific,
* written prior permission. Title to copyright in this software and any
* associated documentation will at all times remain with copyright
* holders. See the file AUTHORS which should have accompanied this software
* for a list of all copyright holders.
*
* This file may be derived from previously copyrighted software. This
* copyright applies only to those changes made by the copyright
* holders listed in the AUTHORS file. The rest of this file is covered by
* the copyright notices, if any, listed below.
*/

View File

@@ -0,0 +1,68 @@
# -*- makefile -*-
SHELL = /bin/sh
VPATH = $(SRCDIR)
# Binary utilities.
# If the host appears to be x86, use the normal tools.
# If it's x86-64, use the compiler and linker in 32-bit mode.
# Otherwise assume cross-tools are installed as i386-elf-*.
X86 = i.86\|pentium.*\|[pk][56]\|nexgen\|viac3\|6x86\|athlon.*\|i86pc
X86_64 = x86_64
ifneq (0, $(shell expr `uname -m` : '$(X86)'))
CC = gcc
LD = ld
OBJCOPY = objcopy
else
ifneq (0, $(shell expr `uname -m` : '$(X86_64)'))
CC = gcc -m32
LD = ld -melf_i386
OBJCOPY = objcopy
else
CC = i386-elf-gcc
LD = i386-elf-ld
OBJCOPY = i386-elf-objcopy
endif
endif
# by default randomizing static libraries can be done using the host compiler
RANLIB = ranlib
# macOS: force compiling with the i686-elf cross compiler suite
UNAME_S = $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
CC = i686-elf-gcc
LD = i686-elf-ld
OBJCOPY = i686-elf-objcopy
RANLIB = i686-elf-ranlib
endif
ifeq ($(strip $(shell command -v $(CC) 2> /dev/null)),)
$(warning *** Compiler ($(CC)) not found. Did you set $$PATH properly? Please refer to the Getting Started section in the documentation for details. ***)
endif
# Compiler and assembler invocation.
DEFINES =
WARNINGS = -Wall -W -Wstrict-prototypes -Wmissing-prototypes -Wsystem-headers -Wno-frame-address
CFLAGS = -g -msoft-float -O -fno-omit-frame-pointer -ffreestanding -fno-pic -fcommon -mno-sse
CPPFLAGS = -nostdinc -I$(SRCDIR) -I$(SRCDIR)/lib
ASFLAGS = -Wa,--gstabs
LDFLAGS =
DEPS = -MMD -MF $(@:.o=.d)
# Turn off -fstack-protector, which we don't support.
ifeq ($(strip $(shell echo | $(CC) -fno-stack-protector -E - > /dev/null 2>&1; echo $$?)),0)
CFLAGS += -fno-stack-protector
endif
# Turn off --build-id in the linker, which confuses the PintOS loader.
ifeq ($(strip $(shell $(LD) --help | grep -q build-id; echo $$?)),0)
LDFLAGS += -Wl,--build-id=none
endif
%.o: %.c
$(CC) -c $< -o $@ $(CFLAGS) $(CPPFLAGS) $(WARNINGS) $(DEFINES) $(DEPS)
%.o: %.S
$(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES) $(DEPS)

View File

@@ -0,0 +1,29 @@
BUILD_SUBDIRS = devices threads userprog vm filesys
all::
@echo "Run 'make' in subdirectories: $(BUILD_SUBDIRS)."
@echo "This top-level make has only 'clean' targets."
CLEAN_SUBDIRS = $(BUILD_SUBDIRS) examples utils
clean::
for d in $(CLEAN_SUBDIRS); do $(MAKE) -C $$d $@; done
rm -f TAGS tags
distclean:: clean
find . -name '*~' -exec rm '{}' \;
TAGS_SUBDIRS = $(BUILD_SUBDIRS) devices lib
TAGS_SOURCES = find $(TAGS_SUBDIRS) -name \*.[chS] -print
TAGS::
etags --members `$(TAGS_SOURCES)`
tags::
ctags -T --no-warn `$(TAGS_SOURCES)`
cscope.files::
$(TAGS_SOURCES) > cscope.files
cscope:: cscope.files
cscope -b -q -k

View File

@@ -0,0 +1,111 @@
# -*- makefile -*-
SRCDIR = ../..
all: kernel.bin loader.bin
include ../../Make.config
include ../Make.vars
include ../../tests/Make.tests
# Compiler and assembler options.
kernel.bin: CPPFLAGS += -I$(SRCDIR)/lib/kernel
# Core kernel.
threads_SRC = threads/start.S # Startup code.
threads_SRC += threads/init.c # Main program.
threads_SRC += threads/thread.c # Thread management core.
threads_SRC += threads/switch.S # Thread switch routine.
threads_SRC += threads/interrupt.c # Interrupt core.
threads_SRC += threads/intr-stubs.S # Interrupt stubs.
threads_SRC += threads/synch.c # Synchronization.
threads_SRC += threads/palloc.c # Page allocator.
threads_SRC += threads/malloc.c # Subpage allocator.
# Device driver code.
devices_SRC = devices/pit.c # Programmable interrupt timer chip.
devices_SRC += devices/timer.c # Periodic timer device.
devices_SRC += devices/kbd.c # Keyboard device.
devices_SRC += devices/vga.c # Video device.
devices_SRC += devices/serial.c # Serial port device.
devices_SRC += devices/block.c # Block device abstraction layer.
devices_SRC += devices/partition.c # Partition block device.
devices_SRC += devices/ide.c # IDE disk block device.
devices_SRC += devices/input.c # Serial and keyboard input.
devices_SRC += devices/intq.c # Interrupt queue.
devices_SRC += devices/rtc.c # Real-time clock.
devices_SRC += devices/shutdown.c # Reboot and power off.
devices_SRC += devices/speaker.c # PC speaker.
# Library code shared between kernel and user programs.
lib_SRC = lib/debug.c # Debug helpers.
lib_SRC += lib/random.c # Pseudo-random numbers.
lib_SRC += lib/stdio.c # I/O library.
lib_SRC += lib/stdlib.c # Utility functions.
lib_SRC += lib/string.c # String functions.
lib_SRC += lib/arithmetic.c # 64-bit arithmetic for GCC.
lib_SRC += lib/ustar.c # Unix standard tar format utilities.
# Kernel-specific library code.
lib/kernel_SRC = lib/kernel/debug.c # Debug helpers.
lib/kernel_SRC += lib/kernel/list.c # Doubly-linked lists.
lib/kernel_SRC += lib/kernel/bitmap.c # Bitmaps.
lib/kernel_SRC += lib/kernel/hash.c # Hash tables.
lib/kernel_SRC += lib/kernel/console.c # printf(), putchar().
# User process code.
userprog_SRC = userprog/process.c # Process loading.
userprog_SRC += userprog/pagedir.c # Page directories.
userprog_SRC += userprog/exception.c # User exception handler.
userprog_SRC += userprog/syscall.c # System call handler.
userprog_SRC += userprog/gdt.c # GDT initialization.
userprog_SRC += userprog/tss.c # TSS management.
# Virtual memory code.
vm_SRC += vm/frame.c # Frame table manager.
vm_SRC += vm/page.c # Page table manager.
vm_SRC += devices/swap.c # Swap block manager.
# Filesystem code.
filesys_SRC = filesys/filesys.c # Filesystem core.
filesys_SRC += filesys/free-map.c # Free sector bitmap.
filesys_SRC += filesys/file.c # Files.
filesys_SRC += filesys/directory.c # Directories.
filesys_SRC += filesys/inode.c # File headers.
filesys_SRC += filesys/fsutil.c # Utilities.
SOURCES = $(foreach dir,$(KERNEL_SUBDIRS),$($(dir)_SRC))
OBJECTS = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(SOURCES)))
DEPENDS = $(patsubst %.o,%.d,$(OBJECTS))
threads/kernel.lds.s: CPPFLAGS += -P
threads/kernel.lds.s: threads/kernel.lds.S threads/loader.h
kernel.o: threads/kernel.lds.s $(OBJECTS)
$(LD) -T $< -o $@ $(OBJECTS)
kernel.bin: kernel.o
$(OBJCOPY) -R .note -R .comment -S $< $@
threads/loader.o: threads/loader.S
$(CC) -c $< -o $@ $(ASFLAGS) $(CPPFLAGS) $(DEFINES)
loader.bin: threads/loader.o loader.ld
$(LD) -N -e 0 -T $(word 2, $^) -o $@ $<
os.dsk: kernel.bin
cat $^ > $@
clean::
rm -f $(OBJECTS) $(DEPENDS)
rm -f threads/loader.o threads/kernel.lds.s threads/loader.d
rm -f kernel.bin.tmp
rm -f kernel.o kernel.lds.s
rm -f kernel.bin loader.bin
rm -f bochsout.txt bochsrc.txt
rm -f results grade
Makefile: $(SRCDIR)/Makefile.build
cp $< $@
-include $(DEPENDS)

View File

@@ -0,0 +1,20 @@
# -*- makefile -*-
all:
include Make.vars
DIRS = $(sort $(addprefix build/,$(KERNEL_SUBDIRS) $(TEST_SUBDIRS) lib/user))
all grade check: $(DIRS) build/Makefile
cd build && $(MAKE) $@
$(DIRS):
mkdir -p $@
build/Makefile: ../Makefile.build
cp $< $@
build/%: $(DIRS) build/Makefile
cd build && $(MAKE) $*
clean:
rm -rf build

View File

@@ -0,0 +1,52 @@
# -*- makefile -*-
$(PROGS): CPPFLAGS += -I$(SRCDIR)/lib/user -I.
# Linker flags.
$(PROGS): LDFLAGS += -nostdlib -static -Wl,-T,$(LDSCRIPT)
$(PROGS): LDSCRIPT = $(SRCDIR)/lib/user/user.lds
# Library code shared between kernel and user programs.
lib_SRC = lib/debug.c # Debug code.
lib_SRC += lib/random.c # Pseudo-random numbers.
lib_SRC += lib/stdio.c # I/O library.
lib_SRC += lib/stdlib.c # Utility functions.
lib_SRC += lib/string.c # String functions.
lib_SRC += lib/arithmetic.c # 64-bit arithmetic for GCC.
lib_SRC += lib/ustar.c # Unix standard tar format utilities.
# User level only library code.
lib/user_SRC = lib/user/debug.c # Debug helpers.
lib/user_SRC += lib/user/syscall.c # System calls.
lib/user_SRC += lib/user/console.c # Console code.
LIB_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(lib_SRC) $(lib/user_SRC)))
LIB_DEP = $(patsubst %.o,%.d,$(LIB_OBJ))
LIB = lib/user/entry.o libc.a
PROGS_SRC = $(foreach prog,$(PROGS),$($(prog)_SRC))
PROGS_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$(PROGS_SRC)))
PROGS_DEP = $(patsubst %.o,%.d,$(PROGS_OBJ))
all: $(PROGS)
define TEMPLATE
$(1)_OBJ = $(patsubst %.c,%.o,$(patsubst %.S,%.o,$($(1)_SRC)))
$(1): $$($(1)_OBJ) $$(LIB) $$(LDSCRIPT)
$$(CC) $$(LDFLAGS) $$($(1)_OBJ) $$(LIB) -o $$@
endef
$(foreach prog,$(PROGS),$(eval $(call TEMPLATE,$(prog))))
libc.a: $(LIB_OBJ)
rm -f $@
ar r $@ $^
$(RANLIB) $@
clean::
rm -f $(PROGS) $(PROGS_OBJ) $(PROGS_DEP)
rm -f $(LIB_DEP) $(LIB_OBJ) lib/user/entry.[do] libc.a
.PHONY: all clean
-include $(LIB_DEP) $(PROGS_DEP)

3
tests/devices/src/devices/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
build
bochsrc.txt
bochsout.txt

View File

@@ -0,0 +1,7 @@
# -*- makefile -*-
kernel.bin: DEFINES =
KERNEL_SUBDIRS = threads devices lib lib/kernel $(TEST_SUBDIRS)
TEST_SUBDIRS = tests/devices
GRADING_FILE = $(SRCDIR)/tests/devices/Grading
SIMULATOR = --qemu

View File

@@ -0,0 +1 @@
include ../Makefile.kernel

View File

@@ -0,0 +1,223 @@
#include "devices/block.h"
#include <list.h>
#include <string.h>
#include <stdio.h>
#include "devices/ide.h"
#include "threads/malloc.h"
/* A block device. */
struct block
{
struct list_elem list_elem; /* Element in all_blocks. */
char name[16]; /* Block device name. */
enum block_type type; /* Type of block device. */
block_sector_t size; /* Size in sectors. */
const struct block_operations *ops; /* Driver operations. */
void *aux; /* Extra data owned by driver. */
unsigned long long read_cnt; /* Number of sectors read. */
unsigned long long write_cnt; /* Number of sectors written. */
};
/* List of all block devices. */
static struct list all_blocks = LIST_INITIALIZER (all_blocks);
/* The block block assigned to each PintOS role. */
static struct block *block_by_role[BLOCK_ROLE_CNT];
static struct block *list_elem_to_block (struct list_elem *);
/* Returns a human-readable name for the given block device
TYPE. */
const char *
block_type_name (enum block_type type)
{
static const char *block_type_names[BLOCK_CNT] =
{
"kernel",
"filesys",
"scratch",
"swap",
"raw",
"foreign",
};
ASSERT (type < BLOCK_CNT);
return block_type_names[type];
}
/* Returns the block device fulfilling the given ROLE, or a null
pointer if no block device has been assigned that role. */
struct block *
block_get_role (enum block_type role)
{
ASSERT (role < BLOCK_ROLE_CNT);
return block_by_role[role];
}
/* Assigns BLOCK the given ROLE. */
void
block_set_role (enum block_type role, struct block *block)
{
ASSERT (role < BLOCK_ROLE_CNT);
block_by_role[role] = block;
}
/* Returns the first block device in kernel probe order, or a
null pointer if no block devices are registered. */
struct block *
block_first (void)
{
return list_elem_to_block (list_begin (&all_blocks));
}
/* Returns the block device following BLOCK in kernel probe
order, or a null pointer if BLOCK is the last block device. */
struct block *
block_next (struct block *block)
{
return list_elem_to_block (list_next (&block->list_elem));
}
/* Returns the block device with the given NAME, or a null
pointer if no block device has that name. */
struct block *
block_get_by_name (const char *name)
{
struct list_elem *e;
for (e = list_begin (&all_blocks); e != list_end (&all_blocks);
e = list_next (e))
{
struct block *block = list_entry (e, struct block, list_elem);
if (!strcmp (name, block->name))
return block;
}
return NULL;
}
/* Verifies that SECTOR is a valid offset within BLOCK.
Panics if not. */
static void
check_sector (struct block *block, block_sector_t sector)
{
if (sector >= block->size)
{
/* We do not use ASSERT because we want to panic here
regardless of whether NDEBUG is defined. */
PANIC ("Access past end of device %s (sector=%"PRDSNu", "
"size=%"PRDSNu")\n", block_name (block), sector, block->size);
}
}
/* Reads sector SECTOR from BLOCK into BUFFER, which must
have room for BLOCK_SECTOR_SIZE bytes.
Internally synchronizes accesses to block devices, so external
per-block device locking is unneeded. */
void
block_read (struct block *block, block_sector_t sector, void *buffer)
{
check_sector (block, sector);
block->ops->read (block->aux, sector, buffer);
block->read_cnt++;
}
/* Write sector SECTOR to BLOCK from BUFFER, which must contain
BLOCK_SECTOR_SIZE bytes. Returns after the block device has
acknowledged receiving the data.
Internally synchronizes accesses to block devices, so external
per-block device locking is unneeded. */
void
block_write (struct block *block, block_sector_t sector, const void *buffer)
{
check_sector (block, sector);
ASSERT (block->type != BLOCK_FOREIGN);
block->ops->write (block->aux, sector, buffer);
block->write_cnt++;
}
/* Returns the number of sectors in BLOCK. */
block_sector_t
block_size (struct block *block)
{
return block->size;
}
/* Returns BLOCK's name (e.g. "hda"). */
const char *
block_name (struct block *block)
{
return block->name;
}
/* Returns BLOCK's type. */
enum block_type
block_type (struct block *block)
{
return block->type;
}
/* Prints statistics for each block device used for a PintOS role. */
void
block_print_stats (void)
{
int i;
for (i = 0; i < BLOCK_ROLE_CNT; i++)
{
struct block *block = block_by_role[i];
if (block != NULL)
{
printf ("%s (%s): %llu reads, %llu writes\n",
block->name, block_type_name (block->type),
block->read_cnt, block->write_cnt);
}
}
}
/* Registers a new block device with the given NAME. If
EXTRA_INFO is non-null, it is printed as part of a user
message. The block device's SIZE in sectors and its TYPE must
be provided, as well as the it operation functions OPS, which
will be passed AUX in each function call. */
struct block *
block_register (const char *name, enum block_type type,
const char *extra_info, block_sector_t size,
const struct block_operations *ops, void *aux)
{
struct block *block = malloc (sizeof *block);
if (block == NULL)
PANIC ("Failed to allocate memory for block device descriptor");
list_push_back (&all_blocks, &block->list_elem);
strlcpy (block->name, name, sizeof block->name);
block->type = type;
block->size = size;
block->ops = ops;
block->aux = aux;
block->read_cnt = 0;
block->write_cnt = 0;
printf ("%s: %'"PRDSNu" sectors (", block->name, block->size);
print_human_readable_size ((uint64_t) block->size * BLOCK_SECTOR_SIZE);
printf (")");
if (extra_info != NULL)
printf (", %s", extra_info);
printf ("\n");
return block;
}
/* Returns the block device corresponding to LIST_ELEM, or a null
pointer if LIST_ELEM is the list end of all_blocks. */
static struct block *
list_elem_to_block (struct list_elem *list_elem)
{
return (list_elem != list_end (&all_blocks)
? list_entry (list_elem, struct block, list_elem)
: NULL);
}

View File

@@ -0,0 +1,74 @@
#ifndef DEVICES_BLOCK_H
#define DEVICES_BLOCK_H
#include <stddef.h>
#include <inttypes.h>
/* Size of a block device sector in bytes.
All IDE disks use this sector size, as do most USB and SCSI
disks. It's not worth it to try to cater to other sector
sizes in PintOS (yet). */
#define BLOCK_SECTOR_SIZE 512
/* Index of a block device sector.
Good enough for devices up to 2 TB. */
typedef uint32_t block_sector_t;
/* Format specifier for printf(), e.g.:
printf ("sector=%"PRDSNu"\n", sector); */
#define PRDSNu PRIu32
/* Higher-level interface for file systems, etc. */
struct block;
/* Type of a block device. */
enum block_type
{
/* Block device types that play a role in PintOS. */
BLOCK_KERNEL, /* PintOS OS kernel. */
BLOCK_FILESYS, /* File system. */
BLOCK_SCRATCH, /* Scratch. */
BLOCK_SWAP, /* Swap. */
BLOCK_ROLE_CNT,
/* Other kinds of block devices that PintOS may see but does
not interact with. */
BLOCK_RAW = BLOCK_ROLE_CNT, /* "Raw" device with unidentified contents. */
BLOCK_FOREIGN, /* Owned by non-PintOS operating system. */
BLOCK_CNT /* Number of PintOS block types. */
};
const char *block_type_name (enum block_type);
/* Finding block devices. */
struct block *block_get_role (enum block_type);
void block_set_role (enum block_type, struct block *);
struct block *block_get_by_name (const char *name);
struct block *block_first (void);
struct block *block_next (struct block *);
/* Block device operations. */
block_sector_t block_size (struct block *);
void block_read (struct block *, block_sector_t, void *);
void block_write (struct block *, block_sector_t, const void *);
const char *block_name (struct block *);
enum block_type block_type (struct block *);
/* Statistics. */
void block_print_stats (void);
/* Lower-level interface to block device drivers. */
struct block_operations
{
void (*read) (void *aux, block_sector_t, void *buffer);
void (*write) (void *aux, block_sector_t, const void *buffer);
};
struct block *block_register (const char *name, enum block_type,
const char *extra_info, block_sector_t size,
const struct block_operations *, void *aux);
#endif /* devices/block.h */

View File

@@ -0,0 +1,527 @@
#include "devices/ide.h"
#include <ctype.h>
#include <debug.h>
#include <stdbool.h>
#include <stdio.h>
#include "devices/block.h"
#include "devices/partition.h"
#include "devices/timer.h"
#include "threads/io.h"
#include "threads/interrupt.h"
#include "threads/synch.h"
/* The code in this file is an interface to an ATA (IDE)
controller. It attempts to comply to [ATA-3]. */
/* ATA command block port addresses. */
#define reg_data(CHANNEL) ((CHANNEL)->reg_base + 0) /* Data. */
#define reg_error(CHANNEL) ((CHANNEL)->reg_base + 1) /* Error. */
#define reg_nsect(CHANNEL) ((CHANNEL)->reg_base + 2) /* Sector Count. */
#define reg_lbal(CHANNEL) ((CHANNEL)->reg_base + 3) /* LBA 0:7. */
#define reg_lbam(CHANNEL) ((CHANNEL)->reg_base + 4) /* LBA 15:8. */
#define reg_lbah(CHANNEL) ((CHANNEL)->reg_base + 5) /* LBA 23:16. */
#define reg_device(CHANNEL) ((CHANNEL)->reg_base + 6) /* Device/LBA 27:24. */
#define reg_status(CHANNEL) ((CHANNEL)->reg_base + 7) /* Status (r/o). */
#define reg_command(CHANNEL) reg_status (CHANNEL) /* Command (w/o). */
/* ATA control block port addresses.
(If we supported non-legacy ATA controllers this would not be
flexible enough, but it's fine for what we do.) */
#define reg_ctl(CHANNEL) ((CHANNEL)->reg_base + 0x206) /* Control (w/o). */
#define reg_alt_status(CHANNEL) reg_ctl (CHANNEL) /* Alt Status (r/o). */
/* Alternate Status Register bits. */
#define STA_BSY 0x80 /* Busy. */
#define STA_DRDY 0x40 /* Device Ready. */
#define STA_DRQ 0x08 /* Data Request. */
/* Control Register bits. */
#define CTL_SRST 0x04 /* Software Reset. */
/* Device Register bits. */
#define DEV_MBS 0xa0 /* Must be set. */
#define DEV_LBA 0x40 /* Linear based addressing. */
#define DEV_DEV 0x10 /* Select device: 0=master, 1=slave. */
/* Commands.
Many more are defined but this is the small subset that we
use. */
#define CMD_IDENTIFY_DEVICE 0xec /* IDENTIFY DEVICE. */
#define CMD_READ_SECTOR_RETRY 0x20 /* READ SECTOR with retries. */
#define CMD_WRITE_SECTOR_RETRY 0x30 /* WRITE SECTOR with retries. */
/* An ATA device. */
struct ata_disk
{
char name[8]; /* Name, e.g. "hda". */
struct channel *channel; /* Channel that disk is attached to. */
int dev_no; /* Device 0 or 1 for master or slave. */
bool is_ata; /* Is device an ATA disk? */
};
/* An ATA channel (aka controller).
Each channel can control up to two disks. */
struct channel
{
char name[8]; /* Name, e.g. "ide0". */
uint16_t reg_base; /* Base I/O port. */
uint8_t irq; /* Interrupt in use. */
struct lock lock; /* Must acquire to access the controller. */
bool expecting_interrupt; /* True if an interrupt is expected, false if
any interrupt would be spurious. */
struct semaphore completion_wait; /* Up'd by interrupt handler. */
struct ata_disk devices[2]; /* The devices on this channel. */
};
/* We support the two "legacy" ATA channels found in a standard PC. */
#define CHANNEL_CNT 2
static struct channel channels[CHANNEL_CNT];
static struct block_operations ide_operations;
static void reset_channel (struct channel *);
static bool check_device_type (struct ata_disk *);
static void identify_ata_device (struct ata_disk *);
static void select_sector (struct ata_disk *, block_sector_t);
static void issue_pio_command (struct channel *, uint8_t command);
static void input_sector (struct channel *, void *);
static void output_sector (struct channel *, const void *);
static void wait_until_idle (const struct ata_disk *);
static bool wait_while_busy (const struct ata_disk *);
static void select_device (const struct ata_disk *);
static void select_device_wait (const struct ata_disk *);
static void interrupt_handler (struct intr_frame *);
/* Initialize the disk subsystem and detect disks. */
void
ide_init (void)
{
size_t chan_no;
for (chan_no = 0; chan_no < CHANNEL_CNT; chan_no++)
{
struct channel *c = &channels[chan_no];
int dev_no;
/* Initialize channel. */
snprintf (c->name, sizeof c->name, "ide%zu", chan_no);
switch (chan_no)
{
case 0:
c->reg_base = 0x1f0;
c->irq = 14 + 0x20;
break;
case 1:
c->reg_base = 0x170;
c->irq = 15 + 0x20;
break;
default:
NOT_REACHED ();
}
lock_init (&c->lock);
c->expecting_interrupt = false;
sema_init (&c->completion_wait, 0);
/* Initialize devices. */
for (dev_no = 0; dev_no < 2; dev_no++)
{
struct ata_disk *d = &c->devices[dev_no];
snprintf (d->name, sizeof d->name,
"hd%c", 'a' + chan_no * 2 + dev_no);
d->channel = c;
d->dev_no = dev_no;
d->is_ata = false;
}
/* Register interrupt handler. */
intr_register_ext (c->irq, interrupt_handler, c->name);
/* Reset hardware. */
reset_channel (c);
/* Distinguish ATA hard disks from other devices. */
if (check_device_type (&c->devices[0]))
check_device_type (&c->devices[1]);
/* Read hard disk identity information. */
for (dev_no = 0; dev_no < 2; dev_no++)
if (c->devices[dev_no].is_ata)
identify_ata_device (&c->devices[dev_no]);
}
}
/* Disk detection and identification. */
static char *descramble_ata_string (char *, int size);
/* Resets an ATA channel and waits for any devices present on it
to finish the reset. */
static void
reset_channel (struct channel *c)
{
bool present[2];
int dev_no;
/* The ATA reset sequence depends on which devices are present,
so we start by detecting device presence. */
for (dev_no = 0; dev_no < 2; dev_no++)
{
struct ata_disk *d = &c->devices[dev_no];
select_device (d);
outb (reg_nsect (c), 0x55);
outb (reg_lbal (c), 0xaa);
outb (reg_nsect (c), 0xaa);
outb (reg_lbal (c), 0x55);
outb (reg_nsect (c), 0x55);
outb (reg_lbal (c), 0xaa);
present[dev_no] = (inb (reg_nsect (c)) == 0x55
&& inb (reg_lbal (c)) == 0xaa);
}
/* Issue soft reset sequence, which selects device 0 as a side effect.
Also enable interrupts. */
outb (reg_ctl (c), 0);
timer_usleep (10);
outb (reg_ctl (c), CTL_SRST);
timer_usleep (10);
outb (reg_ctl (c), 0);
timer_msleep (150);
/* Wait for device 0 to clear BSY. */
if (present[0])
{
select_device (&c->devices[0]);
wait_while_busy (&c->devices[0]);
}
/* Wait for device 1 to clear BSY. */
if (present[1])
{
int i;
select_device (&c->devices[1]);
for (i = 0; i < 3000; i++)
{
if (inb (reg_nsect (c)) == 1 && inb (reg_lbal (c)) == 1)
break;
timer_msleep (10);
}
wait_while_busy (&c->devices[1]);
}
}
/* Checks whether device D is an ATA disk and sets D's is_ata
member appropriately. If D is device 0 (master), returns true
if it's possible that a slave (device 1) exists on this
channel. If D is device 1 (slave), the return value is not
meaningful. */
static bool
check_device_type (struct ata_disk *d)
{
struct channel *c = d->channel;
uint8_t error, lbam, lbah, status;
select_device (d);
error = inb (reg_error (c));
lbam = inb (reg_lbam (c));
lbah = inb (reg_lbah (c));
status = inb (reg_status (c));
if ((error != 1 && (error != 0x81 || d->dev_no == 1))
|| (status & STA_DRDY) == 0
|| (status & STA_BSY) != 0)
{
d->is_ata = false;
return error != 0x81;
}
else
{
d->is_ata = (lbam == 0 && lbah == 0) || (lbam == 0x3c && lbah == 0xc3);
return true;
}
}
/* Sends an IDENTIFY DEVICE command to disk D and reads the
response. Registers the disk with the block device
layer. */
static void
identify_ata_device (struct ata_disk *d)
{
struct channel *c = d->channel;
char id[BLOCK_SECTOR_SIZE];
block_sector_t capacity;
char *model, *serial;
char extra_info[128];
struct block *block;
ASSERT (d->is_ata);
/* Send the IDENTIFY DEVICE command, wait for an interrupt
indicating the device's response is ready, and read the data
into our buffer. */
select_device_wait (d);
issue_pio_command (c, CMD_IDENTIFY_DEVICE);
sema_down (&c->completion_wait);
if (!wait_while_busy (d))
{
d->is_ata = false;
return;
}
input_sector (c, id);
/* Calculate capacity.
Read model name and serial number. */
capacity = *(uint32_t *) &id[60 * 2];
model = descramble_ata_string (&id[10 * 2], 20);
serial = descramble_ata_string (&id[27 * 2], 40);
snprintf (extra_info, sizeof extra_info,
"model \"%s\", serial \"%s\"", model, serial);
/* Disable access to IDE disks over 1 GB, which are likely
physical IDE disks rather than virtual ones. If we don't
allow access to those, we're less likely to scribble on
someone's important data. You can disable this check by
hand if you really want to do so. */
if (capacity >= 1024 * 1024 * 1024 / BLOCK_SECTOR_SIZE)
{
printf ("%s: ignoring ", d->name);
print_human_readable_size (capacity * 512);
printf ("disk for safety\n");
d->is_ata = false;
return;
}
/* Register. */
block = block_register (d->name, BLOCK_RAW, extra_info, capacity,
&ide_operations, d);
partition_scan (block);
}
/* Translates STRING, which consists of SIZE bytes in a funky
format, into a null-terminated string in-place. Drops
trailing whitespace and null bytes. Returns STRING. */
static char *
descramble_ata_string (char *string, int size)
{
int i;
/* Swap all pairs of bytes. */
for (i = 0; i + 1 < size; i += 2)
{
char tmp = string[i];
string[i] = string[i + 1];
string[i + 1] = tmp;
}
/* Find the last non-white, non-null character. */
for (size--; size > 0; size--)
{
int c = string[size - 1];
if (c != '\0' && !isspace (c))
break;
}
string[size] = '\0';
return string;
}
/* Reads sector SEC_NO from disk D into BUFFER, which must have
room for BLOCK_SECTOR_SIZE bytes.
Internally synchronizes accesses to disks, so external
per-disk locking is unneeded. */
static void
ide_read (void *d_, block_sector_t sec_no, void *buffer)
{
struct ata_disk *d = d_;
struct channel *c = d->channel;
lock_acquire (&c->lock);
select_sector (d, sec_no);
issue_pio_command (c, CMD_READ_SECTOR_RETRY);
sema_down (&c->completion_wait);
if (!wait_while_busy (d))
PANIC ("%s: disk read failed, sector=%"PRDSNu, d->name, sec_no);
input_sector (c, buffer);
lock_release (&c->lock);
}
/* Write sector SEC_NO to disk D from BUFFER, which must contain
BLOCK_SECTOR_SIZE bytes. Returns after the disk has
acknowledged receiving the data.
Internally synchronizes accesses to disks, so external
per-disk locking is unneeded. */
static void
ide_write (void *d_, block_sector_t sec_no, const void *buffer)
{
struct ata_disk *d = d_;
struct channel *c = d->channel;
lock_acquire (&c->lock);
select_sector (d, sec_no);
issue_pio_command (c, CMD_WRITE_SECTOR_RETRY);
if (!wait_while_busy (d))
PANIC ("%s: disk write failed, sector=%"PRDSNu, d->name, sec_no);
output_sector (c, buffer);
sema_down (&c->completion_wait);
lock_release (&c->lock);
}
static struct block_operations ide_operations =
{
ide_read,
ide_write
};
/* Selects device D, waiting for it to become ready, and then
writes SEC_NO to the disk's sector selection registers. (We
use LBA mode.) */
static void
select_sector (struct ata_disk *d, block_sector_t sec_no)
{
struct channel *c = d->channel;
ASSERT (sec_no < (1UL << 28));
select_device_wait (d);
outb (reg_nsect (c), 1);
outb (reg_lbal (c), sec_no);
outb (reg_lbam (c), sec_no >> 8);
outb (reg_lbah (c), (sec_no >> 16));
outb (reg_device (c),
DEV_MBS | DEV_LBA | (d->dev_no == 1 ? DEV_DEV : 0) | (sec_no >> 24));
}
/* Writes COMMAND to channel C and prepares for receiving a
completion interrupt. */
static void
issue_pio_command (struct channel *c, uint8_t command)
{
/* Interrupts must be enabled or our semaphore will never be
up'd by the completion handler. */
ASSERT (intr_get_level () == INTR_ON);
c->expecting_interrupt = true;
outb (reg_command (c), command);
}
/* Reads a sector from channel C's data register in PIO mode into
SECTOR, which must have room for BLOCK_SECTOR_SIZE bytes. */
static void
input_sector (struct channel *c, void *sector)
{
insw (reg_data (c), sector, BLOCK_SECTOR_SIZE / 2);
}
/* Writes SECTOR to channel C's data register in PIO mode.
SECTOR must contain BLOCK_SECTOR_SIZE bytes. */
static void
output_sector (struct channel *c, const void *sector)
{
outsw (reg_data (c), sector, BLOCK_SECTOR_SIZE / 2);
}
/* Low-level ATA primitives. */
/* Wait up to 10 seconds for the controller to become idle, that
is, for the BSY and DRQ bits to clear in the status register.
As a side effect, reading the status register clears any
pending interrupt. */
static void
wait_until_idle (const struct ata_disk *d)
{
int i;
for (i = 0; i < 1000; i++)
{
if ((inb (reg_status (d->channel)) & (STA_BSY | STA_DRQ)) == 0)
return;
timer_usleep (10);
}
printf ("%s: idle timeout\n", d->name);
}
/* Wait up to 30 seconds for disk D to clear BSY,
and then return the status of the DRQ bit.
The ATA standards say that a disk may take as long as that to
complete its reset. */
static bool
wait_while_busy (const struct ata_disk *d)
{
struct channel *c = d->channel;
int i;
for (i = 0; i < 3000; i++)
{
if (i == 700)
printf ("%s: busy, waiting...", d->name);
if (!(inb (reg_alt_status (c)) & STA_BSY))
{
if (i >= 700)
printf ("ok\n");
return (inb (reg_alt_status (c)) & STA_DRQ) != 0;
}
timer_msleep (10);
}
printf ("failed\n");
return false;
}
/* Program D's channel so that D is now the selected disk. */
static void
select_device (const struct ata_disk *d)
{
struct channel *c = d->channel;
uint8_t dev = DEV_MBS;
if (d->dev_no == 1)
dev |= DEV_DEV;
outb (reg_device (c), dev);
inb (reg_alt_status (c));
timer_nsleep (400);
}
/* Select disk D in its channel, as select_device(), but wait for
the channel to become idle before and after. */
static void
select_device_wait (const struct ata_disk *d)
{
wait_until_idle (d);
select_device (d);
wait_until_idle (d);
}
/* ATA interrupt handler. */
static void
interrupt_handler (struct intr_frame *f)
{
struct channel *c;
for (c = channels; c < channels + CHANNEL_CNT; c++)
if (f->vec_no == c->irq)
{
if (c->expecting_interrupt)
{
inb (reg_status (c)); /* Acknowledge interrupt. */
sema_up (&c->completion_wait); /* Wake up waiter. */
}
else
printf ("%s: unexpected interrupt\n", c->name);
return;
}
NOT_REACHED ();
}

View File

@@ -0,0 +1,6 @@
#ifndef DEVICES_IDE_H
#define DEVICES_IDE_H
void ide_init (void);
#endif /* devices/ide.h */

View File

@@ -0,0 +1,52 @@
#include "devices/input.h"
#include <debug.h>
#include "devices/intq.h"
#include "devices/serial.h"
/* Stores keys from the keyboard and serial port. */
static struct intq buffer;
/* Initializes the input buffer. */
void
input_init (void)
{
intq_init (&buffer);
}
/* Adds a key to the input buffer.
Interrupts must be off and the buffer must not be full. */
void
input_putc (uint8_t key)
{
ASSERT (intr_get_level () == INTR_OFF);
ASSERT (!intq_full (&buffer));
intq_putc (&buffer, key);
serial_notify ();
}
/* Retrieves a key from the input buffer.
If the buffer is empty, waits for a key to be pressed. */
uint8_t
input_getc (void)
{
enum intr_level old_level;
uint8_t key;
old_level = intr_disable ();
key = intq_getc (&buffer);
serial_notify ();
intr_set_level (old_level);
return key;
}
/* Returns true if the input buffer is full,
false otherwise.
Interrupts must be off. */
bool
input_full (void)
{
ASSERT (intr_get_level () == INTR_OFF);
return intq_full (&buffer);
}

View File

@@ -0,0 +1,12 @@
#ifndef DEVICES_INPUT_H
#define DEVICES_INPUT_H
#include <stdbool.h>
#include <stdint.h>
void input_init (void);
void input_putc (uint8_t);
uint8_t input_getc (void);
bool input_full (void);
#endif /* devices/input.h */

View File

@@ -0,0 +1,114 @@
#include "devices/intq.h"
#include <debug.h>
#include "threads/thread.h"
static int next (int pos);
static void wait (struct intq *q, struct thread **waiter);
static void signal (struct intq *q, struct thread **waiter);
/* Initializes interrupt queue Q. */
void
intq_init (struct intq *q)
{
lock_init (&q->lock);
q->not_full = q->not_empty = NULL;
q->head = q->tail = 0;
}
/* Returns true if Q is empty, false otherwise. */
bool
intq_empty (const struct intq *q)
{
ASSERT (intr_get_level () == INTR_OFF);
return q->head == q->tail;
}
/* Returns true if Q is full, false otherwise. */
bool
intq_full (const struct intq *q)
{
ASSERT (intr_get_level () == INTR_OFF);
return next (q->head) == q->tail;
}
/* Removes a byte from Q and returns it.
If Q is empty, sleeps until a byte is added.
When called from an interrupt handler, Q must not be empty. */
uint8_t
intq_getc (struct intq *q)
{
uint8_t byte;
ASSERT (intr_get_level () == INTR_OFF);
while (intq_empty (q))
{
ASSERT (!intr_context ());
lock_acquire (&q->lock);
wait (q, &q->not_empty);
lock_release (&q->lock);
}
byte = q->buf[q->tail];
q->tail = next (q->tail);
signal (q, &q->not_full);
return byte;
}
/* Adds BYTE to the end of Q.
If Q is full, sleeps until a byte is removed.
When called from an interrupt handler, Q must not be full. */
void
intq_putc (struct intq *q, uint8_t byte)
{
ASSERT (intr_get_level () == INTR_OFF);
while (intq_full (q))
{
ASSERT (!intr_context ());
lock_acquire (&q->lock);
wait (q, &q->not_full);
lock_release (&q->lock);
}
q->buf[q->head] = byte;
q->head = next (q->head);
signal (q, &q->not_empty);
}
/* Returns the position after POS within an intq. */
static int
next (int pos)
{
return (pos + 1) % INTQ_BUFSIZE;
}
/* WAITER must be the address of Q's not_empty or not_full
member. Waits until the given condition is true. */
static void
wait (struct intq *q UNUSED, struct thread **waiter)
{
ASSERT (!intr_context ());
ASSERT (intr_get_level () == INTR_OFF);
ASSERT ((waiter == &q->not_empty && intq_empty (q))
|| (waiter == &q->not_full && intq_full (q)));
*waiter = thread_current ();
thread_block ();
}
/* WAITER must be the address of Q's not_empty or not_full
member, and the associated condition must be true. If a
thread is waiting for the condition, wakes it up and resets
the waiting thread. */
static void
signal (struct intq *q UNUSED, struct thread **waiter)
{
ASSERT (intr_get_level () == INTR_OFF);
ASSERT ((waiter == &q->not_empty && !intq_empty (q))
|| (waiter == &q->not_full && !intq_full (q)));
if (*waiter != NULL)
{
thread_unblock (*waiter);
*waiter = NULL;
}
}

View File

@@ -0,0 +1,43 @@
#ifndef DEVICES_INTQ_H
#define DEVICES_INTQ_H
#include "threads/interrupt.h"
#include "threads/synch.h"
/* An "interrupt queue", a circular buffer shared between
kernel threads and external interrupt handlers.
Interrupt queue functions can be called from kernel threads or
from external interrupt handlers. Except for intq_init(),
interrupts must be off in either case.
The interrupt queue has the structure of a "monitor". Locks
and condition variables from threads/synch.h cannot be used in
this case, as they normally would, because they can only
protect kernel threads from one another, not from interrupt
handlers. */
/* Queue buffer size, in bytes. */
#define INTQ_BUFSIZE 64
/* A circular queue of bytes. */
struct intq
{
/* Waiting threads. */
struct lock lock; /* Only one thread may wait at once. */
struct thread *not_full; /* Thread waiting for not-full condition. */
struct thread *not_empty; /* Thread waiting for not-empty condition. */
/* Queue. */
uint8_t buf[INTQ_BUFSIZE]; /* Buffer. */
int head; /* New data is written here. */
int tail; /* Old data is read here. */
};
void intq_init (struct intq *);
bool intq_empty (const struct intq *);
bool intq_full (const struct intq *);
uint8_t intq_getc (struct intq *);
void intq_putc (struct intq *, uint8_t);
#endif /* devices/intq.h */

View File

@@ -0,0 +1,213 @@
#include "devices/kbd.h"
#include <ctype.h>
#include <debug.h>
#include <stdio.h>
#include <string.h>
#include "devices/input.h"
#include "devices/shutdown.h"
#include "threads/interrupt.h"
#include "threads/io.h"
/* Keyboard data register port. */
#define DATA_REG 0x60
/* Current state of shift keys.
True if depressed, false otherwise. */
static bool left_shift, right_shift; /* Left and right Shift keys. */
static bool left_alt, right_alt; /* Left and right Alt keys. */
static bool left_ctrl, right_ctrl; /* Left and right Ctl keys. */
/* Status of Caps Lock.
True when on, false when off. */
static bool caps_lock;
/* Number of keys pressed. */
static int64_t key_cnt;
static intr_handler_func keyboard_interrupt;
/* Initializes the keyboard. */
void
kbd_init (void)
{
intr_register_ext (0x21, keyboard_interrupt, "8042 Keyboard");
}
/* Prints keyboard statistics. */
void
kbd_print_stats (void)
{
printf ("Keyboard: %lld keys pressed\n", key_cnt);
}
/* Maps a set of contiguous scancodes into characters. */
struct keymap
{
uint8_t first_scancode; /* First scancode. */
const char *chars; /* chars[0] has scancode first_scancode,
chars[1] has scancode first_scancode + 1,
and so on to the end of the string. */
};
/* Keys that produce the same characters regardless of whether
the Shift keys are down. Case of letters is an exception
that we handle elsewhere. */
static const struct keymap invariant_keymap[] =
{
{0x01, "\033"}, /* Escape. */
{0x0e, "\b"},
{0x0f, "\tQWERTYUIOP"},
{0x1c, "\r"},
{0x1e, "ASDFGHJKL"},
{0x2c, "ZXCVBNM"},
{0x37, "*"},
{0x39, " "},
{0x53, "\177"}, /* Delete. */
{0, NULL},
};
/* Characters for keys pressed without Shift, for those keys
where it matters. */
static const struct keymap unshifted_keymap[] =
{
{0x02, "1234567890-="},
{0x1a, "[]"},
{0x27, ";'`"},
{0x2b, "\\"},
{0x33, ",./"},
{0, NULL},
};
/* Characters for keys pressed with Shift, for those keys where
it matters. */
static const struct keymap shifted_keymap[] =
{
{0x02, "!@#$%^&*()_+"},
{0x1a, "{}"},
{0x27, ":\"~"},
{0x2b, "|"},
{0x33, "<>?"},
{0, NULL},
};
static bool map_key (const struct keymap[], unsigned scancode, uint8_t *);
static void
keyboard_interrupt (struct intr_frame *args UNUSED)
{
/* Status of shift keys. */
bool shift = left_shift || right_shift;
bool alt = left_alt || right_alt;
bool ctrl = left_ctrl || right_ctrl;
/* Keyboard scancode. */
unsigned code;
/* False if key pressed, true if key released. */
bool release;
/* Character that corresponds to `code'. */
uint8_t c;
/* Read scancode, including second byte if prefix code. */
code = inb (DATA_REG);
if (code == 0xe0)
code = (code << 8) | inb (DATA_REG);
/* Bit 0x80 distinguishes key press from key release
(even if there's a prefix). */
release = (code & 0x80) != 0;
code &= ~0x80u;
/* Interpret key. */
if (code == 0x3a)
{
/* Caps Lock. */
if (!release)
caps_lock = !caps_lock;
}
else if (map_key (invariant_keymap, code, &c)
|| (!shift && map_key (unshifted_keymap, code, &c))
|| (shift && map_key (shifted_keymap, code, &c)))
{
/* Ordinary character. */
if (!release)
{
/* Reboot if Ctrl+Alt+Del pressed. */
if (c == 0177 && ctrl && alt)
shutdown_reboot ();
/* Handle Ctrl, Shift.
Note that Ctrl overrides Shift. */
if (ctrl && c >= 0x40 && c < 0x60)
{
/* A is 0x41, Ctrl+A is 0x01, etc. */
c -= 0x40;
}
else if (shift == caps_lock)
c = tolower (c);
/* Handle Alt by setting the high bit.
This 0x80 is unrelated to the one used to
distinguish key press from key release. */
if (alt)
c += 0x80;
/* Append to keyboard buffer. */
if (!input_full ())
{
key_cnt++;
input_putc (c);
}
}
}
else
{
/* Maps a keycode into a shift state variable. */
struct shift_key
{
unsigned scancode;
bool *state_var;
};
/* Table of shift keys. */
static const struct shift_key shift_keys[] =
{
{ 0x2a, &left_shift},
{ 0x36, &right_shift},
{ 0x38, &left_alt},
{0xe038, &right_alt},
{ 0x1d, &left_ctrl},
{0xe01d, &right_ctrl},
{0, NULL},
};
const struct shift_key *key;
/* Scan the table. */
for (key = shift_keys; key->scancode != 0; key++)
if (key->scancode == code)
{
*key->state_var = !release;
break;
}
}
}
/* Scans the array of keymaps K for SCANCODE.
If found, sets *C to the corresponding character and returns
true.
If not found, returns false and C is ignored. */
static bool
map_key (const struct keymap k[], unsigned scancode, uint8_t *c)
{
for (; k->first_scancode != 0; k++)
if (scancode >= k->first_scancode
&& scancode < k->first_scancode + strlen (k->chars))
{
*c = k->chars[scancode - k->first_scancode];
return true;
}
return false;
}

View File

@@ -0,0 +1,9 @@
#ifndef DEVICES_KBD_H
#define DEVICES_KBD_H
#include <stdint.h>
void kbd_init (void);
void kbd_print_stats (void);
#endif /* devices/kbd.h */

View File

@@ -0,0 +1,324 @@
#include "devices/partition.h"
#include <packed.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "devices/block.h"
#include "threads/malloc.h"
/* A partition of a block device. */
struct partition
{
struct block *block; /* Underlying block device. */
block_sector_t start; /* First sector within device. */
};
static struct block_operations partition_operations;
static void read_partition_table (struct block *, block_sector_t sector,
block_sector_t primary_extended_sector,
int *part_nr);
static void found_partition (struct block *, uint8_t type,
block_sector_t start, block_sector_t size,
int part_nr);
static const char *partition_type_name (uint8_t);
/* Scans BLOCK for partitions of interest to PintOS. */
void
partition_scan (struct block *block)
{
int part_nr = 0;
read_partition_table (block, 0, 0, &part_nr);
if (part_nr == 0)
printf ("%s: Device contains no partitions\n", block_name (block));
}
/* Reads the partition table in the given SECTOR of BLOCK and
scans it for partitions of interest to PintOS.
If SECTOR is 0, so that this is the top-level partition table
on BLOCK, then PRIMARY_EXTENDED_SECTOR is not meaningful;
otherwise, it should designate the sector of the top-level
extended partition table that was traversed to arrive at
SECTOR, for use in finding logical partitions (see the large
comment below).
PART_NR points to the number of non-empty primary or logical
partitions already encountered on BLOCK. It is incremented as
partitions are found. */
static void
read_partition_table (struct block *block, block_sector_t sector,
block_sector_t primary_extended_sector,
int *part_nr)
{
/* Format of a partition table entry. See [Partitions]. */
struct partition_table_entry
{
uint8_t bootable; /* 0x00=not bootable, 0x80=bootable. */
uint8_t start_chs[3]; /* Encoded starting cylinder, head, sector. */
uint8_t type; /* Partition type (see partition_type_name). */
uint8_t end_chs[3]; /* Encoded ending cylinder, head, sector. */
uint32_t offset; /* Start sector offset from partition table. */
uint32_t size; /* Number of sectors. */
}
PACKED;
/* Partition table sector. */
struct partition_table
{
uint8_t loader[446]; /* Loader, in top-level partition table. */
struct partition_table_entry partitions[4]; /* Table entries. */
uint16_t signature; /* Should be 0xaa55. */
}
PACKED;
struct partition_table *pt;
size_t i;
/* Check SECTOR validity. */
if (sector >= block_size (block))
{
printf ("%s: Partition table at sector %"PRDSNu" past end of device.\n",
block_name (block), sector);
return;
}
/* Read sector. */
ASSERT (sizeof *pt == BLOCK_SECTOR_SIZE);
pt = malloc (sizeof *pt);
if (pt == NULL)
PANIC ("Failed to allocate memory for partition table.");
block_read (block, 0, pt);
/* Check signature. */
if (pt->signature != 0xaa55)
{
if (primary_extended_sector == 0)
printf ("%s: Invalid partition table signature\n", block_name (block));
else
printf ("%s: Invalid extended partition table in sector %"PRDSNu"\n",
block_name (block), sector);
free (pt);
return;
}
/* Parse partitions. */
for (i = 0; i < sizeof pt->partitions / sizeof *pt->partitions; i++)
{
struct partition_table_entry *e = &pt->partitions[i];
if (e->size == 0 || e->type == 0)
{
/* Ignore empty partition. */
}
else if (e->type == 0x05 /* Extended partition. */
|| e->type == 0x0f /* Windows 98 extended partition. */
|| e->type == 0x85 /* Linux extended partition. */
|| e->type == 0xc5) /* DR-DOS extended partition. */
{
printf ("%s: Extended partition in sector %"PRDSNu"\n",
block_name (block), sector);
/* The interpretation of the offset field for extended
partitions is bizarre. When the extended partition
table entry is in the master boot record, that is,
the device's primary partition table in sector 0, then
the offset is an absolute sector number. Otherwise,
no matter how deep the partition table we're reading
is nested, the offset is relative to the start of
the extended partition that the MBR points to. */
if (sector == 0)
read_partition_table (block, e->offset, e->offset, part_nr);
else
read_partition_table (block, e->offset + primary_extended_sector,
primary_extended_sector, part_nr);
}
else
{
++*part_nr;
found_partition (block, e->type, e->offset + sector,
e->size, *part_nr);
}
}
free (pt);
}
/* We have found a primary or logical partition of the given TYPE
on BLOCK, starting at sector START and continuing for SIZE
sectors, which we are giving the partition number PART_NR.
Check whether this is a partition of interest to PintOS, and
if so then add it to the proper element of partitions[]. */
static void
found_partition (struct block *block, uint8_t part_type,
block_sector_t start, block_sector_t size,
int part_nr)
{
if (start >= block_size (block))
printf ("%s%d: Partition starts past end of device (sector %"PRDSNu")\n",
block_name (block), part_nr, start);
else if (start + size < start || start + size > block_size (block))
printf ("%s%d: Partition end (%"PRDSNu") past end of device (%"PRDSNu")\n",
block_name (block), part_nr, start + size, block_size (block));
else
{
enum block_type type = (part_type == 0x20 ? BLOCK_KERNEL
: part_type == 0x21 ? BLOCK_FILESYS
: part_type == 0x22 ? BLOCK_SCRATCH
: part_type == 0x23 ? BLOCK_SWAP
: BLOCK_FOREIGN);
struct partition *p;
char extra_info[128];
char name[16];
p = malloc (sizeof *p);
if (p == NULL)
PANIC ("Failed to allocate memory for partition descriptor");
p->block = block;
p->start = start;
snprintf (name, sizeof name, "%s%d", block_name (block), part_nr);
snprintf (extra_info, sizeof extra_info, "%s (%02x)",
partition_type_name (part_type), part_type);
block_register (name, type, extra_info, size, &partition_operations, p);
}
}
/* Returns a human-readable name for the given partition TYPE. */
static const char *
partition_type_name (uint8_t type)
{
/* Name of each known type of partition.
From util-linux-2.12r/fdisk/i386_sys_types.c.
This initializer makes use of a C99 feature that allows
array elements to be initialized by index. */
static const char *type_names[256] =
{
[0x00] = "Empty",
[0x01] = "FAT12",
[0x02] = "XENIX root",
[0x03] = "XENIX usr",
[0x04] = "FAT16 <32M",
[0x05] = "Extended",
[0x06] = "FAT16",
[0x07] = "HPFS/NTFS",
[0x08] = "AIX",
[0x09] = "AIX bootable",
[0x0a] = "OS/2 Boot Manager",
[0x0b] = "W95 FAT32",
[0x0c] = "W95 FAT32 (LBA)",
[0x0e] = "W95 FAT16 (LBA)",
[0x0f] = "W95 Ext'd (LBA)",
[0x10] = "OPUS",
[0x11] = "Hidden FAT12",
[0x12] = "Compaq diagnostics",
[0x14] = "Hidden FAT16 <32M",
[0x16] = "Hidden FAT16",
[0x17] = "Hidden HPFS/NTFS",
[0x18] = "AST SmartSleep",
[0x1b] = "Hidden W95 FAT32",
[0x1c] = "Hidden W95 FAT32 (LBA)",
[0x1e] = "Hidden W95 FAT16 (LBA)",
[0x20] = "PintOS OS kernel",
[0x21] = "PintOS file system",
[0x22] = "PintOS scratch",
[0x23] = "PintOS swap",
[0x24] = "NEC DOS",
[0x39] = "Plan 9",
[0x3c] = "PartitionMagic recovery",
[0x40] = "Venix 80286",
[0x41] = "PPC PReP Boot",
[0x42] = "SFS",
[0x4d] = "QNX4.x",
[0x4e] = "QNX4.x 2nd part",
[0x4f] = "QNX4.x 3rd part",
[0x50] = "OnTrack DM",
[0x51] = "OnTrack DM6 Aux1",
[0x52] = "CP/M",
[0x53] = "OnTrack DM6 Aux3",
[0x54] = "OnTrackDM6",
[0x55] = "EZ-Drive",
[0x56] = "Golden Bow",
[0x5c] = "Priam Edisk",
[0x61] = "SpeedStor",
[0x63] = "GNU HURD or SysV",
[0x64] = "Novell Netware 286",
[0x65] = "Novell Netware 386",
[0x70] = "DiskSecure Multi-Boot",
[0x75] = "PC/IX",
[0x80] = "Old Minix",
[0x81] = "Minix / old Linux",
[0x82] = "Linux swap / Solaris",
[0x83] = "Linux",
[0x84] = "OS/2 hidden C: drive",
[0x85] = "Linux extended",
[0x86] = "NTFS volume set",
[0x87] = "NTFS volume set",
[0x88] = "Linux plaintext",
[0x8e] = "Linux LVM",
[0x93] = "Amoeba",
[0x94] = "Amoeba BBT",
[0x9f] = "BSD/OS",
[0xa0] = "IBM Thinkpad hibernation",
[0xa5] = "FreeBSD",
[0xa6] = "OpenBSD",
[0xa7] = "NeXTSTEP",
[0xa8] = "Darwin UFS",
[0xa9] = "NetBSD",
[0xab] = "Darwin boot",
[0xb7] = "BSDI fs",
[0xb8] = "BSDI swap",
[0xbb] = "Boot Wizard hidden",
[0xbe] = "Solaris boot",
[0xbf] = "Solaris",
[0xc1] = "DRDOS/sec (FAT-12)",
[0xc4] = "DRDOS/sec (FAT-16 < 32M)",
[0xc6] = "DRDOS/sec (FAT-16)",
[0xc7] = "Syrinx",
[0xda] = "Non-FS data",
[0xdb] = "CP/M / CTOS / ...",
[0xde] = "Dell Utility",
[0xdf] = "BootIt",
[0xe1] = "DOS access",
[0xe3] = "DOS R/O",
[0xe4] = "SpeedStor",
[0xeb] = "BeOS fs",
[0xee] = "EFI GPT",
[0xef] = "EFI (FAT-12/16/32)",
[0xf0] = "Linux/PA-RISC boot",
[0xf1] = "SpeedStor",
[0xf4] = "SpeedStor",
[0xf2] = "DOS secondary",
[0xfd] = "Linux raid autodetect",
[0xfe] = "LANstep",
[0xff] = "BBT",
};
return type_names[type] != NULL ? type_names[type] : "Unknown";
}
/* Reads sector SECTOR from partition P into BUFFER, which must
have room for BLOCK_SECTOR_SIZE bytes. */
static void
partition_read (void *p_, block_sector_t sector, void *buffer)
{
struct partition *p = p_;
block_read (p->block, p->start + sector, buffer);
}
/* Write sector SECTOR to partition P from BUFFER, which must
contain BLOCK_SECTOR_SIZE bytes. Returns after the block has
acknowledged receiving the data. */
static void
partition_write (void *p_, block_sector_t sector, const void *buffer)
{
struct partition *p = p_;
block_write (p->block, p->start + sector, buffer);
}
static struct block_operations partition_operations =
{
partition_read,
partition_write
};

View File

@@ -0,0 +1,8 @@
#ifndef DEVICES_PARTITION_H
#define DEVICES_PARTITION_H
struct block;
void partition_scan (struct block *);
#endif /* devices/partition.h */

View File

@@ -0,0 +1,83 @@
#include "devices/pit.h"
#include <debug.h>
#include <stdint.h>
#include "threads/interrupt.h"
#include "threads/io.h"
/* Interface to 8254 Programmable Interrupt Timer (PIT).
Refer to [8254] for details. */
/* 8254 registers. */
#define PIT_PORT_CONTROL 0x43 /* Control port. */
#define PIT_PORT_COUNTER(CHANNEL) (0x40 + (CHANNEL)) /* Counter port. */
/* PIT cycles per second. */
#define PIT_HZ 1193180
/* Configure the given CHANNEL in the PIT. In a PC, the PIT's
three output channels are hooked up like this:
- Channel 0 is connected to interrupt line 0, so that it can
be used as a periodic timer interrupt, as implemented in
PintOS in devices/timer.c.
- Channel 1 is used for dynamic RAM refresh (in older PCs).
No good can come of messing with this.
- Channel 2 is connected to the PC speaker, so that it can
be used to play a tone, as implemented in PintOS in
devices/speaker.c.
MODE specifies the form of output:
- Mode 2 is a periodic pulse: the channel's output is 1 for
most of the period, but drops to 0 briefly toward the end
of the period. This is useful for hooking up to an
interrupt controller to generate a periodic interrupt.
- Mode 3 is a square wave: for the first half of the period
it is 1, for the second half it is 0. This is useful for
generating a tone on a speaker.
- Other modes are less useful.
FREQUENCY is the number of periods per second, in Hz. */
void
pit_configure_channel (int channel, int mode, int frequency)
{
uint16_t count;
enum intr_level old_level;
ASSERT (channel == 0 || channel == 2);
ASSERT (mode == 2 || mode == 3);
/* Convert FREQUENCY to a PIT counter value. The PIT has a
clock that runs at PIT_HZ cycles per second. We must
translate FREQUENCY into a number of these cycles. */
if (frequency < 19)
{
/* Frequency is too low: the quotient would overflow the
16-bit counter. Force it to 0, which the PIT treats as
65536, the highest possible count. This yields a 18.2
Hz timer, approximately. */
count = 0;
}
else if (frequency > PIT_HZ)
{
/* Frequency is too high: the quotient would underflow to
0, which the PIT would interpret as 65536. A count of 1
is illegal in mode 2, so we force it to 2, which yields
a 596.590 kHz timer, approximately. (This timer rate is
probably too fast to be useful anyhow.) */
count = 2;
}
else
count = (PIT_HZ + frequency / 2) / frequency;
/* Configure the PIT mode and load its counters. */
old_level = intr_disable ();
outb (PIT_PORT_CONTROL, (channel << 6) | 0x30 | (mode << 1));
outb (PIT_PORT_COUNTER (channel), count);
outb (PIT_PORT_COUNTER (channel), count >> 8);
intr_set_level (old_level);
}

View File

@@ -0,0 +1,8 @@
#ifndef DEVICES_PIT_H
#define DEVICES_PIT_H
#include <stdint.h>
void pit_configure_channel (int channel, int mode, int frequency);
#endif /* devices/pit.h */

View File

@@ -0,0 +1,112 @@
#include "devices/rtc.h"
#include <stdio.h>
#include "threads/io.h"
/* This code is an interface to the MC146818A-compatible real
time clock found on PC motherboards. See [MC146818A] for
hardware details. */
/* I/O register addresses. */
#define CMOS_REG_SET 0x70 /* Selects CMOS register exposed by REG_IO. */
#define CMOS_REG_IO 0x71 /* Contains the selected data byte. */
/* Indexes of CMOS registers with real-time clock functions.
Note that all of these registers are in BCD format,
so that 0x59 means 59, not 89. */
#define RTC_REG_SEC 0 /* Second: 0x00...0x59. */
#define RTC_REG_MIN 2 /* Minute: 0x00...0x59. */
#define RTC_REG_HOUR 4 /* Hour: 0x00...0x23. */
#define RTC_REG_MDAY 7 /* Day of the month: 0x01...0x31. */
#define RTC_REG_MON 8 /* Month: 0x01...0x12. */
#define RTC_REG_YEAR 9 /* Year: 0x00...0x99. */
/* Indexes of CMOS control registers. */
#define RTC_REG_A 0x0a /* Register A: update-in-progress. */
#define RTC_REG_B 0x0b /* Register B: 24/12 hour time, irq enables. */
#define RTC_REG_C 0x0c /* Register C: pending interrupts. */
#define RTC_REG_D 0x0d /* Register D: valid time? */
/* Register A. */
#define RTCSA_UIP 0x80 /* Set while time update in progress. */
/* Register B. */
#define RTCSB_SET 0x80 /* Disables update to let time be set. */
#define RTCSB_DM 0x04 /* 0 = BCD time format, 1 = binary format. */
#define RTCSB_24HR 0x02 /* 0 = 12-hour format, 1 = 24-hour format. */
static int bcd_to_bin (uint8_t);
static uint8_t cmos_read (uint8_t index);
/* Returns number of seconds since Unix epoch of January 1,
1970. */
time_t
rtc_get_time (void)
{
static const int days_per_month[12] =
{
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
int sec, min, hour, mday, mon, year;
time_t time;
int i;
/* Get time components.
We repeatedly read the time until it is stable from one read
to another, in case we start our initial read in the middle
of an update. This strategy is not recommended by the
MC146818A datasheet, but it is simpler than any of their
suggestions and, furthermore, it is also used by Linux.
The MC146818A can be configured for BCD or binary format,
but for historical reasons everyone always uses BCD format
except on obscure non-PC platforms, so we don't bother
trying to detect the format in use. */
do
{
sec = bcd_to_bin (cmos_read (RTC_REG_SEC));
min = bcd_to_bin (cmos_read (RTC_REG_MIN));
hour = bcd_to_bin (cmos_read (RTC_REG_HOUR));
mday = bcd_to_bin (cmos_read (RTC_REG_MDAY));
mon = bcd_to_bin (cmos_read (RTC_REG_MON));
year = bcd_to_bin (cmos_read (RTC_REG_YEAR));
}
while (sec != bcd_to_bin (cmos_read (RTC_REG_SEC)));
/* Translate years-since-1900 into years-since-1970.
If it's before the epoch, assume that it has passed 2000.
This will break at 2070, but that's long after our 31-bit
time_t breaks in 2038. */
if (year < 70)
year += 100;
year -= 70;
/* Break down all components into seconds. */
time = (year * 365 + (year - 1) / 4) * 24 * 60 * 60;
for (i = 1; i <= mon; i++)
time += days_per_month[i - 1] * 24 * 60 * 60;
if (mon > 2 && year % 4 == 0)
time += 24 * 60 * 60;
time += (mday - 1) * 24 * 60 * 60;
time += hour * 60 * 60;
time += min * 60;
time += sec;
return time;
}
/* Returns the integer value of the given BCD byte. */
static int
bcd_to_bin (uint8_t x)
{
return (x & 0x0f) + ((x >> 4) * 10);
}
/* Reads a byte from the CMOS register with the given INDEX and
returns the byte read. */
static uint8_t
cmos_read (uint8_t index)
{
outb (CMOS_REG_SET, index);
return inb (CMOS_REG_IO);
}

View File

@@ -0,0 +1,8 @@
#ifndef RTC_H
#define RTC_H
typedef unsigned long time_t;
time_t rtc_get_time (void);
#endif

View File

@@ -0,0 +1,228 @@
#include "devices/serial.h"
#include <debug.h>
#include "devices/input.h"
#include "devices/intq.h"
#include "devices/timer.h"
#include "threads/io.h"
#include "threads/interrupt.h"
#include "threads/synch.h"
#include "threads/thread.h"
/* Register definitions for the 16550A UART used in PCs.
The 16550A has a lot more going on than shown here, but this
is all we need.
Refer to [PC16650D] for hardware information. */
/* I/O port base address for the first serial port. */
#define IO_BASE 0x3f8
/* DLAB=0 registers. */
#define RBR_REG (IO_BASE + 0) /* Receiver Buffer Reg. (read-only). */
#define THR_REG (IO_BASE + 0) /* Transmitter Holding Reg. (write-only). */
#define IER_REG (IO_BASE + 1) /* Interrupt Enable Reg.. */
/* DLAB=1 registers. */
#define LS_REG (IO_BASE + 0) /* Divisor Latch (LSB). */
#define MS_REG (IO_BASE + 1) /* Divisor Latch (MSB). */
/* DLAB-insensitive registers. */
#define IIR_REG (IO_BASE + 2) /* Interrupt Identification Reg. (read-only) */
#define FCR_REG (IO_BASE + 2) /* FIFO Control Reg. (write-only). */
#define LCR_REG (IO_BASE + 3) /* Line Control Register. */
#define MCR_REG (IO_BASE + 4) /* MODEM Control Register. */
#define LSR_REG (IO_BASE + 5) /* Line Status Register (read-only). */
/* Interrupt Enable Register bits. */
#define IER_RECV 0x01 /* Interrupt when data received. */
#define IER_XMIT 0x02 /* Interrupt when transmit finishes. */
/* Line Control Register bits. */
#define LCR_N81 0x03 /* No parity, 8 data bits, 1 stop bit. */
#define LCR_DLAB 0x80 /* Divisor Latch Access Bit (DLAB). */
/* MODEM Control Register. */
#define MCR_OUT2 0x08 /* Output line 2. */
/* Line Status Register. */
#define LSR_DR 0x01 /* Data Ready: received data byte is in RBR. */
#define LSR_THRE 0x20 /* THR Empty. */
/* Transmission mode. */
static enum { UNINIT, POLL, QUEUE } mode;
/* Data to be transmitted. */
static struct intq txq;
static void set_serial (int bps);
static void putc_poll (uint8_t);
static void write_ier (void);
static intr_handler_func serial_interrupt;
/* Initializes the serial port device for polling mode.
Polling mode busy-waits for the serial port to become free
before writing to it. It's slow, but until interrupts have
been initialized it's all we can do. */
static void
init_poll (void)
{
ASSERT (mode == UNINIT);
outb (IER_REG, 0); /* Turn off all interrupts. */
outb (FCR_REG, 0); /* Disable FIFO. */
set_serial (9600); /* 9.6 kbps, N-8-1. */
outb (MCR_REG, MCR_OUT2); /* Required to enable interrupts. */
intq_init (&txq);
mode = POLL;
}
/* Initializes the serial port device for queued interrupt-driven
I/O. With interrupt-driven I/O we don't waste CPU time
waiting for the serial device to become ready. */
void
serial_init_queue (void)
{
enum intr_level old_level;
if (mode == UNINIT)
init_poll ();
ASSERT (mode == POLL);
intr_register_ext (0x20 + 4, serial_interrupt, "serial");
mode = QUEUE;
old_level = intr_disable ();
write_ier ();
intr_set_level (old_level);
}
/* Sends BYTE to the serial port. */
void
serial_putc (uint8_t byte)
{
enum intr_level old_level = intr_disable ();
if (mode != QUEUE)
{
/* If we're not set up for interrupt-driven I/O yet,
use dumb polling to transmit a byte. */
if (mode == UNINIT)
init_poll ();
putc_poll (byte);
}
else
{
/* Otherwise, queue a byte and update the interrupt enable
register. */
if (old_level == INTR_OFF && intq_full (&txq))
{
/* Interrupts are off and the transmit queue is full.
If we wanted to wait for the queue to empty,
we'd have to reenable interrupts.
That's impolite, so we'll send a character via
polling instead. */
putc_poll (intq_getc (&txq));
}
intq_putc (&txq, byte);
write_ier ();
}
intr_set_level (old_level);
}
/* Flushes anything in the serial buffer out the port in polling
mode. */
void
serial_flush (void)
{
enum intr_level old_level = intr_disable ();
while (!intq_empty (&txq))
putc_poll (intq_getc (&txq));
intr_set_level (old_level);
}
/* The fullness of the input buffer may have changed. Reassess
whether we should block receive interrupts.
Called by the input buffer routines when characters are added
to or removed from the buffer. */
void
serial_notify (void)
{
ASSERT (intr_get_level () == INTR_OFF);
if (mode == QUEUE)
write_ier ();
}
/* Configures the serial port for BPS bits per second. */
static void
set_serial (int bps)
{
int base_rate = 1843200 / 16; /* Base rate of 16550A, in Hz. */
uint16_t divisor = base_rate / bps; /* Clock rate divisor. */
ASSERT (bps >= 300 && bps <= 115200);
/* Enable DLAB. */
outb (LCR_REG, LCR_N81 | LCR_DLAB);
/* Set data rate. */
outb (LS_REG, divisor & 0xff);
outb (MS_REG, divisor >> 8);
/* Reset DLAB. */
outb (LCR_REG, LCR_N81);
}
/* Update interrupt enable register. */
static void
write_ier (void)
{
uint8_t ier = 0;
ASSERT (intr_get_level () == INTR_OFF);
/* Enable transmit interrupt if we have any characters to
transmit. */
if (!intq_empty (&txq))
ier |= IER_XMIT;
/* Enable receive interrupt if we have room to store any
characters we receive. */
if (!input_full ())
ier |= IER_RECV;
outb (IER_REG, ier);
}
/* Polls the serial port until it's ready,
and then transmits BYTE. */
static void
putc_poll (uint8_t byte)
{
ASSERT (intr_get_level () == INTR_OFF);
while ((inb (LSR_REG) & LSR_THRE) == 0)
continue;
outb (THR_REG, byte);
}
/* Serial interrupt handler. */
static void
serial_interrupt (struct intr_frame *f UNUSED)
{
/* Inquire about interrupt in UART. Without this, we can
occasionally miss an interrupt running under QEMU. */
inb (IIR_REG);
/* As long as we have room to receive a byte, and the hardware
has a byte for us, receive a byte. */
while (!input_full () && (inb (LSR_REG) & LSR_DR) != 0)
input_putc (inb (RBR_REG));
/* As long as we have a byte to transmit, and the hardware is
ready to accept a byte for transmission, transmit a byte. */
while (!intq_empty (&txq) && (inb (LSR_REG) & LSR_THRE) != 0)
outb (THR_REG, intq_getc (&txq));
/* Update interrupt enable register based on queue status. */
write_ier ();
}

View File

@@ -0,0 +1,11 @@
#ifndef DEVICES_SERIAL_H
#define DEVICES_SERIAL_H
#include <stdint.h>
void serial_init_queue (void);
void serial_putc (uint8_t);
void serial_flush (void);
void serial_notify (void);
#endif /* devices/serial.h */

View File

@@ -0,0 +1,133 @@
#include "devices/shutdown.h"
#include <console.h>
#include <stdio.h>
#include "devices/kbd.h"
#include "devices/serial.h"
#include "devices/timer.h"
#include "threads/io.h"
#include "threads/thread.h"
#ifdef USERPROG
#include "userprog/exception.h"
#endif
#ifdef FILESYS
#include "devices/block.h"
#include "filesys/filesys.h"
#endif
/* Keyboard control register port. */
#define CONTROL_REG 0x64
/* How to shut down when shutdown() is called. */
static enum shutdown_type how = SHUTDOWN_NONE;
static void print_stats (void);
/* Shuts down the machine in the way configured by
shutdown_configure(). If the shutdown type is SHUTDOWN_NONE
(which is the default), returns without doing anything. */
void
shutdown (void)
{
switch (how)
{
case SHUTDOWN_POWER_OFF:
shutdown_power_off ();
break;
case SHUTDOWN_REBOOT:
shutdown_reboot ();
break;
default:
/* Nothing to do. */
break;
}
}
/* Sets TYPE as the way that machine will shut down when PintOS
execution is complete. */
void
shutdown_configure (enum shutdown_type type)
{
how = type;
}
/* Reboots the machine via the keyboard controller. */
void
shutdown_reboot (void)
{
printf ("Rebooting...\n");
/* See [kbd] for details on how to program the keyboard
* controller. */
for (;;)
{
int i;
/* Poll keyboard controller's status byte until
* 'input buffer empty' is reported. */
for (i = 0; i < 0x10000; i++)
{
if ((inb (CONTROL_REG) & 0x02) == 0)
break;
timer_udelay (2);
}
timer_udelay (50);
/* Pulse bit 0 of the output port P2 of the keyboard controller.
* This will reset the CPU. */
outb (CONTROL_REG, 0xfe);
timer_udelay (50);
}
}
/* Powers down the machine we're running on,
as long as we're running on Bochs or QEMU. */
void
shutdown_power_off (void)
{
const char s[] = "Shutdown";
const char *p;
#ifdef FILESYS
filesys_done ();
#endif
print_stats ();
printf ("Powering off...\n");
serial_flush ();
/* This is a special power-off sequence supported by Bochs and
QEMU, but not by physical hardware. */
for (p = s; *p != '\0'; p++){
outb (0x8900, *p);
}
outw (0x604, 0x00 | 0x2000);
/* This will power off a VMware VM if "gui.exitOnCLIHLT = TRUE"
is set in its configuration file. (The "pintos" script does
that automatically.) */
asm volatile ("cli; hlt" : : : "memory");
/* None of those worked. */
printf ("still running...\n");
for (;;);
}
/* Print statistics about PintOS execution. */
static void
print_stats (void)
{
timer_print_stats ();
thread_print_stats ();
#ifdef FILESYS
block_print_stats ();
#endif
console_print_stats ();
kbd_print_stats ();
#ifdef USERPROG
exception_print_stats ();
#endif
}

View File

@@ -0,0 +1,19 @@
#ifndef DEVICES_SHUTDOWN_H
#define DEVICES_SHUTDOWN_H
#include <debug.h>
/* How to shut down when PintOS has nothing left to do. */
enum shutdown_type
{
SHUTDOWN_NONE, /* Loop forever. */
SHUTDOWN_POWER_OFF, /* Power off the machine (if possible). */
SHUTDOWN_REBOOT, /* Reboot the machine (if possible). */
};
void shutdown (void);
void shutdown_configure (enum shutdown_type);
void shutdown_reboot (void) NO_RETURN;
void shutdown_power_off (void) NO_RETURN;
#endif /* devices/shutdown.h */

View File

@@ -0,0 +1,68 @@
#include "devices/speaker.h"
#include "devices/pit.h"
#include "threads/io.h"
#include "threads/interrupt.h"
#include "devices/timer.h"
/* Speaker port enable I/O register. */
#define SPEAKER_PORT_GATE 0x61
/* Speaker port enable bits. */
#define SPEAKER_GATE_ENABLE 0x03
/* Sets the PC speaker to emit a tone at the given FREQUENCY, in
Hz. */
void
speaker_on (int frequency)
{
if (frequency >= 20 && frequency <= 20000)
{
/* Set the timer channel that's connected to the speaker to
output a square wave at the given FREQUENCY, then
connect the timer channel output to the speaker. */
enum intr_level old_level = intr_disable ();
pit_configure_channel (2, 3, frequency);
outb (SPEAKER_PORT_GATE, inb (SPEAKER_PORT_GATE) | SPEAKER_GATE_ENABLE);
intr_set_level (old_level);
}
else
{
/* FREQUENCY is outside the range of normal human hearing.
Just turn off the speaker. */
speaker_off ();
}
}
/* Turn off the PC speaker, by disconnecting the timer channel's
output from the speaker. */
void
speaker_off (void)
{
enum intr_level old_level = intr_disable ();
outb (SPEAKER_PORT_GATE, inb (SPEAKER_PORT_GATE) & ~SPEAKER_GATE_ENABLE);
intr_set_level (old_level);
}
/* Briefly beep the PC speaker. */
void
speaker_beep (void)
{
/* Only attempt to beep the speaker if interrupts are enabled,
because we don't want to freeze the machine during the beep.
We could add a hook to the timer interrupt to avoid that
problem, but then we'd risk failing to ever stop the beep if
PintOS crashes for some unrelated reason. There's nothing
more annoying than a machine whose beeping you can't stop
without a power cycle.
We can't just enable interrupts while we sleep. For one
thing, we get called (indirectly) from printf, which should
always work, even during boot before we're ready to enable
interrupts. */
if (intr_get_level () == INTR_ON)
{
speaker_on (440);
timer_msleep (250);
speaker_off ();
}
}

View File

@@ -0,0 +1,8 @@
#ifndef DEVICES_SPEAKER_H
#define DEVICES_SPEAKER_H
void speaker_on (int frequency);
void speaker_off (void);
void speaker_beep (void);
#endif /* devices/speaker.h */

View File

@@ -0,0 +1,82 @@
#include "devices/swap.h"
#include "devices/block.h"
#include "threads/synch.h"
#include "threads/vaddr.h"
#include <bitmap.h>
#include <debug.h>
#include <stdio.h>
/* Pointer to the swap device */
static struct block *swap_device;
/* Pointer to a bitmap to track used swap pages */
static struct bitmap *swap_bitmap;
/* Lock that protects swap_bitmap from unsynchronised access */
static struct lock swap_lock;
/* Number of sectors needed to store a page */
#define PAGE_SECTORS (PGSIZE / BLOCK_SECTOR_SIZE)
/* Sets up the swap space */
void
swap_init (void)
{
// locate the swap block allocated to the kernel
swap_device = block_get_role (BLOCK_SWAP);
if (swap_device == NULL) {
printf ("no swap device--swap disabled\n");
swap_bitmap = bitmap_create (0);
} else {
// create a bitmap with 1 slot per page-sized chunk of memory on the swap block
swap_bitmap = bitmap_create (block_size (swap_device) / PAGE_SECTORS);
}
if (swap_bitmap == NULL){
PANIC ("couldn't create swap bitmap");
}
lock_init (&swap_lock);
}
/* Swaps page at VADDR out of memory, returns the swap-slot used */
size_t
swap_out (const void *vaddr)
{
// find available swap-slot for the page to be swapped out
lock_acquire (&swap_lock);
size_t slot = bitmap_scan_and_flip (swap_bitmap, 0, 1, false);
lock_release (&swap_lock);
if (slot == BITMAP_ERROR)
return BITMAP_ERROR;
// calculate block sector from swap-slot number
size_t sector = slot * PAGE_SECTORS;
// loop over each sector of the page, copying it from memory into swap
for (size_t i = 0; i < PAGE_SECTORS; i++)
block_write (swap_device, sector + i, vaddr + i * BLOCK_SECTOR_SIZE);
return slot;
}
/* Swaps page on disk in swap-slot SLOT into memory at VADDR */
void
swap_in (void *vaddr, size_t slot)
{
// calculate block sector from swap-slot number
size_t sector = slot * PAGE_SECTORS;
// loop over each sector of the page, copying it from swap into memory
for (size_t i = 0; i < PAGE_SECTORS; i++)
block_read (swap_device, sector + i, vaddr + i * BLOCK_SECTOR_SIZE);
// clear the swap-slot previously used by this page
swap_drop (slot);
}
/* Clears the swap-slot SLOT so that it can be used for another page */
void
swap_drop (size_t slot)
{
bitmap_reset (swap_bitmap, slot);
}

View File

@@ -0,0 +1,11 @@
#ifndef DEVICES_SWAP_H
#define DEVICES_SWAP_H 1
#include <stddef.h>
void swap_init (void);
size_t swap_out (const void *vaddr);
void swap_in (void *vaddr, size_t slot);
void swap_drop (size_t slot);
#endif /* devices/swap.h */

View File

@@ -0,0 +1,293 @@
#include "devices/timer.h"
#include <debug.h>
#include <inttypes.h>
#include <round.h>
#include <stdio.h>
#include <list.h>
#include "devices/pit.h"
#include "threads/interrupt.h"
#include "threads/synch.h"
#include "threads/thread.h"
/* See [8254] for hardware details of the 8254 timer chip. */
#if TIMER_FREQ < 19
#error 8254 timer requires TIMER_FREQ >= 19
#endif
#if TIMER_FREQ > 1000
#error TIMER_FREQ <= 1000 recommended
#endif
struct asleep_thread
{
int64_t end_at; /* Number of timer ticks to stop sleeping at. */
struct semaphore semaphore; /* Semaphore used to block the thread. */
struct list_elem elem; /* List element. */
};
/* List of threads that are sleeping. */
static struct list sleeping_threads;
/* Number of timer ticks since OS booted. */
static int64_t ticks;
/* Number of loops per timer tick.
Initialized by timer_calibrate(). */
static unsigned loops_per_tick;
static intr_handler_func timer_interrupt;
static bool too_many_loops (unsigned loops);
static void busy_wait (int64_t loops);
static void real_time_sleep (int64_t num, int32_t denom);
static void real_time_delay (int64_t num, int32_t denom);
static bool sleeping_threads_less (const struct list_elem *a,
const struct list_elem *b,
void *aux UNUSED);
/* Sets up the timer to interrupt TIMER_FREQ times per second,
and registers the corresponding interrupt. */
void
timer_init (void)
{
pit_configure_channel (0, 2, TIMER_FREQ);
list_init (&sleeping_threads);
intr_register_ext (0x20, timer_interrupt, "8254 Timer");
}
/* Calibrates loops_per_tick, used to implement brief delays. */
void
timer_calibrate (void)
{
unsigned high_bit, test_bit;
ASSERT (intr_get_level () == INTR_ON);
printf ("Calibrating timer... ");
/* Approximate loops_per_tick as the largest power-of-two
still less than one timer tick. */
loops_per_tick = 1u << 10;
while (!too_many_loops (loops_per_tick << 1))
{
loops_per_tick <<= 1;
ASSERT (loops_per_tick != 0);
}
/* Refine the next 8 bits of loops_per_tick. */
high_bit = loops_per_tick;
for (test_bit = high_bit >> 1; test_bit != high_bit >> 10; test_bit >>= 1)
if (!too_many_loops (high_bit | test_bit))
loops_per_tick |= test_bit;
printf ("%'"PRIu64" loops/s.\n", (uint64_t) loops_per_tick * TIMER_FREQ);
}
/* Returns the number of timer ticks since the OS booted. */
int64_t
timer_ticks (void)
{
enum intr_level old_level = intr_disable ();
int64_t t = ticks;
intr_set_level (old_level);
return t;
}
/* Returns the number of timer ticks elapsed since THEN, which
should be a value once returned by timer_ticks(). */
int64_t
timer_elapsed (int64_t then)
{
return timer_ticks () - then;
}
/* Sleeps for approximately TICKS timer ticks. Interrupts must
be turned on. */
void
timer_sleep (int64_t ticks)
{
enum intr_level old_level;
int64_t start = timer_ticks ();
ASSERT (intr_get_level () == INTR_ON);
if (ticks < 0)
return;
struct asleep_thread st;
st.end_at = start + ticks;
sema_init (&st.semaphore, 0);
old_level = intr_disable ();
list_insert_ordered (&sleeping_threads, &st.elem, &sleeping_threads_less,
NULL);
intr_set_level (old_level);
sema_down (&st.semaphore);
}
/* Sleeps for approximately MS milliseconds. Interrupts must be
turned on. */
void
timer_msleep (int64_t ms)
{
real_time_sleep (ms, 1000);
}
/* Sleeps for approximately US microseconds. Interrupts must be
turned on. */
void
timer_usleep (int64_t us)
{
real_time_sleep (us, 1000 * 1000);
}
/* Sleeps for approximately NS nanoseconds. Interrupts must be
turned on. */
void
timer_nsleep (int64_t ns)
{
real_time_sleep (ns, 1000 * 1000 * 1000);
}
/* Busy-waits for approximately MS milliseconds. Interrupts need
not be turned on.
Busy waiting wastes CPU cycles, and busy waiting with
interrupts off for the interval between timer ticks or longer
will cause timer ticks to be lost. Thus, use timer_msleep()
instead if interrupts are enabled. */
void
timer_mdelay (int64_t ms)
{
real_time_delay (ms, 1000);
}
/* Sleeps for approximately US microseconds. Interrupts need not
be turned on.
Busy waiting wastes CPU cycles, and busy waiting with
interrupts off for the interval between timer ticks or longer
will cause timer ticks to be lost. Thus, use timer_usleep()
instead if interrupts are enabled. */
void
timer_udelay (int64_t us)
{
real_time_delay (us, 1000 * 1000);
}
/* Sleeps execution for approximately NS nanoseconds. Interrupts
need not be turned on.
Busy waiting wastes CPU cycles, and busy waiting with
interrupts off for the interval between timer ticks or longer
will cause timer ticks to be lost. Thus, use timer_nsleep()
instead if interrupts are enabled.*/
void
timer_ndelay (int64_t ns)
{
real_time_delay (ns, 1000 * 1000 * 1000);
}
/* Prints timer statistics. */
void
timer_print_stats (void)
{
printf ("Timer: %"PRId64" ticks\n", timer_ticks ());
}
/* Timer interrupt handler. */
static void
timer_interrupt (struct intr_frame *args UNUSED)
{
ticks++;
for (struct list_elem *e = list_begin (&sleeping_threads);
e != list_end (&sleeping_threads); e = list_next (e))
{
struct asleep_thread *st = list_entry (e, struct asleep_thread, elem);
if (ticks >= st->end_at)
{
list_remove (&st->elem);
sema_up (&st->semaphore);
}
else
break;
}
thread_tick ();
}
/* Returns true if LOOPS iterations waits for more than one timer
tick, otherwise false. */
static bool
too_many_loops (unsigned loops)
{
/* Wait for a timer tick. */
int64_t start = ticks;
while (ticks == start)
barrier ();
/* Run LOOPS loops. */
start = ticks;
busy_wait (loops);
/* If the tick count changed, we iterated too long. */
barrier ();
return start != ticks;
}
/* Iterates through a simple loop LOOPS times, for implementing
brief delays.
Marked NO_INLINE because code alignment can significantly
affect timings, so that if this function was inlined
differently in different places the results would be difficult
to predict. */
static void NO_INLINE
busy_wait (int64_t loops)
{
while (loops-- > 0)
barrier ();
}
/* Sleep for approximately NUM/DENOM seconds. */
static void
real_time_sleep (int64_t num, int32_t denom)
{
/* Convert NUM/DENOM seconds into timer ticks, rounding down.
(NUM / DENOM) s
---------------------- = NUM * TIMER_FREQ / DENOM ticks.
1 s / TIMER_FREQ ticks
*/
int64_t ticks = num * TIMER_FREQ / denom;
ASSERT (intr_get_level () == INTR_ON);
if (ticks > 0)
{
/* We're waiting for at least one full timer tick. Use
timer_sleep() because it will yield the CPU to other
processes. */
timer_sleep (ticks);
}
else
{
/* Otherwise, use a busy-wait loop for more accurate
sub-tick timing. */
real_time_delay (num, denom);
}
}
/* Busy-wait for approximately NUM/DENOM seconds. */
static void
real_time_delay (int64_t num, int32_t denom)
{
/* Scale the numerator and denominator down by 1000 to avoid
the possibility of overflow. */
ASSERT (denom % 1000 == 0);
busy_wait (loops_per_tick * num / 1000 * TIMER_FREQ / (denom / 1000));
}
/* list_less_func for sleeping_threads list */
bool
sleeping_threads_less (const struct list_elem *a, const struct list_elem *b,
void *aux UNUSED)
{
struct asleep_thread *sta = list_entry (a, struct asleep_thread, elem);
struct asleep_thread *stb = list_entry (b, struct asleep_thread, elem);
return sta->end_at < stb->end_at;
}

View File

@@ -0,0 +1,29 @@
#ifndef DEVICES_TIMER_H
#define DEVICES_TIMER_H
#include <round.h>
#include <stdint.h>
/* Number of timer interrupts per second. */
#define TIMER_FREQ 100
void timer_init (void);
void timer_calibrate (void);
int64_t timer_ticks (void);
int64_t timer_elapsed (int64_t);
/* Sleep and yield the CPU to other threads. */
void timer_sleep (int64_t ticks);
void timer_msleep (int64_t milliseconds);
void timer_usleep (int64_t microseconds);
void timer_nsleep (int64_t nanoseconds);
/* Busy waits. */
void timer_mdelay (int64_t milliseconds);
void timer_udelay (int64_t microseconds);
void timer_ndelay (int64_t nanoseconds);
void timer_print_stats (void);
#endif /* devices/timer.h */

View File

@@ -0,0 +1,172 @@
#include "devices/vga.h"
#include <round.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include "devices/speaker.h"
#include "threads/io.h"
#include "threads/interrupt.h"
#include "threads/vaddr.h"
/* VGA text screen support. See [FREEVGA] for more information. */
/* Number of columns and rows on the text display. */
#define COL_CNT 80
#define ROW_CNT 25
/* Current cursor position. (0,0) is in the upper left corner of
the display. */
static size_t cx, cy;
/* Attribute value for gray text on a black background. */
#define GRAY_ON_BLACK 0x07
/* Framebuffer. See [FREEVGA] under "VGA Text Mode Operation".
The character at (x,y) is fb[y][x][0].
The attribute at (x,y) is fb[y][x][1]. */
static uint8_t (*fb)[COL_CNT][2];
static void clear_row (size_t y);
static void cls (void);
static void newline (void);
static void move_cursor (void);
static void find_cursor (size_t *x, size_t *y);
/* Initializes the VGA text display. */
static void
init (void)
{
/* Already initialized? */
static bool inited;
if (!inited)
{
fb = ptov (0xb8000);
find_cursor (&cx, &cy);
inited = true;
}
}
/* Writes C to the VGA text display, interpreting control
characters in the conventional ways. */
void
vga_putc (int c)
{
/* Disable interrupts to lock out interrupt handlers
that might write to the console. */
enum intr_level old_level = intr_disable ();
init ();
switch (c)
{
case '\n':
newline ();
break;
case '\f':
cls ();
break;
case '\b':
if (cx > 0)
cx--;
break;
case '\r':
cx = 0;
break;
case '\t':
cx = ROUND_UP (cx + 1, 8);
if (cx >= COL_CNT)
newline ();
break;
case '\a':
intr_set_level (old_level);
speaker_beep ();
intr_disable ();
break;
default:
fb[cy][cx][0] = c;
fb[cy][cx][1] = GRAY_ON_BLACK;
if (++cx >= COL_CNT)
newline ();
break;
}
/* Update cursor position. */
move_cursor ();
intr_set_level (old_level);
}
/* Clears the screen and moves the cursor to the upper left. */
static void
cls (void)
{
size_t y;
for (y = 0; y < ROW_CNT; y++)
clear_row (y);
cx = cy = 0;
move_cursor ();
}
/* Clears row Y to spaces. */
static void
clear_row (size_t y)
{
size_t x;
for (x = 0; x < COL_CNT; x++)
{
fb[y][x][0] = ' ';
fb[y][x][1] = GRAY_ON_BLACK;
}
}
/* Advances the cursor to the first column in the next line on
the screen. If the cursor is already on the last line on the
screen, scrolls the screen upward one line. */
static void
newline (void)
{
cx = 0;
cy++;
if (cy >= ROW_CNT)
{
cy = ROW_CNT - 1;
memmove (&fb[0], &fb[1], sizeof fb[0] * (ROW_CNT - 1));
clear_row (ROW_CNT - 1);
}
}
/* Moves the hardware cursor to (cx,cy). */
static void
move_cursor (void)
{
/* See [FREEVGA] under "Manipulating the Text-mode Cursor". */
uint16_t cp = cx + COL_CNT * cy;
outw (0x3d4, 0x0e | (cp & 0xff00));
outw (0x3d4, 0x0f | (cp << 8));
}
/* Reads the current hardware cursor position into (*X,*Y). */
static void
find_cursor (size_t *x, size_t *y)
{
/* See [FREEVGA] under "Manipulating the Text-mode Cursor". */
uint16_t cp;
outb (0x3d4, 0x0e);
cp = inb (0x3d5) << 8;
outb (0x3d4, 0x0f);
cp |= inb (0x3d5);
*x = cp % COL_CNT;
*y = cp / COL_CNT;
}

View File

@@ -0,0 +1,6 @@
#ifndef DEVICES_VGA_H
#define DEVICES_VGA_H
void vga_putc (int);
#endif /* devices/vga.h */

View File

@@ -0,0 +1,189 @@
#include <stdint.h>
/* On x86, division of one 64-bit integer by another cannot be
done with a single instruction or a short sequence. Thus, GCC
implements 64-bit division and remainder operations through
function calls. These functions are normally obtained from
libgcc, which is automatically included by GCC in any link
that it does.
Some x86-64 machines, however, have a compiler and utilities
that can generate 32-bit x86 code without having any of the
necessary libraries, including libgcc. Thus, we can make
PintOS work on these machines by simply implementing our own
64-bit division routines, which are the only routines from
libgcc that PintOS requires.
Completeness is another reason to include these routines. If
PintOS is completely self-contained, then that makes it that
much less mysterious. */
/* Uses x86 DIVL instruction to divide 64-bit N by 32-bit D to
yield a 32-bit quotient. Returns the quotient.
Traps with a divide error (#DE) if the quotient does not fit
in 32 bits. */
static inline uint32_t
divl (uint64_t n, uint32_t d)
{
uint32_t n1 = n >> 32;
uint32_t n0 = n;
uint32_t q, r;
asm ("divl %4"
: "=d" (r), "=a" (q)
: "0" (n1), "1" (n0), "rm" (d));
return q;
}
/* Returns the number of leading zero bits in X,
which must be nonzero. */
static int
nlz (uint32_t x)
{
/* This technique is portable, but there are better ways to do
it on particular systems. With sufficiently new enough GCC,
you can use __builtin_clz() to take advantage of GCC's
knowledge of how to do it. Or you can use the x86 BSR
instruction directly. */
int n = 0;
if (x <= 0x0000FFFF)
{
n += 16;
x <<= 16;
}
if (x <= 0x00FFFFFF)
{
n += 8;
x <<= 8;
}
if (x <= 0x0FFFFFFF)
{
n += 4;
x <<= 4;
}
if (x <= 0x3FFFFFFF)
{
n += 2;
x <<= 2;
}
if (x <= 0x7FFFFFFF)
n++;
return n;
}
/* Divides unsigned 64-bit N by unsigned 64-bit D and returns the
quotient. */
static uint64_t
udiv64 (uint64_t n, uint64_t d)
{
if ((d >> 32) == 0)
{
/* Proof of correctness:
Let n, d, b, n1, and n0 be defined as in this function.
Let [x] be the "floor" of x. Let T = b[n1/d]. Assume d
nonzero. Then:
[n/d] = [n/d] - T + T
= [n/d - T] + T by (1) below
= [(b*n1 + n0)/d - T] + T by definition of n
= [(b*n1 + n0)/d - dT/d] + T
= [(b(n1 - d[n1/d]) + n0)/d] + T
= [(b[n1 % d] + n0)/d] + T, by definition of %
which is the expression calculated below.
(1) Note that for any real x, integer i: [x] + i = [x + i].
To prevent divl() from trapping, [(b[n1 % d] + n0)/d] must
be less than b. Assume that [n1 % d] and n0 take their
respective maximum values of d - 1 and b - 1:
[(b(d - 1) + (b - 1))/d] < b
<=> [(bd - 1)/d] < b
<=> [b - 1/d] < b
which is a tautology.
Therefore, this code is correct and will not trap. */
uint64_t b = 1ULL << 32;
uint32_t n1 = n >> 32;
uint32_t n0 = n;
uint32_t d0 = d;
return divl (b * (n1 % d0) + n0, d0) + b * (n1 / d0);
}
else
{
/* Based on the algorithm and proof available from
http://www.hackersdelight.org/revisions.pdf. */
if (n < d)
return 0;
else
{
uint32_t d1 = d >> 32;
int s = nlz (d1);
uint64_t q = divl (n >> 1, (d << s) >> 32) >> (31 - s);
return n - (q - 1) * d < d ? q - 1 : q;
}
}
}
/* Divides unsigned 64-bit N by unsigned 64-bit D and returns the
remainder. */
static uint32_t
umod64 (uint64_t n, uint64_t d)
{
return n - d * udiv64 (n, d);
}
/* Divides signed 64-bit N by signed 64-bit D and returns the
quotient. */
static int64_t
sdiv64 (int64_t n, int64_t d)
{
uint64_t n_abs = n >= 0 ? (uint64_t) n : -(uint64_t) n;
uint64_t d_abs = d >= 0 ? (uint64_t) d : -(uint64_t) d;
uint64_t q_abs = udiv64 (n_abs, d_abs);
return (n < 0) == (d < 0) ? (int64_t) q_abs : -(int64_t) q_abs;
}
/* Divides signed 64-bit N by signed 64-bit D and returns the
remainder. */
static int32_t
smod64 (int64_t n, int64_t d)
{
return n - d * sdiv64 (n, d);
}
/* These are the routines that GCC calls. */
long long __divdi3 (long long n, long long d);
long long __moddi3 (long long n, long long d);
unsigned long long __udivdi3 (unsigned long long n, unsigned long long d);
unsigned long long __umoddi3 (unsigned long long n, unsigned long long d);
/* Signed 64-bit division. */
long long
__divdi3 (long long n, long long d)
{
return sdiv64 (n, d);
}
/* Signed 64-bit remainder. */
long long
__moddi3 (long long n, long long d)
{
return smod64 (n, d);
}
/* Unsigned 64-bit division. */
unsigned long long
__udivdi3 (unsigned long long n, unsigned long long d)
{
return udiv64 (n, d);
}
/* Unsigned 64-bit remainder. */
unsigned long long
__umoddi3 (unsigned long long n, unsigned long long d)
{
return umod64 (n, d);
}

View File

@@ -0,0 +1,28 @@
#ifndef __LIB_CTYPE_H
#define __LIB_CTYPE_H
static inline int islower (int c) { return c >= 'a' && c <= 'z'; }
static inline int isupper (int c) { return c >= 'A' && c <= 'Z'; }
static inline int isalpha (int c) { return islower (c) || isupper (c); }
static inline int isdigit (int c) { return c >= '0' && c <= '9'; }
static inline int isalnum (int c) { return isalpha (c) || isdigit (c); }
static inline int isxdigit (int c) {
return isdigit (c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
static inline int isspace (int c) {
return (c == ' ' || c == '\f' || c == '\n'
|| c == '\r' || c == '\t' || c == '\v');
}
static inline int isblank (int c) { return c == ' ' || c == '\t'; }
static inline int isgraph (int c) { return c > 32 && c < 127; }
static inline int isprint (int c) { return c >= 32 && c < 127; }
static inline int iscntrl (int c) { return (c >= 0 && c < 32) || c == 127; }
static inline int isascii (int c) { return c >= 0 && c < 128; }
static inline int ispunct (int c) {
return isprint (c) && !isalnum (c) && !isspace (c);
}
static inline int tolower (int c) { return isupper (c) ? c - 'A' + 'a' : c; }
static inline int toupper (int c) { return islower (c) ? c - 'a' + 'A' : c; }
#endif /* lib/ctype.h */

View File

@@ -0,0 +1,32 @@
#include <debug.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
/* Prints the call stack, that is, a list of addresses, one in
each of the functions we are nested within. gdb or addr2line
may be applied to kernel.o to translate these into file names,
line numbers, and function names. */
void
debug_backtrace (void)
{
static bool explained;
void **frame;
printf ("Call stack: %p", __builtin_return_address (0));
for (frame = __builtin_frame_address (1);
(uintptr_t) frame >= 0x1000 && frame[0] != NULL;
frame = frame[0])
printf (" %p", frame[1]);
printf (".\n");
if (!explained)
{
explained = true;
printf ("The `backtrace' program can make call stacks useful.\n"
"Read \"Backtraces\" in the \"Debugging Tools\" chapter\n"
"of the PintOS documentation for more information.\n");
}
}

View File

@@ -0,0 +1,39 @@
#ifndef __LIB_DEBUG_H
#define __LIB_DEBUG_H
/* GCC lets us add "attributes" to functions, function
parameters, etc. to indicate their properties.
See the GCC manual for details. */
#define UNUSED __attribute__ ((unused))
#define NO_RETURN __attribute__ ((noreturn))
#define NO_INLINE __attribute__ ((noinline))
#define PRINTF_FORMAT(FMT, FIRST) __attribute__ ((format (printf, FMT, FIRST)))
/* Halts the OS, printing the source file name, line number, and
function name, plus a user-specific message. */
#define PANIC(...) debug_panic (__FILE__, __LINE__, __func__, __VA_ARGS__)
void debug_panic (const char *file, int line, const char *function,
const char *message, ...) PRINTF_FORMAT (4, 5) NO_RETURN;
void debug_backtrace (void);
void debug_backtrace_all (void);
#endif
/* This is outside the header guard so that debug.h may be
included multiple times with different settings of NDEBUG. */
#undef ASSERT
#undef NOT_REACHED
#ifndef NDEBUG
#define ASSERT(CONDITION) \
if (CONDITION) { } else { \
PANIC ("assertion `%s' failed.", #CONDITION); \
}
#define NOT_REACHED() PANIC ("executed an unreachable statement");
#else
#define ASSERT(CONDITION) ((void) 0)
#define NOT_REACHED() for (;;)
#endif /* lib/debug.h */

View File

@@ -0,0 +1,48 @@
#ifndef __LIB_INTTYPES_H
#define __LIB_INTTYPES_H
#include <stdint.h>
#define PRId8 "hhd"
#define PRIi8 "hhi"
#define PRIo8 "hho"
#define PRIu8 "hhu"
#define PRIx8 "hhx"
#define PRIX8 "hhX"
#define PRId16 "hd"
#define PRIi16 "hi"
#define PRIo16 "ho"
#define PRIu16 "hu"
#define PRIx16 "hx"
#define PRIX16 "hX"
#define PRId32 "d"
#define PRIi32 "i"
#define PRIo32 "o"
#define PRIu32 "u"
#define PRIx32 "x"
#define PRIX32 "X"
#define PRId64 "lld"
#define PRIi64 "lli"
#define PRIo64 "llo"
#define PRIu64 "llu"
#define PRIx64 "llx"
#define PRIX64 "llX"
#define PRIdMAX "jd"
#define PRIiMAX "ji"
#define PRIoMAX "jo"
#define PRIuMAX "ju"
#define PRIxMAX "jx"
#define PRIXMAX "jX"
#define PRIdPTR "td"
#define PRIiPTR "ti"
#define PRIoPTR "to"
#define PRIuPTR "tu"
#define PRIxPTR "tx"
#define PRIXPTR "tX"
#endif /* lib/inttypes.h */

View File

@@ -0,0 +1,372 @@
#include "bitmap.h"
#include <debug.h>
#include <limits.h>
#include <round.h>
#include <stdio.h>
#include "threads/malloc.h"
#ifdef FILESYS
#include "filesys/file.h"
#endif
/* Element type.
This must be an unsigned integer type at least as wide as int.
Each bit represents one bit in the bitmap.
If bit 0 in an element represents bit K in the bitmap,
then bit 1 in the element represents bit K+1 in the bitmap,
and so on. */
typedef unsigned long elem_type;
/* Number of bits in an element. */
#define ELEM_BITS (sizeof (elem_type) * CHAR_BIT)
/* From the outside, a bitmap is an array of bits. From the
inside, it's an array of elem_type (defined above) that
simulates an array of bits. */
struct bitmap
{
size_t bit_cnt; /* Number of bits. */
elem_type *bits; /* Elements that represent bits. */
};
/* Returns the index of the element that contains the bit
numbered BIT_IDX. */
static inline size_t
elem_idx (size_t bit_idx)
{
return bit_idx / ELEM_BITS;
}
/* Returns an elem_type where only the bit corresponding to
BIT_IDX is turned on. */
static inline elem_type
bit_mask (size_t bit_idx)
{
return (elem_type) 1 << (bit_idx % ELEM_BITS);
}
/* Returns the number of elements required for BIT_CNT bits. */
static inline size_t
elem_cnt (size_t bit_cnt)
{
return DIV_ROUND_UP (bit_cnt, ELEM_BITS);
}
/* Returns the number of bytes required for BIT_CNT bits. */
static inline size_t
byte_cnt (size_t bit_cnt)
{
return sizeof (elem_type) * elem_cnt (bit_cnt);
}
/* Returns a bit mask in which the bits actually used in the last
element of B's bits are set to 1 and the rest are set to 0. */
static inline elem_type
last_mask (const struct bitmap *b)
{
int last_bits = b->bit_cnt % ELEM_BITS;
return last_bits ? ((elem_type) 1 << last_bits) - 1 : (elem_type) -1;
}
/* Creation and destruction. */
/* Initializes B to be a bitmap of BIT_CNT bits
and sets all of its bits to false.
Returns true if success, false if memory allocation
failed. */
struct bitmap *
bitmap_create (size_t bit_cnt)
{
struct bitmap *b = malloc (sizeof *b);
if (b != NULL)
{
b->bit_cnt = bit_cnt;
b->bits = malloc (byte_cnt (bit_cnt));
if (b->bits != NULL || bit_cnt == 0)
{
bitmap_set_all (b, false);
return b;
}
free (b);
}
return NULL;
}
/* Creates and returns a bitmap with BIT_CNT bits in the
BLOCK_SIZE bytes of storage preallocated at BLOCK.
BLOCK_SIZE must be at least bitmap_needed_bytes(BIT_CNT). */
struct bitmap *
bitmap_create_in_buf (size_t bit_cnt, void *block, size_t block_size UNUSED)
{
struct bitmap *b = block;
ASSERT (block_size >= bitmap_buf_size (bit_cnt));
b->bit_cnt = bit_cnt;
b->bits = (elem_type *) (b + 1);
bitmap_set_all (b, false);
return b;
}
/* Returns the number of bytes required to accomodate a bitmap
with BIT_CNT bits (for use with bitmap_create_in_buf()). */
size_t
bitmap_buf_size (size_t bit_cnt)
{
return sizeof (struct bitmap) + byte_cnt (bit_cnt);
}
/* Destroys bitmap B, freeing its storage.
Not for use on bitmaps created by
bitmap_create_preallocated(). */
void
bitmap_destroy (struct bitmap *b)
{
if (b != NULL)
{
free (b->bits);
free (b);
}
}
/* Bitmap size. */
/* Returns the number of bits in B. */
size_t
bitmap_size (const struct bitmap *b)
{
return b->bit_cnt;
}
/* Setting and testing single bits. */
/* Atomically sets the bit numbered IDX in B to VALUE. */
void
bitmap_set (struct bitmap *b, size_t idx, bool value)
{
ASSERT (b != NULL);
ASSERT (idx < b->bit_cnt);
if (value)
bitmap_mark (b, idx);
else
bitmap_reset (b, idx);
}
/* Atomically sets the bit numbered BIT_IDX in B to true. */
void
bitmap_mark (struct bitmap *b, size_t bit_idx)
{
size_t idx = elem_idx (bit_idx);
elem_type mask = bit_mask (bit_idx);
/* This is equivalent to `b->bits[idx] |= mask' except that it
is guaranteed to be atomic on a uniprocessor machine. See
the description of the OR instruction in [IA32-v2b]. */
asm ("orl %1, %0" : "=m" (b->bits[idx]) : "r" (mask) : "cc");
}
/* Atomically sets the bit numbered BIT_IDX in B to false. */
void
bitmap_reset (struct bitmap *b, size_t bit_idx)
{
size_t idx = elem_idx (bit_idx);
elem_type mask = bit_mask (bit_idx);
/* This is equivalent to `b->bits[idx] &= ~mask' except that it
is guaranteed to be atomic on a uniprocessor machine. See
the description of the AND instruction in [IA32-v2a]. */
asm ("andl %1, %0" : "=m" (b->bits[idx]) : "r" (~mask) : "cc");
}
/* Atomically toggles the bit numbered IDX in B;
that is, if it is true, makes it false,
and if it is false, makes it true. */
void
bitmap_flip (struct bitmap *b, size_t bit_idx)
{
size_t idx = elem_idx (bit_idx);
elem_type mask = bit_mask (bit_idx);
/* This is equivalent to `b->bits[idx] ^= mask' except that it
is guaranteed to be atomic on a uniprocessor machine. See
the description of the XOR instruction in [IA32-v2b]. */
asm ("xorl %1, %0" : "=m" (b->bits[idx]) : "r" (mask) : "cc");
}
/* Returns the value of the bit numbered IDX in B. */
bool
bitmap_test (const struct bitmap *b, size_t idx)
{
ASSERT (b != NULL);
ASSERT (idx < b->bit_cnt);
return (b->bits[elem_idx (idx)] & bit_mask (idx)) != 0;
}
/* Setting and testing multiple bits. */
/* Sets all bits in B to VALUE. */
void
bitmap_set_all (struct bitmap *b, bool value)
{
ASSERT (b != NULL);
bitmap_set_multiple (b, 0, bitmap_size (b), value);
}
/* Sets the CNT bits starting at START in B to VALUE. */
void
bitmap_set_multiple (struct bitmap *b, size_t start, size_t cnt, bool value)
{
size_t i;
ASSERT (b != NULL);
ASSERT (start <= b->bit_cnt);
ASSERT (start + cnt <= b->bit_cnt);
for (i = 0; i < cnt; i++)
bitmap_set (b, start + i, value);
}
/* Returns the number of bits in B between START and START + CNT,
exclusive, that are set to VALUE. */
size_t
bitmap_count (const struct bitmap *b, size_t start, size_t cnt, bool value)
{
size_t i, value_cnt;
ASSERT (b != NULL);
ASSERT (start <= b->bit_cnt);
ASSERT (start + cnt <= b->bit_cnt);
value_cnt = 0;
for (i = 0; i < cnt; i++)
if (bitmap_test (b, start + i) == value)
value_cnt++;
return value_cnt;
}
/* Returns true if any bits in B between START and START + CNT,
exclusive, are set to VALUE, and false otherwise. */
bool
bitmap_contains (const struct bitmap *b, size_t start, size_t cnt, bool value)
{
size_t i;
ASSERT (b != NULL);
ASSERT (start <= b->bit_cnt);
ASSERT (start + cnt <= b->bit_cnt);
for (i = 0; i < cnt; i++)
if (bitmap_test (b, start + i) == value)
return true;
return false;
}
/* Returns true if any bits in B between START and START + CNT,
exclusive, are set to true, and false otherwise.*/
bool
bitmap_any (const struct bitmap *b, size_t start, size_t cnt)
{
return bitmap_contains (b, start, cnt, true);
}
/* Returns true if no bits in B between START and START + CNT,
exclusive, are set to true, and false otherwise.*/
bool
bitmap_none (const struct bitmap *b, size_t start, size_t cnt)
{
return !bitmap_contains (b, start, cnt, true);
}
/* Returns true if every bit in B between START and START + CNT,
exclusive, is set to true, and false otherwise. */
bool
bitmap_all (const struct bitmap *b, size_t start, size_t cnt)
{
return !bitmap_contains (b, start, cnt, false);
}
/* Finding set or unset bits. */
/* Finds and returns the starting index of the first group of CNT
consecutive bits in B at or after START that are all set to
VALUE.
If there is no such group, returns BITMAP_ERROR. */
size_t
bitmap_scan (const struct bitmap *b, size_t start, size_t cnt, bool value)
{
ASSERT (b != NULL);
ASSERT (start <= b->bit_cnt);
if (cnt <= b->bit_cnt)
{
size_t last = b->bit_cnt - cnt;
size_t i;
for (i = start; i <= last; i++)
if (!bitmap_contains (b, i, cnt, !value))
return i;
}
return BITMAP_ERROR;
}
/* Finds the first group of CNT consecutive bits in B at or after
START that are all set to VALUE, flips them all to !VALUE,
and returns the index of the first bit in the group.
If there is no such group, returns BITMAP_ERROR.
If CNT is zero, returns 0.
Bits are set atomically, but testing bits is not atomic with
setting them. */
size_t
bitmap_scan_and_flip (struct bitmap *b, size_t start, size_t cnt, bool value)
{
size_t idx = bitmap_scan (b, start, cnt, value);
if (idx != BITMAP_ERROR)
bitmap_set_multiple (b, idx, cnt, !value);
return idx;
}
/* File input and output. */
#ifdef FILESYS
/* Returns the number of bytes needed to store B in a file. */
size_t
bitmap_file_size (const struct bitmap *b)
{
return byte_cnt (b->bit_cnt);
}
/* Reads B from FILE. Returns true if successful, false
otherwise. */
bool
bitmap_read (struct bitmap *b, struct file *file)
{
bool success = true;
if (b->bit_cnt > 0)
{
off_t size = byte_cnt (b->bit_cnt);
success = file_read_at (file, b->bits, size, 0) == size;
b->bits[elem_cnt (b->bit_cnt) - 1] &= last_mask (b);
}
return success;
}
/* Writes B to FILE. Return true if successful, false
otherwise. */
bool
bitmap_write (const struct bitmap *b, struct file *file)
{
off_t size = byte_cnt (b->bit_cnt);
return file_write_at (file, b->bits, size, 0) == size;
}
#endif /* FILESYS */
/* Debugging. */
/* Dumps the contents of B to the console as hexadecimal. */
void
bitmap_dump (const struct bitmap *b)
{
hex_dump (0, b->bits, byte_cnt (b->bit_cnt), false);
}

View File

@@ -0,0 +1,51 @@
#ifndef __LIB_KERNEL_BITMAP_H
#define __LIB_KERNEL_BITMAP_H
#include <stdbool.h>
#include <stddef.h>
#include <inttypes.h>
/* Bitmap abstract data type. */
/* Creation and destruction. */
struct bitmap *bitmap_create (size_t bit_cnt);
struct bitmap *bitmap_create_in_buf (size_t bit_cnt, void *, size_t byte_cnt);
size_t bitmap_buf_size (size_t bit_cnt);
void bitmap_destroy (struct bitmap *);
/* Bitmap size. */
size_t bitmap_size (const struct bitmap *);
/* Setting and testing single bits. */
void bitmap_set (struct bitmap *, size_t idx, bool);
void bitmap_mark (struct bitmap *, size_t idx);
void bitmap_reset (struct bitmap *, size_t idx);
void bitmap_flip (struct bitmap *, size_t idx);
bool bitmap_test (const struct bitmap *, size_t idx);
/* Setting and testing multiple bits. */
void bitmap_set_all (struct bitmap *, bool);
void bitmap_set_multiple (struct bitmap *, size_t start, size_t cnt, bool);
size_t bitmap_count (const struct bitmap *, size_t start, size_t cnt, bool);
bool bitmap_contains (const struct bitmap *, size_t start, size_t cnt, bool);
bool bitmap_any (const struct bitmap *, size_t start, size_t cnt);
bool bitmap_none (const struct bitmap *, size_t start, size_t cnt);
bool bitmap_all (const struct bitmap *, size_t start, size_t cnt);
/* Finding set or unset bits. */
#define BITMAP_ERROR SIZE_MAX
size_t bitmap_scan (const struct bitmap *, size_t start, size_t cnt, bool);
size_t bitmap_scan_and_flip (struct bitmap *, size_t start, size_t cnt, bool);
/* File input and output. */
#ifdef FILESYS
struct file;
size_t bitmap_file_size (const struct bitmap *);
bool bitmap_read (struct bitmap *, struct file *);
bool bitmap_write (const struct bitmap *, struct file *);
#endif
/* Debugging. */
void bitmap_dump (const struct bitmap *);
#endif /* lib/kernel/bitmap.h */

View File

@@ -0,0 +1,191 @@
#include <console.h>
#include <stdarg.h>
#include <stdio.h>
#include "devices/serial.h"
#include "devices/vga.h"
#include "threads/init.h"
#include "threads/interrupt.h"
#include "threads/synch.h"
static void vprintf_helper (char, void *);
static void putchar_have_lock (uint8_t c);
/* The console lock.
Both the vga and serial layers do their own locking, so it's
safe to call them at any time.
But this lock is useful to prevent simultaneous printf() calls
from mixing their output, which looks confusing. */
static struct lock console_lock;
/* True in ordinary circumstances: we want to use the console
lock to avoid mixing output between threads, as explained
above.
False in early boot before the point that locks are functional
or the console lock has been initialized, or after a kernel
panics. In the former case, taking the lock would cause an
assertion failure, which in turn would cause a panic, turning
it into the latter case. In the latter case, if it is a buggy
lock_acquire() implementation that caused the panic, we'll
likely just recurse. */
static bool use_console_lock;
/* It's possible, if you add enough debug output to PintOS, to
try to recursively grab console_lock from a single thread. As
a real example, I added a printf() call to palloc_free().
Here's a real backtrace that resulted:
lock_console()
vprintf()
printf() - palloc() tries to grab the lock again
palloc_free()
thread_schedule_tail() - another thread dying as we switch threads
schedule()
thread_yield()
intr_handler() - timer interrupt
intr_set_level()
serial_putc()
putchar_have_lock()
putbuf()
sys_write() - one process writing to the console
syscall_handler()
intr_handler()
This kind of thing is very difficult to debug, so we avoid the
problem by simulating a recursive lock with a depth
counter. */
static int console_lock_depth;
/* Number of characters written to console. */
static int64_t write_cnt;
/* Enable console locking. */
void
console_init (void)
{
lock_init (&console_lock);
use_console_lock = true;
}
/* Notifies the console that a kernel panic is underway,
which warns it to avoid trying to take the console lock from
now on. */
void
console_panic (void)
{
use_console_lock = false;
}
/* Prints console statistics. */
void
console_print_stats (void)
{
printf ("Console: %lld characters output\n", write_cnt);
}
/* Acquires the console lock. */
static void
acquire_console (void)
{
if (!intr_context () && use_console_lock)
{
if (lock_held_by_current_thread (&console_lock))
console_lock_depth++;
else
lock_acquire (&console_lock);
}
}
/* Releases the console lock. */
static void
release_console (void)
{
if (!intr_context () && use_console_lock)
{
if (console_lock_depth > 0)
console_lock_depth--;
else
lock_release (&console_lock);
}
}
/* Returns true if the current thread has the console lock,
false otherwise. */
static bool
console_locked_by_current_thread (void)
{
return (intr_context ()
|| !use_console_lock
|| lock_held_by_current_thread (&console_lock));
}
/* The standard vprintf() function,
which is like printf() but uses a va_list.
Writes its output to both vga display and serial port. */
int
vprintf (const char *format, va_list args)
{
int char_cnt = 0;
acquire_console ();
__vprintf (format, args, vprintf_helper, &char_cnt);
release_console ();
return char_cnt;
}
/* Writes string S to the console, followed by a new-line
character. */
int
puts (const char *s)
{
acquire_console ();
while (*s != '\0')
putchar_have_lock (*s++);
putchar_have_lock ('\n');
release_console ();
return 0;
}
/* Writes the N characters in BUFFER to the console. */
void
putbuf (const char *buffer, size_t n)
{
acquire_console ();
while (n-- > 0)
putchar_have_lock (*buffer++);
release_console ();
}
/* Writes C to the vga display and serial port. */
int
putchar (int c)
{
acquire_console ();
putchar_have_lock (c);
release_console ();
return c;
}
/* Helper function for vprintf(). */
static void
vprintf_helper (char c, void *char_cnt_)
{
int *char_cnt = char_cnt_;
(*char_cnt)++;
putchar_have_lock (c);
}
/* Writes C to the vga display and serial port.
The caller has already acquired the console lock if
appropriate. */
static void
putchar_have_lock (uint8_t c)
{
ASSERT (console_locked_by_current_thread ());
write_cnt++;
serial_putc (c);
vga_putc (c);
}

View File

@@ -0,0 +1,8 @@
#ifndef __LIB_KERNEL_CONSOLE_H
#define __LIB_KERNEL_CONSOLE_H
void console_init (void);
void console_panic (void);
void console_print_stats (void);
#endif /* lib/kernel/console.h */

View File

@@ -0,0 +1,123 @@
#include <debug.h>
#include <console.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include "threads/init.h"
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "threads/switch.h"
#include "threads/vaddr.h"
#include "devices/serial.h"
#include "devices/shutdown.h"
/* Halts the OS, printing the source file name, line number, and
function name, plus a user-specific message. */
void
debug_panic (const char *file, int line, const char *function,
const char *message, ...)
{
static int level;
va_list args;
intr_disable ();
console_panic ();
level++;
if (level == 1)
{
printf ("Kernel PANIC at %s:%d in %s(): ", file, line, function);
va_start (args, message);
vprintf (message, args);
printf ("\n");
va_end (args);
debug_backtrace ();
}
else if (level == 2)
printf ("Kernel PANIC recursion at %s:%d in %s().\n",
file, line, function);
else
{
/* Don't print anything: that's probably why we recursed. */
}
serial_flush ();
shutdown ();
for (;;);
}
/* Print call stack of a thread.
The thread may be running, ready, or blocked. */
static void
print_stacktrace(struct thread *t, void *aux UNUSED)
{
void *retaddr = NULL, **frame = NULL;
const char *status = "UNKNOWN";
switch (t->status) {
case THREAD_RUNNING:
status = "RUNNING";
break;
case THREAD_READY:
status = "READY";
break;
case THREAD_BLOCKED:
status = "BLOCKED";
break;
default:
break;
}
printf ("Call stack of thread `%s' (status %s):", t->name, status);
if (t == thread_current())
{
frame = __builtin_frame_address (1);
retaddr = __builtin_return_address (0);
}
else
{
/* Retrieve the values of the base and instruction pointers
as they were saved when this thread called switch_threads. */
struct switch_threads_frame * saved_frame;
saved_frame = (struct switch_threads_frame *)t->stack;
/* Skip threads if they have been added to the all threads
list, but have never been scheduled.
We can identify because their `stack' member either points
at the top of their kernel stack page, or the
switch_threads_frame's 'eip' member points at switch_entry.
See also threads.c. */
if (t->stack == (uint8_t *)t + PGSIZE || saved_frame->eip == switch_entry)
{
printf (" thread was never scheduled.\n");
return;
}
frame = (void **) saved_frame->ebp;
retaddr = (void *) saved_frame->eip;
}
printf (" %p", retaddr);
for (; (uintptr_t) frame >= 0x1000 && frame[0] != NULL; frame = frame[0])
printf (" %p", frame[1]);
printf (".\n");
}
/* Prints call stack of all threads. */
void
debug_backtrace_all (void)
{
enum intr_level oldlevel = intr_disable ();
thread_foreach (print_stacktrace, 0);
intr_set_level (oldlevel);
}

View File

@@ -0,0 +1,437 @@
/* Hash table.
This data structure is thoroughly documented in the Tour of
PintOS for Task 3.
See hash.h for basic information. */
#include "hash.h"
#include "../debug.h"
#include "threads/malloc.h"
#define list_elem_to_hash_elem(LIST_ELEM) \
list_entry(LIST_ELEM, struct hash_elem, list_elem)
static struct list *find_bucket (struct hash *, struct hash_elem *);
static struct hash_elem *find_elem (struct hash *, struct list *,
struct hash_elem *);
static void insert_elem (struct hash *, struct list *, struct hash_elem *);
static void remove_elem (struct hash *, struct hash_elem *);
static void rehash (struct hash *);
/* Initializes hash table H to compute hash values using HASH and
compare hash elements using LESS, given auxiliary data AUX. */
bool
hash_init (struct hash *h,
hash_hash_func *hash, hash_less_func *less, void *aux)
{
h->elem_cnt = 0;
h->bucket_cnt = 4;
h->buckets = malloc (sizeof *h->buckets * h->bucket_cnt);
h->hash = hash;
h->less = less;
h->aux = aux;
if (h->buckets != NULL)
{
hash_clear (h, NULL);
return true;
}
else
return false;
}
/* Removes all the elements from H.
If DESTRUCTOR is non-null, then it is called for each element
in the hash. DESTRUCTOR may, if appropriate, deallocate the
memory used by the hash element. However, modifying hash
table H while hash_clear() is running, using any of the
functions hash_clear(), hash_destroy(), hash_insert(),
hash_replace(), or hash_delete(), yields undefined behavior,
whether done in DESTRUCTOR or elsewhere. */
void
hash_clear (struct hash *h, hash_action_func *destructor)
{
size_t i;
for (i = 0; i < h->bucket_cnt; i++)
{
struct list *bucket = &h->buckets[i];
if (destructor != NULL)
while (!list_empty (bucket))
{
struct list_elem *list_elem = list_pop_front (bucket);
struct hash_elem *hash_elem = list_elem_to_hash_elem (list_elem);
destructor (hash_elem, h->aux);
}
list_init (bucket);
}
h->elem_cnt = 0;
}
/* Destroys hash table H.
If DESTRUCTOR is non-null, then it is first called for each
element in the hash. DESTRUCTOR may, if appropriate,
deallocate the memory used by the hash element. However,
modifying hash table H while hash_clear() is running, using
any of the functions hash_clear(), hash_destroy(),
hash_insert(), hash_replace(), or hash_delete(), yields
undefined behavior, whether done in DESTRUCTOR or
elsewhere. */
void
hash_destroy (struct hash *h, hash_action_func *destructor)
{
if (destructor != NULL)
hash_clear (h, destructor);
free (h->buckets);
}
/* Inserts NEW into hash table H and returns a null pointer, if
no equal element is already in the table.
If an equal element is already in the table, returns it
without inserting NEW. */
struct hash_elem *
hash_insert (struct hash *h, struct hash_elem *new)
{
struct list *bucket = find_bucket (h, new);
struct hash_elem *old = find_elem (h, bucket, new);
if (old == NULL)
insert_elem (h, bucket, new);
rehash (h);
return old;
}
/* Inserts NEW into hash table H, replacing any equal element
already in the table, which is returned. */
struct hash_elem *
hash_replace (struct hash *h, struct hash_elem *new)
{
struct list *bucket = find_bucket (h, new);
struct hash_elem *old = find_elem (h, bucket, new);
if (old != NULL)
remove_elem (h, old);
insert_elem (h, bucket, new);
rehash (h);
return old;
}
/* Finds and returns an element equal to E in hash table H, or a
null pointer if no equal element exists in the table. */
struct hash_elem *
hash_find (struct hash *h, struct hash_elem *e)
{
return find_elem (h, find_bucket (h, e), e);
}
/* Finds, removes, and returns an element equal to E in hash
table H. Returns a null pointer if no equal element existed
in the table.
If the elements of the hash table are dynamically allocated,
or own resources that are, then it is the caller's
responsibility to deallocate them. */
struct hash_elem *
hash_delete (struct hash *h, struct hash_elem *e)
{
struct hash_elem *found = find_elem (h, find_bucket (h, e), e);
if (found != NULL)
{
remove_elem (h, found);
rehash (h);
}
return found;
}
/* Calls ACTION for each element in hash table H in arbitrary
order.
Modifying hash table H while hash_apply() is running, using
any of the functions hash_clear(), hash_destroy(),
hash_insert(), hash_replace(), or hash_delete(), yields
undefined behavior, whether done from ACTION or elsewhere. */
void
hash_apply (struct hash *h, hash_action_func *action)
{
size_t i;
ASSERT (action != NULL);
for (i = 0; i < h->bucket_cnt; i++)
{
struct list *bucket = &h->buckets[i];
struct list_elem *elem, *next;
for (elem = list_begin (bucket); elem != list_end (bucket); elem = next)
{
next = list_next (elem);
action (list_elem_to_hash_elem (elem), h->aux);
}
}
}
/* Initializes I for iterating hash table H.
Iteration idiom:
struct hash_iterator i;
hash_first (&i, h);
while (hash_next (&i))
{
struct foo *f = hash_entry (hash_cur (&i), struct foo, elem);
...do something with f...
}
Modifying hash table H during iteration, using any of the
functions hash_clear(), hash_destroy(), hash_insert(),
hash_replace(), or hash_delete(), invalidates all
iterators. */
void
hash_first (struct hash_iterator *i, struct hash *h)
{
ASSERT (i != NULL);
ASSERT (h != NULL);
i->hash = h;
i->bucket = i->hash->buckets;
i->elem = list_elem_to_hash_elem (list_head (i->bucket));
}
/* Advances I to the next element in the hash table and returns
it. Returns a null pointer if no elements are left. Elements
are returned in arbitrary order.
Modifying a hash table H during iteration, using any of the
functions hash_clear(), hash_destroy(), hash_insert(),
hash_replace(), or hash_delete(), invalidates all
iterators. */
struct hash_elem *
hash_next (struct hash_iterator *i)
{
ASSERT (i != NULL);
i->elem = list_elem_to_hash_elem (list_next (&i->elem->list_elem));
while (i->elem == list_elem_to_hash_elem (list_end (i->bucket)))
{
if (++i->bucket >= i->hash->buckets + i->hash->bucket_cnt)
{
i->elem = NULL;
break;
}
i->elem = list_elem_to_hash_elem (list_begin (i->bucket));
}
return i->elem;
}
/* Returns the current element in the hash table iteration, or a
null pointer at the end of the table. Undefined behavior
after calling hash_first() but before hash_next(). */
struct hash_elem *
hash_cur (struct hash_iterator *i)
{
return i->elem;
}
/* Returns the number of elements in H. */
size_t
hash_size (struct hash *h)
{
return h->elem_cnt;
}
/* Returns true if H contains no elements, false otherwise. */
bool
hash_empty (struct hash *h)
{
return h->elem_cnt == 0;
}
/* Fowler-Noll-Vo hash constants, for 32-bit word sizes. */
#define FNV_32_PRIME 16777619u
#define FNV_32_BASIS 2166136261u
/* Returns a hash of the SIZE bytes in BUF. */
unsigned
hash_bytes (const void *buf_, size_t size)
{
/* Fowler-Noll-Vo 32-bit hash, for bytes. */
const unsigned char *buf = buf_;
unsigned hash;
ASSERT (buf != NULL);
hash = FNV_32_BASIS;
while (size-- > 0)
hash = (hash * FNV_32_PRIME) ^ *buf++;
return hash;
}
/* Returns a hash of string S. */
unsigned
hash_string (const char *s_)
{
const unsigned char *s = (const unsigned char *) s_;
unsigned hash;
ASSERT (s != NULL);
hash = FNV_32_BASIS;
while (*s != '\0')
hash = (hash * FNV_32_PRIME) ^ *s++;
return hash;
}
/* Returns a hash of integer I. */
unsigned
hash_int (int i)
{
return hash_bytes (&i, sizeof i);
}
/* Returns a hash of pointer P */
unsigned
hash_ptr (const void *p)
{
return hash_bytes (&p, sizeof p);
}
/* Returns the bucket in H that E belongs in. */
static struct list *
find_bucket (struct hash *h, struct hash_elem *e)
{
size_t bucket_idx = h->hash (e, h->aux) & (h->bucket_cnt - 1);
return &h->buckets[bucket_idx];
}
/* Searches BUCKET in H for a hash element equal to E. Returns
it if found or a null pointer otherwise. */
static struct hash_elem *
find_elem (struct hash *h, struct list *bucket, struct hash_elem *e)
{
struct list_elem *i;
for (i = list_begin (bucket); i != list_end (bucket); i = list_next (i))
{
struct hash_elem *hi = list_elem_to_hash_elem (i);
if (!h->less (hi, e, h->aux) && !h->less (e, hi, h->aux))
return hi;
}
return NULL;
}
/* Returns X with its lowest-order bit set to 1 turned off. */
static inline size_t
turn_off_least_1bit (size_t x)
{
return x & (x - 1);
}
/* Returns true if X is a power of 2, otherwise false. */
static inline size_t
is_power_of_2 (size_t x)
{
return x != 0 && turn_off_least_1bit (x) == 0;
}
/* Element per bucket ratios. */
#define MIN_ELEMS_PER_BUCKET 1 /* Elems/bucket < 1: reduce # of buckets. */
#define BEST_ELEMS_PER_BUCKET 2 /* Ideal elems/bucket. */
#define MAX_ELEMS_PER_BUCKET 4 /* Elems/bucket > 4: increase # of buckets. */
/* Changes the number of buckets in hash table H to match the
ideal. This function can fail because of an out-of-memory
condition, but that'll just make hash accesses less efficient;
we can still continue. */
static void
rehash (struct hash *h)
{
size_t old_bucket_cnt, new_bucket_cnt;
struct list *new_buckets, *old_buckets;
size_t i;
ASSERT (h != NULL);
/* Save old bucket info for later use. */
old_buckets = h->buckets;
old_bucket_cnt = h->bucket_cnt;
/* Calculate the number of buckets to use now.
We want one bucket for about every BEST_ELEMS_PER_BUCKET.
We must have at least four buckets, and the number of
buckets must be a power of 2. */
new_bucket_cnt = h->elem_cnt / BEST_ELEMS_PER_BUCKET;
if (new_bucket_cnt < 4)
new_bucket_cnt = 4;
while (!is_power_of_2 (new_bucket_cnt))
new_bucket_cnt = turn_off_least_1bit (new_bucket_cnt);
/* Don't do anything if the bucket count wouldn't change. */
if (new_bucket_cnt == old_bucket_cnt)
return;
/* Allocate new buckets and initialize them as empty. */
new_buckets = malloc (sizeof *new_buckets * new_bucket_cnt);
if (new_buckets == NULL)
{
/* Allocation failed. This means that use of the hash table will
be less efficient. However, it is still usable, so
there's no reason for it to be an error. */
return;
}
for (i = 0; i < new_bucket_cnt; i++)
list_init (&new_buckets[i]);
/* Install new bucket info. */
h->buckets = new_buckets;
h->bucket_cnt = new_bucket_cnt;
/* Move each old element into the appropriate new bucket. */
for (i = 0; i < old_bucket_cnt; i++)
{
struct list *old_bucket;
struct list_elem *elem, *next;
old_bucket = &old_buckets[i];
for (elem = list_begin (old_bucket);
elem != list_end (old_bucket); elem = next)
{
struct list *new_bucket
= find_bucket (h, list_elem_to_hash_elem (elem));
next = list_next (elem);
list_remove (elem);
list_push_front (new_bucket, elem);
}
}
free (old_buckets);
}
/* Inserts E into BUCKET (in hash table H). */
static void
insert_elem (struct hash *h, struct list *bucket, struct hash_elem *e)
{
h->elem_cnt++;
list_push_front (bucket, &e->list_elem);
}
/* Removes E from hash table H. */
static void
remove_elem (struct hash *h, struct hash_elem *e)
{
h->elem_cnt--;
list_remove (&e->list_elem);
}

View File

@@ -0,0 +1,104 @@
#ifndef __LIB_KERNEL_HASH_H
#define __LIB_KERNEL_HASH_H
/* Hash table.
This data structure is thoroughly documented in the PintOS
manual: Appendix A Reference Guide (A.8 Hash Table)
This is a standard hash table with chaining. To locate an
element in the table, we compute a hash function over the
element's data and use that as an index into an array of
doubly linked lists, then linearly search the list.
The chain lists do not use dynamic allocation. Instead, each
structure that can potentially be in a hash must embed a
struct hash_elem member. All of the hash functions operate on
these `struct hash_elem's. The hash_entry macro allows
conversion from a struct hash_elem back to a structure object
that contains it. This is the same technique used in the
linked list implementation. Refer to lib/kernel/list.h for a
detailed explanation. */
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "list.h"
/* Hash element. */
struct hash_elem
{
struct list_elem list_elem;
};
/* Converts pointer to hash element HASH_ELEM into a pointer to
the structure that HASH_ELEM is embedded inside. Supply the
name of the outer structure STRUCT and the member name MEMBER
of the hash element. See the big comment at the top of the
file for an example. */
#define hash_entry(HASH_ELEM, STRUCT, MEMBER) \
((STRUCT *) ((uint8_t *) &(HASH_ELEM)->list_elem \
- offsetof (STRUCT, MEMBER.list_elem)))
/* Computes and returns the hash value for hash element E, given
auxiliary data AUX. */
typedef unsigned hash_hash_func (const struct hash_elem *e, void *aux);
/* Compares the value of two hash elements A and B, given
auxiliary data AUX. Returns true if A is less than B, or
false if A is greater than or equal to B. */
typedef bool hash_less_func (const struct hash_elem *a,
const struct hash_elem *b,
void *aux);
/* Performs some operation on hash element E, given auxiliary
data AUX. */
typedef void hash_action_func (struct hash_elem *e, void *aux);
/* Hash table. */
struct hash
{
size_t elem_cnt; /* Number of elements in table. */
size_t bucket_cnt; /* Number of buckets, a power of 2. */
struct list *buckets; /* Array of `bucket_cnt' lists. */
hash_hash_func *hash; /* Hash function. */
hash_less_func *less; /* Comparison function. */
void *aux; /* Auxiliary data for `hash' and `less'. */
};
/* A hash table iterator. */
struct hash_iterator
{
struct hash *hash; /* The hash table. */
struct list *bucket; /* Current bucket. */
struct hash_elem *elem; /* Current hash element in current bucket. */
};
/* Basic life cycle. */
bool hash_init (struct hash *, hash_hash_func *, hash_less_func *, void *aux);
void hash_clear (struct hash *, hash_action_func *);
void hash_destroy (struct hash *, hash_action_func *);
/* Search, insertion, deletion. */
struct hash_elem *hash_insert (struct hash *, struct hash_elem *);
struct hash_elem *hash_replace (struct hash *, struct hash_elem *);
struct hash_elem *hash_find (struct hash *, struct hash_elem *);
struct hash_elem *hash_delete (struct hash *, struct hash_elem *);
/* Iteration. */
void hash_apply (struct hash *, hash_action_func *);
void hash_first (struct hash_iterator *, struct hash *);
struct hash_elem *hash_next (struct hash_iterator *);
struct hash_elem *hash_cur (struct hash_iterator *);
/* Information. */
size_t hash_size (struct hash *);
bool hash_empty (struct hash *);
/* Sample hash functions. */
unsigned hash_bytes (const void *, size_t);
unsigned hash_string (const char *);
unsigned hash_int (int);
unsigned hash_ptr (const void *);
#endif /* lib/kernel/hash.h */

View File

@@ -0,0 +1,527 @@
#include "list.h"
#include "../debug.h"
/* Our doubly linked lists have two header elements: the "head"
just before the first element and the "tail" just after the
last element. The `prev' link of the front header is null, as
is the `next' link of the back header. Their other two links
point toward each other via the interior elements of the list.
An empty list looks like this:
+------+ +------+
<---| head |<--->| tail |--->
+------+ +------+
A list with two elements in it looks like this:
+------+ +-------+ +-------+ +------+
<---| head |<--->| 1 |<--->| 2 |<--->| tail |--->
+------+ +-------+ +-------+ +------+
The symmetry of this arrangement eliminates lots of special
cases in list processing. For example, take a look at
list_remove(): it takes only two pointer assignments and no
conditionals. That's a lot simpler than the code would be
without header elements.
(Because only one of the pointers in each header element is used,
we could in fact combine them into a single header element
without sacrificing this simplicity. But using two separate
elements allows us to do a little bit of checking on some
operations, which can be valuable.) */
static bool is_sorted (struct list_elem *a, struct list_elem *b,
list_less_func *less, void *aux) UNUSED;
/* Returns true if ELEM is a head, false otherwise. */
static inline bool
is_head (struct list_elem *elem)
{
return elem != NULL && elem->prev == NULL && elem->next != NULL;
}
/* Returns true if ELEM is an interior element,
false otherwise. */
static inline bool
is_interior (struct list_elem *elem)
{
return elem != NULL && elem->prev != NULL && elem->next != NULL;
}
/* Returns true if ELEM is a tail, false otherwise. */
static inline bool
is_tail (struct list_elem *elem)
{
return elem != NULL && elem->prev != NULL && elem->next == NULL;
}
/* Initializes LIST as an empty list. */
void
list_init (struct list *list)
{
ASSERT (list != NULL);
list->head.prev = NULL;
list->head.next = &list->tail;
list->tail.prev = &list->head;
list->tail.next = NULL;
}
/* Returns the beginning of LIST. */
struct list_elem *
list_begin (struct list *list)
{
ASSERT (list != NULL);
return list->head.next;
}
/* Returns the element after ELEM in its list. If ELEM is the
last element in its list, returns the list tail. Results are
undefined if ELEM is itself a list tail. */
struct list_elem *
list_next (struct list_elem *elem)
{
ASSERT (is_head (elem) || is_interior (elem));
return elem->next;
}
/* Returns LIST's tail.
list_end() is often used in iterating through a list from
front to back. See the big comment at the top of list.h for
an example. */
struct list_elem *
list_end (struct list *list)
{
ASSERT (list != NULL);
return &list->tail;
}
/* Returns the LIST's reverse beginning, for iterating through
LIST in reverse order, from back to front. */
struct list_elem *
list_rbegin (struct list *list)
{
ASSERT (list != NULL);
return list->tail.prev;
}
/* Returns the element before ELEM in its list. If ELEM is the
first element in its list, returns the list head. Results are
undefined if ELEM is itself a list head. */
struct list_elem *
list_prev (struct list_elem *elem)
{
ASSERT (is_interior (elem) || is_tail (elem));
return elem->prev;
}
/* Returns LIST's head.
list_rend() is often used in iterating through a list in
reverse order, from back to front. Here's typical usage,
following the example from the top of list.h:
for (e = list_rbegin (&foo_list); e != list_rend (&foo_list);
e = list_prev (e))
{
struct foo *f = list_entry (e, struct foo, elem);
...do something with f...
}
*/
struct list_elem *
list_rend (struct list *list)
{
ASSERT (list != NULL);
return &list->head;
}
/* Return's LIST's head.
list_head() can be used for an alternate style of iterating
through a list, e.g.:
e = list_head (&list);
while ((e = list_next (e)) != list_end (&list))
{
...
}
*/
struct list_elem *
list_head (struct list *list)
{
ASSERT (list != NULL);
return &list->head;
}
/* Return's LIST's tail. */
struct list_elem *
list_tail (struct list *list)
{
ASSERT (list != NULL);
return &list->tail;
}
/* Inserts ELEM just before BEFORE, which may be either an
interior element or a tail. The latter case is equivalent to
list_push_back(). Undefined behavior if ELEM is already in the list. */
void
list_insert (struct list_elem *before, struct list_elem *elem)
{
ASSERT (is_interior (before) || is_tail (before));
ASSERT (elem != NULL);
// Sanity checks to prevent (some) loop lists
ASSERT (before != elem);
ASSERT (before->prev != elem);
elem->prev = before->prev;
elem->next = before;
before->prev->next = elem;
before->prev = elem;
}
/* Removes elements FIRST though LAST (exclusive) from their
current list, then inserts them just before BEFORE, which may
be either an interior element or a tail. */
void
list_splice (struct list_elem *before,
struct list_elem *first, struct list_elem *last)
{
ASSERT (is_interior (before) || is_tail (before));
if (first == last)
return;
last = list_prev (last);
ASSERT (is_interior (first));
ASSERT (is_interior (last));
/* Cleanly remove FIRST...LAST from its current list. */
first->prev->next = last->next;
last->next->prev = first->prev;
/* Splice FIRST...LAST into new list. */
first->prev = before->prev;
last->next = before;
before->prev->next = first;
before->prev = last;
}
/* Inserts ELEM at the beginning of LIST, so that it becomes the
front in LIST. */
void
list_push_front (struct list *list, struct list_elem *elem)
{
list_insert (list_begin (list), elem);
}
/* Inserts ELEM at the end of LIST, so that it becomes the
back in LIST. */
void
list_push_back (struct list *list, struct list_elem *elem)
{
list_insert (list_end (list), elem);
}
/* Removes ELEM from its list and returns the element that
followed it. Undefined behavior if ELEM is not in a list.
A list element must be treated very carefully after removing
it from its list. Calling list_next() or list_prev() on ELEM
will return the item that was previously before or after ELEM,
but, e.g., list_prev(list_next(ELEM)) is no longer ELEM!
The list_remove() return value provides a convenient way to
iterate and remove elements from a list:
for (e = list_begin (&list); e != list_end (&list); e = list_remove (e))
{
...do something with e...
}
If you need to free() elements of the list then you need to be
more conservative. Here's an alternate strategy that works
even in that case:
while (!list_empty (&list))
{
struct list_elem *e = list_pop_front (&list);
...do something with e...
}
*/
struct list_elem *
list_remove (struct list_elem *elem)
{
ASSERT (is_interior (elem));
elem->prev->next = elem->next;
elem->next->prev = elem->prev;
return elem->next;
}
/* Removes the front element from LIST and returns it.
Undefined behavior if LIST is empty before removal. */
struct list_elem *
list_pop_front (struct list *list)
{
struct list_elem *front = list_front (list);
list_remove (front);
return front;
}
/* Removes the back element from LIST and returns it.
Undefined behavior if LIST is empty before removal. */
struct list_elem *
list_pop_back (struct list *list)
{
struct list_elem *back = list_back (list);
list_remove (back);
return back;
}
/* Returns the front element in LIST.
Undefined behavior if LIST is empty. */
struct list_elem *
list_front (struct list *list)
{
ASSERT (!list_empty (list));
return list->head.next;
}
/* Returns the back element in LIST.
Undefined behavior if LIST is empty. */
struct list_elem *
list_back (struct list *list)
{
ASSERT (!list_empty (list));
return list->tail.prev;
}
/* Returns the number of elements in LIST.
Runs in O(n) in the number of elements. */
size_t
list_size (struct list *list)
{
struct list_elem *e;
size_t cnt = 0;
for (e = list_begin (list); e != list_end (list); e = list_next (e))
cnt++;
return cnt;
}
/* Returns true if LIST is empty, false otherwise. */
bool
list_empty (struct list *list)
{
return list_begin (list) == list_end (list);
}
/* Swaps the `struct list_elem *'s that A and B point to. */
static void
swap (struct list_elem **a, struct list_elem **b)
{
struct list_elem *t = *a;
*a = *b;
*b = t;
}
/* Reverses the order of LIST. */
void
list_reverse (struct list *list)
{
if (!list_empty (list))
{
struct list_elem *e;
for (e = list_begin (list); e != list_end (list); e = e->prev)
swap (&e->prev, &e->next);
swap (&list->head.next, &list->tail.prev);
swap (&list->head.next->prev, &list->tail.prev->next);
}
}
/* Returns true only if the list elements A through B (exclusive)
are in order according to LESS given auxiliary data AUX. */
static bool
is_sorted (struct list_elem *a, struct list_elem *b,
list_less_func *less, void *aux)
{
if (a != b)
while ((a = list_next (a)) != b)
if (less (a, list_prev (a), aux))
return false;
return true;
}
/* Finds a run, starting at A and ending not after B, of list
elements that are in nondecreasing order according to LESS
given auxiliary data AUX. Returns the (exclusive) end of the
run.
A through B (exclusive) must form a non-empty range. */
static struct list_elem *
find_end_of_run (struct list_elem *a, struct list_elem *b,
list_less_func *less, void *aux)
{
ASSERT (a != NULL);
ASSERT (b != NULL);
ASSERT (less != NULL);
ASSERT (a != b);
do
{
a = list_next (a);
}
while (a != b && !less (a, list_prev (a), aux));
return a;
}
/* Merges A0 through A1B0 (exclusive) with A1B0 through B1
(exclusive) to form a combined range also ending at B1
(exclusive). Both input ranges must be nonempty and sorted in
nondecreasing order according to LESS given auxiliary data
AUX. The output range will be sorted the same way. */
static void
inplace_merge (struct list_elem *a0, struct list_elem *a1b0,
struct list_elem *b1,
list_less_func *less, void *aux)
{
ASSERT (a0 != NULL);
ASSERT (a1b0 != NULL);
ASSERT (b1 != NULL);
ASSERT (less != NULL);
ASSERT (is_sorted (a0, a1b0, less, aux));
ASSERT (is_sorted (a1b0, b1, less, aux));
while (a0 != a1b0 && a1b0 != b1)
if (!less (a1b0, a0, aux))
a0 = list_next (a0);
else
{
a1b0 = list_next (a1b0);
list_splice (a0, list_prev (a1b0), a1b0);
}
}
/* Sorts LIST according to LESS given auxiliary data AUX, using a
natural iterative merge sort that runs in O(n lg n) time and
O(1) space in the number of elements in LIST. */
void
list_sort (struct list *list, list_less_func *less, void *aux)
{
size_t output_run_cnt; /* Number of runs output in current pass. */
ASSERT (list != NULL);
ASSERT (less != NULL);
/* Pass over the list repeatedly, merging adjacent runs of
nondecreasing elements, until only one run is left. */
do
{
struct list_elem *a0; /* Start of first run. */
struct list_elem *a1b0; /* End of first run, start of second. */
struct list_elem *b1; /* End of second run. */
output_run_cnt = 0;
for (a0 = list_begin (list); a0 != list_end (list); a0 = b1)
{
/* Each iteration produces one output run. */
output_run_cnt++;
/* Locate two adjacent runs of nondecreasing elements
A0...A1B0 and A1B0...B1. */
a1b0 = find_end_of_run (a0, list_end (list), less, aux);
if (a1b0 == list_end (list))
break;
b1 = find_end_of_run (a1b0, list_end (list), less, aux);
/* Merge the runs. */
inplace_merge (a0, a1b0, b1, less, aux);
}
}
while (output_run_cnt > 1);
ASSERT (is_sorted (list_begin (list), list_end (list), less, aux));
}
/* Inserts ELEM in the proper position in LIST, which must be
sorted according to LESS given auxiliary data AUX.
Runs in O(n) average case in the number of elements in LIST. */
void
list_insert_ordered (struct list *list, struct list_elem *elem,
list_less_func *less, void *aux)
{
struct list_elem *e;
ASSERT (list != NULL);
ASSERT (elem != NULL);
ASSERT (less != NULL);
for (e = list_begin (list); e != list_end (list); e = list_next (e))
if (less (elem, e, aux))
break;
return list_insert (e, elem);
}
/* Iterates through LIST and removes all but the first in each
set of adjacent elements that are equal according to LESS
given auxiliary data AUX. If DUPLICATES is non-null, then the
elements from LIST are appended to DUPLICATES. */
void
list_unique (struct list *list, struct list *duplicates,
list_less_func *less, void *aux)
{
struct list_elem *elem, *next;
ASSERT (list != NULL);
ASSERT (less != NULL);
if (list_empty (list))
return;
elem = list_begin (list);
while ((next = list_next (elem)) != list_end (list))
if (!less (elem, next, aux) && !less (next, elem, aux))
{
list_remove (next);
if (duplicates != NULL)
list_push_back (duplicates, next);
}
else
elem = next;
}
/* Returns the element in LIST with the largest value according
to LESS given auxiliary data AUX. If there is more than one
maximum, returns the one that appears earlier in the list. If
the list is empty, returns its tail. */
struct list_elem *
list_max (struct list *list, list_less_func *less, void *aux)
{
struct list_elem *max = list_begin (list);
if (max != list_end (list))
{
struct list_elem *e;
for (e = list_next (max); e != list_end (list); e = list_next (e))
if (less (max, e, aux))
max = e;
}
return max;
}
/* Returns the element in LIST with the smallest value according
to LESS given auxiliary data AUX. If there is more than one
minimum, returns the one that appears earlier in the list. If
the list is empty, returns its tail. */
struct list_elem *
list_min (struct list *list, list_less_func *less, void *aux)
{
struct list_elem *min = list_begin (list);
if (min != list_end (list))
{
struct list_elem *e;
for (e = list_next (min); e != list_end (list); e = list_next (e))
if (less (e, min, aux))
min = e;
}
return min;
}

View File

@@ -0,0 +1,181 @@
#ifndef __LIB_KERNEL_LIST_H
#define __LIB_KERNEL_LIST_H
/* Doubly linked list.
This implementation of a doubly linked list does not require
use of dynamically allocated memory. Instead, each structure
that is a potential list element must embed a struct list_elem
member. All of the list functions operate on these `struct
list_elem's. The list_entry macro allows conversion from a
struct list_elem back to a structure object that contains it.
For example, suppose there is a need for a list of `struct
foo'. `struct foo' should contain a `struct list_elem'
member, like so:
struct foo
{
struct list_elem elem;
int bar;
...other members...
};
Then a list of `struct foo' can be be declared and initialized
like so:
struct list foo_list;
list_init (&foo_list);
Iteration is a typical situation where it is necessary to
convert from a struct list_elem back to its enclosing
structure. Here's an example using foo_list:
struct list_elem *e;
for (e = list_begin (&foo_list); e != list_end (&foo_list);
e = list_next (e))
{
struct foo *f = list_entry (e, struct foo, elem);
...do something with f...
}
You can find real examples of list usage throughout the
source; for example, malloc.c, palloc.c, and thread.c in the
threads directory all use lists.
The interface for this list is inspired by the list<> template
in the C++ STL. If you're familiar with list<>, you should
find this easy to use. However, it should be emphasized that
these lists do *no* type checking and can't do much other
correctness checking. If you screw up, it will bite you.
Glossary of list terms:
- "front": The first element in a list. Undefined in an
empty list. Returned by list_front().
- "back": The last element in a list. Undefined in an empty
list. Returned by list_back().
- "tail": The element figuratively just after the last
element of a list. Well defined even in an empty list.
Returned by list_end(). Used as the end sentinel for an
iteration from front to back.
- "beginning": In a non-empty list, the front. In an empty
list, the tail. Returned by list_begin(). Used as the
starting point for an iteration from front to back.
- "head": The element figuratively just before the first
element of a list. Well defined even in an empty list.
Returned by list_rend(). Used as the end sentinel for an
iteration from back to front.
- "reverse beginning": In a non-empty list, the back. In an
empty list, the head. Returned by list_rbegin(). Used as
the starting point for an iteration from back to front.
- "interior element": An element that is not the head or
tail, that is, a real list element. An empty list does
not have any interior elements.
*/
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/* List element. */
struct list_elem
{
struct list_elem *prev; /* Previous list element. */
struct list_elem *next; /* Next list element. */
};
/* List. */
struct list
{
struct list_elem head; /* List head. */
struct list_elem tail; /* List tail. */
};
/* Converts pointer to list element LIST_ELEM into a pointer to
the structure that LIST_ELEM is embedded inside. Supply the
name of the outer structure STRUCT and the member name MEMBER
of the list element. See the big comment at the top of the
file for an example. */
#define list_entry(LIST_ELEM, STRUCT, MEMBER) \
((STRUCT *) ((uint8_t *) &(LIST_ELEM)->next \
- offsetof (STRUCT, MEMBER.next)))
/* List initialization.
A list may be initialized by calling list_init():
struct list my_list;
list_init (&my_list);
or with an initializer using LIST_INITIALIZER:
struct list my_list = LIST_INITIALIZER (my_list); */
#define LIST_INITIALIZER(NAME) { { NULL, &(NAME).tail }, \
{ &(NAME).head, NULL } }
void list_init (struct list *);
/* List traversal. */
struct list_elem *list_begin (struct list *);
struct list_elem *list_next (struct list_elem *);
struct list_elem *list_end (struct list *);
struct list_elem *list_rbegin (struct list *);
struct list_elem *list_prev (struct list_elem *);
struct list_elem *list_rend (struct list *);
struct list_elem *list_head (struct list *);
struct list_elem *list_tail (struct list *);
/* List insertion. */
void list_insert (struct list_elem *, struct list_elem *);
void list_splice (struct list_elem *before,
struct list_elem *first, struct list_elem *last);
void list_push_front (struct list *, struct list_elem *);
void list_push_back (struct list *, struct list_elem *);
/* List removal. */
struct list_elem *list_remove (struct list_elem *);
struct list_elem *list_pop_front (struct list *);
struct list_elem *list_pop_back (struct list *);
/* List elements. */
struct list_elem *list_front (struct list *);
struct list_elem *list_back (struct list *);
/* List properties. */
size_t list_size (struct list *);
bool list_empty (struct list *);
/* Miscellaneous. */
void list_reverse (struct list *);
/* Compares the value of two list elements A and B, given
auxiliary data AUX. Returns true if A is less than B, or
false if A is greater than or equal to B. */
typedef bool list_less_func (const struct list_elem *a,
const struct list_elem *b,
void *aux);
/* Operations on lists with ordered elements. */
void list_sort (struct list *,
list_less_func *, void *aux);
void list_insert_ordered (struct list *, struct list_elem *,
list_less_func *, void *aux);
void list_unique (struct list *, struct list *duplicates,
list_less_func *, void *aux);
/* Max and min. */
struct list_elem *list_max (struct list *, list_less_func *, void *aux);
struct list_elem *list_min (struct list *, list_less_func *, void *aux);
#endif /* lib/kernel/list.h */

View File

@@ -0,0 +1,6 @@
#ifndef __LIB_KERNEL_STDIO_H
#define __LIB_KERNEL_STDIO_H
void putbuf (const char *, size_t);
#endif /* lib/kernel/stdio.h */

View File

@@ -0,0 +1,34 @@
#ifndef __LIB_LIMITS_H
#define __LIB_LIMITS_H
#define CHAR_BIT 8
#define SCHAR_MAX 127
#define SCHAR_MIN (-SCHAR_MAX - 1)
#define UCHAR_MAX 255
#ifdef __CHAR_UNSIGNED__
#define CHAR_MIN 0
#define CHAR_MAX UCHAR_MAX
#else
#define CHAR_MIN SCHAR_MIN
#define CHAR_MAX SCHAR_MAX
#endif
#define SHRT_MAX 32767
#define SHRT_MIN (-SHRT_MAX - 1)
#define USHRT_MAX 65535
#define INT_MAX 2147483647
#define INT_MIN (-INT_MAX - 1)
#define UINT_MAX 4294967295U
#define LONG_MAX 2147483647L
#define LONG_MIN (-LONG_MAX - 1)
#define ULONG_MAX 4294967295UL
#define LLONG_MAX 9223372036854775807LL
#define LLONG_MIN (-LLONG_MAX - 1)
#define ULLONG_MAX 18446744073709551615ULL
#endif /* lib/limits.h */

View File

@@ -0,0 +1,10 @@
#ifndef __LIB_PACKED_H
#define __LIB_PACKED_H
/* The "packed" attribute, when applied to a structure, prevents
GCC from inserting padding bytes between or after structure
members. It must be specified at the time of the structure's
definition, normally just after the closing brace. */
#define PACKED __attribute__ ((packed))
#endif /* lib/packed.h */

View File

@@ -0,0 +1,83 @@
#include "random.h"
#include <stdbool.h>
#include <stdint.h>
#include "debug.h"
/* RC4-based pseudo-random number generator (PRNG).
RC4 is a stream cipher. We're not using it here for its
cryptographic properties, but because it is easy to implement
and its output is plenty random for non-cryptographic
purposes.
See http://en.wikipedia.org/wiki/RC4_(cipher) for information
on RC4.*/
/* RC4 state. */
static uint8_t s[256]; /* S[]. */
static uint8_t s_i, s_j; /* i, j. */
/* Already initialized? */
static bool inited;
/* Swaps the bytes pointed to by A and B. */
static inline void
swap_byte (uint8_t *a, uint8_t *b)
{
uint8_t t = *a;
*a = *b;
*b = t;
}
/* Initializes or reinitializes the PRNG with the given SEED. */
void
random_init (unsigned seed)
{
uint8_t *seedp = (uint8_t *) &seed;
int i;
uint8_t j;
for (i = 0; i < 256; i++)
s[i] = i;
for (i = j = 0; i < 256; i++)
{
j += s[i] + seedp[i % sizeof seed];
swap_byte (s + i, s + j);
}
s_i = s_j = 0;
inited = true;
}
/* Writes SIZE random bytes into BUF. */
void
random_bytes (void *buf_, size_t size)
{
uint8_t *buf;
if (!inited)
random_init (0);
for (buf = buf_; size-- > 0; buf++)
{
uint8_t s_k;
s_i++;
s_j += s[s_i];
swap_byte (s + s_i, s + s_j);
s_k = s[s_i] + s[s_j];
*buf = s[s_k];
}
}
/* Returns a pseudo-random unsigned long.
Use random_ulong() % n to obtain a random number in the range
0...n (exclusive). */
unsigned long
random_ulong (void)
{
unsigned long ul;
random_bytes (&ul, sizeof ul);
return ul;
}

View File

@@ -0,0 +1,10 @@
#ifndef __LIB_RANDOM_H
#define __LIB_RANDOM_H
#include <stddef.h>
void random_init (unsigned seed);
void random_bytes (void *, size_t);
unsigned long random_ulong (void);
#endif /* lib/random.h */

View File

@@ -0,0 +1,18 @@
#ifndef __LIB_ROUND_H
#define __LIB_ROUND_H
/* Yields X rounded up to the nearest multiple of STEP.
For X >= 0, STEP >= 1 only. */
#define ROUND_UP(X, STEP) (((X) + (STEP) - 1) / (STEP) * (STEP))
/* Yields X divided by STEP, rounded up.
For X >= 0, STEP >= 1 only. */
#define DIV_ROUND_UP(X, STEP) (((X) + (STEP) - 1) / (STEP))
/* Yields X rounded down to the nearest multiple of STEP.
For X >= 0, STEP >= 1 only. */
#define ROUND_DOWN(X, STEP) ((X) / (STEP) * (STEP))
/* There is no DIV_ROUND_DOWN. It would be simply X / STEP. */
#endif /* lib/round.h */

View File

@@ -0,0 +1,14 @@
#ifndef __LIB_STDARG_H
#define __LIB_STDARG_H
/* GCC has <stdarg.h> functionality as built-ins,
so all we need is to use it. */
typedef __builtin_va_list va_list;
#define va_start(LIST, ARG) __builtin_va_start (LIST, ARG)
#define va_end(LIST) __builtin_va_end (LIST)
#define va_arg(LIST, TYPE) __builtin_va_arg (LIST, TYPE)
#define va_copy(DST, SRC) __builtin_va_copy (DST, SRC)
#endif /* lib/stdarg.h */

View File

@@ -0,0 +1,9 @@
#ifndef __LIB_STDBOOL_H
#define __LIB_STDBOOL_H
#define bool _Bool
#define true 1
#define false 0
#define __bool_true_false_are_defined 1
#endif /* lib/stdbool.h */

View File

@@ -0,0 +1,12 @@
#ifndef __LIB_STDDEF_H
#define __LIB_STDDEF_H
#define NULL ((void *) 0)
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *) 0)->MEMBER)
/* GCC predefines the types we need for ptrdiff_t and size_t,
so that we don't have to guess. */
typedef __PTRDIFF_TYPE__ ptrdiff_t;
typedef __SIZE_TYPE__ size_t;
#endif /* lib/stddef.h */

View File

@@ -0,0 +1,51 @@
#ifndef __LIB_STDINT_H
#define __LIB_STDINT_H
typedef signed char int8_t;
#define INT8_MAX 127
#define INT8_MIN (-INT8_MAX - 1)
typedef signed short int int16_t;
#define INT16_MAX 32767
#define INT16_MIN (-INT16_MAX - 1)
typedef signed int int32_t;
#define INT32_MAX 2147483647
#define INT32_MIN (-INT32_MAX - 1)
typedef signed long long int int64_t;
#define INT64_MAX 9223372036854775807LL
#define INT64_MIN (-INT64_MAX - 1)
typedef unsigned char uint8_t;
#define UINT8_MAX 255
typedef unsigned short int uint16_t;
#define UINT16_MAX 65535
typedef unsigned int uint32_t;
#define UINT32_MAX 4294967295U
typedef unsigned long long int uint64_t;
#define UINT64_MAX 18446744073709551615ULL
typedef int32_t intptr_t;
#define INTPTR_MIN INT32_MIN
#define INTPTR_MAX INT32_MAX
typedef uint32_t uintptr_t;
#define UINTPTR_MAX UINT32_MAX
typedef int64_t intmax_t;
#define INTMAX_MIN INT64_MIN
#define INTMAX_MAX INT64_MAX
typedef uint64_t uintmax_t;
#define UINTMAX_MAX UINT64_MAX
#define PTRDIFF_MIN INT32_MIN
#define PTRDIFF_MAX INT32_MAX
#define SIZE_MAX UINT32_MAX
#endif /* lib/stdint.h */

View File

@@ -0,0 +1,655 @@
#include <stdio.h>
#include <ctype.h>
#include <inttypes.h>
#include <round.h>
#include <stdint.h>
#include <string.h>
/* Auxiliary data for vsnprintf_helper(). */
struct vsnprintf_aux
{
char *p; /* Current output position. */
int length; /* Length of output string. */
int max_length; /* Max length of output string. */
};
static void vsnprintf_helper (char, void *);
/* Like vprintf(), except that output is stored into BUFFER,
which must have space for BUF_SIZE characters. Writes at most
BUF_SIZE - 1 characters to BUFFER, followed by a null
terminator. BUFFER will always be null-terminated unless
BUF_SIZE is zero. Returns the number of characters that would
have been written to BUFFER, not including a null terminator,
had there been enough room. */
int
vsnprintf (char *buffer, size_t buf_size, const char *format, va_list args)
{
/* Set up aux data for vsnprintf_helper(). */
struct vsnprintf_aux aux;
aux.p = buffer;
aux.length = 0;
aux.max_length = buf_size > 0 ? buf_size - 1 : 0;
/* Do most of the work. */
__vprintf (format, args, vsnprintf_helper, &aux);
/* Add null terminator. */
if (buf_size > 0)
*aux.p = '\0';
return aux.length;
}
/* Helper function for vsnprintf(). */
static void
vsnprintf_helper (char ch, void *aux_)
{
struct vsnprintf_aux *aux = aux_;
if (aux->length++ < aux->max_length)
*aux->p++ = ch;
}
/* Like printf(), except that output is stored into BUFFER,
which must have space for BUF_SIZE characters. Writes at most
BUF_SIZE - 1 characters to BUFFER, followed by a null
terminator. BUFFER will always be null-terminated unless
BUF_SIZE is zero. Returns the number of characters that would
have been written to BUFFER, not including a null terminator,
had there been enough room. */
int
snprintf (char *buffer, size_t buf_size, const char *format, ...)
{
va_list args;
int retval;
va_start (args, format);
retval = vsnprintf (buffer, buf_size, format, args);
va_end (args);
return retval;
}
/* Writes formatted output to the console.
In the kernel, the console is both the video display and first
serial port.
In userspace, the console is file descriptor 1. */
int
printf (const char *format, ...)
{
va_list args;
int retval;
va_start (args, format);
retval = vprintf (format, args);
va_end (args);
return retval;
}
/* printf() formatting internals. */
/* A printf() conversion. */
struct printf_conversion
{
/* Flags. */
enum
{
MINUS = 1 << 0, /* '-' */
PLUS = 1 << 1, /* '+' */
SPACE = 1 << 2, /* ' ' */
POUND = 1 << 3, /* '#' */
ZERO = 1 << 4, /* '0' */
GROUP = 1 << 5 /* '\'' */
}
flags;
/* Minimum field width. */
int width;
/* Numeric precision.
-1 indicates no precision was specified. */
int precision;
/* Type of argument to format. */
enum
{
CHAR = 1, /* hh */
SHORT = 2, /* h */
INT = 3, /* (none) */
INTMAX = 4, /* j */
LONG = 5, /* l */
LONGLONG = 6, /* ll */
PTRDIFFT = 7, /* t */
SIZET = 8 /* z */
}
type;
};
struct integer_base
{
int base; /* Base. */
const char *digits; /* Collection of digits. */
int x; /* `x' character to use, for base 16 only. */
int group; /* Number of digits to group with ' flag. */
};
static const struct integer_base base_d = {10, "0123456789", 0, 3};
static const struct integer_base base_o = {8, "01234567", 0, 3};
static const struct integer_base base_x = {16, "0123456789abcdef", 'x', 4};
static const struct integer_base base_X = {16, "0123456789ABCDEF", 'X', 4};
static const char *parse_conversion (const char *format,
struct printf_conversion *,
va_list *);
static void format_integer (uintmax_t value, bool is_signed, bool negative,
const struct integer_base *,
const struct printf_conversion *,
void (*output) (char, void *), void *aux);
static void output_dup (char ch, size_t cnt,
void (*output) (char, void *), void *aux);
static void format_string (const char *string, int length,
struct printf_conversion *,
void (*output) (char, void *), void *aux);
void
__vprintf (const char *format, va_list args,
void (*output) (char, void *), void *aux)
{
for (; *format != '\0'; format++)
{
struct printf_conversion c;
/* Literally copy non-conversions to output. */
if (*format != '%')
{
output (*format, aux);
continue;
}
format++;
/* %% => %. */
if (*format == '%')
{
output ('%', aux);
continue;
}
/* Parse conversion specifiers. */
format = parse_conversion (format, &c, &args);
/* Do conversion. */
switch (*format)
{
case 'd':
case 'i':
{
/* Signed integer conversions. */
intmax_t value;
switch (c.type)
{
case CHAR:
value = (signed char) va_arg (args, int);
break;
case SHORT:
value = (short) va_arg (args, int);
break;
case INT:
value = va_arg (args, int);
break;
case INTMAX:
value = va_arg (args, intmax_t);
break;
case LONG:
value = va_arg (args, long);
break;
case LONGLONG:
value = va_arg (args, long long);
break;
case PTRDIFFT:
value = va_arg (args, ptrdiff_t);
break;
case SIZET:
value = va_arg (args, size_t);
if (value > SIZE_MAX / 2)
value = value - SIZE_MAX - 1;
break;
default:
NOT_REACHED ();
}
format_integer (value < 0 ? -value : value,
true, value < 0, &base_d, &c, output, aux);
}
break;
case 'o':
case 'u':
case 'x':
case 'X':
{
/* Unsigned integer conversions. */
uintmax_t value;
const struct integer_base *b;
switch (c.type)
{
case CHAR:
value = (unsigned char) va_arg (args, unsigned);
break;
case SHORT:
value = (unsigned short) va_arg (args, unsigned);
break;
case INT:
value = va_arg (args, unsigned);
break;
case INTMAX:
value = va_arg (args, uintmax_t);
break;
case LONG:
value = va_arg (args, unsigned long);
break;
case LONGLONG:
value = va_arg (args, unsigned long long);
break;
case PTRDIFFT:
value = va_arg (args, ptrdiff_t);
#if UINTMAX_MAX != PTRDIFF_MAX
value &= ((uintmax_t) PTRDIFF_MAX << 1) | 1;
#endif
break;
case SIZET:
value = va_arg (args, size_t);
break;
default:
NOT_REACHED ();
}
switch (*format)
{
case 'o': b = &base_o; break;
case 'u': b = &base_d; break;
case 'x': b = &base_x; break;
case 'X': b = &base_X; break;
default: NOT_REACHED ();
}
format_integer (value, false, false, b, &c, output, aux);
}
break;
case 'c':
{
/* Treat character as single-character string. */
char ch = va_arg (args, int);
format_string (&ch, 1, &c, output, aux);
}
break;
case 's':
{
/* String conversion. */
const char *s = va_arg (args, char *);
if (s == NULL)
s = "(null)";
/* Limit string length according to precision.
Note: if c.precision == -1 then strnlen() will get
SIZE_MAX for MAXLEN, which is just what we want. */
format_string (s, strnlen (s, c.precision), &c, output, aux);
}
break;
case 'p':
{
/* Pointer conversion.
Format pointers as %#x. */
void *p = va_arg (args, void *);
c.flags = POUND;
format_integer ((uintptr_t) p, false, false,
&base_x, &c, output, aux);
}
break;
case 'f':
case 'e':
case 'E':
case 'g':
case 'G':
case 'n':
/* We don't support floating-point arithmetic,
and %n can be part of a security hole. */
__printf ("<<no %%%c in kernel>>", output, aux, *format);
break;
default:
__printf ("<<no %%%c conversion>>", output, aux, *format);
break;
}
}
}
/* Parses conversion option characters starting at FORMAT and
initializes C appropriately. Returns the character in FORMAT
that indicates the conversion (e.g. the `d' in `%d'). Uses
*ARGS for `*' field widths and precisions. */
static const char *
parse_conversion (const char *format, struct printf_conversion *c,
va_list *args)
{
/* Parse flag characters. */
c->flags = 0;
for (;;)
{
switch (*format++)
{
case '-':
c->flags |= MINUS;
break;
case '+':
c->flags |= PLUS;
break;
case ' ':
c->flags |= SPACE;
break;
case '#':
c->flags |= POUND;
break;
case '0':
c->flags |= ZERO;
break;
case '\'':
c->flags |= GROUP;
break;
default:
format--;
goto not_a_flag;
}
}
not_a_flag:
if (c->flags & MINUS)
c->flags &= ~ZERO;
if (c->flags & PLUS)
c->flags &= ~SPACE;
/* Parse field width. */
c->width = 0;
if (*format == '*')
{
format++;
c->width = va_arg (*args, int);
}
else
{
for (; isdigit (*format); format++)
c->width = c->width * 10 + *format - '0';
}
if (c->width < 0)
{
c->width = -c->width;
c->flags |= MINUS;
}
/* Parse precision. */
c->precision = -1;
if (*format == '.')
{
format++;
if (*format == '*')
{
format++;
c->precision = va_arg (*args, int);
}
else
{
c->precision = 0;
for (; isdigit (*format); format++)
c->precision = c->precision * 10 + *format - '0';
}
if (c->precision < 0)
c->precision = -1;
}
if (c->precision >= 0)
c->flags &= ~ZERO;
/* Parse type. */
c->type = INT;
switch (*format++)
{
case 'h':
if (*format == 'h')
{
format++;
c->type = CHAR;
}
else
c->type = SHORT;
break;
case 'j':
c->type = INTMAX;
break;
case 'l':
if (*format == 'l')
{
format++;
c->type = LONGLONG;
}
else
c->type = LONG;
break;
case 't':
c->type = PTRDIFFT;
break;
case 'z':
c->type = SIZET;
break;
default:
format--;
break;
}
return format;
}
/* Performs an integer conversion, writing output to OUTPUT with
auxiliary data AUX. The integer converted has absolute value
VALUE. If IS_SIGNED is true, does a signed conversion with
NEGATIVE indicating a negative value; otherwise does an
unsigned conversion and ignores NEGATIVE. The output is done
according to the provided base B. Details of the conversion
are in C. */
static void
format_integer (uintmax_t value, bool is_signed, bool negative,
const struct integer_base *b,
const struct printf_conversion *c,
void (*output) (char, void *), void *aux)
{
char buf[64], *cp; /* Buffer and current position. */
int x; /* `x' character to use or 0 if none. */
int sign; /* Sign character or 0 if none. */
int precision; /* Rendered precision. */
int pad_cnt; /* # of pad characters to fill field width. */
int digit_cnt; /* # of digits output so far. */
/* Determine sign character, if any.
An unsigned conversion will never have a sign character,
even if one of the flags requests one. */
sign = 0;
if (is_signed)
{
if (c->flags & PLUS)
sign = negative ? '-' : '+';
else if (c->flags & SPACE)
sign = negative ? '-' : ' ';
else if (negative)
sign = '-';
}
/* Determine whether to include `0x' or `0X'.
It will only be included with a hexadecimal conversion of a
nonzero value with the # flag. */
x = (c->flags & POUND) && value ? b->x : 0;
/* Accumulate digits into buffer.
This algorithm produces digits in reverse order, so later we
will output the buffer's content in reverse. */
cp = buf;
digit_cnt = 0;
while (value > 0)
{
if ((c->flags & GROUP) && digit_cnt > 0 && digit_cnt % b->group == 0)
*cp++ = ',';
*cp++ = b->digits[value % b->base];
value /= b->base;
digit_cnt++;
}
/* Append enough zeros to match precision.
If requested precision is 0, then a value of zero is
rendered as a null string, otherwise as "0".
If the # flag is used with base 8, the result must always
begin with a zero. */
precision = c->precision < 0 ? 1 : c->precision;
while (cp - buf < precision && cp < buf + sizeof buf - 1)
*cp++ = '0';
if ((c->flags & POUND) && b->base == 8 && (cp == buf || cp[-1] != '0'))
*cp++ = '0';
/* Calculate number of pad characters to fill field width. */
pad_cnt = c->width - (cp - buf) - (x ? 2 : 0) - (sign != 0);
if (pad_cnt < 0)
pad_cnt = 0;
/* Do output. */
if ((c->flags & (MINUS | ZERO)) == 0)
output_dup (' ', pad_cnt, output, aux);
if (sign)
output (sign, aux);
if (x)
{
output ('0', aux);
output (x, aux);
}
if (c->flags & ZERO)
output_dup ('0', pad_cnt, output, aux);
while (cp > buf)
output (*--cp, aux);
if (c->flags & MINUS)
output_dup (' ', pad_cnt, output, aux);
}
/* Writes CH to OUTPUT with auxiliary data AUX, CNT times. */
static void
output_dup (char ch, size_t cnt, void (*output) (char, void *), void *aux)
{
while (cnt-- > 0)
output (ch, aux);
}
/* Formats the LENGTH characters starting at STRING according to
the conversion specified in C. Writes output to OUTPUT with
auxiliary data AUX. */
static void
format_string (const char *string, int length,
struct printf_conversion *c,
void (*output) (char, void *), void *aux)
{
int i;
if (c->width > length && (c->flags & MINUS) == 0)
output_dup (' ', c->width - length, output, aux);
for (i = 0; i < length; i++)
output (string[i], aux);
if (c->width > length && (c->flags & MINUS) != 0)
output_dup (' ', c->width - length, output, aux);
}
/* Wrapper for __vprintf() that converts varargs into a
va_list. */
void
__printf (const char *format,
void (*output) (char, void *), void *aux, ...)
{
va_list args;
va_start (args, aux);
__vprintf (format, args, output, aux);
va_end (args);
}
/* Dumps the SIZE bytes in BUF to the console as hex bytes
arranged 16 per line. Numeric offsets are also included,
starting at OFS for the first byte in BUF. If ASCII is true
then the corresponding ASCII characters are also rendered
alongside. */
void
hex_dump (uintptr_t ofs, const void *buf_, size_t size, bool ascii)
{
const uint8_t *buf = buf_;
const size_t per_line = 16; /* Maximum bytes per line. */
while (size > 0)
{
size_t start, end, n;
size_t i;
/* Number of bytes on this line. */
start = ofs % per_line;
end = per_line;
if (end - start > size)
end = start + size;
n = end - start;
/* Print line. */
printf ("%08jx ", (uintmax_t) ROUND_DOWN (ofs, per_line));
for (i = 0; i < start; i++)
printf (" ");
for (; i < end; i++)
printf ("%02hhx%c",
buf[i - start], i == per_line / 2 - 1? '-' : ' ');
if (ascii)
{
for (; i < per_line; i++)
printf (" ");
printf ("|");
for (i = 0; i < start; i++)
printf (" ");
for (; i < end; i++)
printf ("%c",
isprint (buf[i - start]) ? buf[i - start] : '.');
for (; i < per_line; i++)
printf (" ");
printf ("|");
}
printf ("\n");
ofs += n;
buf += n;
size -= n;
}
}
/* Prints SIZE, which represents a number of bytes, in a
human-readable format, e.g. "256 kB". */
void
print_human_readable_size (uint64_t size)
{
if (size == 1)
printf ("1 byte");
else
{
static const char *factors[] = {"bytes", "kB", "MB", "GB", "TB", NULL};
const char **fp;
for (fp = factors; size >= 1024 && fp[1] != NULL; fp++)
size /= 1024;
printf ("%"PRIu64" %s", size, *fp);
}
}

View File

@@ -0,0 +1,40 @@
#ifndef __LIB_STDIO_H
#define __LIB_STDIO_H
#include <debug.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/* Include lib/user/stdio.h or lib/kernel/stdio.h, as
appropriate. */
#include_next <stdio.h>
/* Predefined file handles. */
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
/* Standard functions. */
int printf (const char *, ...) PRINTF_FORMAT (1, 2);
int snprintf (char *, size_t, const char *, ...) PRINTF_FORMAT (3, 4);
int vprintf (const char *, va_list) PRINTF_FORMAT (1, 0);
int vsnprintf (char *, size_t, const char *, va_list) PRINTF_FORMAT (3, 0);
int putchar (int);
int puts (const char *);
/* Nonstandard functions. */
void hex_dump (uintptr_t ofs, const void *, size_t size, bool ascii);
void print_human_readable_size (uint64_t sz);
/* Internal functions. */
void __vprintf (const char *format, va_list args,
void (*output) (char, void *), void *aux);
void __printf (const char *format,
void (*output) (char, void *), void *aux, ...);
/* Try to be helpful. */
#define sprintf dont_use_sprintf_use_snprintf
#define vsprintf dont_use_vsprintf_use_vsnprintf
#endif /* lib/stdio.h */

Some files were not shown because too many files have changed in this diff Show More