《linux设备驱动开发详解》第七章
======================================
并发控制:1.中断屏蔽、2.原子操作、3自旋锁(自旋锁、读写自旋锁、顺序锁、读-拷贝-更新)、4信号量(信号量读写信号量)
这里只摘录读写自旋锁rwlock、信号量semaphore
自旋锁会导致死循环,锁定期间不允许阻塞,因此要求锁定的临界区小。信号量允许临界区阻塞,可以适用于临界区大的情况。
自旋锁实际上是忙等锁,当锁不可用时,CPU一直循环执行“测试并设置”给锁直到可用而取得该锁,CPU在等待自旋锁时不做任何有用的工作,仅仅是等待。因此,只有在占用锁的时间极短的情况下,使用自旋锁才是合理的。
自旋锁锁定期间不能调用可能引起进程调度的函数。如 copy_from_user
copy_to_user、 kmalloc、 msleep
读写自旋锁
自旋锁的衍生锁读写自旋锁rwlock允许读的并发。读写自旋锁是一种比自旋锁颗粒度更小的锁机制,它保留了“自旋”的概念,但是在写操作方面,只能最多有1个写进程,在读操作方面,同时可以有很多个读执行单元。
1. 定义和初始化读写自旋锁
rwlock_t my_rwlock = RW_LOCK_UNLOCKED;//静态初始化
rwlock_t my_rwlock;
rwlock_init(&my_rwlock);
2.读锁定
void read_lock(rwlock_t *lock)
void read_lock_irqsave(rwlock_t *lock,unsigned long flags)//进程本CPU内 中断,保存目前的CPU中断位信息
void read_lock_irq(rwlock_t *lock);//禁止本CPU内中断
void read_lock_bh(rwlock_t *lock);//禁止中断的底半部
3.读解锁
void read_unlock(rwlock_t *lock)
void read_unlock_irqstore(rwlock_t *lock,unsigned long flags)
void read_unlock_irq(rwlock_t *lock);
void read_unlock_bh(rwlock_t *lock);
4.写锁定
void write_lock(rwlock_t *lock)
void write_lock_irqsave(rwlock_t *lock,unsigned long flags)
void write_lock_irq(rwlock_t *lock);
void write_lock_bh(rwlock_t *lock);
int write_trylock(rwlock_t *lock);该宏尝试获得自旋锁rwlock,如果能立即获得锁,它获得锁并返回真,否则立即返回假,实际上不再“在原地打转”
5.写解锁
void write_unlock(rwlock_t *lock)
void write_lock_irqrestore(rwlock_t *lock,unsigned long flags)
void write_unlock_irq(rwlock_t *lock)
void write_unlock_bh(rwlock_t *lock)
使用
-
rwlock_t lock;//定义rwlock
-
rwlock_init(&lock);//初始化rwlock
-
-
//读时获取锁
-
read_lock(&lock);
-
... //临界资源
-
read_unnlock(&lock);
-
-
//写时获取锁
-
write_lock_irqsave(&lock,flags)
-
... //临界资源
-
write_unlock_irqsave(&lock,flags);
信号量的使用
信号量semaphore,与自旋锁不同的是,当获取不到信号量时,进程不会原地打转而是进入休眠的等待状态
1.定义信号量
struct semaphore sem;
void sema_init(struct semaphore *sem,int val);
// #define init_MUTEX(sem) sema_init(sem,1)
// #define init_MUTEX_LOCKED(sem) sema_init(sema,0)
快捷方式:
DECLARE_MUTEX(name);//设置val=1
DECLARE_MUTEX_LOCKED(name)//设置val=0
2.获得信号量
该函数用于获得信号量sem,它会导致睡眠,因此不能再中断上下文使用
void down(struct semaphore *sem);
该函数功能与down类似,不同:因为down而进入睡眠状态的进程不能被信号打断,但是down_interruptible而进入睡眠状态能被信号打断,信号也会导致该函数返回,这时候函数的返回值非0
void down_interruptible(struct semaphore *sem)
该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,返回非0值,可以在中断上下文使用
int down_trylock(struct semaphore *sem)
在使用down_interruptible获取信号量时,对返回值一般会进行检查,如果非0,通常立即返回-ERESTARTSYS
if(down_interruptible(&sem))
return -ERESTARTSYS
=====================
注释:ERESTARSYS
在ldd3 p115这么解释:在见到这个ERESTARTSYS这个返回代码后,内核的高层代码要么会从头重新启动该调用,要么将该错误返回给用户。如果我们返回-ERESTARTSYS,则必须首先撤销已经做出的任何用户可见的修改,这样,系统调用可正确重试。如果无法撤销这些操作,则返回-EINTR。
当收到 -ERESTARTSYS 这个返回值后,对于linux来讲,会自动的重新调用这个调用就可以了。
======================
4.释放信号量
该函数释放信号量sem,唤醒等待者
void up(struct semaphore *sem);
使用:
-
DECLARE_MUTEX(sem);
-
down(&sem);//获取信号量、保护临界区
-
...
-
...
-
up(&sem);//释放信号量
====================================================
实验并测试
对 read write ioct 中的 copy_from_user copy_to_user memset()进行数据保护,然后在应用程序中通过 time() diff(),进行应用程序测试 程序运行时间,通过判断两个同时执行 的app应用程序来说明。
-
#include <linux/semaphore.h>// semaphore
-
#include <linux/delay.h> //ssleep
-
-
-
struct globalmem_dev{
-
struct cdev cdev;
-
unsigned char mem[GLOBALMEM_SIZE];
-
struct semaphore sem;
-
};
-
-
globalmem_ioctl中
-
if(down_interruptible(&dev->sem))
-
return -ERESTARTSYS;
-
memset(dev->mem,0,GLOBALMEM_SIZE);
-
ssleep(1);//////////////应用程序中,测试时间用
-
up(&dev->sem);
-
-
read中
-
count = GLOBALMEM_SIZE - p;
-
if(down_interruptible(&dev->sem))
-
return -ERESTARTSYS;
-
if(copy_to_user(buf,(void *)(dev->mem+p),count))
-
{
-
ret = -EFAULT;//bad address
-
}
-
else
-
{
-
*ppos += count;
-
ret = count;
-
printk(KERN_INFO"read %u byte(s) form %lu\n",count,p);
-
}
-
ssleep(1);/////////////////////////应用程序中,测试时间用
-
up(&dev->sem);
-
-
write中
-
if(down_interruptible(&dev->sem))
-
return -ERESTARTSYS;
-
if(copy_from_user(dev->mem + p,buf,count))
-
ret = -EFAULT;//bad address
-
else
-
{
-
*ppos += count;
-
ret = count;
-
printk(KERN_INFO"written %u byte(s) from %lu\n",count,p);
-
}
-
ssleep(1); /////////////////////应用程序中,测试时间用
-
up(&dev->sem);
-
-
init初始化中
-
globalmem_setup_cdev(globalmem_devp,0);
-
-
init_MUTEX(&globalmem_devp->sem);
-
//sema_init(&globalmem_devp->sem,1);
测试应用程序中添加:
-
-
time1 = time(NULL);
-
sleep(2);
-
-
fd = open("/dev/globalmem",O_RDWR,S_IRUSR|S_IWUSR);//
-
ret = write(fd,"yuweixian\0",strlen("yuweixian\0"));//attentions: "\n" 驱动程序中1s延时
-
ret = lseek(fd,0,SEEK_SET);
-
ret = read(fd,buf,strlen(buf));驱动程序中1秒延时
-
printf("%s\n",buf);
-
close(fd);
-
time2 = time(NULL);
-
-
printf("app running %f seconds.\n",difftime(time2,time1));
测试方法:
sudo insmod ./globalmem.ko
cat /proc/devices
mknod /dev/globalmem c 240 0
在root权限下,
执行./app
在另一个终端root下 ./app
-
maphore/application# ./app
-
aaaaaaaaaa
-
yuweixian
-
app running 4.000000 seconds.
另一个终端中
-
maphore/application# ./app
-
aaaaaaaaaa
-
yuweixian
-
app running 5.000000 seconds.
分析:
第一个执行app1应用程序时间分析:
1.sleep(2), 这里有2s时间
2.write、open对应驱动程序中个1s,这里2s时间
3.总结:一共大约4秒时间
第二个执行的app2应用程序时间分析:
由于驱动程序中使用 信号量,对临界区资源进行保护,导致app2程序会挂起,等待app1程序先执行,所以时间会长点
2. rwlock 读写锁 测试
将globalmem修改,在open函数中添加
-
#include <linux/spinlock.h> //rwlock
-
-
static int rwlock_count = 0;//global parameter
-
-
struct globalmem_dev{
-
struct cdev cdev;
-
unsigned char mem[GLOBALMEM_SIZE];
-
rwlock_t my_rwlock;
-
};
-
int globalmem_open(struct inode *inode,struct file *filp)
-
{
-
struct globalmem_dev *dev;
-
dev = container_of(inode->i_cdev,struct globalmem_dev,cdev);
-
filp->private_data = dev;
//说明:通过, 只能打开一个进程
-
write_lock(&dev->my_rwlock);
-
if(rwlock_count)
-
{
-
write_unlock(&dev->my_rwlock);//有第二个进程打开,返回EBUSY,设备忙
-
return -EBUSY;
-
}
-
rwlock_count++;
-
write_unlock(&dev->my_rwlock);
-
-
return 0;
-
}
-
int globalmem_release(struct inode *inode,struct file *filp)
-
{
-
rwlock_count--;
-
return 0;
-
}
应用程序测试代码:
-
fd = open("/dev/globalmem",O_RDWR,S_IRUSR|S_IWUSR);
-
if(fd!=-1)
-
{
-
-
sleep(3);
-
//write
-
ret = write(fd,"yuweixian\0",strlen("yuweixian\0"));//attentions: "\n"
-
-
//lseek set to file offset
-
ret = lseek(fd,0,SEEK_SET);
-
-
//read
-
ret = read(fd,buf,strlen(buf));
-
printf("%s\n",buf);
-
-
close(fd);
-
exit(EXIT_SUCCESS);
-
}
-
else
-
{
-
perror("open file\n");
-
exit(EXIT_FAILURE);
-
}
测试方法:
sudo insmod ./globalmem.ko
cat /proc/devices
mknod /dev/globalmem c 240 0
在root权限下,
执行./app
在另一个终端root下 ./app是,会出现打开错误信息
并发控制资料:
阅读(1489) | 评论(0) | 转发(0) |