CS342: Operating Systems Lab Department of ...

3 downloads 0 Views 484KB Size Report
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