summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Braun <rbraun@sceen.net>2017-08-27 16:35:44 +0200
committerRichard Braun <rbraun@sceen.net>2017-08-27 16:35:44 +0200
commit000c3defddf008c495f089cb8194b99a10e599cd (patch)
tree4172cd604d3ae475b0c3570bf6c812df143a355b
parent3824ad00551b43083c39638ce62ece92c34bae94 (diff)
kern/thread: implement timed sleeps
-rw-r--r--kern/thread.c132
-rw-r--r--kern/thread.h13
-rw-r--r--kern/thread_i.h1
3 files changed, 104 insertions, 42 deletions
diff --git a/kern/thread.c b/kern/thread.c
index 2a1e124e..73e1cfb8 100644
--- a/kern/thread.c
+++ b/kern/thread.c
@@ -109,6 +109,7 @@
#include <kern/syscnt.h>
#include <kern/task.h>
#include <kern/thread.h>
+#include <kern/timer.h>
#include <kern/turnstile.h>
#include <kern/work.h>
#include <machine/cpu.h>
@@ -2419,49 +2420,14 @@ thread_join(struct thread *thread)
thread_join_common(thread);
}
-void
-thread_sleep(struct spinlock *interlock, const void *wchan_addr,
- const char *wchan_desc)
-{
- struct thread_runq *runq;
- struct thread *thread;
- unsigned long flags;
-
- thread = thread_self();
- assert(thread->preempt == 1);
-
- runq = thread_runq_local();
- spinlock_lock_intr_save(&runq->lock, &flags);
-
- if (interlock != NULL) {
- thread_preempt_disable();
- spinlock_unlock(interlock);
- }
-
- thread_set_wchan(thread, wchan_addr, wchan_desc);
- thread->state = THREAD_SLEEPING;
-
- runq = thread_runq_schedule(runq);
- assert(thread->state == THREAD_RUNNING);
-
- spinlock_unlock_intr_restore(&runq->lock, flags);
-
- if (interlock != NULL) {
- spinlock_lock(interlock);
- thread_preempt_enable_no_resched();
- }
-
- assert(thread->preempt == 1);
-}
-
-void
-thread_wakeup(struct thread *thread)
+static int
+thread_wakeup_common(struct thread *thread, int error)
{
struct thread_runq *runq;
unsigned long flags;
if ((thread == NULL) || (thread == thread_self())) {
- return;
+ return ERROR_INVAL;
}
/*
@@ -2477,7 +2443,7 @@ thread_wakeup(struct thread *thread)
if (thread->state == THREAD_RUNNING) {
thread_unlock_runq(runq, flags);
- return;
+ return ERROR_INVAL;
}
thread_clear_wchan(thread);
@@ -2495,10 +2461,98 @@ thread_wakeup(struct thread *thread)
spinlock_lock(&runq->lock);
}
+ thread->wakeup_error = error;
thread_runq_wakeup(runq, thread);
spinlock_unlock(&runq->lock);
cpu_intr_restore(flags);
thread_preempt_enable();
+
+ return 0;
+}
+
+int
+thread_wakeup(struct thread *thread)
+{
+ return thread_wakeup_common(thread, 0);
+}
+
+struct thread_timeout_waiter {
+ struct thread *thread;
+ struct timer timer;
+};
+
+static void
+thread_timeout(struct timer *timer)
+{
+ struct thread_timeout_waiter *waiter;
+
+ waiter = structof(timer, struct thread_timeout_waiter, timer);
+ thread_wakeup_common(waiter->thread, ERROR_TIMEDOUT);
+}
+
+static int
+thread_sleep_common(struct spinlock *interlock, const void *wchan_addr,
+ const char *wchan_desc, bool timed, uint64_t ticks)
+{
+ struct thread_timeout_waiter waiter;
+ struct thread_runq *runq;
+ struct thread *thread;
+ unsigned long flags;
+
+ thread = thread_self();
+ assert(thread->preempt == 1);
+
+ if (timed) {
+ waiter.thread = thread;
+ timer_init(&waiter.timer, thread_timeout, TIMER_INTR);
+ timer_schedule(&waiter.timer, ticks);
+ }
+
+ runq = thread_runq_local();
+ spinlock_lock_intr_save(&runq->lock, &flags);
+
+ if (interlock != NULL) {
+ thread_preempt_disable();
+ spinlock_unlock(interlock);
+ }
+
+ thread_set_wchan(thread, wchan_addr, wchan_desc);
+ thread->state = THREAD_SLEEPING;
+
+ runq = thread_runq_schedule(runq);
+ assert(thread->state == THREAD_RUNNING);
+
+ spinlock_unlock_intr_restore(&runq->lock, flags);
+
+ if (timed) {
+ timer_cancel(&waiter.timer);
+ }
+
+ if (interlock != NULL) {
+ spinlock_lock(interlock);
+ thread_preempt_enable_no_resched();
+ }
+
+ assert(thread->preempt == 1);
+
+ return thread->wakeup_error;
+}
+
+void
+thread_sleep(struct spinlock *interlock, const void *wchan_addr,
+ const char *wchan_desc)
+{
+ int error;
+
+ error = thread_sleep_common(interlock, wchan_addr, wchan_desc, false, 0);
+ assert(!error);
+}
+
+int
+thread_timedsleep(struct spinlock *interlock, const void *wchan_addr,
+ const char *wchan_desc, uint64_t ticks)
+{
+ return thread_sleep_common(interlock, wchan_addr, wchan_desc, true, ticks);
}
void __init
diff --git a/kern/thread.h b/kern/thread.h
index b3bf93f0..04eea2b5 100644
--- a/kern/thread.h
+++ b/kern/thread.h
@@ -36,6 +36,7 @@
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <stdnoreturn.h>
#include <kern/atomic.h>
@@ -210,18 +211,24 @@ void thread_join(struct thread *thread);
* address should refer to a relevant synchronization object, normally
* containing the interlock, but not necessarily.
*
+ * When bounding the duration of the sleep, the caller must pass an absolute
+ * time in ticks, and ERROR_TIMEDOUT is returned if that time is reached
+ * before the thread is awaken.
+ *
* Implies a memory barrier.
*/
void thread_sleep(struct spinlock *interlock, const void *wchan_addr,
const char *wchan_desc);
+int thread_timedsleep(struct spinlock *interlock, const void *wchan_addr,
+ const char *wchan_desc, uint64_t ticks);
/*
* Schedule a thread for execution on a processor.
*
- * No action is performed if the target thread is NULL, the calling thread,
- * or already in the running state.
+ * If the target thread is NULL, the calling thread, or already in the
+ * running state, no action is performed and ERROR_INVAL is returned.
*/
-void thread_wakeup(struct thread *thread);
+int thread_wakeup(struct thread *thread);
/*
* Start running threads on the local processor.
diff --git a/kern/thread_i.h b/kern/thread_i.h
index cd349771..9ff4b764 100644
--- a/kern/thread_i.h
+++ b/kern/thread_i.h
@@ -107,6 +107,7 @@ struct thread {
bool in_runq; /* (r) */
const void *wchan_addr; /* (r) */
const char *wchan_desc; /* (r) */
+ int wakeup_error; /* (r) */
unsigned short state; /* (r) */
/* Sleep queue available for lending */