Tecniche di locking: semafori
Esistono diverse tecniche di locking a disposizione degli sviluppatori; le principali sono gli spinlock e i semafori. Questi ultimi possono essere usati come mutex.
L'esempio seguente aggiunge al driver precedente il locking per-file utilizzando i semafori:
Codice:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <asm/uaccess.h>
static char *buf;
static int bsize = PAGE_SIZE;
struct kmisc_data {
struct semaphore ksem;
};
static int kmisc_open(struct inode *ino, struct file *filp)
{
struct kmisc_data *kdata = kmalloc(sizeof(*kdata), GFP_KERNEL);
if (!kdata)
return -ENOMEM;
init_MUTEX(&kdata->ksem);
filp->private_data = kdata;
return 0;
}
static int kmisc_release(struct inode *ino, struct file *filp)
{
kfree(filp->private_data);
return 0;
}
ssize_t kmisc_read(struct file *filp, char __user *dst, size_t count, loff_t *off)
{
ssize_t ret = 0;
struct kmisc_data *kdata = filp->private_data;
if (filp->f_flags & O_NONBLOCK) {
if (down_trylock(&kdata->ksem))
return -EAGAIN;
} else {
if (down_interruptible(&kdata->ksem))
return -EINTR;
}
printk(KERN_INFO "%s: reading up to %d bytes at offset %lld\n", __func__,
count, *off);
if ((*off+count)>bsize)
count = bsize-*off;
if (count) {
if (copy_to_user(dst, buf+*off, count))
ret = -EFAULT;
else {
ret = count;
*off += count;
}
}
up(&kdata->ksem);
return ret;
}
ssize_t kmisc_write(struct file *filp, const char __user *src, size_t count, loff_t *off)
{
ssize_t ret = 0;
struct kmisc_data *kdata = filp->private_data;
if (filp->f_flags & O_NONBLOCK) {
if (down_trylock(&kdata->ksem))
return -EAGAIN;
} else {
if (down_interruptible(&kdata->ksem))
return -EINTR;
}
if ((*off+count)>bsize) {
printk(KERN_WARNING "%s: discarding %lld bytes on %d\n", __func__,
(*off+count)-bsize, count);
count = bsize-*off;
}
printk(KERN_INFO "%s: writing %d bytes at offset %lld\n", __func__,
count, *off);
if (count) {
if (copy_from_user(buf+*off, src, count))
ret = -EFAULT;
else {
ret = count;
*off += count;
}
}
up(&kdata->ksem);
return ret;
}
static struct file_operations kops = {
.owner = THIS_MODULE,
.open = kmisc_open,
.release = kmisc_release,
.read = kmisc_read,
.write = kmisc_write,
};
static struct miscdevice kmisc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "kmisc",
.fops = &kops
};
static int __init kmisc_init(void)
{
int ret;
buf = kzalloc(bsize, GFP_KERNEL);
if (!buf)
return -ENOMEM;
ret = misc_register(&kmisc);
if (!ret)
printk(KERN_INFO "kmisc registered on minor %d\n", kmisc.minor);
else
kfree(buf);
return ret;
}
static void __exit kmisc_exit(void)
{
misc_deregister(&kmisc);
kfree(buf);
}
module_init(kmisc_init);
module_exit(kmisc_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ilsensine");
MODULE_DESCRIPTION("miscdevice module");
Da notare che il locking non viene fatto tramite down(), ma tramite alcune varianti. Innanzitutto se il file è stato aperto come non bloccante (O_NONBLOCK), dobbiamo onorare questa richiesta: dobbiamo quindi _tentare_ il lock con down_trylock e ritornare -EAGAIN se il nostro codice non è in grado di acquisire il lock senza attendere. Se il file è stato aperto normalmente, è invece preferibile la forma down_interruptible: questa down ritorna anche nel caso che il processo riceva un segnale, consentendo quindi un rapido ritorno in userspace per la gestione dello stesso. L'errore da ritornare in questo caso è il classico -EINTR.
La down() pura invece non ritorna mai, a meno di non aver acquisito il semaforo. Neanche un SIGKILL riuscirà ad uccidere un processo fermo dentro una down().
Notate che tutte le down, tranne la versione trylock, possono bloccare se il semaforo non è acquisibile: quindi non possono essere utilizzate in regioni del codice che non possono essere schedulate (notoriamente irq service routine, dentro uno spinlock, con gli irq disabilitati, ecc). E' legale, per contro, schedulare con un semaforo acquisito (alcuni driver, che non gradiscono multiple open, bloccano un semaforo dentro la open e lo rilasciano nella release).
Notare infine che il semaforo può essere usato anche come...semaforo, invece che come mutex; in questo caso per inizializzarlo usare sema_init (init_MUTEX non fa altro che inizializzare il semaforo a 1).