Sep 12, 2017 - of the project. To help you understand the details, we list below function thread_create() from. 49 file threads/thread.c: 50 tid_t. 51 thread_create ...
1 2 3 4
CS342: Operating Systems Lab Department of Computer Science and Engineering, Indian Institute of Technology, Guwahati North Guwahati, Assam 781 039 Exercise UP-04
5 6 7 8
OS Lessons: Exec(), Wait() and Denying Writes to Executables Rating: Hard Last update: 12 Sept 2017
9 10 11 12
Please do not start on this exercise before you have successfully completed Exercise UP-02. Attempting to progress without a good understanding of the solutions needed in Exercise UP-02 is likely to be expensive on your time and efforts. It is also a good idea to complete Exercise UP-03 first.
13
Tasks for the Exercise:
14 15 16 17
In this exercise, we work on system calls exec() and wait(). System call exec() specifications may be modelled on function process_execute() that was settled in an earlier exercise. However, you must also make sure that your implementation does not report a successful completion of the system call until after the child process/thread is at a stage where it will run as a process.
18 19 20
The latter requirement is not trivial. A new process can fail to reach a successful birth for many reasons. Thus, a successful birth should be report at the completion of all stages; and, not at the start when memory for struct thread is allocated.
21 22
PintDoc suggests that wait()implementation is a difficult task. The remark about the difficulty is not without its merit.
23 24 25 26 27
Described below is a possible design suggestion that you may use for these two system calls. It is not a praise-worthy plan but it worked for us. We used a set of 4 arrays indexed by thread identifier. For each thread, the array-set records data that is not conveniently recorded in struct thread. Example of information that is inconvenient to record in struct thread is information that is needed even if the thread is not created successfully or data that is needed after the thread has exited.
28 29 30 31
Some details of our implementation of these 4 arrays is given below but we do expect that the keen students will improve on these and construct better data-structures to record data about each thread that PintOS kernel needs outside the life-span of a thread. Struct thread is a convenient datastructure to hold data needed during the active life-span of a thread.
32 33 34 35 36 37 38 39
1. One of the arrays we used provides mapping from thread-identifier (tid) to the matching thread’s struct thread. 2. The second array was used to record the successful birth of thread tid. A successful birth is achieved only when the process’s program code has been loaded. A tid is allocated as soon as space is allocated for data-structure struct thread. PintDoc requires that exec() does not return thread’s tid before the thread is properly established. 3. The third array is used to park exit-status of a thread for system call wait() from the thread’s parent. 1|Page
40 41
4. Our final array helps in managing accesses to these data-structures during and after the thread’s THREAD_DYING state.
42 43
Swiss Army Knife:
44 45 46
Struct thread supports the creation of a new thread by providing a fake past to the new thread. This cleverly created past of the thread helps the thread to join ready_list and be able to run when scheduled in the same way as the other threads.
47 48 49 50
In implementing exec(), the students need to understand the structure in some details. Indeed, this complexity is the reason, we delayed the implementation of exec()and wait() as the last exercise of the project. To help you understand the details, we list below function thread_create() from file threads/thread.c:
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
tid_t thread_create (const char *name, int priority, thread_func *function, void *aux) { struct thread *t; struct kernel_thread_frame *kf; struct switch_entry_frame *ef; struct switch_threads_frame *sf; tid_t tid; enum intr_level old_level; ASSERT (function != NULL); /* Allocate thread. */ t = palloc_get_page (PAL_ZERO); if (t == NULL) return TID_ERROR; /* Initialize thread. */ init_thread (t, name, priority); tid = t->tid = allocate_tid (); /* Prepare thread for first run by initializing its stack. Do this atomically so intermediate values for the 'stack' member cannot be observed. */ old_level = intr_disable (); /* Stack frame for kernel_thread(). */ kf = alloc_frame (t, sizeof *kf); kf->eip = NULL; kf->function = function; kf->aux = aux; /* Stack frame for switch_entry(). */ ef = alloc_frame (t, sizeof *ef); ef->eip = (void (*) (void)) kernel_thread; /* Stack frame for switch_threads(). */ sf = alloc_frame (t, sizeof *sf); 2|Page
90 91 92 93 94 95 96 97 98 99 100
sf->eip = switch_entry; sf->ebp = 0; intr_set_level (old_level); /* Add to run queue. */ thread_unblock (t); }
return tid;
101 102 103 104
A newly created thread, begins its life by executing function kernel_thread(). The function is copied below to aid our explanation. Note in the code above, how a call to function kernel_thread() is included in the code. This is not the way your first programming course taught you! The function run will be initiated by the PintOS scheduler.
105 106 107 108 109 110 111 112 113 114 115
/* Function used as the basis for a kernel thread. */ static void kernel_thread (thread_func *function, void *aux) { ASSERT (function != NULL);
116 117 118 119 120
You need to recognize that the function finds its arguments on a stack created previously during thread_create()execution. The arguments were inserted by the parent thread! Argument aux is really the command line specifying the user program with arguments to be run in the process being assembled. And, argument function, is function start_process() – different from the user program you might have expected!
121 122 123
Function start_process() is responsible to load the user program and setup user-program’s initial stack. All the needed information is in argument aux. We discussed this issue in exercise UP01.
124 125 126
Function kernel_thread() is run as a new entity (child thread) separate from the thread that called function thread_create() . The separation occurred near the end of the function thread_create() as should be noted through the code:
127 128 129 130 131 132 133
intr_enable (); function (aux); thread_exit ();
/* The scheduler runs with interrupts off. */ /* Execute the thread function. */ /*If function() returns, kill the thread. */
}
/* Add to run queue. */ thread_unblock (t); For the sake of completeness we say, all context switch from a running thread to a new running thread occur through function switch_threads(). Thus, the thread exiting status THREAD_RUNNING and the thread entering status THREAD_RUNNING seamlessly execute the same code in function switch_threads().
3|Page
134 135 136 137
The first switch for a thread is special, as it has no past to resume from. This void was filled by a fake past as we discussed earlier. Function thread_create() provides frame switch_entry() and it is no surprise that it causes the thread to “return-from-call” to the start of function kernel_thread(). This is the function every thread runs at their birth.
138 139 140
It may be interesting and useful to read the appendix before reading this section again. But this is a magic trick every good computer student must learn and master. Appendix A.2.3 Thread Switching is the authentic source of information for PintOS lovers.
141
Now Enjoy Coding the Final Part of Project:
142 143 144
The specifications for system calls exec() and wait() are near replica of functions process_execute() and process_wait() in file userprog/process.c . But, the differences are important too.
145 146
We expect (and recommend) that you work on this implementation in three phases. In Phase 1, focus on developing code to correctly implement system call exec().
147
In the Phase2, include system call wait() into your goals.
148 149
In the final phase, you work on the specifications set in Section 3.3.5 Denying Writes to Executables of PintDoc.
150 151
The success of each phase, as determined by the success status of command make check, is given below to help you monitor your progress.
152 153
Status of our “make check”:
154
After full implementation of exec():
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
[vmm@progsrv build]$ make check pass tests/userprog/args-none pass tests/userprog/args-single pass tests/userprog/args-multiple pass tests/userprog/args-many pass tests/userprog/args-dbl-space pass tests/userprog/sc-bad-sp pass tests/userprog/sc-bad-arg pass tests/userprog/sc-boundary pass tests/userprog/sc-boundary-2 pass tests/userprog/halt pass tests/userprog/exit pass tests/userprog/create-normal pass tests/userprog/create-empty pass tests/userprog/create-null pass tests/userprog/create-bad-ptr pass tests/userprog/create-long pass tests/userprog/create-exists pass tests/userprog/create-bound pass tests/userprog/open-normal pass tests/userprog/open-missing pass tests/userprog/open-boundary pass tests/userprog/open-empty 4|Page
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
pass tests/userprog/open-null pass tests/userprog/open-bad-ptr pass tests/userprog/open-twice pass tests/userprog/close-normal pass tests/userprog/close-twice pass tests/userprog/close-stdin pass tests/userprog/close-stdout pass tests/userprog/close-bad-fd pass tests/userprog/read-normal pass tests/userprog/read-bad-ptr pass tests/userprog/read-boundary pass tests/userprog/read-zero pass tests/userprog/read-stdout pass tests/userprog/read-bad-fd pass tests/userprog/write-normal pass tests/userprog/write-bad-ptr pass tests/userprog/write-boundary pass tests/userprog/write-zero pass tests/userprog/write-stdin pass tests/userprog/write-bad-fd FAIL tests/userprog/exec-once FAIL tests/userprog/exec-arg FAIL tests/userprog/exec-multiple pass tests/userprog/exec-missing pass tests/userprog/exec-bad-ptr FAIL tests/userprog/wait-simple FAIL tests/userprog/wait-twice FAIL tests/userprog/wait-killed pass tests/userprog/wait-bad-pid FAIL tests/userprog/multi-recurse pass tests/userprog/multi-child-fd FAIL tests/userprog/rox-simple FAIL tests/userprog/rox-child FAIL tests/userprog/rox-multichild pass tests/userprog/bad-read pass tests/userprog/bad-write pass tests/userprog/bad-read2 pass tests/userprog/bad-write2 pass tests/userprog/bad-jump pass tests/userprog/bad-jump2 FAIL tests/userprog/no-vm/multi-oom pass tests/filesys/base/lg-create pass tests/filesys/base/lg-full pass tests/filesys/base/lg-random pass tests/filesys/base/lg-seq-block pass tests/filesys/base/lg-seq-random pass tests/filesys/base/sm-create pass tests/filesys/base/sm-full pass tests/filesys/base/sm-random pass tests/filesys/base/sm-seq-block pass tests/filesys/base/sm-seq-random FAIL tests/filesys/base/syn-read pass tests/filesys/base/syn-remove FAIL tests/filesys/base/syn-write 13 of 76 tests failed. make: *** [check] Error 1 5|Page
234
On completion of exec() and wait() system calls:
235
Our success status improved to just 4 FAILs:
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
pass tests/userprog/wait-twice pass tests/userprog/wait-killed pass tests/userprog/wait-bad-pid pass tests/userprog/multi-recurse pass tests/userprog/multi-child-fd FAIL tests/userprog/rox-simple FAIL tests/userprog/rox-child FAIL tests/userprog/rox-multichild pass tests/userprog/bad-read pass tests/userprog/bad-write pass tests/userprog/bad-read2 pass tests/userprog/bad-write2 pass tests/userprog/bad-jump pass tests/userprog/bad-jump2 pass tests/userprog/no-vm/multi-oom pass tests/filesys/base/lg-create pass tests/filesys/base/lg-full pass tests/filesys/base/lg-random pass tests/filesys/base/lg-seq-block pass tests/filesys/base/lg-seq-random pass tests/filesys/base/sm-create pass tests/filesys/base/sm-full pass tests/filesys/base/sm-random pass tests/filesys/base/sm-seq-block pass tests/filesys/base/sm-seq-random pass tests/filesys/base/syn-read pass tests/filesys/base/syn-remove FAIL tests/filesys/base/syn-write 4 of 76 tests failed. make: *** [check] Error 1
266 267
On completion of all three phases:
268 269 270
And, finally after successful implementation of Denying Writes to Executables we could pass all tests. This part does require a lot of thought and several score lines of kernel code. We provide necessary support below.
271 272
The output below is also a confirmation that if a student meticulously follows the instructions, all tests in User Program project can be successfully completed.
273 274 275 276
In brief, the implementation requires that for each executable file that has been loaded in one or more processes running on the system, we maintain a count of the processes that have loaded the executable file. The denial of write obligation on the executable file stays till the last process loaded with the file has terminated.
277 278 279 280
A smart student would have already sensed that it requires some smart programming as we cannot know which process loaded with this executable file will be the last to terminate. It also stands to reason that the process that loaded the executable file first (and hence initiates the constraint “deny write on the file”), is likely to be among the early processes to finish ahead of those who loaded the 6|Page
281 282 283
same file at later time. In a careless implementation, the constraint “deny write on file” may be lifted inadvertently as soon as this (first) process terminates even though there may be other processes running file code are still active in the system.
284 285 286 287 288 289 290
The problem described in the last paragraph is not the only tricky issue that you need to handle. You also need to understand that not all threads are subject to a wait() call from their parent thread. That is, wait() is not a reliable indicator of the termination of a process. However, all resources given to a thread must be returned on its completion. That is, to avoid resource leakage every page carrying a struct thread data-structure needs to be freed by calling palloc_free_page(). Likewise, every open files must be closed. If we fail to deallocate all resources, the kernel shall degrades over time. Our resource deallocation plan cannot rely on function wait() to free resources.
291 292 293
The test tests/userprog/no-vm/multi-oom only succeeded when the implementation supported 2040 threads each with ability to host 128 open files simultaneously. These numbers were used to set the sizes for the arrays in our implementation of the project.
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
[vmm@progsrv ~]$ cd pintos/src/userprog/build/ [vmm@progsrv build]$ make check pass tests/userprog/args-none pass tests/userprog/args-single pass tests/userprog/args-multiple pass tests/userprog/args-many pass tests/userprog/args-dbl-space pass tests/userprog/sc-bad-sp pass tests/userprog/sc-bad-arg pass tests/userprog/sc-boundary pass tests/userprog/sc-boundary-2 pass tests/userprog/halt pass tests/userprog/exit pass tests/userprog/create-normal pass tests/userprog/create-empty pass tests/userprog/create-null pass tests/userprog/create-bad-ptr pass tests/userprog/create-long pass tests/userprog/create-exists pass tests/userprog/create-bound pass tests/userprog/open-normal pass tests/userprog/open-missing pass tests/userprog/open-boundary pass tests/userprog/open-empty pass tests/userprog/open-null pass tests/userprog/open-bad-ptr pass tests/userprog/open-twice pass tests/userprog/close-normal pass tests/userprog/close-twice pass tests/userprog/close-stdin pass tests/userprog/close-stdout pass tests/userprog/close-bad-fd pass tests/userprog/read-normal pass tests/userprog/read-bad-ptr pass tests/userprog/read-boundary pass tests/userprog/read-zero pass tests/userprog/read-stdout pass tests/userprog/read-bad-fd 7|Page
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374
pass tests/userprog/write-normal pass tests/userprog/write-bad-ptr pass tests/userprog/write-boundary pass tests/userprog/write-zero pass tests/userprog/write-stdin pass tests/userprog/write-bad-fd pass tests/userprog/exec-once pass tests/userprog/exec-arg pass tests/userprog/exec-multiple pass tests/userprog/exec-missing pass tests/userprog/exec-bad-ptr pass tests/userprog/wait-simple pass tests/userprog/wait-twice pass tests/userprog/wait-killed pass tests/userprog/wait-bad-pid pass tests/userprog/multi-recurse pass tests/userprog/multi-child-fd pass tests/userprog/rox-simple pass tests/userprog/rox-child pass tests/userprog/rox-multichild pass tests/userprog/bad-read pass tests/userprog/bad-write pass tests/userprog/bad-read2 pass tests/userprog/bad-write2 pass tests/userprog/bad-jump pass tests/userprog/bad-jump2 pass tests/userprog/no-vm/multi-oom pass tests/filesys/base/lg-create pass tests/filesys/base/lg-full pass tests/filesys/base/lg-random pass tests/filesys/base/lg-seq-block pass tests/filesys/base/lg-seq-random pass tests/filesys/base/sm-create pass tests/filesys/base/sm-full pass tests/filesys/base/sm-random pass tests/filesys/base/sm-seq-block pass tests/filesys/base/sm-seq-random pass tests/filesys/base/syn-read pass tests/filesys/base/syn-remove pass tests/filesys/base/syn-write All 76 tests passed.
375
PS: A comment
376 377 378 379 380
The extension described above was completed separate from the extensions to project Threads. (Actually, project UserProg was done first). In August/Sept 2017, attempts were made to combine the code from these two projects. The combined code caused problems with test multi-oom as struct thread does not have enough space for stack to accommodate all data elements and the kernel stack.
381
We could accommodate only 99 files in a process. This causes some other tests to fail.
382
It was decided to separate allocation for pointers strcut file * into a separate calloc() area. 8|Page
383 384
After these changes, time (6 minutes) for test multi-oom is tight when using simulator bochs. You may need to adjust for this time limitation by using a longer time duration for this test.
385
To our utter surprise, setting the limit for (open) files at 63 helps pass all tests.
386
Appendix: A Visit to PintOS Corporation
387 388
PintOS Corporation (PintCorp) has many employees and there is a significant turn-over of these employees. New employees are contracted and old leave PintCorp regularly.
389 390 391 392 393 394 395
The company has a famous coffee house, where employees come often during their work to rest, relax and of course to drink. Talking business here is an absolute no-no. The coffee house has three doors. One of the door at the front is the entrance for the new employees. Each new employee gets to enjoy a coffee break before work begins. There is a separate door to let the employees who were away come back to work. They too get to enjoy their coffee before resuming duties. These two doors are for entry only – no one is allowed out of these doors. Employees at work enter and exit coffee house through the third door.
396 397 398 399
Employees spend as much time as they like in the coffee house. They can come in any time and leave any time. An employee, who is not new to the company, goes to his work-desk and resumes work diligently. The employees work till the work is complete or they want to have a coffee or they need to go out of the Corporate area.
400 401 402 403
The arrangements for the new employee are practical. A new employee does not have a desk to work from. They are given directions to the desk store. That is where they report to start their work at PintCorp. The store gives the new employee a desk to work from. The new employee sets their desk up and begin working.
404 405 406 407
PintCorp has no manager! Every employee follows the rules and work with the corporation till the work is finished. The existing employees are able to hire new employees into PintCorp. A final fact about PintCorp is that no more than one employee ever works at a time. Others are either out of office or having a coffee break.
408 409 410
The employee who hires a new employee may wait for the hired employee to finish work before returning to work. But, some employees continue to work without waiting for the hired employees to finish their assigned works.
411 412 413
If you have read the story carefully, you know that PintCorp calls its employees threads. Each has a number tid. The coffee shop is called THREAD_READY. The working employee is termed THREAD_RUNNING. And, those away are known as THREAD_BLOCKED.
414
9|Page
415
Code Written
416
Original PintOS versus Code implementing only project User Program (but not project Threads)
File
Original
After User Program
threads/thread.c (thread.h)
587 (141)
709 (166)
userprog/exception.c
161
163
userprog/process.c (process.h)
465 (11)
657 (14)
userprog/syscall.c (syscall.h)
20 (6)
332 (7)
417 418
Original PintOS versus combined projects Threads and User Program
Files
Original
Combined
devices/timer.c (timer.h)
255 (29)
328 (34)
threads/synch.c (synch.h)
338 (51)
571 (56)
threads/thread.c (thread.h)
587 (141)
991 (193)
userprog/process.c (process.h)
465 (11)
667 (14)
userprog/syscall.c (syscall.h)
20 (6)
335 (7)
userprog/exception.c
161
163
419 420 421
Contributing Authors: Vishv Malhotra, Gautam Barua, Rashmi Dutta Baruah
10 | P a g e