Kernel timing issues An introduction to the use of

advertisement
Kernel timing issues
An introduction to the use of
kernel timers and work queues
Kernel semaphores
• Our ‘stash.c’ device-driver exhibited some
‘race conditions’ when we used it with two
or more ‘reader’ processes (or with two or
more ‘writer’ processes)
• We can eliminate such ‘races’ if our driver
enforces a ‘one writer/one reader’ policy
• This is easy to do by using ‘semaphores’
Mutual-exclusion syntax
• Declare a semaphore: struct semaphore sem;
• To initialize this semaphore:
init_MUTEX( &sem );
• To acquire this semaphore:
down_interruptible( &sem );
• To release this semaphore:
up( &sem);
‘open()’ uses file->f_fmode
• You can implement an ‘open()’ method in
your device-driver that lets only one task
at a time open your device for reading:
{
if ( file->f_fmode & FMODE_READ )
down_interruptible( &sem );
return 0; // success
}
‘release()’ uses file->f_fmode
• You can implement a ‘release()’ method in
your device-driver that lets a ‘reader’ task
release its earlier acquired semaphore:
{
if ( file->f_fmode & FMODE_READ )
up( &sem );
return 0; // success
}
struct file_operations
struct file_operations my_fops =
{
owner:
THIS_MODULE,
read:
my_read,
write:
my_write,
open:
my_open,
release:
my_release,
};
‘newstash.c’
• We wrote a new version of ‘stash.c’ that
illustrates the use of two semaphores to
restrict device-file access to one ‘writer’
and one ‘reader’ at a time
• Other tasks that want to ‘read’ or ‘write’ are
put to sleep if they try to ‘open’ the devicefile, but are woken up when the
appropriate semaphore gets ‘released’
Kernel timers
• Another facility Linux offers lets drivers put
a process to sleep until a fixed amount of
time has elapsed (as measured in jiffies)
• When the timer expires, a driver-defined
action will be performed, which can ‘wake
up’ the process that was put to sleep, or
could perform some alternative action (for
example, the kernel timer could start over)
Programming syntax
• Declare a timer: struct timer_list mytimer;
• Initialize this timer: init_timer( &mytimer );
mytimer.func = mytimeraction;
mytimer.data = (unsigned long)mydata;
mytimer.expires = <number-of-jiffies>
• Install this timer: add_timer( &mytimer );
• Modify this timer: mod_timer( &mytimer, <jifs> );
• Delete this timer: del_timer( &mytimer );
• Delete it safely: del_timer_sync( &mytimer);
A kernel-timer caution
• A kernel timer’s timeout-action cannot do
anything that could cause the current task
to ‘sleep’ (such as copying data between
user-space and kernel-space, or trying to
allocate more kernel memory)
• However, to aid debugging, a timer CAN
use ‘printk()’ within its timeout-routine
‘trytimer.c’
• We have posted an example that shows
how a Linux kernel timer can be used to
perform a periodic action (such as using
‘printk()’ to issue a message every time the
time expires, then restart the timer
• Notice that our demo is not able to issue
messages directly to the console – its
timer-function executes without a ‘tty’
Delaying work
• If a device-driver needs to perform actions
that require using process resources (like
a tty), or that may possibly ‘sleep’, then it
can defer that work – using a ‘workqueue’
Programming syntax
• Declare:
struct workqueue_struct *myqueue;
struct work_struct
thework;
• Define:
void dowork( void *data ) { /* actions */ };
• Initialize: myqueue = create_singlethread_workqueue( “mywork” );
INIT_WORK( &thework, dowork, <data-pointer> );
• Schedule: queue_dalayed_work( myqueue, &thework, <delay> );
• Cleanup:
if ( !cancel_delayed_work( &thework ) )
flush_workqueue( myqueue );
destroy_workqueue( myqueue );
‘tryworkq.c’ and ‘defermsg.c’
• We have posted demo-modules that show
the use of workqueues to perform actions
later, either as soon as a ‘process context’
is available, or after a prescribed time
• Further details on the options for using an
existing kernel workqueue or a workqueue
of your own creation may be found in our
textbook (Chapter 7 of LDD3)
Applying these ideas
• To demonstrate a programming situation in
which using kernel timers is valuable, we
created the ‘foo.c’ device-driver, plus an
application that uses it (‘watchfoo.cpp’)
• You can compile and install the module,
then execute the application: $ watchfoo
• But you will notice there are two ‘problems’
(excess cpu usage and loop-termination)
Reducing CPU’s usage
• The ‘watchfoo’ program rereads ‘/dev/foo’
constantly (numerous times per second),
much faster than the human eye can see
• If you run the ‘top’ utility, you will see that
up to 99-percent of the available CPU time
is being consumed by ‘watchfoo’
• You can add a kernel timer to the ‘foo.c’
driver to curtail this excessive reading
In-class exercise
• Modify ‘foo.c’ (call it ‘timedfoo.c’) as follows
• Create an integer flag-variable (‘ready’) as a
global object in your module
• When your ‘read()’ function gets called, it should
sleep until ‘ready’ equals TRUE; it should set
‘ready’ equal to FALSE when it awakens, but
should set a timer to expire after 1/10 seconds
• Your timer’s action-function should set ‘ready’
back to TRUE, then wake up any sleeping tasks
Implementation hints
• You need a wait-queue (so your driver’s
‘reader’ tasks can sleep on it)
• You need a timer action-function
• You need to organize your timer-function’s
data-items into a single structure (because
the timer-function has only one argument)
• Your timer-function must do two things:
– Change ‘ready’ to TRUE
– Wake up any ‘sleepers’
Download