int thread_join(int thread_id, void** ret_val) { struct proc* maint=get_main_thread(proc); if(maint->last_tid >= thread_id && proc->tid != thread_id) { struct proc *p; int havekids; void* retval; acquire(&ptable.lock); for(;;) { // Scan through table looking for zombie children. havekids = 0; for(p = ptable.proc; p < &ptable.proc[NPROC]; p++) { if(p->parent != maint || p->tid!=thread_id) continue; havekids = 1; // check if another thread is waiting for this thread if(p->other_thread_waiting >= 0 && p->other_thread_waiting != proc->tid) { release(&ptable.lock); return -2; } // flag that p is waited for p->other_thread_waiting=proc->tid; if(p->state == ZOMBIE) { // Found one. retval=p->ret_val; clear_thread(p); *ret_val=retval; release(&ptable.lock); return 0; } } // No point waiting if we don't have any children. if(!havekids || proc->killed) { release(&ptable.lock); return -1; } // Wait for children to exit. (See wakeup1 call in proc_exit.) sleep(proc, &ptable.lock); //DOC: wait-sleep } } return -1; }
int main(int argc, char* argv[]) { struct timespec event_ts; // local event timestamp int num_thrds = 0; // number of user threads to simulate thread_data_t *tarr = NULL; // array of thread data structures int sel_sid = -1; // index of thread selected by scheduler thread_data_t *tsel = NULL; // pointer to the selected thread int tmp = 0; int (*sched_func)() = NULL; // scheduling function picked at run time if (argc < 3) { fprintf(stderr, usage, argv[0]); exit(1); } tmp = strlen(argv[1]); if (argv[1][tmp-1] == '+') { unblock_signal = 1; argv[1][tmp-1] = '\0'; } for (int i=0 ; i < NUM_SCHED ; i++) { if (strcmp(argv[1], MY_SCHED[i].name) == 0) { sched_func = MY_SCHED[i].func; break; } } if (sched_func == NULL) { errx(1, "Scheduling function %s not found!", argv[1]); } num_thrds = strtol(argv[2], NULL, 0); if (argc > 3) min_time = strtol(argv[3], NULL, 0); if (argc > 4) max_time = strtol(argv[4], NULL, 0); // Get simulation start time for logging reference clock_gettime(CLOCK_REALTIME, &sim_ts); pthread_mutex_init(&simlock, NULL); pthread_cond_init(&sched_cv, NULL); pthread_cond_init(&run_cv, NULL); init_thread_Q(&unblockQ, num_thrds); init_thread_Q(&readyQ, num_thrds); init_thread_Q(&blockedQ, num_thrds); pthread_mutex_lock(&simlock); // Create and fork number of threads as requested tarr = calloc(num_thrds, sizeof(thread_data_t)); for (int i=0 ; i < num_thrds ; i++) { tarr[i].sid = i; // Thread is put to blocked state right after start tarr[i].state = BLOCKED; add_to_Q_back(&blockedQ, &tarr[i]); fork_thread(&tarr[i]); LOG("Thread tid=%d created with sid=%d\n", tarr[i].tid, tarr[i].sid); } run_sid = -1; // no thread is initially running block_sid = -1; // no thread is initially entering block state do { if (block_sid != -1) { // Scheduler was waken up by the running thread blocking // Handle thread entering blocked state from running state if (run_sid != -1) errx(1, "thread[%d] was blocked while thread[%d] is still running", block_sid, run_sid); add_to_Q_back(&blockedQ, &tarr[block_sid]); block_sid = -1; } else if (run_sid != -1) { // Scheduler was waken up by the tick_ts timeout // Preempt running (and non-blocking) thread to ready state thread_data_t *t = &tarr[run_sid]; if (t->state != RUNNING) errx(1, "thread[%d] to be preempted is in %d state", run_sid, t->state); clock_gettime(CLOCK_REALTIME, &event_ts); t->remain_usec -= TIMEUSEC(&t->event_ts, &event_ts); t->event_ts = event_ts; LOG("%8ld: scheduler preempts running thread[%d] with remain_usec=%d\n", TIMEUSEC(&sim_ts, &event_ts), run_sid, t->remain_usec); // Change the preempted thread into ready state t->state = READY; add_to_Q_back(&readyQ, t); run_sid = -1; // Signal the thread to stop running pthread_cond_signal(&run_cv); } while (unblockQ.head >= 0) { // Handle threads unblocking from blocked state into ready state thread_data_t *t = remove_from_Q_front(&unblockQ); clock_gettime(CLOCK_REALTIME, &event_ts); t->event_ts = event_ts; LOG("%8ld: scheduler changes thread[%d] with remain_usec=%d to ready\n", TIMEUSEC(&sim_ts, &event_ts), t->sid, t->remain_usec); // Change the unblocking thread into ready state t->state = READY; remove_from_Q(&blockedQ, t->sid); add_to_Q_back(&readyQ, t); // Signal the thread to become formally unblocked pthread_cond_signal(&t->block_cv); } if (unblockQ.head >= 0) // unblockQ is not empty errx(1, "unblockQ has pending threads from %d to %d!?!", unblockQ.head, unblockQ.tail); if (run_sid != -1) errx(1, "scheduler is running with running thread[%d]!?!", run_sid); if (block_sid != -1) errx(1, "thread[%d] was blocking but not handled!?!", block_sid); if (readyQ.head == -1 && blockedQ.head == -1) { // no thread is running, neither in ready or blocked list clock_gettime(CLOCK_REALTIME, &event_ts); LOG("%8ld: all threads appear to have terminated\n", TIMEUSEC(&sim_ts, &event_ts)); break; } if (tsel != NULL && tsel->state == RUNNING) { // Scheduler was waken up (by unblocking or terminating threads) before // last selected thread could startrunning -- do not select again /*** Do nothing ***/ } else { // Either scheduler did not select a thread last time, or the selected // thread is not running any more (blocked, preempted or finished) // Run the scheduling algorithm to select a new thread sel_sid = sched_func(); clock_gettime(CLOCK_REALTIME, &event_ts); if (sel_sid < 0) { tsel = NULL; LOG("%8ld: scheduler cannot find any thread to run\n", TIMEUSEC(&sim_ts, &event_ts)); } else { tsel = &tarr[sel_sid]; if (tsel->state != READY) errx(1, "scheduler selects thread[%d] not in ready state", sel_sid); LOG("%8ld: scheduler selects thread[%d] with remain_usec=%d to run\n", TIMEUSEC(&sim_ts, &event_ts), sel_sid, tsel->remain_usec); // Change the selected thread to running state tsel->state = RUNNING; remove_from_Q(&readyQ, tsel->sid); // Signal the selected thread to run pthread_cond_signal(&tsel->ready_cv); } } // Scheduler wait for either a signal or tick_ts until next preemption timeadd(&event_ts, &event_ts, &tick_ts); pthread_cond_timedwait(&sched_cv, &simlock, &event_ts); } while (1); pthread_mutex_unlock(&simlock); sched_yield(); for (int i=0 ; i < num_thrds ; i++) { int rv; int tid = wait(&rv); // wait for an (only) child to exit LOG("Thread tid=%d exited with status %d\n", tid, WEXITSTATUS(rv)); } for (int i=0 ; i < num_thrds ; i++) { clear_thread(&tarr[i]); } free(tarr); destroy_thread_Q(&unblockQ); pthread_mutex_destroy(&simlock); pthread_cond_destroy(&sched_cv); pthread_cond_destroy(&run_cv); return 0; }
// Exit the current process. Does not return. // An exited process remains in the zombie state // until its parent calls wait() to find out it exited. void exit(void) { struct proc *p; int fd; int rmBrothers=0; if(!is_main_thread(proc)) { // Kill main thread proc->parent->killed=1; if(proc->parent->state==SLEEPING) proc->parent->state=RUNNABLE; //clear_thread(proc); rmBrothers=1; //return; } if(proc == initproc) panic("init exiting"); cprintf("#"); //release(&ptable.lock); if(!holding(&ptable.lock)) { cprintf("[ex%d]", proc->pid); acquire(&ptable.lock); } cprintf("%"); // Pass abandoned proc children to init & // kill all threads beneath (children who are not procs) for(p = ptable.proc; p < &ptable.proc[NPROC]; p++) { if(p->parent == proc) { if(is_main_thread(p) && rmBrothers==0) { p->parent = initproc; if(p->state == ZOMBIE) wakeup1(initproc); } else { clear_thread(p); } } } if(rmBrothers) goto end; // Close all open files. for(fd = 0; fd < NOFILE; fd++) { if(proc->ofile[fd]) { fileclose(proc->ofile[fd]); proc->ofile[fd] = 0; } } iput(proc->cwd); proc->cwd = 0; end: // Parent might be sleeping in wait(). wakeup1(proc->parent); // Jump into the scheduler, never to return. proc->state = ZOMBIE; sched(); panic("zombie exit"); }