Chinaunix首页 | 论坛 | 博客
  • 博客访问: 4470193
  • 博文数量: 1148
  • 博客积分: 25453
  • 博客等级: 上将
  • 技术积分: 11949
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-06 21:14
文章分类

全部博文(1148)

文章存档

2012年(15)

2011年(1078)

2010年(58)

分类: LINUX

2011-12-20 20:26:23


《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)

使用
  1. rwlock_t lock;//定义rwlock
  2. rwlock_init(&lock);//初始化rwlock

  3. //读时获取锁
  4. read_lock(&lock);
  5. ... //临界资源
  6. read_unnlock(&lock);

  7. //写时获取锁
  8. write_lock_irqsave(&lock,flags)
  9. ... //临界资源
  10. 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);

使用:
  1. DECLARE_MUTEX(sem);
  2. down(&sem);//获取信号量、保护临界区
  3. ...
  4. ...
  5. up(&sem);//释放信号量
====================================================

实验并测试

1. semaphore使用,修改并增加 “从globalmem学习字符设备驱动” 程序代码

代码附件: globalmem_semaphore.rar   将rar修改为.tar.bz2

对 read write ioct 中的 copy_from_user copy_to_user memset()进行数据保护,然后在应用程序中通过 time()  diff(),进行应用程序测试 程序运行时间,通过判断两个同时执行 的app应用程序来说明。

  1. #include <linux/semaphore.h>// semaphore
  2. #include <linux/delay.h> //ssleep


  3. struct globalmem_dev{
  4.     struct cdev cdev;
  5.     unsigned char mem[GLOBALMEM_SIZE];
  6.     struct semaphore sem;
  7. };

  8. globalmem_ioctl中
  9. if(down_interruptible(&dev->sem))
  10.       return -ERESTARTSYS;
  11.       memset(dev->mem,0,GLOBALMEM_SIZE);
  12.       ssleep(1);//////////////应用程序中,测试时间用
  13.       up(&dev->sem);

  14. read中
  15.     count = GLOBALMEM_SIZE - p;
  16.     if(down_interruptible(&dev->sem))
  17.         return -ERESTARTSYS;
  18.     if(copy_to_user(buf,(void *)(dev->mem+p),count))
  19.     {
  20.         ret = -EFAULT;//bad address
  21.     }
  22.     else
  23.     {
  24.         *ppos += count;
  25.         ret = count;
  26.         printk(KERN_INFO"read %u byte(s) form %lu\n",count,p);
  27.     }
  28.     ssleep(1);/////////////////////////应用程序中,测试时间用
  29.     up(&dev->sem);

  30. write中
  31. if(down_interruptible(&dev->sem))
  32.         return -ERESTARTSYS;
  33.     if(copy_from_user(dev->mem + p,buf,count))
  34.         ret = -EFAULT;//bad address
  35.     else
  36.     {
  37.         *ppos += count;
  38.         ret = count;
  39.         printk(KERN_INFO"written %u byte(s) from %lu\n",count,p);
  40.     }
  41.     ssleep(1); /////////////////////应用程序中,测试时间用
  42.     up(&dev->sem);

  43. init初始化中
  44. globalmem_setup_cdev(globalmem_devp,0);

  45.     init_MUTEX(&globalmem_devp->sem);
  46.     //sema_init(&globalmem_devp->sem,1);
测试应用程序中添加:
  1. time1 = time(NULL);
  2. sleep(2);

  3. fd = open("/dev/globalmem",O_RDWR,S_IRUSR|S_IWUSR);//
  4. ret = write(fd,"yuweixian\0",strlen("yuweixian\0"));//attentions: "\n" 驱动程序中1s延时
  5. ret = lseek(fd,0,SEEK_SET);
  6. ret = read(fd,buf,strlen(buf));驱动程序中1秒延时
  7. printf("%s\n",buf);
  8. close(fd);
  9. time2 = time(NULL);

  10. 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
  1. maphore/application# ./app
  2. aaaaaaaaaa
  3. yuweixian
  4. app running 4.000000 seconds.
另一个终端中
  1. maphore/application# ./app
  2. aaaaaaaaaa
  3. yuweixian
  4. app running 5.000000 seconds.
分析:
第一个执行app1应用程序时间分析:
1.sleep(2), 这里有2s时间
2.write、open对应驱动程序中个1s,这里2s时间
3.总结:一共大约4秒时间

第二个执行的app2应用程序时间分析:
由于驱动程序中使用 信号量,对临界区资源进行保护,导致app2程序会挂起,等待app1程序先执行,所以时间会长点



2. rwlock 读写锁 测试

程序代码: globalmem_rwlock.rar   将rar修改为tar.bz2,

将globalmem修改,在open函数中添加
  1. #include <linux/spinlock.h> //rwlock
  2. static int rwlock_count = 0;//global parameter

  3. struct globalmem_dev{
  4.     struct cdev cdev; 
  5.     unsigned char mem[GLOBALMEM_SIZE];
  6.     rwlock_t my_rwlock;
  7. };
  1. int globalmem_open(struct inode *inode,struct file *filp)
  2. {
  3.     struct globalmem_dev *dev;
  4.     dev = container_of(inode->i_cdev,struct globalmem_dev,cdev);
  5.     filp->private_data = dev;
//说明:通过, 只能打开一个进程
  1.     write_lock(&dev->my_rwlock);
  2.     if(rwlock_count)
  3.     {
  4.         write_unlock(&dev->my_rwlock);//有第二个进程打开,返回EBUSY,设备忙
  5.         return -EBUSY;
  6.     }
  7.     rwlock_count++;
  8.     write_unlock(&dev->my_rwlock);
  9.     
  10.     return 0;
  11. }
  1. int globalmem_release(struct inode *inode,struct file *filp)
  2. {
  3.     rwlock_count--;
  4.     return 0;
  5. }
应用程序测试代码:
  1. fd = open("/dev/globalmem",O_RDWR,S_IRUSR|S_IWUSR);
  2.     if(fd!=-1)
  3.     {
  4.     
  5.         sleep(3);
  6.         //write
  7.         ret = write(fd,"yuweixian\0",strlen("yuweixian\0"));//attentions: "\n"

  8.         //lseek set to file offset
  9.         ret = lseek(fd,0,SEEK_SET);

  10.         //read
  11.         ret = read(fd,buf,strlen(buf));
  12.         printf("%s\n",buf);

  13.         close(fd);
  14.         exit(EXIT_SUCCESS);
  15.     }
  16.     else
  17.     {
  18.         perror("open file\n");
  19.         exit(EXIT_FAILURE);
  20.     }

测试方法:
sudo insmod ./globalmem.ko 
cat /proc/devices
mknod /dev/globalmem c 240 0
在root权限下,
执行./app
在另一个终端root下  ./app是,会出现打开错误信息



并发控制资料:



阅读(1461) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~