信号量(semaphore)是用于保护临界区的一种常用手段。与自旋锁相同,只有得到信号量的进程才能执行临界区代码。但不同的是,当获取不到信号量时,进程不会“自旋”而是进入休眠等待状态。
To use semaphores,kernel code must include
信号量的初始化:
直接创建信号量:
void sema_init(struct semaphore *sem, int val);
声明和初始化互斥体的宏:
DECLARE_MUTEX_LOCKED(name);
动态分配互斥体:
void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);
获得信号量:
void down(struct semaphore *sem);
int down_interruptible(struct semaphore *sem);
int down_trylock(struct semaphore *sem);
释放信号量:
void up(struct semaphore *sem);
正如读写锁,信号量也有读写信号量。
信号量对所有的调用者执行互斥。但很多任务可以划分两种不同的工作类型:一些任务只需要读取受保护的数据结构,而其他的来做写入的动作。允许多个并发的读取是可能的,这样做会大大提高性能。
一个rwsem可允许一个写入者或无限多个读取者拥有该信号量。写入者具有更高的优先级;所以在读远远大于写的时候用rwsem会大大提高新能。在APUE中,在讲到线程同步时提到了读写锁,那里的读写锁同样是非常适合于对数据结构读的次数远大于写的情况。
下面继续用scull的例子来看下使用信号量实现设备文件只能被一个进程打开的例子:
代码添加如下:
static DECLARE_MUTEX(scull_lock);//define mutex
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 */
if(down_trylock(&scull_lock)){
return -EBUSY;
}
return 0; /* success */
}
int scull_release(struct inode *inode, struct file *filp)
{
up(&scull_lock);
return 0;
}
接下来继续完成增加并发控制的scull驱动。要注意的是信号量必须在scull设备对系统其他部分可用前被初始化。
代码改动如下:
/*
* Representation of scull quantum sets.
*/
struct scull_dev {
unsigned char mem[SCULL_SIZE];
struct cdev cdev; /* Char device structure */
struct semaphore sem; /*semaphore*/
};
ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
unsigned long p = *f_pos;
int ret = 0;
struct scull_dev *dev = filp->private_data;
if(p >= SCULL_SIZE)
return count ? -ENXIO : 0 ;
if(count > SCULL_SIZE - p)
count = SCULL_SIZE - p;
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
if(copy_to_user(buf, (void *)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*f_pos += count;
ret = count;
printk(KERN_WARNING "read %d bytes from %d\n", count, p);
}
printk(KERN_WARNING "ret: %d\n",ret);
up(&dev->sem);
return ret;
}
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
unsigned long p = *f_pos;
int ret = 0;
struct scull_dev *dev = filp->private_data;
if(p >= SCULL_SIZE )
return count ? -ENXIO : 0 ;
if(count > SCULL_SIZE - p)
count = SCULL_SIZE - p;
if(down_interruptible(&dev->sem))
return -ERESTARTSYS;
if(copy_from_user(dev->mem + p, buf, count))
{
ret = -EFAULT;
}
else
{
*f_pos += count;
ret = count;
printk(KERN_WARNING "write %d bytes to %d\n", count , p);
}
up(&dev->sem);
return ret;
}