Task states about runnability: include/linux/sched.h
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
...
TASK_RUNNING: the task is runnable – either currently running or on a run queue waiting to run
TASK_INTERRUPTIBLE: the task is sleeping waiting for some condition to exist – can be awakened prematurely if it receives a signal
TASK_UNINTERRUPTIBLE: the task is sleeping waiting for some condition to exist
– cannot be awakened prematurely if it receives a signal
OSTEP 4.4 summary:
TASK_RUNNING and is currently runningTASK_RUNNING and is on a run queue waiting to runTASK_INTERRUPTIBLE or TASK_UNINTERRUPTIBLEtask_structs are linked together in hierarchy via children/sibling list_heads.
Per-CPU run_queue links tasks with state TASK_RUNNING (i.e. running and runnable tasks).
Need a separate list_head embedded into task_struct for run queue:
Per-event wait_queue links tasks with state TASK_INTERRUPTIBLE/TASK_UNINTERRUPTIBLE (i.e. blocked tasks) that are waiting for the same condition to exist
Compare with run_queue: wait queue entry is NOT embedded into task_struct.
Wait queue kernel data structures (psuedo-code):
struct wait_queue_head {
spin_lock_t lock;
struct list_head task_list;
};
struct waitqueue {
struct task_struct *task;
wait_queue_func_t func; // callback function, e.g. try_to_wake_up()
struct list_head entry;
};
wait_event_interruptible() macro, defined in wait.h (kernel 3.12.74, chosen for simplicity):
#define __wait_event_interruptible(wq, condition, ret) \
do { \
DEFINE_WAIT(__wait); \
\
for (;;) { \
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); \
if (condition) \
break; \
if (!signal_pending(current)) { \
schedule(); \
continue; \
} \
ret = -ERESTARTSYS; \
break; \
} \
finish_wait(&wq, &__wait); \
} while (0)
/**
* wait_event_interruptible - sleep until a condition gets true
* @wq: the waitqueue to wait on
* @condition: a C expression for the event to wait for
*
* The process is put to sleep (TASK_INTERRUPTIBLE) until the
* @condition evaluates to true or a signal is received.
* The @condition is checked each time the waitqueue @wq is woken up.
*
* wake_up() has to be called after changing any variable that could
* change the result of the wait condition.
*
* The function will return -ERESTARTSYS if it was interrupted by a
* signal and 0 if @condition evaluated to true.
*/
#define wait_event_interruptible(wq, condition) \
({ \
int __ret = 0; \
if (!(condition)) \
__wait_event_interruptible(wq, condition, __ret); \
__ret; \
})
Wait event loop:
prepare_to_wait(): add yourself to wait queue, change state to TASK_INTERRUPTIBLEsignal_pending(): check for “spurious wakeup”, i.e. signal interrupted sleep before condition was met
schedule(): put yourself to sleepfinish_wait(): change state to TASK_RUNNING, remove yourself from the wait queuePerform 1-4 in a loop since there can be multiple waiters on the same condition (e.g. recall condition variable from L06-thread), spurious wakeup
Additional notes:
wait_event_interruptible() macro
for wait event loop referencewait_event_interruptible() is a generic macro, probably not appropriate to use directly (e.g. for HW5)
signal_pending() differentlyCore scheduling logic defined in kernel/sched/core.c
schedule():
pick_next_task() choose a new task to run from the run queuecontext_switch(): put current task to sleep, start running new taskSleeping:
wait_event()schedule()pick_next_task()context_switch()Waking up
wake_up()try_to_wake_up() on each taskschedule() and previously sleeping task gets chosenfinish_wait() to remove from wait queue and continue executionWhen a user process makes read() system call, for example:
Device driver issues an I/O request to the device
wait_event() -> schedule() -> pick_next_task() -> context_switch()Another process starts running
The device completes the I/O request and raises a hardware interrupt
wake_up(): enqueue blocked tasks back on run queueschedule() -> pick_next_task() -> context_switch()read()Last updated: 2023-02-19