7    Simple Lock Routines

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:

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]
.
.
.

  1. 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]

  2. 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:

    [Return to example]

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]

  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]

7.2    Initializing a Simple Lock

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 */ }

  1. 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]

  2. 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]

  3. 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]

7.3    Asserting Exclusive Access on a Resource

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:

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) {
.
.
.
}

  1. 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]

  2. 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]

Figure 7-1:  Two Instances of the xx Module Asserting an Exclusive Lock

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:

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); } }
.
.
.

  1. 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]

  2. 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]

  3. 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]

  4. 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]

  5. 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]

Figure 7-2:  One Instance of the xx Module Releasing an Exclusive Lock

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:

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 */
.
.
.
}

  1. 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.

    [Return to example]

  2. 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]

  3. 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]

  4. 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]

Figure 7-3:  The xx Module Trying to Assert an Exclusive Lock

7.6    Terminating a Simple Lock

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]

  1. 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]

  2. 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]

  3. 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]

  4. 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]

  5. 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]

  6. 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]

  7. 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]

  8. 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]

  9. 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]

7.7    Using the spl Routines with Simple Locks

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]
.
.
.

  1. 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]

  2. 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]

  3. 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]

  4. 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]

  5. 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]

  6. 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]