Title: A ringbuffer application
1A ringbuffer application
- Introduction to process blocking and the Linux
kernels support for sleeping and waking
2Devices 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
3Devices 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
4We 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) - Such a technique is called busy waiting
- But it could waste a lot of valuable CPU time
before any benefit was realized!
do status inb( 0x64 ) while ( (
status READY ) 0 )
5Avoid 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 its a
system-performance bottleneck! - So modern operating systems support an
alternative strategy, which allows those tasks
that could proceed to do so
6blocking 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
7blocking 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
8What 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
9What 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
10run 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)
11Some 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
12Kernel waitqueues
waitqueue
waitqueue
waitqueue
waitqueue
13Kernels 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
14Use of Linux wait-queues
- include
- wait_queue_head_t my_queue
- init_waitqueue_head( my_queue )
- sleep_on( my_queue )
- wake_up( my_queue )
- But cant unload driver if task stays asleep!
15interruptible is preferred
- include
- wait_queue_head_t wq
- init_waitqueue_head( wq )
- wait_event_interruptible( wq, )
- wake_up_interruptible( wq )
-
- An interruptible sleep can be awoken by a
signal, - in case you might want to unload your driver!
16A convenient macro
- DECLARE_WAIT_QUEUE_HEAD( wq )
- This statement can be placed outside your
- modules functions (i.e., a global object)
- It combines declaration with initialization
- wait_queue_head_t wq
- init_waitqueue_head( wq )
17Our 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!
18Ringbuffer
- 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
19ringbuffer 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)
20Ringbuffer (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 ( next1 )RINGSIZE
21read-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
22write-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
23Demonstration 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
24The 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 arw /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)
25In-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
26In-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 - Dont forget use create_proc_info_entry() in
your init_module() function, and use
remove_proc_entry() during cleanup