A ‘ringbuffer’ application Introduction to process ‘blocking’ ‘sleeping’ and ‘waking’

advertisement
A ‘ringbuffer’ application
Introduction to process ‘blocking’
and the Linux kernel’s support for
‘sleeping’ and ‘waking’
Devices might be ‘idle’
• With our previous device-driver examples
(i.e., dram, cmosram), the data to be read
was already there, just waiting to be input
• But with certain other character devices,
such as a keyboard, a program may want
to input its data before any new data has
actually been entered by a user
• In such cases we prefer to wait until data
arrives rather than to abandon reading
Devices might be ‘busy’
• Sometimes an application wants to ‘write’
some data to a character device, such as
a printer, but the device temporarily is not
able to accept more data, being still busy
with processing previously written data
• Again, in such situations we prefer to just
wait until the device becomes ready for us
to send it more data rather than to give up
We could do ‘busy waiting’…
• It is possible for a device-driver to ‘poll’ a
status-bit continuously until data is ready,
(or until a device is no longer “too busy”):
do
{
status = inb( 0x64 );
}
while ( ( status & READY ) == 0 );
• Such a technique is called ‘busy waiting’
• But it could waste a lot of valuable CPU
time before any benefit was realized!
Avoid ‘busy waiting’
• In a multitasking system we would want to
avoid having any processes use the ‘busy
waiting’ strategy whenever possible, as it
‘stalls’ any progress by other tasks – it’s a
system-performance ‘bottleneck’!
• So modern operating systems support an
alternative strategy, which allows those
tasks that could proceed to do so
‘blocking’ while idle
• If a task is trying to read from a device-file
when no data is present, but new data is
expected to arrive, the operating system
can ‘block’ that task from consuming any
valuable CPU time while it is waiting, by
‘putting the task to sleep’ – yet arranging
for that task to be ‘awakened’ as soon as
some fresh data has actually arrived
‘blocking’ while busy
• Similarly, if a task is trying to ‘write’ to a
device-file, but that device is ‘busy’ with
previously written data, then the OS can
put this task to sleep, preventing it from
wasting any CPU time during its delay so
that other tasks can do useful work – but
arranging for this ‘sleeping’ task to be
‘woken up’ as soon as the device is no
longer ‘busy’ and can accept fresh data
What does ‘sleep’ mean?
• The Linux kernel puts a task to sleep by simply
modifying the value of its ‘state’ variable:
– TASK_RUNNING
– TASK_STOPPED
– TASK_UNINTERRUPTIBLE
– TASK_INTERRUPTIBLE
…
• Only tasks with ‘state == TASK_RUNNING’ are
granted time on the CPU by the ‘scheduler’
What does ‘wakeup’ mean?
• A sleeping task is one whose ‘task.state’ is
equal to ‘TASK_INTERRUPTIBLE’ or to
‘TASK_UNINTERRUPTIBLE’
• A sleeping task is ‘woken up’ by changing
its ‘task,state’ to be ‘TASK_RUNNING’
• When the Linux scheduler sees that a task
is in the ‘TASK_RUNNING’ state, it grants
that task some CPU time for execution
‘run’ queues and ‘wait’ queues
• In order for Linux to efficiently manage the
scheduling of its various ‘tasks’, separate
queues are maintained for ‘running’ tasks
and for tasks that temporarily are ‘blocked’
while waiting for a particular event to occur
(such as the arrival of new data from the
keyboard, or the exhaustion of prior data
sent to the printer)
Some tasks are ‘ready-to-run’
Those tasks that are ready-to-run comprise a sub-list of all the tasks,
and they are arranged on a queue known as the ‘run-queue’
Those tasks that are blocked while awaiting a specific event to occur
are put on alternative sub-lists, called ‘wait queues’, associated with
the particular event(s) that will allow a blocked task to be unblocked
Kernel waitqueues
waitqueue
waitqueue
waitqueue
waitqueue
Kernel’s support-routines
• The Linux kernel makes it easy for drivers
to perform the ‘sleep’ and ‘wakeup’ actions
while avoiding potential ‘race conditions’
that are inherent in a ‘preemptive’ kernel
• Your driver can use the support-routines
by including the header: <linux/sched.h>
Use of Linux wait-queues
•
•
•
•
•
#include <linux/sched.h>
wait_queue_head_t
my_queue;
init_waitqueue_head( &my_queue );
sleep_on( &my_queue );
wake_up( &my_queue );
• But can’t unload driver if task stays asleep!
‘interruptible’ is preferred
#include <linux/sched.h>
wait_queue_head_t
wq;
init_waitqueue_head( &wq );
wait_event_interruptible( wq, <condition> );
wake_up_interruptible( &wq );
An ‘interruptible’ sleep can be awoken by a signal,
in case you might want to ‘unload’ your driver!
A convenient ‘macro’
• DECLARE_WAIT_QUEUE_HEAD( wq );
• This statement can be placed outside your
module’s functions (i.e., a ‘global’ object)
• It combines declaration with initialization:
wait_queue_head_t wq;
init_waitqueue_head( &wq );
Our ‘stash’ device
•
•
•
•
Device works like a public ‘clipboard’
It uses kernel memory to store its data
It allows ‘communication’ between tasks
What one task writes, another can read!
Ringbuffer
•
•
•
•
•
A first-in first-out data-structure (FIFO)
Uses a storage array of finite length
Uses two array-indices: ‘head’ and ‘tail’
Data is added at the current ‘tail’ position
Data is removed from the ‘head’ position
‘ringbuffer’ depicted
HEAD
D
A
T
A
D
A
T
A
D
A
T
A
D
A
T
A
D
A
T
A
TAIL
FIFO rules:
The next data to be added goes in at the current ‘tail’ position,
and the next data to be removed comes from the ‘head’ position
The ringbuffer is ‘empty’ when ‘head’ equals ‘tail’, and
it is ‘full’ if ‘tail’ + 1 equals ‘head’ (modulo RINGSIZE)
Ringbuffer (continued)
•
•
•
•
•
One array-position is always left unused
Condition ‘head == tail’ means “empty”
Condition tail == head-1 means “full”
Both ‘head’ and ‘tail’ will “wraparound”
Calculation: next = ( next+1 )%RINGSIZE;
read-algorithm for ‘stash’
• if ( ringbuffer_is_empty )
{
// sleep, until another task supplies some data
// or else exit if a signal is received by this task
}
•
•
•
•
Remove a byte from the ringbuffer;
Copy the byte to user-space;
Awaken any sleeping writers;
return 1;
write-algorithm for ‘stash’
• if ( ringbuffer_is_full )
{
// sleep, until some data is removed by another task
// or else exit if a signal is received by this task
}
•
•
•
•
Copy a byte from user-space;
Insert this byte into ringbuffer;
Awaken any sleeping readers;
return 1;
Demonstration of ‘stash’
• Quick demo: we can use I/O redirection
• For demonstrating ‘write’ to /dev/stash:
$ echo “Hello” > /dev/stash
• For demonstrating ‘read’ from /dev/stash:
$ cat /dev/stash
The ‘device’ file-node
• We cannot use the ‘stash.c’ device-driver
until a device-node has been created that
allows both ‘read’ and ‘write’ access (the
SysAdmin must usually do this setup):
#root mknod /dev/stash c 40 0
#root chmod a+rw /dev/stash
• But you can do it, by using a module that
resembles our ‘tempcdev.c’ demo (if you
just modify its module-data appropriately)
In-class exercise #1
• Download a fresh copy of our ‘tempcdev.c’
module and edit it, so that it will create the
‘/dev/stash’ device-file when you install it
• Then you can try using our ‘stash.c’ demo
to send data from one task to another task
by using the ‘echo’ and ‘cat’ commands
In-class exercise #2
• Add a ‘get_info()’ function to this driver to
create a pseudo-file (named ‘/proc/stash’)
that will show the current contents of the
ringbuffer (if any) and the current values
for the ‘head’ and ‘tail’ buffer-indices
• Don’t forget: use ‘create_proc_info_entry()’
in your ‘init_module()’ function, and use
‘remove_proc_entry()’ during ‘cleanup’
Download