Linux设备驱动并发控制
1. 并发及竞态的概念
1.1 并发与竞态的概念
并发:指多个执行单元同时、并发地被执行;
竞态:并发执行单元的运行,很容易会对共享资源(包含硬件、软件的全局变量静态变量等)的访问造成竞态。
1.2 竞态发生的几种情况
在Linux内核中,有如下几种竞态:
1)对称多处理器的多个CPU;
2)单CPU内的进程与抢占它的进程之间;
3)进程与中断(硬中断、软中断、tasklet、底半部)之间。
2.并发控制的具体机制
2.1 中断屏蔽
2.2 原子操作
void atomic_set(atomic_t *v, int i); /*设置原子变量的值为i*/
atomic_t v = ATOMIC_INIT(0);/*定义原子变量v并初始化为0*/
atomic_read(atomic_t *v);/*返回原子变量的值*/
void atomic_add(int i, atomic_t *v); /*原子变量增加i*/
void atomic_sub(int i, atomic_t *v); /*原子变量减少i*/
void atomic_inc(atomic_t *v); /*原子变量自增1*/
void atomic_dec(tomic_t *v); /*原子变量自减1*/
5)操作并测试
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
6) 操作并返回
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
2.3 位作原子操作
void set_bit(nr, void *addr);
2) 清除位
void clear_bit(nr, void *addr);
3)改变位
void change_bit(nr, void *addr);
4)测试位
test_bit(nr, void * addr);
5)测试并操作位
int test_and_set_bit(nr, void *addr);
int test_and_clear_bit(nr, void *addr);
int test_and_change_bit(nr, void *addr);
2.3 自旋锁
1. 自旋锁
理解自选锁的最简单的方法是把自旋锁看成是一个变量。该变量把一个临界区标记为“我当前在运行,请稍等一会”或标记为“我当前不在运行,可以被使用”。自旋锁的相关操作由如下4种:
1)定义自旋锁
spinlock_t lock;
2)初始化自旋锁
spin_lock_init(lock);
3)获得自旋锁
spin_lock(lock);<span style="white-space:pre"> </span>/*如果能获得自旋锁将立即返回,否则将自旋在那里。*/
spin_trylock(lock);<span style="white-space:pre"> </span>/*如果获得自旋锁将返回真,否则将返回假*/
4)释放自旋锁
spin_unlock(lock);
自旋锁主要是针对SMP或单CPU内内核可被将占的情况,无法避免中断和底半部的影响,因而在自旋锁的基础上有了衍生。
spin_lock_irq();
spin_lock_irqsave();
spin_lock_bh();
注意:
1)自旋锁实际上为忙等待,只使用于较短时间的占用,当临界区很大,或临界区内有共享设备时,长时间的占用可能或降低系统的性能;
2)自旋锁可能会导致死锁,例如对自旋锁的递归调用,当一个CPU试图调用一个已经占用的自旋锁时就会产生死锁;
3)自旋锁期间不能调用系统调用的函数,如copy_from_user()、copy_to_user()、kmalloc()、msleep()等。
2. 读写锁
在自旋锁的基础上粒度更细,在资源共享方面,运行有1个写的执行单元,可以有多个读的共享单元。
读写所一般这样被使用:
/*定义读写所*/
rwlock_t lock;
/*初始化读写锁*/
rwlock_init(&lock);
/*读时获取锁*/
read_lock(&lock);
.../*临界资源*/
read_unlock(&lock);
/*写时获取锁*/
write_lock_irqsave(&lock, flags);
.../*临界资源*/
write_unlock_irqrestore(&lock, flags);
3. 顺序锁
/*读开始*/
unsigned read_seqbegin(const seqlock_t *sl);
read_seqbegin_irqsave(lock, flags);
/*释放锁*/
read_seqretry_irqrestore(lock, iv, flags);
/*重读*/
do
{
seqnum = read_seqbegin(&seqlock_a);
} while(read_seqretry(&seqlock_a, seqnum) )
4. RCU(读-拷贝-更新)
2.4 信号量
1. 信号量的概念
信号量的是在保护临界区一种较为常见的方法,使用同自旋锁;与自旋锁不同的是,自旋锁无法获取时系统进入忙等待,而信号量是进入休眠状态。
信号量的使用方法:
1)定义:
<span style="font-weight: normal;">struct semaphore sem;</span>
2) 信号量的初始化<span style="font-weight: normal;">/*信号量初始化*/
sema_init(struct semaphore *sem, int val); /*将信号量初始化为val*/
/*一般的使用方法被定义为宏*/
#define init_MUTEX(sem) sema_init(sem, 1) /*初始化信号量为1*/
#define init_MUTEX_LOCKED(sem) sem_init(sem, 0) /*初始化信号量为0*/</span>
3) 信号量的获取
<span style="font-weight: normal;">/*信号量初始化*/
sema_init(struct semaphore *sem, int val); /*将信号量初始化为val*/
/*一般的使用方法被定义为宏*/
#define init_MUTEX(sem) sema_init(sem, 1) /*初始化信号量为1*/
#define init_MUTEX_LOCKED(sem) sem_init(sem, 0) /*初始化信号量为0*/</span>
<span style="font-weight: normal;">/*用于获取信号量sem, 会导致睡眠,因此不能在中断上下文中使用,不能被信号打断*/
void down(struct semaphore *sem);
/*会被信号打断;有信号打断则返回非0*/
int down_interruptible(struct semaphore *sem);
if(down_interruptible(&sem) )
return - ERESTARTSYS;
/*如果可以获取信号量则立即获取并返回0,如果不可以获取则立即返回非0值。不会导致调用者睡眠,可被用在中断上下文*/
int down_trylock(struct semaphore *sem);</span>
4) 信号量的释放
<span style="font-weight: normal;">void up(struct semaphore *sem);</span>
<span style="font-weight: normal;">/*用于获取信号量sem, 会导致睡眠,因此不能在中断上下文中使用,不能被信号打断*/
void down(struct semaphore *sem);
/*会被信号打断;有信号打断则返回非0*/
int down_interruptible(struct semaphore *sem);
if(down_interruptible(&sem) )
return - ERESTARTSYS;
/*如果可以获取信号量则立即获取并返回0,如果不可以获取则立即返回非0值。不会导致调用者睡眠,可被用在中断上下文*/
int down_trylock(struct semaphore *sem);</span>
<span style="font-weight: normal;">void up(struct semaphore *sem);</span>
一般使用方法:
<span style="font-weight: normal;">DECLARE_MUTEX(mount_sem);
down(&mount_sem); /*获取信号量,包含临界区*/
...
critical section /*临界区*/
...
up(&mount_sem); /*释放信号量*/</span>
信号量的同步意味着代码的顺序执行,使用方法上可以将信号量初始化为0;如下图,执行单元A在执行代码b前,需在执行单元b执行完代码c之后。
3. 完成量用于同步
代码的同步也可以用完成量来实现,功能需求同2中信号量的同步,实现过程如下图所示。
4. 信号量VS自旋锁
信号量和自旋锁都是较为常用的互斥手段,只是适用的层次不同,信号量的实现依赖于自旋锁,为了保证信号量结构存取的原子性,在多CPU中需要用自旋锁来互斥;而信号量适用于进程间的竞争,因而这样进程上下文的切换开销很大。因此,当占用资源时间较长时适用信号量,较短时适用自旋锁。选择哪个要看临界区的性质和系统特点。
1)使用信号量的开销是进程上下文切换时间Tsw,使用自旋锁的开销是等待自旋锁Tcs,如果Tcs较小则使用自旋锁,否则使用信号量;
2)当临界区可能存在阻塞时,因为阻塞会引起进程切换,如果使用自旋锁会导致死锁,因此这是使用信号量;
3)如果共享资源是在中断或软中断下使用的,则使用自旋锁。
使用方法:
struct rw_semaphore my_rws; /*定义读写信号量*/
init_rwsem(struct rw_semaphore *sem); /*初始化读写信号量*/
/*读时获取信号量*/
down_read(&rw_sem);
...
up_read(&rw_sem);
/*写时获取信号量*/
down_write(&rw_sem);
...
up_write(&rw_sem);
2.5 互斥体
使用方法:
struct mutex my_mutex; /*定义互斥体*/
mutex_init(&my_mutex); /*初始化互斥体*/
mutex_lock(&my_mutex); /*获取互斥体*/
...
mutex_unlock(&my_mutex); /*释放互斥体*/
3. 应用实例
/************************************************************************
* Filename: globalmem.c
* File Description: A virtual mem char driver
* Author: Vinvian Cheng
* Emaile:
* Ver: 1.0.0
* Date: 2014.11.11
* History:
* 1.
************************************************************************/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
/*struct mem*/
#define GLOBALMEM_SIZE<span style="white-space:pre"> </span>0x1000<span style="white-space:pre"> </span>/*全局内存最大4K字节*/
#define MEM_CLEAR 0x1 /*清0全局内存*/
#define GLOBALMEM_MAJOR 254 /*预设的globalmem的主设备号*/
static globalmem_major = GLOBALMEM_MAJOR;
/*globalmem struct*/
struct globalmem_dev{
struct cdev cdev; /*cdev struct*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局内存,私有数据*/
struct semaphore sem; /*信号量*/
};
/*dev struct piont*/
struct globalmem_dev *globalmem_devp = NULL;
/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp)
{
/*将设备结构体指针赋值给文件私有数据指针*/
filp->private_data = globalmem_devp;
return 0;
}
/*文件释放函数*/
int globalmem_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* ioctl设备控制函数 */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filp->private_data;/*获得设备结构体指针*/
switch (cmd)
{
case MEM_CLEAR:
{
if (down_interruptible(&dev->sem))
{
return - ERESTARTSYS;
}
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk(KERN_INFO "globalmem is set to zero\n");
up(&dev->sem);
break;
}
default:
return - EINVAL;
}
return 0;
}
/*读函数*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,
loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
if (down_interruptible(&dev->sem))
{
return - ERESTARTSYS;
}
/*内核空间->用户空间*/
if (copy_to_user(buf, (void*)(dev->mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %ld\n", count, p);
}
up(&dev->sem);
return ret;
}
/*写函数*/
static ssize_t globalmem_write(struct file *filp, const char __user *buf,
size_t size, loff_t *ppos)
{
unsigned long p = *ppos;
unsigned int count = size;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)
count = GLOBALMEM_SIZE - p;
if (down_interruptible(&dev->sem))
{
return - ERESTARTSYS;
}
/*用户空间->内核空间*/
if (copy_from_user(dev->mem + p, buf, count) )
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
up(&dev->sem);
return ret;
}
/* seek文件定位函数 */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0;
switch (orig)
{
case 0: /*相对文件开始位置偏移*/
if (offset < 0)
{
ret = - EINVAL;
break;
}
if ((unsigned int)offset > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
filp->f_pos = (unsigned int)offset;
ret = filp->f_pos;
break;
case 1: /*相对文件当前位置偏移*/
if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
{
ret = - EINVAL;
break;
}
if ((filp->f_pos + offset) < 0)
{
ret = - EINVAL;
break;
}
filp->f_pos += offset;
ret = filp->f_pos;
break;
default:
ret = - EINVAL;
break;
}
return ret;
}
/*文件操作结构体*/
static const struct file_operations globalmem_fops =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.ioctl = globalmem_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/***********************************************************************
* Function Name: globalmem_setup_cdev
* Paramter:
* struct globalmem_dev *dev [INOUT];
* int index [IN];
* Function Descrition:
* cdev init and register.
* Return: void
* Author: Vinvian 2014/11/11
***********************************************************************/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
int err = 0;
int devno = MKDEV(globalmem_major, index);
cdev_init(&dev->cdev, &globalmem_fops); //dev init
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &globalmem_fops;
err = cdev_add(&dev->cdev, devno, 1);
if (err)
printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/***********************************************************************
* Function Name: globalmem_init
* Paramter: type name [IN]: void
* Function Descrition:
* Module init
* Return: 0
* Error:
* Author: Vinvian 2014/11/11
***********************************************************************/
static int globalmem_init(void)
{
printk(KERN_INFO " Globlamem Driver for init!\n");
int result;
dev_t devno = MKDEV(globalmem_major, 0); //Create det_t with major and minor devNo
/*Regsiter devNo*/
if (globalmem_major) //has major no
result = register_chrdev_region(devno, 1, "globalmem");
else //no major no
{
result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
globalmem_major = MAJOR(devno);
}
if (result < 0)
return result;
/*malloc dev struct mem*/
globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
if (!globalmem_devp) /*申请失败*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
/*init cdev*/
globalmem_setup_cdev(globalmem_devp, 0);
/*init sema*/
sema_init(&globalmem_devp->sem, 1);
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return result;
}
/***********************************************************************
* Function Name: globalmem_exit
* Paramter: type name [IN]: void
* Function Descrition:
* Module exit
* Return: void
* Error:
* Author: Vinvian 2014/11/11
***********************************************************************/
void globalmem_exit(void)
{
/*release cdev*/
cdev_del(&globalmem_devp->cdev);
/*release struct dev mem*/
kfree(globalmem_devp);
/*release devNo*/
unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);
}
module_init(globalmem_init);
module_exit(globalmem_exit);
module_param(globalmem_major, int, S_IRUGO);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Vinvian");
MODULE_DESCRIPTION("A globalmem driver Module");
MODULE_ALIAS("A globalmem driver");