void ke_init(void) { uint32_t endian = 0x44332211; unsigned char *pendian = (unsigned char *) &endian; /* Endian check */ if (pendian[0] != 0x11 || pendian[3] != 0x44) fatal("cannot run kernel on a big endian machine"); isa_init(); ke = calloc(1, sizeof(struct kernel_t)); ke->current_pid = 1000; /* Initial assigned pid */ /* Initialize mutex for variables controlling calls to 'ke_process_events()' */ pthread_mutex_init(&ke->process_events_mutex, NULL); /* Debug categories */ isa_inst_debug_category = debug_new_category(); isa_call_debug_category = debug_new_category(); elf_debug_category = debug_new_category(); ld_debug_category = debug_new_category(); syscall_debug_category = debug_new_category(); ctx_debug_category = debug_new_category(); /* Initialize GPU kernel */ gk_init(); /* Record start time */ ke_init_time = ke_timer(); }
/* Function that suspends the host thread waiting for a timer to expire, * and then schedules a call to 'ke_process_events'. */ void *ke_host_thread_timer(void *arg) { struct ctx_t *ctx = (struct ctx_t *) arg; uint64_t now = ke_timer(); struct timespec ts; uint64_t sleep_time; /* In usec */ /* Detach this thread - we don't want the parent to have to join it to release * its resources. The thread termination can be observed by thread-safely checking * the 'ctx->host_thread_timer_active' flag. */ pthread_detach(pthread_self()); /* Calculate sleep time, and sleep only if it is greater than 0 */ if (ctx->host_thread_timer_wakeup > now) { sleep_time = ctx->host_thread_timer_wakeup - now; ts.tv_sec = sleep_time / 1000000; ts.tv_nsec = (sleep_time % 1000000) * 1000; /* nsec */ nanosleep(&ts, NULL); } /* Timer expired, schedule call to 'ke_process_events' */ pthread_mutex_lock(&ke->process_events_mutex); ke->process_events_force = 1; ctx->host_thread_timer_active = 0; pthread_mutex_unlock(&ke->process_events_mutex); return NULL; }
/* Check for events detected in spawned host threads, like waking up contexts or * sending signals. * The list is only processed if flag 'ke->process_events_force' is set. */ void ke_process_events() { struct ctx_t *ctx, *next; uint64_t now = ke_timer(); /* Check if events need actually be checked. */ pthread_mutex_lock(&ke->process_events_mutex); if (!ke->process_events_force) { pthread_mutex_unlock(&ke->process_events_mutex); return; } /* By default, no subsequent call to 'ke_process_events' is assumed */ ke->process_events_force = 0; /* * LOOP 1 * Look at the list of suspended contexts and try to find * one that needs to be woken up. */ for (ctx = ke->suspended_list_head; ctx; ctx = next) { /* Save next */ next = ctx->suspended_next; /* Context is suspended in 'nanosleep' system call. */ if (ctx_get_status(ctx, ctx_nanosleep)) { uint32_t rmtp = ctx->regs->ecx; uint64_t zero = 0; uint32_t sec, usec; uint64_t diff; /* If 'ke_host_thread_suspend' is still running for this context, do nothing. */ if (ctx->host_thread_suspend_active) continue; /* Timeout expired */ if (ctx->wakeup_time <= now) { if (rmtp) mem_write(ctx->mem, rmtp, 8, &zero); syscall_debug("syscall 'nanosleep' - continue (pid %d)\n", ctx->pid); syscall_debug(" return=0x%x\n", ctx->regs->eax); ctx_clear_status(ctx, ctx_suspended | ctx_nanosleep); continue; } /* Context received a signal */ if (ctx->signal_masks->pending & ~ctx->signal_masks->blocked) { if (rmtp) { diff = ctx->wakeup_time - now; sec = diff / 1000000; usec = diff % 1000000; mem_write(ctx->mem, rmtp, 4, &sec); mem_write(ctx->mem, rmtp + 4, 4, &usec); } ctx->regs->eax = -EINTR; syscall_debug("syscall 'nanosleep' - interrupted by signal (pid %d)\n", ctx->pid); ctx_clear_status(ctx, ctx_suspended | ctx_nanosleep); continue; } /* No event available, launch 'ke_host_thread_suspend' again */ ctx->host_thread_suspend_active = 1; if (pthread_create(&ctx->host_thread_suspend, NULL, ke_host_thread_suspend, ctx)) fatal("syscall 'poll': could not create child thread"); continue; } /* Context suspended in 'rt_sigsuspend' system call */ if (ctx_get_status(ctx, ctx_sigsuspend)) { /* Context received a signal */ if (ctx->signal_masks->pending & ~ctx->signal_masks->blocked) { signal_handler_check_intr(ctx); ctx->signal_masks->blocked = ctx->signal_masks->backup; syscall_debug("syscall 'rt_sigsuspend' - interrupted by signal (pid %d)\n", ctx->pid); ctx_clear_status(ctx, ctx_suspended | ctx_sigsuspend); continue; } /* No event available. The context will never awake on its own, so no * 'ke_host_thread_suspend' is necessary. */ continue; } /* Context suspended in 'poll' system call */ if (ctx_get_status(ctx, ctx_poll)) { uint32_t prevents = ctx->regs->ebx + 6; uint16_t revents = 0; struct fd_t *fd; struct pollfd host_fds; int err; /* If 'ke_host_thread_suspend' is still running for this context, do nothing. */ if (ctx->host_thread_suspend_active) continue; /* Get file descriptor */ fd = fdt_entry_get(ctx->fdt, ctx->wakeup_fd); if (!fd) fatal("syscall 'poll': invalid 'wakeup_fd'"); /* Context received a signal */ if (ctx->signal_masks->pending & ~ctx->signal_masks->blocked) { signal_handler_check_intr(ctx); syscall_debug("syscall 'poll' - interrupted by signal (pid %d)\n", ctx->pid); ctx_clear_status(ctx, ctx_suspended | ctx_poll); continue; } /* Perform host 'poll' call */ host_fds.fd = fd->host_fd; host_fds.events = ((ctx->wakeup_events & 4) ? POLLOUT : 0) | ((ctx->wakeup_events & 1) ? POLLIN : 0); err = poll(&host_fds, 1, 0); if (err < 0) fatal("syscall 'poll': unexpected error in host 'poll'"); /* POLLOUT event available */ if (ctx->wakeup_events & host_fds.revents & POLLOUT) { revents = POLLOUT; mem_write(ctx->mem, prevents, 2, &revents); ctx->regs->eax = 1; syscall_debug("syscall poll - continue (pid %d) - POLLOUT occurred in file\n", ctx->pid); syscall_debug(" retval=%d\n", ctx->regs->eax); ctx_clear_status(ctx, ctx_suspended | ctx_poll); continue; } /* POLLIN event available */ if (ctx->wakeup_events & host_fds.revents & POLLIN) { revents = POLLIN; mem_write(ctx->mem, prevents, 2, &revents); ctx->regs->eax = 1; syscall_debug("syscall poll - continue (pid %d) - POLLIN occurred in file\n", ctx->pid); syscall_debug(" retval=%d\n", ctx->regs->eax); ctx_clear_status(ctx, ctx_suspended | ctx_poll); continue; } /* Timeout expired */ if (ctx->wakeup_time && ctx->wakeup_time < now) { revents = 0; mem_write(ctx->mem, prevents, 2, &revents); syscall_debug("syscall poll - continue (pid %d) - time out\n", ctx->pid); syscall_debug(" return=0x%x\n", ctx->regs->eax); ctx_clear_status(ctx, ctx_suspended | ctx_poll); continue; } /* No event available, launch 'ke_host_thread_suspend' again */ ctx->host_thread_suspend_active = 1; if (pthread_create(&ctx->host_thread_suspend, NULL, ke_host_thread_suspend, ctx)) fatal("syscall 'poll': could not create child thread"); continue; } /* Context suspended in a 'write' system call */ if (ctx_get_status(ctx, ctx_write)) { struct fd_t *fd; int count, err; uint32_t pbuf; void *buf; struct pollfd host_fds; /* If 'ke_host_thread_suspend' is still running for this context, do nothing. */ if (ctx->host_thread_suspend_active) continue; /* Context received a signal */ if (ctx->signal_masks->pending & ~ctx->signal_masks->blocked) { signal_handler_check_intr(ctx); syscall_debug("syscall 'write' - interrupted by signal (pid %d)\n", ctx->pid); ctx_clear_status(ctx, ctx_suspended | ctx_write); continue; } /* Get file descriptor */ fd = fdt_entry_get(ctx->fdt, ctx->wakeup_fd); if (!fd) fatal("syscall 'write': invalid 'wakeup_fd'"); /* Check if data is ready in file by polling it */ host_fds.fd = fd->host_fd; host_fds.events = POLLOUT; err = poll(&host_fds, 1, 0); if (err < 0) fatal("syscall 'write': unexpected error in host 'poll'"); /* If data is ready in the file, wake up context */ if (host_fds.revents) { pbuf = ctx->regs->ecx; count = ctx->regs->edx; buf = malloc(count); mem_read(ctx->mem, pbuf, count, buf); count = write(fd->host_fd, buf, count); if (count < 0) fatal("syscall 'write': unexpected error in host 'write'"); ctx->regs->eax = count; free(buf); syscall_debug("syscall write - continue (pid %d)\n", ctx->pid); syscall_debug(" return=0x%x\n", ctx->regs->eax); ctx_clear_status(ctx, ctx_suspended | ctx_write); continue; } /* Data is not ready to be written - launch 'ke_host_thread_suspend' again */ ctx->host_thread_suspend_active = 1; if (pthread_create(&ctx->host_thread_suspend, NULL, ke_host_thread_suspend, ctx)) fatal("syscall 'write': could not create child thread"); continue; } /* Context suspended in 'read' system call */ if (ctx_get_status(ctx, ctx_read)) { struct fd_t *fd; uint32_t pbuf; int count, err; void *buf; struct pollfd host_fds; /* If 'ke_host_thread_suspend' is still running for this context, do nothing. */ if (ctx->host_thread_suspend_active) continue; /* Context received a signal */ if (ctx->signal_masks->pending & ~ctx->signal_masks->blocked) { signal_handler_check_intr(ctx); syscall_debug("syscall 'read' - interrupted by signal (pid %d)\n", ctx->pid); ctx_clear_status(ctx, ctx_suspended | ctx_read); continue; } /* Get file descriptor */ fd = fdt_entry_get(ctx->fdt, ctx->wakeup_fd); if (!fd) fatal("syscall 'read': invalid 'wakeup_fd'"); /* Check if data is ready in file by polling it */ host_fds.fd = fd->host_fd; host_fds.events = POLLIN; err = poll(&host_fds, 1, 0); if (err < 0) fatal("syscall 'read': unexpected error in host 'poll'"); /* If data is ready, perform host 'read' call and wake up */ if (host_fds.revents) { pbuf = ctx->regs->ecx; count = ctx->regs->edx; buf = malloc(count); count = read(fd->host_fd, buf, count); if (count < 0) fatal("syscall 'read': unexpected error in host 'read'"); ctx->regs->eax = count; mem_write(ctx->mem, pbuf, count, buf); free(buf); syscall_debug("syscall 'read' - continue (pid %d)\n", ctx->pid); syscall_debug(" return=0x%x\n", ctx->regs->eax); ctx_clear_status(ctx, ctx_suspended | ctx_read); continue; } /* Data is not ready. Launch 'ke_host_thread_suspend' again */ ctx->host_thread_suspend_active = 1; if (pthread_create(&ctx->host_thread_suspend, NULL, ke_host_thread_suspend, ctx)) fatal("syscall 'read': could not create child thread"); continue; } /* Context suspended in a 'waitpid' system call */ if (ctx_get_status(ctx, ctx_waitpid)) { struct ctx_t *child; uint32_t pstatus; /* A zombie child is available to 'waitpid' it */ child = ctx_get_zombie(ctx, ctx->wakeup_pid); if (child) { /* Continue with 'waitpid' system call */ pstatus = ctx->regs->ecx; ctx->regs->eax = child->pid; if (pstatus) mem_write(ctx->mem, pstatus, 4, &child->exit_code); ctx_set_status(child, ctx_finished); syscall_debug("syscall waitpid - continue (pid %d)\n", ctx->pid); syscall_debug(" return=0x%x\n", ctx->regs->eax); ctx_clear_status(ctx, ctx_suspended | ctx_waitpid); continue; } /* No event available. Since this context won't awake on its own, no * 'ke_host_thread_suspend' is needed. */ continue; } } /* * LOOP 2 * Check list of all contexts for expired timers. */ for (ctx = ke->context_list_head; ctx; ctx = ctx->context_next) { int sig[3] = { 14, 26, 27 }; /* SIGALRM, SIGVTALRM, SIGPROF */ int i; /* If there is already a 'ke_host_thread_timer' running, do nothing. */ if (ctx->host_thread_timer_active) continue; /* Check for any expired 'itimer': itimer_value < now * In this case, send corresponding signal to process. * Then calculate next 'itimer' occurrence: itimer_value = now + itimer_interval */ for (i = 0; i < 3; i++ ) { /* Timer inactive or not expired yet */ if (!ctx->itimer_value[i] || ctx->itimer_value[i] > now) continue; /* Timer expired - send a signal. * The target process might be suspended, so the host thread is canceled, and a new * call to 'ke_process_events' is scheduled. Since 'ke_process_events_mutex' is * already locked, the thread-unsafe version of 'ctx_host_thread_suspend_cancel' is used. */ __ctx_host_thread_suspend_cancel(ctx); ke->process_events_force = 1; sim_sigset_add(&ctx->signal_masks->pending, sig[i]); /* Calculate next occurrence */ ctx->itimer_value[i] = 0; if (ctx->itimer_interval[i]) ctx->itimer_value[i] = now + ctx->itimer_interval[i]; } /* Calculate the time when next wakeup occurs. */ ctx->host_thread_timer_wakeup = 0; for (i = 0; i < 3; i++) { if (!ctx->itimer_value[i]) continue; assert(ctx->itimer_value[i] >= now); if (!ctx->host_thread_timer_wakeup || ctx->itimer_value[i] < ctx->host_thread_timer_wakeup) ctx->host_thread_timer_wakeup = ctx->itimer_value[i]; } /* If a new timer was set, launch 'ke_host_thread_timer' again */ if (ctx->host_thread_timer_wakeup) { ctx->host_thread_timer_active = 1; if (pthread_create(&ctx->host_thread_timer, NULL, ke_host_thread_timer, ctx)) fatal("%s: could not create child thread", __FUNCTION__); } } /* * LOOP 3 * Process pending signals in running contexts to launch signal handlers */ for (ctx = ke->running_list_head; ctx; ctx = ctx->running_next) { signal_handler_check(ctx); } /* Unlock */ pthread_mutex_unlock(&ke->process_events_mutex); }
/* Function that suspends the host thread waiting for an event to occur. * When the event finally occurs (i.e., before the function finishes, a * call to 'ke_process_events' is scheduled. * The argument 'arg' is the associated guest context. */ void *ke_host_thread_suspend(void *arg) { struct ctx_t *ctx = (struct ctx_t *) arg; uint64_t now = ke_timer(); /* Detach this thread - we don't want the parent to have to join it to release * its resources. The thread termination can be observed by atomically checking * the 'ctx->host_thread_suspend_active' flag. */ pthread_detach(pthread_self()); /* Context suspended in 'poll' system call */ if (ctx_get_status(ctx, ctx_nanosleep)) { uint64_t timeout; /* Calculate remaining sleep time in microseconds */ timeout = ctx->wakeup_time > now ? ctx->wakeup_time - now : 0; usleep(timeout); } else if (ctx_get_status(ctx, ctx_poll)) { struct fd_t *fd; struct pollfd host_fds; int err, timeout; /* Get file descriptor */ fd = fdt_entry_get(ctx->fdt, ctx->wakeup_fd); if (!fd) fatal("syscall 'poll': invalid 'wakeup_fd'"); /* Calculate timeout for host call in milliseconds from now */ if (!ctx->wakeup_time) timeout = -1; else if (ctx->wakeup_time < now) timeout = 0; else timeout = (ctx->wakeup_time - now) / 1000; /* Perform blocking host 'poll' */ host_fds.fd = fd->host_fd; host_fds.events = ((ctx->wakeup_events & 4) ? POLLOUT : 0) | ((ctx->wakeup_events & 1) ? POLLIN : 0); err = poll(&host_fds, 1, timeout); if (err < 0) fatal("syscall 'poll': unexpected error in host 'poll'"); } else if (ctx_get_status(ctx, ctx_read)) { struct fd_t *fd; struct pollfd host_fds; int err; /* Get file descriptor */ fd = fdt_entry_get(ctx->fdt, ctx->wakeup_fd); if (!fd) fatal("syscall 'read': invalid 'wakeup_fd'"); /* Perform blocking host 'poll' */ host_fds.fd = fd->host_fd; host_fds.events = POLLIN; err = poll(&host_fds, 1, -1); if (err < 0) fatal("syscall 'read': unexpected error in host 'poll'"); } else if (ctx_get_status(ctx, ctx_write)) { struct fd_t *fd; struct pollfd host_fds; int err; /* Get file descriptor */ fd = fdt_entry_get(ctx->fdt, ctx->wakeup_fd); if (!fd) fatal("syscall 'write': invalid 'wakeup_fd'"); /* Perform blocking host 'poll' */ host_fds.fd = fd->host_fd; host_fds.events = POLLOUT; err = poll(&host_fds, 1, -1); if (err < 0) fatal("syscall 'write': unexpected error in host 'write'"); } /* Event occurred - thread finishes */ pthread_mutex_lock(&ke->process_events_mutex); ke->process_events_force = 1; ctx->host_thread_suspend_active = 0; pthread_mutex_unlock(&ke->process_events_mutex); return NULL; }
void sim_stats_summary(void) { long long now = ke_timer(); long long gpu_now = gk_timer(); long long inst_count; double sec_count; double inst_per_sec; double inst_per_cycle; double branch_acc; double cycles_per_sec; /* Check if any simulation was actually performed */ inst_count = cpu_sim_kind == cpu_sim_functional ? ke->inst_count : cpu->inst; if (!inst_count) return; /* Statistics */ fprintf(stderr, "\n"); fprintf(stderr, ";\n"); fprintf(stderr, "; Simulation Statistics Summary\n"); fprintf(stderr, ";\n"); fprintf(stderr, "\n"); /* CPU functional simulation */ sec_count = (double) now / 1e6; inst_per_sec = sec_count > 0.0 ? (double) inst_count / sec_count : 0.0; fprintf(stderr, "[ CPU ]\n"); fprintf(stderr, "Time = %.2f\n", sec_count); fprintf(stderr, "Instructions = %lld\n", inst_count); fprintf(stderr, "InstructionsPerSecond = %.0f\n", inst_per_sec); fprintf(stderr, "Contexts = %d\n", ke->running_list_max); fprintf(stderr, "Memory = %lu\n", mem_max_mapped_space); fprintf(stderr, "SimEnd = %s\n", map_value(&ke_sim_finish_map, ke_sim_finish)); /* CPU detailed simulation */ if (cpu_sim_kind == cpu_sim_detailed) { inst_per_cycle = cpu->cycle ? (double) cpu->inst / cpu->cycle : 0.0; branch_acc = cpu->branches ? (double) (cpu->branches - cpu->mispred) / cpu->branches : 0.0; cycles_per_sec = sec_count > 0.0 ? (double) cpu->cycle / sec_count : 0.0; fprintf(stderr, "Cycles = %lld\n", cpu->cycle); fprintf(stderr, "InstructionsPerCycle = %.4g\n", inst_per_cycle); fprintf(stderr, "BranchPredictionAccuracy = %.4g\n", branch_acc); fprintf(stderr, "CyclesPerSecond = %.0f\n", cycles_per_sec); } fprintf(stderr, "\n"); /* GPU functional simulation */ if (gk->ndrange_count) { sec_count = (double) gpu_now / 1e6; inst_per_sec = sec_count > 0.0 ? (double) gk->inst_count / sec_count : 0.0; fprintf(stderr, "[ GPU ]\n"); fprintf(stderr, "Time = %.2f\n", sec_count); fprintf(stderr, "NDRangeCount = %d\n", gk->ndrange_count); fprintf(stderr, "Instructions = %lld\n", gk->inst_count); fprintf(stderr, "InstructionsPerSecond = %.0f\n", inst_per_sec); /* GPU detailed simulation */ if (gpu_sim_kind == gpu_sim_detailed) { inst_per_cycle = gpu->cycle ? (double) gk->inst_count / gpu->cycle : 0.0; cycles_per_sec = sec_count > 0.0 ? (double) gpu->cycle / sec_count : 0.0; fprintf(stderr, "Cycles = %lld\n", gpu->cycle); fprintf(stderr, "InstructionsPerCycle = %.4g\n", inst_per_cycle); fprintf(stderr, "CyclesPerSecond = %.0f\n", cycles_per_sec); } fprintf(stderr, "\n"); } }
/* Return a counter of microseconds relative to the first time the GPU started to run. * This counter runs only while the GPU is active, stopping and resuming after calls * to 'gk_timer_stop()' and 'gk_timer_start()', respectively. */ long long gk_timer(void) { return gk->timer_running ? ke_timer() - gk->timer_start_time + gk->timer_acc : gk->timer_acc; }
void gk_timer_stop(void) { assert(gk->timer_running); gk->timer_acc += ke_timer() - gk->timer_start_time; gk->timer_running = 0; }
void gk_timer_start(void) { assert(!gk->timer_running); gk->timer_start_time = ke_timer(); gk->timer_running = 1; }