diff options
Diffstat (limited to 'notes/3.txt')
| -rw-r--r-- | notes/3.txt | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/notes/3.txt b/notes/3.txt new file mode 100644 index 0000000..bc64f88 --- /dev/null +++ b/notes/3.txt | |||
| @@ -0,0 +1,241 @@ | |||
| 1 | Project 2 | ||
| 2 | ========= | ||
| 3 | |||
| 4 | Working with Disks | ||
| 5 | ------------------ | ||
| 6 | |||
| 7 | Assumes you ran make in src/userprog and src/examples. | ||
| 8 | |||
| 9 | * Create a 2 MB hard disk for pintos | ||
| 10 | |||
| 11 | # [src/userprog/build] | ||
| 12 | pintos-mkdisk filesys.dsk --filesys-size=2 | ||
| 13 | |||
| 14 | * Format Disk | ||
| 15 | |||
| 16 | # -f ... format virtual disk | ||
| 17 | pintos -f -q | ||
| 18 | |||
| 19 | * Copy file to filesystem | ||
| 20 | |||
| 21 | # -p FILE ... file to put on virtual disk | ||
| 22 | # -a FILE ... newname on virtual disk | ||
| 23 | pintos -p ../../examples/echo -a echo -- -q | ||
| 24 | |||
| 25 | * Execute echo, and get file 'echo' from the virtual disk | ||
| 26 | |||
| 27 | pintos -g echo -- -q run 'echo x' | ||
| 28 | |||
| 29 | Putting all together, we can run an minimal example like that: | ||
| 30 | |||
| 31 | # [src/userprog/build] | ||
| 32 | pintos --filesys-size=2 -p ../../examples/halt -a halt -- -f -q run 'halt' | ||
| 33 | |||
| 34 | Getting Started | ||
| 35 | --------------- | ||
| 36 | |||
| 37 | * Fix the problem with the .note.gnu.build-id segment | ||
| 38 | |||
| 39 | * Change the stack setup in process.c#setup_stack() to | ||
| 40 | |||
| 41 | *esp = PHYS_BASE - 12; | ||
| 42 | |||
| 43 | * Change process_wait() to an infinite loop | ||
| 44 | |||
| 45 | This should be enough to see 'system call!' when executing | ||
| 46 | the 'halt' example. | ||
| 47 | |||
| 48 | Next, we need to implement user memory access and the | ||
| 49 | the system call dispatcher, as well as the basic | ||
| 50 | system calls halt, exit and write. | ||
| 51 | |||
| 52 | A simple implementation of user memory access first checks | ||
| 53 | whether the address is in user space, and the calls load_page. | ||
| 54 | |||
| 55 | For an initial system call dispatcher, we convert the stack pointer | ||
| 56 | saved by the processor during the interrupt to kernel space, and | ||
| 57 | then dispatch to halt, exit and write. For now, exit just terminates | ||
| 58 | the process, and write uses printf, ignoring the fd argument. | ||
| 59 | The return value is stored into %eax. | ||
| 60 | |||
| 61 | Notes: | ||
| 62 | * halt(): There is no function shutdown() in init.h, only | ||
| 63 | shutdown_poweroff in shutdown.h | ||
| 64 | |||
| 65 | * When accessing data from user space in kernel space, we need to be | ||
| 66 | sure that the entire address ranged accessed is in user space. | ||
| 67 | Note that pointers are not necessarily aligned, and thus might | ||
| 68 | involve two user pages. | ||
| 69 | Furthermore, buffers need to be copied to kernel space; | ||
| 70 | otherwise, concurrent user space operations could corrupt the kernel. | ||
| 71 | Linux allows at most one kernel page for such buffers; we follow | ||
| 72 | the same route. | ||
| 73 | |||
| 74 | * Debugging: the function hex_dump() is useful; no need to | ||
| 75 | reimplement it. | ||
| 76 | |||
| 77 | * Something went wrong with the write system call, and this | ||
| 78 | is rather tricky to debug. | ||
| 79 | I invoked the system call directly, using inline | ||
| 80 | assembly; this worked fine? | ||
| 81 | Then I tried to debug the user space program; to this | ||
| 82 | end, lookup the code address you are interested in, | ||
| 83 | and use gdb together with objdump for debugging: | ||
| 84 | |||
| 85 | Debugging 'write(1,"USA\n",4)' | ||
| 86 | |||
| 87 | break *0x0804820e # break at <write> | ||
| 88 | cont # pushl 0xc(%esp) | ||
| 89 | info registers # esp = 0xbfffffbc | ||
| 90 | x/1w (0xbfffffbc+0xc) # ==> 4 (length) | ||
| 91 | stepi # pushl 0x8(%esp) | ||
| 92 | info registers # esp = 0x......b8 | ||
| 93 | x/1w 0xbfffffb8 # ==> 4 (TOS) | ||
| 94 | x/1w (0xbfffffb8+8) # ==> 1 (wrong) !!! | ||
| 95 | |||
| 96 | Apparently, the inline assembler in pintos does not use | ||
| 97 | the right constraints. | ||
| 98 | |||
| 99 | Stat: | ||
| 100 | pintos/src/lib/user/syscall.c | 6 +- | ||
| 101 | pintos/src/userprog/process.c | 5 ++- | ||
| 102 | pintos/src/userprog/syscall.c | 92 ++++++++++++++++++++++++++++++++++++++-- | ||
| 103 | |||
| 104 | Reading and Implementation Time: 6 hours | ||
| 105 | Debugging Syscalls: 5 hours | ||
| 106 | |||
| 107 | |||
| 108 | Argument Passing | ||
| 109 | ---------------- | ||
| 110 | First, we tokenize the command using strtok_r, and then setup | ||
| 111 | the stack. | ||
| 112 | |||
| 113 | Notes: | ||
| 114 | * As noted in the doc, just using strtok_r seems fine. | ||
| 115 | However, as strtok_r modifies the string even if only | ||
| 116 | the first token is needed, some copying is involved | ||
| 117 | if it is used to obtain the filename. | ||
| 118 | * Due to the detailed description in the documentation, | ||
| 119 | setting up the stack is mostly implementation work. | ||
| 120 | * One of the trickier implementation aspects is that we | ||
| 121 | modify the stack in kernel space, but need to convert | ||
| 122 | pointers to user space before pushing them on the stack. | ||
| 123 | * Debugging: Optimizations were really troublesome debugging | ||
| 124 | this task; making setup_stack non-static at least helped | ||
| 125 | to set a suitable breakpoint. In the end, printf was the | ||
| 126 | better debugging aid for this task. | ||
| 127 | |||
| 128 | Stat: | ||
| 129 | pintos/src/userprog/process.c | 116 +++++++++++++++++++++++++++++++++-------- | ||
| 130 | |||
| 131 | Design and Implementation Time: 4 hours | ||
| 132 | |||
| 133 | |||
| 134 | Process Management: exec, wait and exit | ||
| 135 | --------------------------------------- | ||
| 136 | The wait system call requires that all children | ||
| 137 | of a process are known, that the exit code of | ||
| 138 | a process is stored until collected by the parent, | ||
| 139 | and that the parent can block until the child | ||
| 140 | process terminates. | ||
| 141 | |||
| 142 | One difficult aspect in the design is that kernel | ||
| 143 | threads are not processes, and that child threads | ||
| 144 | may exit after their parent. It is important to | ||
| 145 | note that threads do not need to wait for their | ||
| 146 | children, but that we need to keep the exit status | ||
| 147 | until the parent exits. | ||
| 148 | |||
| 149 | In the original design, a thread is cleaned up when | ||
| 150 | in the scheduler right after it died. In our design | ||
| 151 | we delay the cleanup if the parent thread is still alive. | ||
| 152 | |||
| 153 | Another issue is that thread_create needs to block | ||
| 154 | until the load process of the child thread has finished. | ||
| 155 | |||
| 156 | Notes: | ||
| 157 | * I wanted to use the same semaphore for startup and wait. | ||
| 158 | This works, but we need one additional variable (or bit) | ||
| 159 | to distinguish failure at load time from failure at | ||
| 160 | runtime. | ||
| 161 | * Ugly 1: thread_create only gives back a tid, | ||
| 162 | so it is not possible to directly access the semaphore | ||
| 163 | in process_execute. Therefore we need to iterate over the | ||
| 164 | child list (which is not that bad, because if loading failed, | ||
| 165 | the child needs to be removed from the list anyway). | ||
| 166 | * Ugly 2: We up the semaphore used to synchronize | ||
| 167 | with process_execute and process_wait in thread.c, for | ||
| 168 | all threads. | ||
| 169 | * As also noted by rene, it is important to identifiy memory leaks, | ||
| 170 | as early as possible. To this end, first add debug messages to | ||
| 171 | page_alloc/page_free, and then run test programs to identify leaking | ||
| 172 | pages. Then debug, add conditional breakpoints to stop when a leaking | ||
| 173 | page is allocated, and inspect the stacktrace to find the culprit. | ||
| 174 | |||
| 175 | Stats: | ||
| 176 | pintos/src/threads/thread.c | 31 +++++++++++++++++--- | ||
| 177 | pintos/src/threads/thread.h | 8 +++++ | ||
| 178 | pintos/src/userprog/process.c | 60 ++++++++++++++++++++++++++++++++-------- | ||
| 179 | pintos/src/userprog/syscall.c | 19 +++++++++--- | ||
| 180 | |||
| 181 | Design and Implementation Time: 7 hours | ||
| 182 | |||
| 183 | File I/O System Calls | ||
| 184 | --------------------- | ||
| 185 | For file I/O we need to implement synchronization (filesys is not thread safe). | ||
| 186 | The documentation states that it is not recommended to modify the code in | ||
| 187 | the filesys directory for now. A very simple solution is to use one lock for all filesystem operations, including process.c#load. | ||
| 188 | Furthermore, we need to deny writes to a a file currently running as a user | ||
| 189 | space process. | ||
| 190 | |||
| 191 | Notes: | ||
| 192 | * init_thread() must not aquire locks, and thus not allocate pages. | ||
| 193 | Otherwise, the initialization of the init thread fails. | ||
| 194 | * The {lg,sm}-full tests failed in the initial implementation; | ||
| 195 | apparently, the read/write system calls should always read/write the | ||
| 196 | full amount of bytes specified to pass this tests. This was not | ||
| 197 | clear from the assignment. | ||
| 198 | * It is not obvious that file_close calls file_allow_write. But an | ||
| 199 | executable should not be writeable during its execution. Therefore, | ||
| 200 | one needs to make sure that it stays write protected after loading | ||
| 201 | has finished. I solve this by keeping the executable open during | ||
| 202 | execution. | ||
| 203 | * The multi-oom test failed again; debugging revealed that I forgot | ||
| 204 | to close all files at process_exit. | ||
| 205 | |||
| 206 | Stats: | ||
| 207 | |||
| 208 | pintos/src/threads/thread.c | 1 + | ||
| 209 | pintos/src/threads/thread.h | 6 +- | ||
| 210 | pintos/src/userprog/process.c | 53 ++++- | ||
| 211 | pintos/src/userprog/process.h | 2 + | ||
| 212 | pintos/src/userprog/syscall.c | 435 +++++++++++++++++++++++++++++++----- | ||
| 213 | pintos/src/userprog/syscall.h | 1 + | ||
| 214 | 6 files changed, 381 insertions(+), 117 deletions(-) | ||
| 215 | Design and Implementation Time: 8 hours | ||
| 216 | |||
| 217 | Improved User Memory Access | ||
| 218 | --------------------------- | ||
| 219 | Looking at Project 3, it is a much better idea to not check whether a user | ||
| 220 | space page is valid, but just let the page fault handler do the job. | ||
| 221 | I decided to exit the process in the page fault handler if the address | ||
| 222 | is in user space. One needs to take care of temporary memory allocated | ||
| 223 | by the syscall handler, to avoid memory leaks. To this end, temporary kernel | ||
| 224 | pages allocated in the handler are recorded and either freed at the end | ||
| 225 | of the syscall or the end of the process. | ||
| 226 | |||
| 227 | Notes: | ||
| 228 | * When using this approach, it is vital to copy user buffers | ||
| 229 | before reading or writing. With virtual memory, a page fault may | ||
| 230 | require to access the file system, and thus may cause race | ||
| 231 | conditions during access to the file system | ||
| 232 | |||
| 233 | Stats: | ||
| 234 | pintos/src/threads/thread.h | 5 +- | ||
| 235 | pintos/src/userprog/exception.c | 17 ++- | ||
| 236 | pintos/src/userprog/process.c | 2 +- | ||
| 237 | pintos/src/userprog/syscall.c | 314 +++++++++++++++++++-------------------- | ||
| 238 | pintos/src/userprog/syscall.h | 2 +- | ||
| 239 | 5 files changed, 173 insertions(+), 167 deletions(-) | ||
| 240 | |||
| 241 | Implementation Time: 3 hours | ||
