user11246874
user11246874

Reputation:

Linux Kernel Where to lock and unlock semaphores?

Within the Linux Kernel (specifically for device drivers), how would I know what variables to lock and when they need locking? In particular, why does the locking in the following code only happen after dev has been set, even though dev points to a global variable scull_devices?

struct scull_qset {
    void **data;    /* pointer to an array of pointers which each point to a quantum buffer */
    struct scull_qset *next;
};

struct scull_dev {
    struct scull_qset *data;  /* Pointer to first quantum set */
    int quantum;              /* the current quantum size */
    int qset;                 /* the current array size */
    unsigned long size;       /* amount of data stored here */
    unsigned int access_key;  /* used by sculluid and scullpriv */
    struct semaphore sem;     /* mutual exclusion semaphore */
    struct cdev cdev;         /* Char device structure initialized in scull_init_module */
};

struct scull_dev *scull_devices;  /* allocated dynamically in scull_init_module */

int scull_open(struct inode *inode, struct file *filp)
{
    struct scull_dev *dev; /* device information */

    dev = container_of(inode->i_cdev, struct scull_dev, cdev);
    filp->private_data = dev; /* for other methods */

    /* now trim to 0 the length of the device if open was write-only */
    if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
        if (down_interruptible(&dev->sem))
            return -ERESTARTSYS;
        scull_trim(dev); /* empty out the scull device */
        up(&dev->sem);
    }
    return 0;          /* success */
}

If the code for scull_init_module is needed for a more complete picture, here it is:

int scull_major = SCULL_MAJOR;
int scull_minor = 0;
int scull_quantum = SCULL_QUANTUM;
int scull_qset = SCULL_QSET;
int scull_nr_devs = SCULL_NR_DEVS;


int scull_init_module(void)
{
    int result, i;
    dev_t dev = 0;

    /* assigns major and minor numbers (left out for brevity sake) */

    /* 
     * allocate the devices -- we can't have them static, as the number
     * can be specified at load time
     */
    scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
    if (!scull_devices) {
        result = -ENOMEM;
        goto fail; 
    }
    memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));

    /* Initialize each device. */
    for (i = 0; i < scull_nr_devs; i++) {
        scull_devices[i].quantum = scull_quantum;
        scull_devices[i].qset = scull_qset;
        init_MUTEX(&scull_devices[i].sem);
        scull_setup_cdev(&scull_devices[i], i);
    }

    /* some other stuff left out for brevity sake */

    return 0; /* succeed */

  fail:            /* isn't this a little redundant? */
    scull_cleanup_module();
    return result;
}


/*
 * Set up the char_dev structure for this device.
 */
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
    int err, devno = MKDEV(scull_major, scull_minor + index);

    cdev_init(&dev->cdev, &scull_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &scull_fops;
    err = cdev_add (&dev->cdev, devno, 1);
    /* Fail gracefully if need be */
    if (err)
        printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}

Upvotes: 1

Views: 1117

Answers (2)

user2526111
user2526111

Reputation: 464

lock - it is way to protect critical section

critical section - in your driver code, if multiple instances are accessing same area, that is critical section.

multiple instances - it could be thread, regular ioctl cmd(from user space), and softirq and irq. It depends on your driver implementation.

Based on "context", you should use different lock too.

thread context which can sleep -> semaphore/mutex non-sleeping context -> spinlock softirq, tasklet -> spin_lock_bh irq -> spin_lock_irq, spin_lock_irqsave

It is completely based on your requirements.

Let's take an example. If you are working on network driver, your netdev has stats and packet buffer and those needs to be protected by lock since it can be updated by multiple instances like net_rx_softirq, net_tx_softirq, ioctl/netlink from userspace request an so on.

In this case, based on your resource's context, you need to use different lock/mutex and sometimes you need more than 1 lock.

Upvotes: 0

ensc
ensc

Reputation: 6984

Locking in the example has nothing to do with the global scull_devices variable, but the locking is used to protect attributes of one scull_dev.

E.g. assume there exists a read() operation which copies size bytes from data while the mentioned scroll_trim() operation frees data.

So, when process #1 calls open() and process #2 tries to read() from an already opened device at the same time, the read() operation can access freed data and oopses.

That is why you need to protect data against races. Semaphores are one way; mutexes another one which is often more appropriate. Spinlocks and atomic variables might work too.

Upvotes: 1

Related Questions