After you decide that the simple lock method is the appropriate method for locking specific resources, you use the simple lock routines to accomplish the locking. To use simple locks in a kernel module, perform the following tasks:
Declare a simple lock data structure
Initialize a simple lock
Assert exclusive access on a resource
Release a previously asserted simple lock
Try to obtain a simple lock
Terminate a simple lock
Use the
spl
routines with simple locks
To illustrate the use of these routines, the chapter uses code from
an example kernel module called
xx
that operates on some
XX
device.
This example module locks a
kern_str
structure resource called
xx_kern_str
.
7.1 Declaring a Simple Lock Data Structure
Before using a simple lock, declare a simple lock data structure for
the resource you want to lock by using the
decl_simple_lock_data
routine.
The following code fragment shows a call to
decl_simple_lock_data
in the
xx
kernel module:
.
.
.
#include <kern/lock.h> [1]
.
.
.
struct xx_kern_str { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ decl_simple_lock_data( , lk_xx_kern_str); /* SMP lock for xx_kern_str */ }xx_kern_str[NNONE]; [2]
.
.
.
Includes the header file
/usr/sys/include/kern/lock.h
.
The
lock.h
file defines the simple spin
lock and complex lock structures that kernel modules use for synchronization
on single-processor and multiprocessor systems.
[Return to example]
Declares an array of
kern_str
structures
and calls it
xx_kern_str
.
The
xx
module
uses the
decl_simple_lock_data
routine to declare a simple
lock structure as a field of the
xx_kern_str
structure.
The
decl_simple_lock_data
routine declares a simple
lock structure,
slock
, of the specified
name.
You declare a simple lock structure to protect kernel module
data structures and device register access.
You use
decl_simple_lock_data
to declare a simple lock structure and then pass it to the following
simple lock-specific routines:
simple_lock_init
,
simple_lock
,
simple_lock_try
,
simple_unlock
, and
simple_lock_terminate
.
The
decl_simple_lock_data
routine can take two arguments:
The first argument (not passed in this call) specifies the
class of the declaration.
For example, you pass the keyword
extern
if you want to declare the simple lock structure as an external
structure.
This argument would be specified in this call if
lk_xx_kern_str
was declared in another program module.
The second argument specifies the name you want the
decl_simple_lock_data
routine to assign to the declaration of the
simple lock structure.
In this call to the routine, the name for the simple
lock structure is
lk_xx_kern_str
.
You can also declare a simple lock structure by using the typedef
simple_lock_data_t
, as in the following example:
.
.
.
struct xx_kern_str { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ simple_lock_data_t lk_xx_kern_str; /* SMP lock for xx_kern_str */ }xx_kern_str[NNONE]; [1]
Declares an array of
kern_str
structures
and calls it
xx_kern_str
.
The
xx
module
declares a simple lock structure as a field of the
xx_kern_str
structure to protect the integrity of the data stored in the
sc_openf
and
sc_count
fields.
A kernel module's
kern_str
structure is one resource that often requires protection
in an SMP environment because kernel module routines use it to share data.
It is possible that more than one kernel thread might need to access the fields
of an
xx_kern_str
structure.
[Return to example]
After declaring the simple lock data structure, you initialize it by
calling the
simple_lock_init
routine.
The following code
fragment shows a call to
simple_lock_init
by the
xx
kernel module's
xxcattach
routine.
The
xxcattach
routine performs the tasks necessary to establish communication
with the actual device.
One of these tasks is to initialize any global data
structures.
Thus, the
xxcattach
routine initializes the
simple lock structure
lk_xx_kern_str
.
The code fragment also shows the declaration of the simple lock structure
in the
xx_kern_str
structure.
.
.
.
#include <kern/lock.h> [1]
.
.
.
struct xx_kern_str { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ simple_lock_data_t lk_xx_kern_str; /* SMP lock for xx_kern_str */ }xx_kern_str[NNONE]; [2]
.
.
.
xxcattach(struct controller *ctlr) { register struct xx_kern_str *sc = &xx_kern_str[ctlr->ctlr_num]; /* Tasks to perform controller-specific initialization */
.
.
.
simple_lock_init(&sc->lk_xx_kern_str); [3]
.
.
.
/* Perform any other controller-specific initialization tasks */ }
Includes the header file
/usr/sys/include/kern/lock.h
.
The
lock.h
file defines the simple spin
lock and complex lock structures that kernel modules use for synchronization
on single-processor and multiprocessor systems.
[Return to example]
Declares an array of
kern_str
structures
and calls it
xx_kern_str
.
The
xx
kernel
module declares a simple lock structure as a field of the
xx_kern_str
structure to protect the integrity of the data stored in the
sc_openf
and
sc_count
fields.
A kernel module's
kern_str
structure is one resource that often requires protection
in an SMP environment because kernel module routines use it to share data.
It is possible that more than one kernel thread might need to access the fields
of an
xx_kern_str
structure.
[Return to example]
Calls the
simple_lock_init
routine to initialize
the simple lock structure called
lk_xx_kern_str
.
The
simple_lock_init
routine takes one argument:
a pointer to a simple lock structure.
You can declare this simple lock structure
by using the
decl_simple_lock_data
routine.
In this call,
the
xxcattach
routine passes the address of the
lk_xx_kern_str
field of the
xx_kern_str
structure
pointer.
You need to initialize the simple lock structure only once.
[Return to example]
After declaring and initializing the simple lock data structure, you
can assert exclusive access by calling the
simple_lock
routine.
The following code fragment shows a call to
simple_lock
by the
xx
kernel module's
xxopen
routine.
The
xxopen
routine is called as the result of an
open
system call.
The
xxopen
routine performs the following tasks:
Checks to ensure that the open is unique
Marks the device as open
Returns the value 0 (zero) to the
open
system call to indicate success
The code fragment also shows the declaration of the simple lock structure
in the
xx_kern_str
structure and the initialization of
the simple lock structure by thel module's
xxcattach
routine.
See
Section 7.2
for explanations
of these tasks.
.
.
.
#include <kern/lock.h>
.
.
.
struct xx_kern_str { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ simple_lock_data_t lk_xx_kern_str; /* SMP lock for xx_kern_str */ }xx_kern_str[NXX];
.
.
.
xxcattach(struct controller *ctlr) { register struct xx_kern_str *sc = &xx_kern_str[ctlr->ctlr_num]; /* Tasks to perform controller-specific initialization */
.
.
.
simple_lock_init(&sc->lk_xx_kern_str);
.
.
.
}
.
.
.
xxopen(dev, flag, format) dev_t dev; int flag; int format; register int unit = minor(dev); struct controller *ctlr = xxinfo[unit]; struct xx_kern_str *sc = &xx_kern_str[unit]; if(unit >= NXX) return ENODEV; [1] simple_lock(&sc->lk_xx_kern_str); [2] if (sc->sc_openf == DN_OPEN) {
.
.
.
}
If the number of device units on the system is greater than
NXX
, returns the error code
ENODEV
, which indicates
that no such device exists on the system.
This example test is used to ensure
that a valid device exists.
[Return to example]
Calls the
simple_lock
routine to assert
an exclusive access on the following code block.
The
simple_lock
routine takes one argument: a pointer
to a simple lock structure.
You can declare this simple lock structure by
using the
decl_simple_lock_data
routine.
In this call,
the
xxopen
routine passes the address of the
lk_xx_kern_str
field of the
xx_kern_str
structure
pointer.
Figure 7-1
shows what happens when two
instances of the
xx
kernel module execute on two CPUs.
As the figure shows, the kernel thread emanating from CPU1 obtains the simple
lock on the code block that follows item 2 in the code fragment before the
kernel thread emanating from CPU2.
The reason for locking this code block
is to prevent data corruption of any future writes to the
xx_kern_str
structure.
The CPU2 kernel thread spins while waiting for the CPU1
kernel thread to free the simple lock.
[Return to example]
7.4 Releasing a Previously Asserted Simple Lock
After asserting a simple lock (with exclusive access), you must release
the lock by calling the
simple_unlock
routine.
The following
code fragment shows calls to
simple_unlock
by the
xx
kernel module's
xxopen
routine.
The
xxopen
routine is called as the result of an
open
system call.
The
xxopen
routine performs the following tasks:
Checks to ensure that the open is unique
Marks the device as open
Returns the value 0 (zero) to the
open
system call to indicate success
The code fragment also shows the declaration of the simple lock structure
in the
xx_kern_str
structure and the initialization of
the simple lock structure by the kernel module's
xxcattach
routine.
.
.
.
#include <kern/lock.h>
.
.
.
struct xx_kern_str { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ simple_lock_data_t lk_xx_kern_str; /* SMP lock for xx_kern_str */ }xx_kern_str[NXX];
.
.
.
xxcattach(struct controller *ctlr) { register struct xx_kern_str *sc = &xx_kern_str[ctlr->ctlr_num]; /* Tasks to perform controller-specific initialization */
.
.
.
simple_lock_init(&sc->lk_xx_kern_str);
.
.
.
}
.
.
.
xxopen(dev, flag, format) dev_t dev; int flag; int format; register int unit = minor(dev); struct controller *ctlr = xxinfo[unit]; struct xx_kern_str *sc = &xx_kern_str[unit]; if(unit >= NXX) return ENODEV; [1] simple_lock(&sc->lk_xx_kern_str); [2] if (sc->sc_openf == DN_OPEN) [3] { simple_unlock(&sc->lk_xx_kern_str); return (EBUSY); } if ((ctlr !=0) && (ctlr->alive & ALV_ALIVE)) [4] { sc->sc_openf = DN_OPEN; simple_unlock(&sc->lk_xx_kern_str); return(0); } else [5] { simple_unlock(&sc->lk_xx_kern_str); return(ENXIO); } }
.
.
.
If the number of device units on the system is greater than
NXX
, returns the error code
ENODEV
, which indicates
that no such device exists on the system.
This example test is used to ensure
that a valid device exists.
[Return to example]
Calls the
simple_lock
routine to assert
an exclusive access on the following code block.
The
simple_lock
routine takes one argument: a pointer
to a simple lock structure.
You can declare this simple lock structure by
using the
decl_simple_lock_data
routine.
In this call,
the
xxopen
routine passes the address of the
lk_xx_kern_str
field of the
xx_kern_str
structure
pointer.
[Return to example]
If the
sc_openf
field of the
sc
pointer is equal to
DN_OPEN
, calls the
simple_unlock
routine and returns the error code
EBUSY
, which indicates that the
NONE
device has already
been opened.
This example test is used to ensure that only one unit of the
kernel module can be opened at a time.
This type of open is referred to as
an exclusive access open.
The
simple_unlock
routine releases a simple lock
for the resource associated with the specified simple lock structure pointer.
This simple lock was previously asserted by calling the
simple_lock
or
simple_lock_try
routine.
In this call, the
locked resource is referenced in the code block beginning with item 3.
The
simple_unlock
routine takes one argument: a pointer
to a simple lock structure.
You can declare this simple lock structure by
using the
decl_simple_lock_data
routine.
In this call,
the
xxopen
routine passes the address of the
lk_xx_kern_str
field of the
xx_kern_str
structure
pointer.
[Return to example]
If the
ctlr
pointer is not equal to 0 (zero)
and the
alive
field of
ctlr
has the
ALV_ALIVE
bit set, then the device exists.
If this is the case,
the
xxopen
routine sets the
sc_openf
field of the
sc
pointer to the open bit
DN_OPEN
, calls
simple_unlock
to free the lock, and returns
the value 0 (zero) to indicate a successful open.
[Return to example]
If the device does not exist,
xxopen
calls
simple_unlock
to free the lock and returns the error code
ENXIO
, which indicates that the device does not exist.
Figure 7-2
shows what happens when one
instance of the
xx
kernel module releases a previously
asserted exclusive lock on the code block that opens the device.
In
Figure 7-1, the CP1 kernel thread obtained the simple
lock on the code block that opens the device.
The CP2 kernel thread spun while
waiting for the simple lock to be freed.
After CPU 1 released the simple lock,
CPU2 obtained the lock.
In
Figure 7-2, the
CPU1 kernel thread makes another attempt to lock the code block that opens
the device.
This time it spins until the CPU2 kernel thread releases the simple
lock.
[Return to example]
7.5 Trying to Obtain a Simple Lock
In addition to explicitly asserting a simple lock, you can also try
to assert the simple lock by calling the
simple_lock_try
routine.
The main difference between
simple_lock
and
simple_lock_try
is that
simple_lock_try
returns
immediately if the resource is already locked, while
simple_lock
spins until the lock has been obtained.
Thus, call
simple_lock_try
when you need a simple lock but the code cannot spin until the
lock is obtained.
The following code fragment shows a call to
simple_lock_try
by the
xx
kernel module's
xxopen
routine.
The
xxopen
routine is called as the result of an
open
system call.
The
xxopen
routine performs the following tasks:
Checks to ensure that the open is unique
Marks the device as open
Returns the value 0 (zero) to the
open
system call to indicate success
The code fragment also shows the declaration of the simple lock structure
in the
xx_kern_str
structure and the initialization of
the simple lock structure by the kernel module's
xxcattach
routine.
.
.
.
#include <kern/lock.h>
.
.
.
struct xx_kern_str { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ simple_lock_data_t lk_xx_kern_str; /* SMP lock for xx_kern_str */ }xx_kern_str[NXX];
.
.
.
xxcattach(struct controller *ctlr) { register struct xx_kern_str *sc = &xx_kern_str[ctlr->ctlr_num]; /* Tasks to perform controller-specific initialization */
.
.
.
simple_lock_init(&sc->lk_xx_kern_str);
.
.
.
}
.
.
.
xxopen(dev, flag, format) dev_t dev; int flag; int format; register int unit = minor(dev); struct controller *ctlr = xxinfo[unit]; struct xx_kern_str *sc = &xx_kern_str[unit]; boolean_t try_ret_val; [1] if(unit >= NXX) return ENODEV; [2] try_ret_val = simple_lock_try(&sc->lk_xx_kern_str); [3] if (try_ret_val == TRUE) [4] { if (sc->sc_openf == DN_OPEN)
.
.
.
else /* Perform some other tasks if simple_lock_try fails * * to assert an exclusive access */
.
.
.
}
Declares a variable to store the return value from the
simple_lock_try
routine.
The
simple_lock_try
routine returns one of the following
values:
TRUE
The
simple_lock_try
routine successfully asserted the simple lock.
FALSE
The
simple_lock_try
routine failed to assert the simple lock.
If the number of device units on the system is greater than
NXX
, returns the error code
ENODEV
, which indicates
that no such device exists on the system.
This example test is used to ensure
that a valid device exists.
[Return to example]
Calls the
simple_lock_try
routine to try
to assert an exclusive access on the following code block.
The
simple_lock_try
routine takes one argument:
a pointer to a simple lock structure.
You can declare this simple lock structure
by using the
decl_simple_lock_data
routine.
In this call,
the
xxopen
routine passes the address of the
lk_xx_kern_str
field of the
xx_kern_str
structure
pointer.
[Return to example]
If the return from
simple_lock_try
is
TRUE
, checks the
sc_openf
field to determine
if this is a unique open.
Otherwise, if the return from
simple_lock_try
is
FALSE
, performs some other tasks.
Figure 7-3
shows what happens when
two instances of the
xx
kernel module try to assert an
exclusive lock on the code block that opens the device.
As the figure shows,
the CPU1 and CPU2 kernel threads try to assert an exclusive lock on the code
block that opens the device.
In this case, the CPU1 kernel thread successfully
obtains the lock.
To indicate this success,
simple_lock_try
returns the value
TRUE
.
At the same time, the CPU2 kernel
thread fails to obtain the lock and
simple_lock_try
immediately
returns the value
FALSE
to indicate this.
[Return to example]
After unlocking a simple lock (with exclusive access) and knowing that
you are finished using the lock for this resource, you can terminate the lock
by calling the
simple_lock_terminate
routine.
Typically,
you terminate any locks in the kernel module's controller (or device)
unattach
routine.
These routines are associated with loadable modules
(for example, drivers).
One task associated with a controller or device
unattach
routine is to terminate any locks initialized in the kernel
module's
unattach
routine.
The following code fragment shows a call to
simple_lock_terminate
by the
xx
kernel module's
xx_ctlr_unattach
routine.
The code fragment also shows the declaration of the simple
lock structure in the
xx_kern_str
structure, the initialization
of the simple lock structure by the kernel module's
xxcattach
routine, and the unlocking of the simple lock structure by the module's
xxopen
routine.
.
.
.
#include <kern/lock.h> [1]
.
.
.
struct xx_kern_str { int sc_openf; /* Open flag */ int sc_count; /* Count of characters written to device */ simple_lock_data_t lk_xx_kern_str; /* SMP lock for xx_kern_str */ }xx_kern_str[NXX]; [2]
.
.
.
xxcattach(struct controller *ctlr) { register struct xx_kern_str *sc = &xx_kern_str[ctlr->ctlr_num]; /* Tasks to perform controller-specific initialization */
.
.
.
simple_lock_init(&sc->lk_xx_kern_str); [3]
.
.
.
}
.
.
.
xxopen(dev, flag, format) dev_t dev; int flag; int format; register int unit = minor(dev); struct controller *ctlr = xxinfo[unit]; struct xx_kern_str *sc = &xx_kern_str[unit]; if(unit >= NXX) return ENODEV; [4] simple_lock(&sc->lk_xx_kern_str); [5] if (sc->sc_openf == DN_OPEN) [6] { simple_unlock(&sc->lk_xx_kern_str); return (EBUSY); } if ((ctlr !=0) && (ctlr->alive & ALV_ALIVE)) [7] { sc->sc_openf = DN_OPEN; simple_unlock(&sc->lk_xx_kern_str); return(0); } else [8] { simple_unlock(&sc->lk_xx_kern_str); return(ENXIO); } }
.
.
.
xx_ctlr_unattach(bus, ctlr) struct bus *bus; struct controller *ctlr; { register int unit = ctlr->ctlr_num; if ((unit > num_xx) || (unit < 0) { return(1); } if (xx_is_dynamic == 0) { return(1); } /* Performs controller unattach tasks */
.
.
.
simple_lock_terminate(&sc->lk_xx_kern_str); [9]
Includes the header file
/usr/sys/include/kern/lock.h
.
The
lock.h
file defines the simple spin
lock and complex lock structures that kernel modules use for synchronization
on single-processor and multiprocessor systems.
[Return to example]
Declares an array of
kern_str
structures
and calls it
xx_kern_str
.
The
xx
kernel
module declares a simple lock structure as a field of the
xx_kern_str
structure to protect the integrity of the data stored in the
sc_openf
and
sc_count
fields.
A kernel module's
kern_str
structure is one resource that often requires protection
in an SMP environment because kernel module routines use it to share data.
It is possible that more than one kernel thread might need to access the fields
of an
xx_kern_str
structure.
[Return to example]
Calls the
simple_lock_init
routine to initialize
the simple lock structure called
lk_xx_kern_str
.
The
simple_lock_init
routine takes one argument:
a pointer to a simple lock structure.
You can declare this simple lock structure
by using the
decl_simple_lock_data
routine.
In this call,
the
xxcattach
routine passes the address of the
lk_xx_kern_str
field of the
xx_kern_str
structure
pointer.
You need to initialize the simple lock structure only once.
After
initializing a simple lock structure, kernel modules can call
simple_lock
to assert exclusive access on the associated resource or
simple_lock_try
to attempt to assert exclusive access on the associated
resource.
[Return to example]
If the number of device units on the system is greater than
NXX
, returns the error code
ENODEV
, which indicates
that no such device exists on the system.
This example test is used to ensure
that a valid device exists.
[Return to example]
Calls the
simple_lock
routine to assert
an exclusive access on the following code block.
The
simple_lock
routine takes one argument: a pointer
to a simple lock structure.
You can declare this simple lock structure by
using the
decl_simple_lock_data
routine.
In this call,
the
xxopen
routine passes the address of the
lk_xx_kern_str
field of the
xx_kern_str
structure
pointer.
[Return to example]
If the
sc_openf
field of the
sc
pointer is equal to
DN_OPEN
, calls the
simple_unlock
routine and returns the error code
EBUSY
, which indicates that the
NONE
device has already
been opened.
This example test is used to ensure that only one unit of the
module can be opened at a time.
This type of open is referred to as an exclusive
access open.
The
simple_unlock
routine releases a simple lock
for the resource associated with the specified simple lock structure pointer.
This simple lock was previously asserted by calling the
simple_lock
or
simple_lock_try
routine.
In this call, the
locked resource is referenced in the code block beginning with item 6.
The
simple_unlock
routine takes one argument: a pointer
to a simple lock structure.
You can declare this simple lock structure by
using the
decl_simple_lock_data
routine.
In this call,
the
xxopen
routine passes the address of the
lk_xx_kern_str
field of the
xx_kern_str
structure
pointer.
[Return to example]
If the
ctlr
pointer is not equal to 0 (zero)
and the
alive
field of
ctlr
has the
ALV_ALIVE
bit set, then the device exists.
If this is the case,
the
xxopen
routine sets the
sc_openf
field of the
sc
pointer to the open bit
DN_OPEN
, calls
simple_unlock
to free the lock, and returns
the value 0 (zero) to indicate a successful open.
[Return to example]
If the device does not exist,
xxopen
calls
simple_unlock
to free the lock and returns the error code
ENXIO
, which indicates that the device does not exist.
[Return to example]
Calls the
simple_lock_terminate
routine
to determine that the
xx
module is permanently done using
this simple lock.
The
simple_lock_terminate
routine takes one argument:
a pointer to a simple lock structure.
You can declare this simple lock structure
by using the
decl_simple_lock_data
routine.
In this call,
the
xx_ctlr_unattach
routine passes the address of the
lk_xx_kern_str
field of the
xx_kern_str
structure
pointer.
In calling
simple_lock_terminate
, the
xx
kernel module must not reference this simple lock again.
[Return to example]
The
spl
routines block out asynchronous events on
the CPU on which the
spl
call is performed.
Simple locks
block out other CPUs.
You need to use both the
spl
routines
and the simple lock routines when synchronizing with kernel threads and interrupt
service routines.
The following code fragment shows calls to the
spl
and simple lock routines:
.
.
.
#include <kern/lock.h> [1]
.
.
.
struct tty_kern_str {
.
.
.
decl_simple_lock_data( , lk_tty_kern_str); /* SMP lock for tty_kern_str */
.
.
.
}tty_kern_str[NSOMEDEVICE]; [2]
.
.
.
simple_lock_init(&sc->lk_tty_kern_str);
.
.
.
s = spltty(); [3] simple_lock(&lk_tty_kern_str); [4]
.
.
.
/* Manipulate resource */
.
.
.
simple_unlock(&lk_tty_kern_str); [5] splx(s); [6]
.
.
.
Includes the header file
/usr/sys/include/kern/lock.h
.
The
lock.h
file defines the simple spin
lock and complex lock structures that kernel modules use for synchronization
on single-processor and multiprocessor systems.
[Return to example]
Declares an array of
kern_str
structures
and calls it
lk_tty_kern_str
.
This example module uses
the
decl_simple_lock_data
routine to declare a simple lock
structure as a field of the
tty_kern_str
structure.
[Return to example]
Calls the
spltty
routine to mask out all tty (terminal device) interrupts.
The
spltty
routine takes no arguments.
The
spltty
routine returns an integer value that represents the CPU
priority level that existed before the call.
Note that the routine masks out
all tty interrupts on the CPU on which it is called.
[Return to example]
Calls the
simple_lock
routine to assert
a lock with exclusive access for the resource associated with the
slock
structure pointer, which in this example is
lk_tty_kern_str
.
Note that the routine ensures that no other kernel thread running
on other CPUs can gain access to this resource.
This contrasts with the
spl
routines, which block out kernel threads running on this CPU.
[Return to example]
After manipulating the resource, calls
simple_unlock
to release the simple lock.
This makes the resource available to
kernel threads running on other CPUs.
[Return to example]
Calls the
splx
routine to reset the CPU priority
to the level specified by the value returned by
spltty
.
The
splx
routine takes one argument: a CPU priority
level.
This level must be a value returned by a previous call to one of the
spl
routines, in this example
spltty
.
Calling
splx
releases the priority on this CPU.
[Return to example]