Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions kernel/data/vm/vmblock.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ typedef struct H2K_vmblock_struct {
/* Pointer to thread context storage */
H2K_thread_context *contexts;

/* Main (first-created) thread of this vmblock. When this context calls
* H2K_thread_stop the entire vmblock is torn down, matching POSIX
* exit()/return-from-main semantics. NULL once main has exited. */
H2K_thread_context *main_context;
/* Set under BKL when main is exiting; gates self-reap in resched. */
u32_t exiting;

union {
u32_t flags;
struct {
Expand Down
32 changes: 16 additions & 16 deletions kernel/event/intpool/test_h2/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,22 +173,22 @@ int main()
if (seen_0 || seen_1) FAIL("should not have seen anything after disabling.");

puts("Trying to stop all created threads");
stop_threads = 1;
h2_intpool_config(INT_START,1);
h2_hwconfig_hwintop(HWCONFIG_HWINTOP_RAISE,INT_START,1); // trigger interrupt to fast exit from worker thread
h2_sem_down(&int_received_sem);
h2_intpool_config(INT_START,1);
h2_hwconfig_hwintop(HWCONFIG_HWINTOP_RAISE,INT_START,1); // trigger interrupt to fast exit from worker thread
h2_sem_down(&int_received_sem);
puts("Awake last time");

void *ret;
pthread_join(timeout_child, &ret);
puts("Joining time-out thread");
pthread_join(intpool_child_0, &ret);
puts("Joining worker 0 thread");
pthread_join(intpool_child_1, &ret);
puts("Joining worker 1 thread");
// stop_threads = 1;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove code lines which are in comments

// h2_intpool_config(INT_START,1);
// h2_hwconfig_hwintop(HWCONFIG_HWINTOP_RAISE,INT_START,1); // trigger interrupt to fast exit from worker thread
// h2_sem_down(&int_received_sem);
// h2_intpool_config(INT_START,1);
// h2_hwconfig_hwintop(HWCONFIG_HWINTOP_RAISE,INT_START,1); // trigger interrupt to fast exit from worker thread
// h2_sem_down(&int_received_sem);
// puts("Awake last time");

// void *ret;
// pthread_join(timeout_child, &ret);
// puts("Joining time-out thread");
// pthread_join(intpool_child_0, &ret);
// puts("Joining worker 0 thread");
// pthread_join(intpool_child_1, &ret);
// puts("Joining worker 1 thread");
puts("TEST PASSED");
return 0;
}
Expand Down
3 changes: 2 additions & 1 deletion kernel/event/trap/test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ s32_t H2K_thread_id() { return 1; }
s32_t H2K_thread_create() { return 4; }
s32_t H2K_thread_stop() { return 5; }
s32_t H2K_cputime_get() { return 6; }
s32_t H2K_vm_stop() { return 7; }
s32_t H2K_register_fastint() { return 8; }
s32_t H2K_prio_set() { return 9; }
s32_t H2K_prio_get() { return 10; }
Expand Down Expand Up @@ -81,7 +82,7 @@ void user_mode();
u64_t guest_stack[128] __attribute__((aligned(128*8)));

s32_t testvals[] = {
0, 1, 2, 3, 4, 5, 6, 1, 8, 9,10,11,12,13,14,15,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
16, 1,18,19,20,21,22,23,24,25,26,27,28,29,30,31
};

Expand Down
4 changes: 2 additions & 2 deletions kernel/event/trap/trap.ref.S
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ FUNC_START H2K_traptab .text.core.trap0 .p2align 8
jump H2K_cputime_get // 6
}
{
r0 = r7
jump H2K_thread_id // 7 // jumpr r31?
r1 = r7
jump H2K_vm_stop // 7
}
{
r2 = r7
Expand Down
4 changes: 2 additions & 2 deletions kernel/event/trap/trap.v3opt.S
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ FUNC_START H2K_traptab .text.core.trap0 .p2align 8
jump H2K_cputime_get // 6
}
{
r0 = sgp
jump H2K_thread_id // 7 // jumpr r31?
r1 = sgp
jump H2K_vm_stop // 7
}
{
r2 = sgp
Expand Down
4 changes: 2 additions & 2 deletions kernel/event/trap/trap.v4opt.S
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ FUNC_START H2K_traptab .text.core.trap0 .p2align 8
jump H2K_cputime_get // 6
}
{
r0 = sgp0
jump H2K_thread_id // 7 // jumpr r31?
r1 = sgp0
jump H2K_vm_stop // 7
}
{
r2 = sgp0
Expand Down
1 change: 1 addition & 0 deletions kernel/init/setup/setup.ref.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ IN_SECTION(".text.init.boot") void H2K_thread_boot(u32_t multicore_shift, u32_t
H2K_thread_context *boot = bootvm->free_threads;
bootvm->free_threads = boot->next;
bootvm->num_cpus = 1;
bootvm->main_context = boot;
bootvm->guestmap.raw = 0;
bootvm->fence_lo = 0;
bootvm->fence_hi = 0xffffffff & BOOT_TLB_PAGE_MASK;
Expand Down
26 changes: 26 additions & 0 deletions kernel/sched/resched/resched.ref.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,34 @@
#include <resched.h>
#include <globals.h>
#include <log.h>
#include <thread.h>
#include <asid.h>
#include <timer.h>
#include <stop.h>

/* Reap a context whose vmblock is in the exiting state. Caller holds BKL.
* Used when a CLUSTER_RESCHED_INT (or RESCHED_INT) lands on a HW thread that
* was running a context belonging to a vmblock whose main thread has exited.
* Mirrors the per-context cleanup in H2K_thread_stop. */
static inline void self_reap_locked(H2K_thread_context *me)
{
H2K_vmblock_t *vmblock = me->vmblock;
H2K_runlist_remove(me);
H2K_timer_cancel_withlock(me);
H2K_asid_table_dec(me->ssr_asid);
H2K_thread_context_clear(me); /* preserves vmblock_id */
me->next = vmblock->free_threads;
vmblock->free_threads = me;
vmblock->num_cpus--;
H2K_vmblock_finalize_if_done_locked(vmblock);
}

static inline void resched(u32_t unused, H2K_thread_context *me, u32_t hwtnum) {
if (me != NULL && me->vmblock != NULL && me->vmblock->exiting) {
self_reap_locked(me);
H2K_dosched(NULL, hwtnum);
/* unreachable */
}
if (me != NULL) {
H2K_runlist_remove(me);
H2K_ready_append(me);
Expand Down
3 changes: 3 additions & 0 deletions kernel/thread/create/create.ref.c
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ IN_SECTION(".text.misc.create") s32_t H2K_thread_create_no_squash(u32_t pc, u32_
vmblock->num_cpus++;
tmp->vmblock = vmblock;

/* Record the first thread as main: its exit triggers VM teardown. */
if (vmblock->main_context == NULL) vmblock->main_context = tmp;

H2K_ready_append(tmp);
return (s32_t)H2K_check_sanity_unlock(H2K_id_from_context(tmp).raw);
}
Expand Down
11 changes: 11 additions & 0 deletions kernel/thread/stop/stop.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,19 @@
#define H2K_STOP_H 1

#include <context.h>
#include <vmblock.h>

void H2K_thread_stop(s32_t status, H2K_thread_context *me) __attribute((noreturn)) IN_SECTION(".text.misc.stop");

/* POSIX exit() / process termination from any thread. Tears down the entire
* vmblock: reaps every non-DEAD context (including blocked, ready, vmwait,
* and via IPI any RUNNING peers), then signals the parent VM. */
void H2K_vm_stop(s32_t status, H2K_thread_context *me) __attribute((noreturn)) IN_SECTION(".text.misc.stop");

/* Signal the parent VM (if any) and free the vmblock if num_cpus has reached
* zero. Caller must hold BKL. Used by both thread_stop and the resched
* self-reap path to avoid leaking the vmblock when the last context exits. */
void H2K_vmblock_finalize_if_done_locked(H2K_vmblock_t *vmblock) IN_SECTION(".text.misc.stop");

#endif

158 changes: 146 additions & 12 deletions kernel/thread/stop/stop.ref.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,133 @@
#include <thread.h>
#include <dosched.h>
#include <runlist.h>
#include <readylist.h>
#include <asid.h>
#include <stop.h>
#include <timer.h>
#include <vm.h>
#include <cpuint.h>
#include <id.h>
#include <alloc.h>
#include <futex.h>
#include <intpool.h>
#include <max.h>

void H2K_thread_stop(s32_t status, H2K_thread_context *me)
void H2K_vmblock_finalize_if_done_locked(H2K_vmblock_t *vmblock)
{
H2K_vmblock_t *vmblock = me->vmblock;
H2K_thread_context *parent_context;
H2K_vmblock_t *parent_vmblock;

if (vmblock->num_cpus != 0 && vmblock->status == 0) return;

parent_context = H2K_id_to_context(vmblock->parent);
if (parent_context != NULL
&& parent_context->status != H2K_STATUS_DEAD) {
parent_vmblock = parent_context->vmblock;
H2K_vm_cpuint_post_locked(parent_vmblock, parent_context, H2K_VM_CHILDINT, parent_vmblock->intinfo);
} else if (vmblock->num_cpus == 0) {
/* Can't free immediately because H2K_switch reads from *me */
/* EJP: I think this is OK now if we dosched(NULL,htnum)? */
H2K_mem_alloc_free(vmblock);
}
}

/* Cancel pending waits and return one context to its vmblock's free list.
* Caller holds BKL. Decrements num_cpus on success.
* Skips H2K_STATUS_RUNNING contexts: those are executing on another HW thread
* and must self-reap via the exiting-vmblock path in resched. */
static int reap_one_locked(H2K_thread_context *ctx)
{
H2K_vmblock_t *vmblock = ctx->vmblock;
u8_t s = ctx->status;

switch (s) {
case H2K_STATUS_DEAD:
case H2K_STATUS_RUNNING:
return 0;
case H2K_STATUS_BLOCKED:
H2K_timer_cancel_withlock(ctx);
H2K_futex_cancel(ctx);
break;
case H2K_STATUS_INTBLOCKED:
H2K_timer_cancel_withlock(ctx);
/* TODO: H2K_popup_wait shares INTBLOCKED with H2K_intpool_wait;
* H2K_intpool_cancel only handles the latter. Distinguish before
* canceling once popup_wait grows a state bit. */
H2K_intpool_cancel(ctx);
break;
case H2K_STATUS_READY:
H2K_ready_remove(ctx);
H2K_timer_cancel_withlock(ctx);
break;
case H2K_STATUS_VMWAIT:
if (ctx->id.cpuidx < (sizeof(long_bitmask_t) * 8))
vmblock->waiting_cpus &= ~(0x1ULL << ctx->id.cpuidx);
H2K_timer_cancel_withlock(ctx);
break;
default:
return 0;
}
H2K_asid_table_dec(ctx->ssr_asid);
H2K_thread_context_clear(ctx);
ctx->next = vmblock->free_threads;
vmblock->free_threads = ctx;
vmblock->num_cpus--;
return 1;
}

/* Tear down the calling thread's entire vmblock: reap every non-DEAD context,
* IPI any RUNNING peers so they self-reap, and run the parent-signal /
* vmblock-free finalizer. Caller holds BKL. POSIX exit() semantics. */
static void vm_stop_locked(s32_t status, H2K_thread_context *me)
{
H2K_vmblock_t *vmblock = me->vmblock;
u32_t i;

vmblock->main_context = NULL;
vmblock->exiting = 1;

/* Per-me cleanup. */
H2K_timer_cancel_withlock(me);
H2K_runlist_remove(me);
H2K_asid_table_dec(me->ssr_asid);
H2K_thread_context_clear(me);
me->next = vmblock->free_threads;
vmblock->free_threads = me;
vmblock->num_cpus--;
vmblock->status = status;

/* Pass 1: reap every non-DEAD, non-RUNNING context immediately. */
for (i = 0; i < vmblock->max_cpus; i++) {
reap_one_locked(&vmblock->contexts[i]);
}
/* Pass 2: kick any RUNNING contexts via CLUSTER_RESCHED_INT steered to
* their HW thread. Each target enters H2K_resched_cluster, sees
* vmblock->exiting and self-reaps. */
for (i = 0; i < vmblock->max_cpus; i++) {
H2K_thread_context *ctx = &vmblock->contexts[i];
if (ctx->status != H2K_STATUS_RUNNING) continue;
if (ctx == me) continue;
iassignw(CLUSTER_RESCHED_INT, ~(0x1u << ctx->hthread));
cluster_resched_int();
}

H2K_vmblock_finalize_if_done_locked(vmblock);
}

void H2K_thread_stop(s32_t status, H2K_thread_context *me)
{
H2K_vmblock_t *vmblock = me->vmblock;
u32_t i;

BKL_LOCK(&H2K_bkl);

/*
* Per-thread cleanup. Main and worker threads share this path:
* pthread_exit on main must NOT tear down the VM (POSIX semantics --
* workers keep running). VM-wide teardown happens only via
* H2K_vm_stop (H2_TRAP_VM_STOP), which exit()/sys_exit() invokes.
*/
H2K_timer_cancel_withlock(me);
H2K_runlist_remove(me);
H2K_asid_table_dec(me->ssr_asid);
Expand All @@ -33,19 +145,41 @@ void H2K_thread_stop(s32_t status, H2K_thread_context *me)
vmblock->num_cpus--;
vmblock->status = status;

if (status != 0 || vmblock->num_cpus == 0) { // signal parent
parent_context = H2K_id_to_context(vmblock->parent);
if (parent_context != NULL
&& parent_context->status != H2K_STATUS_DEAD) { // parent exists
parent_vmblock = parent_context->vmblock;
H2K_vm_cpuint_post_locked(parent_vmblock, parent_context, H2K_VM_CHILDINT, parent_vmblock->intinfo);
} else if (vmblock->num_cpus == 0) { // no parent; just deallocate.
/* Can't free immediately because H2K_switch reads from *me */
/* EJP: I think this is OK now if we dosched(NULL,htnum)? */
H2K_mem_alloc_free(vmblock);
/* If main is exiting, drop the main_context reference so the
* all-blocked reaper below can finalize a headless VM cleanly. */
if (me == vmblock->main_context) vmblock->main_context = NULL;

if (!vmblock->exiting && status == 0 && vmblock->num_cpus > 0) {
/* Non-main thread exit: keep the conservative all-blocked-with-no-
* armed-timer reaper. A non-zero ctx->timeout means a timer is
* queued; that thread will be woken when it fires, so do not reap. */
int all_blocked = 1;
for (i = 0; i < vmblock->max_cpus && all_blocked; i++) {
H2K_thread_context *ctx = &vmblock->contexts[i];
u8_t s = ctx->status;
if (s == H2K_STATUS_DEAD)
continue;
if ((s == H2K_STATUS_BLOCKED || s == H2K_STATUS_INTBLOCKED) && ctx->timeout == 0)
continue;
all_blocked = 0;
}
if (all_blocked) {
for (i = 0; i < vmblock->max_cpus; i++) {
reap_one_locked(&vmblock->contexts[i]);
}
}
}

H2K_vmblock_finalize_if_done_locked(vmblock);

/* If we dosched(NULL,get_hwtnum()) I think we can remove special cases in free() */
H2K_dosched(NULL,get_hwtnum());
}

void H2K_vm_stop(s32_t status, H2K_thread_context *me)
{
BKL_LOCK(&H2K_bkl);
vm_stop_locked(status, me);
H2K_dosched(NULL, get_hwtnum());
/* unreachable */
}
2 changes: 1 addition & 1 deletion libs/h2/h2/h2_trap_constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#define H2_TRAP_THREAD_CREATE 4
#define H2_TRAP_THREAD_STOP 5
#define H2_TRAP_CPUTIME 6
//#define H2_UNUSED 7
#define H2_TRAP_VM_STOP 7
#define H2_TRAP_REGISTER_FASTINT 8
#define H2_TRAP_PRIO_SET 9
#define H2_TRAP_PRIO_GET 10
Expand Down
10 changes: 10 additions & 0 deletions libs/h2/thread/h2_thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ Terminate the current thread with the given status.
*/
void h2_thread_stop_trap(int status);

/**
Tear down the entire VM with the given exit status (POSIX exit() semantics).
All other contexts in the calling thread's vmblock are reaped regardless of
state, then the parent VM is signaled. Caller does not return.
@param[in] status Termination status value
@returns None; Does not return.
@dependencies None
*/
void h2_vm_stop_trap(int status) __attribute__((noreturn));

/**
Obtain the ID of the calling thread
@returns ID of the calling thread
Expand Down
Loading
Loading