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_UNINTERRUPTIBLE
task_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_INTERRUPTIBLE
signal_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