• Execution
of a system call
• Sleeping
• Waking
up
• Example
Blocking I/O
• In
previous session we looked at how
to implement the read and write driver methods.
• At that point, however, we skipped
over one important issue: how does a driver respond if it cannot immediately
satisfy the request?
• A call to read may come when no data
is available, but more is expected in the future. Or a process could attempt to
write, but your device is not ready to accept the data, because your output buffer
is full. The calling process usually does not care about such issues; the
programmer simply expects to call read or write and have the call return after
the necessary work has been done.
• So, in such cases, your driver
should (by default) block the process, putting it to sleep until the request
can proceed.
• This section shows how to put a
process to sleep and wake it up again later on.
• Lest look at a few concepts first.
Introduction to
sleeping
• What does it mean for a process to
“sleep”? When a process is put to sleep, it is marked as being in a special
state and removed from the scheduler’s run queue. Until something comes along
to change that state, the process will not be scheduled on any CPU and,
therefore, will not run.
Rules
• A couple of rules that you must keep
in mind to be able to code sleeps in a safe manner.
• The first of these rules is: never
sleep when you are running in an atomic context.
• An Atomic context is simply a state
where multiple steps must be performed without any sort of concurrent access.
• It
means your driver cannot sleep
while holding a spinlock, seqlock, or RCU lock.
• Another thing to remember with
sleeping is that, when you wake up, you never know how long your process may
have been out of the CPU or what may have changed in the mean time. You also do
not usually know if another process may have been sleeping for the same event;
that process may wake before you and grab whatever resource you were waiting
for. The end result is that you can make no assumptions about the state of the
system after you wake up, and you must check to ensure that the condition you
were waiting for is, indeed, true.
• Another point is that your process
cannot sleep unless it is assured that somebody else, somewhere, will wake it
up.
How to sleep
• Linux
kernel uses data structure called “wait queues” to accomplish process sleeping.
• A
wait queue is a list of processes waiting for a specific event to occur.
• In Linux, a wait queue is managed by
means of a “wait queue head,” a structure of type wait_queue_head_t, which is
defined in <linux/wait.h>. A wait queue head can be defined and
initialized
• Statically (useful to declare as
global variable)
– DECLARE_WAIT_QUEUE_HEAD(name);
• Dynamically (useful to embed wait
queue inside another data structure)
– wait_queue_head_t my_queue;
– init_waitqueue_head(&my_queue);
• A
process sleeps with an expectation that some condition will become true in the
future
• Several ways to make a kernel
process sleep
• wait_event(queue, condition);
– Sleeps until the task is woken up
and the given C expression is true.
– Caution: can't be interrupted (can't
kill the user-space process!)
• int wait_event_interruptible(queue,
condition);
– Can be interrupted by any signal.
Returns -ERESTARTSYS if interrupted.
• int wait_event_timeout(queue,
condition, timeout);
– Wait
for limited time(expressed in jiffies); after that time period expires, the
macro returns with the value of “0” regardless of how condition evaluates. Reurns non-zero if the condition was
met.
• int
wait_event_interruptible_timeout(queue, condition,
• timeout);
– Same as above, interruptible.
Returns 0 if the timeout elapsed, -ERESTARTSYS if interrupted, positive value
if the condition was met.
• “Condition”
is arbitary boolean expression that is evaluated. Until condition is evaluated
to a true value, the process continues to sleep.
Waking up
• Typically
the processes are woken up by some
other thread of execution (a different process, or an interrupt handler)
• wake_up(&queue);
– Wakes up all processes in the wait
queue
• wake_up_interruptible(&queue);
– Wakes up all processes waiting in an
interruptible sleep on the given queue
Example
• Process
sleep in read() for data to become availabe
static
ssize_t sample_char_read(struct file * file, char __user * buf,
size_t count, loff_t
*ppos)
{
wait_event_interruptible(queue, flag);
printk("sample_char_read
size(%ld)\n", count);
return 0;
}
• Write()
wakes up the sleeping process
static
ssize_t sample_char_write(struct file *filp, const char *buf,
size_t size, loff_t *offp)
{
flag = 1;
wake_up_interruptible(&queue);
printk("sample_char_write
size(%ld)\n", size);
return size;
}
Quick Reference
• #include <linux/wait.h>
• typedef struct { /* ... */ }
wait_queue_head_t;
• void init_waitqueue_head(wait_queue_head_t
*queue);
• DECLARE_WAIT_QUEUE_HEAD(queue);
– he defined type for Linux wait
queues. A wait_queue_head_t must be explicitly initialized with either init_waitqueue_head
at runtime or DECLARE_WAIT_ QUEUE_HEAD at compile time.
• void wait_event(wait_queue_head_t q, int
condition);
• int
wait_event_interruptible(wait_queue_head_t q, int condition);
• int
wait_event_timeout(wait_queue_head_t q, int condition, int time);
• int
wait_event_interruptible_timeout(wait_queue_head_t q, int condition, int time);
– Cause the process to sleep on the
given queue until the given condition evaluates to a true value.
• void wake_up(struct wait_queue **q);
• void wake_up_interruptible(struct
wait_queue **q);
– Wake processes that are sleeping on
the queue q. The _interruptible form wakes only interruptible processes.
• #include <linux/sched.h>
• set_current_state(int state);
• Sets the execution state of the
current process. TASK_RUNNING means it is ready to run, while the sleep states
are TASK_INTERRUPTIBLE and TASK_UNINTERRUPTIBLE.
• Void
schedule (void)
– Selects a runnable process from the
run queue. The chosen process can be current or a different one.
No comments:
Post a Comment