Skip to main content

Threads and Processes

Kernel threading and user process management is a key part of driver development

Kernel Threads

Kernel threads are small processes in the kernel that are able to block/signal/etc.
They are extremely useful for poller processes or cache flushing processes.

Creating kernel threads in Ethereal is extremely easy, only two lines of code are required.

The interface to make a kernel thread is known as process_createKernel:

process_t *process_createKernel(char *name, unsigned int flags, unsigned int priority, kthread_t entrypoint, void *data);

kthread_t is declared as:

typedef void (*kthread_t)(void *data);

After you create a kernel thread, insert it by doing:

scheduler_insertThread(kthread->main_thread);

Your thread is now in the scheduler and you can assume it is already running.

Current core data

note

To access this interface, include kernel/processor_data.h

Ethereal provides interfaces to get the data on the CPU being run on (and subsequently current process/thread) via the current_cpu macro.

This macro is implementation specific and nothing may be assumed of it

Here is the processor_t structure that it contains:


typedef struct _processor {
int cpu_id; // CPU ID
page_t *current_dir; // Current page directory
struct thread *current_thread; // Current thread of the process

struct process *current_process; // Current process of the CPU

struct process *idle_process; // Idle process of the CPU

#if defined(__ARCH_X86_64__) || defined(__ARCH_I386__)

#ifdef __ARCH_X86_64__
uintptr_t kstack; // (0x40) Kernel-mode stack loaded in TSS
uintptr_t ustack; // (0x48) Usermode stack, saved in SYSCALL entrypoint
#endif

int lapic_id;

/* CPU basic information */
char cpu_model[48];
const char *cpu_manufacturer;
int cpu_model_number;
int cpu_family;
#endif

scheduler_cpu_t sched; // Scheduler data
uint64_t idle_time; // Time the processor has spent idling
} processor_t;
caution

Unless you wrap your code in #ifdefs, anything marked as x86_64 or i386 specific is off limits.

Useful variables:

  • cpu_id: Current CPU ID
  • current_dir: Page directory
  • current_process: Currently running process
  • current_thread: Currently running thread

Sleep API

Sleeping and blocking threads is well supported in Ethereal.

Overview

Ethereal's sleep system operates on a basis of prepare, notify, enter.

  • Prepare yourself for sleeping with a sleep_untilXXX or other function
  • Notify anyone else that you are sleeping and to wake you up (this is up to you)
  • Enter sleep state.

Here's an example that sleeps for a second:

sleep_untilTime(current_cpu->current_thread, 1, 0);
int w = sleep_enter();

sleep_enter will enter you into the sleep state.

It returns one of a few wakeup reasons:

  • WAKEUP_ANOTHER_THREAD: Another thread used sleep_wakeup to wake you up
  • WAKEUP_TIME: You wokeup on time expiration
  • WAKEUP_SIGNAL: You were signalled
  • WAKEUP_COND: Condition woke you up

Waiting for another thread to wake you up

You can use the sleep_untilNever call for this:

int sleep_untilNever(thread_t *thr);

Simply have the other thread use sleep_wakeup to wake you up:

int sleep_wakeup(thread_t *thread);

Wakeups from this will return WAKEUP_ANOTHER_THREAD (or another reason)

Waiting for a duration

You can use sleep_untilTime for this:

int sleep_untilTime(struct thread *thread, unsigned long seconds, unsigned long subseconds);

Wakeups from this will return WAKEUP_TIME if the time expires (or another reason)

Waiting in a queue

First, you'll need to create the queue using sleep_createQueue

sleep_queue_t *sleep_createQueue(char *name);

Then to sleep in it:

int sleep_inQueue(sleep_queue_t *queue);

Others can wakeup you up via:

int sleep_wakeupQueue(sleep_queue_t *queue, int amounts);

Use -1 for all threads in the queue.

Wakeups from this will return WAKEUP_ANOTHER_THREAD (or another reason)

Waiting for a condition

caution

This interface may not be well supported

Use sleep_untilCondition:

int sleep_untilCondition(struct thread *thread, sleep_condition_t condition, void *context);

The condition function simply takes context as an argument and returns 1 when the condition is met.

Wakeups from this will return WAKEUP_COND (or another reason)

Synchronization

Spinlocks and mutexes are fun!

Spinlocks

Make a spinlock via spinlock_create or declare it as an empty structure:

spinlock_t *spinlock_create(char *name);

Interfaces (you should be well familiar):

void spinlock_acquire(spinlock_t *spinlock);
int spinlock_tryAcquire(spinlock_t *spinlock);
void spinlock_release(spinlock_t *spinlock);
void spinlock_destroy(spinlock_t *spinlock);
note

IRQs are disabled during acquisition and restored after release

Mutexes

Make a mutex via mutex_create (you cannot declare it as an empty structure):

mutex_t *mutex_create(char *name);

Interfaces (you should be well familiar):

void mutex_acquire(mutex_t *mutex);
int mutex_tryAcquire(mutex_t *mutex);
void mutex_release(mutex_t *mutex);
void mutex_destroy(mutex_t *mutex);