Linux设备驱动并发控制

Linux设备驱动并发控制

本文共包含三部分:1.并发及竞态的概念及发生的场合;2.并发控制的具体处理机制;3.相关的具体应用实例。

1. 并发及竞态的概念

1.1 并发与竞态的概念

并发:指多个执行单元同时、并发地被执行;

        竞态:并发执行单元的运行,很容易会对共享资源(包含硬件、软件的全局变量静态变量等)的访问造成竞态。

1.2 竞态发生的几种情况

在Linux内核中,有如下几种竞态:

1)对称多处理器的多个CPU;

2)单CPU内的进程与抢占它的进程之间;

3)进程与中断(硬中断、软中断、tasklet、底半部)之间。

2.并发控制的具体机制

2.1 中断屏蔽

在linux内核中避免竞态的一种简单有效的方法:在进入临界区钱将中断屏蔽。中断屏蔽不仅避免了中断与进程间的竞态;而且在Linux内核中,由于进程调度、异步IO等都是通过中断来完成的,因而这样也可以有效的避免进程间的调度。
中断屏蔽的方法:

1) 中断屏蔽的普通方法
local_irq_disable(); /*屏蔽中断*/
critical section /*临界区*/
local_irq_enable(); /*打开中断*/

2)中断屏蔽(保存中断位的信息)
local_irq_save(flags);  /*屏蔽中断*/
critical section /*临界区*/
local_irq_restor(flags); /*打开中断*/

3)中断屏蔽的方法--只操作底半部
local_bh_disable(); /*屏蔽中断*/
critical section/*临界区*/
local_bh_enable(); /*打开中断*/

注意:中断不可长时间屏蔽,应尽快处理完临界区内容,尽快恢复中断,否则可能会导致数据丢失乃至整个系统崩溃。

2.2  原子操作

原子操作指在代码的执行过程中,不会被别的代码路径所中断的操作。

原子操作有两种:位和整型,和cpu架构有关系。

1. 整型原子操作
1)设置原子变量的值:
void atomic_set(atomic_t *v, int i);  /*设置原子变量的值为i*/
atomic_t v = ATOMIC_INIT(0);/*定义原子变量v并初始化为0*/

2)获取原子变量的值
atomic_read(atomic_t *v);/*返回原子变量的值*/

3)原子变量加/减
void atomic_add(int i,  atomic_t *v); /*原子变量增加i*/
void atomic_sub(int i, atomic_t *v); /*原子变量减少i*/

4)原子变量自增/自减
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 位作原子操作

1) 设置位
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;">/*用于获取信号量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;">DECLARE_MUTEX(mount_sem);
down(&mount_sem);	/*获取信号量,包含临界区*/
...
critical section	/*临界区*/
...
up(&mount_sem);	/*释放信号量*/</span>


2. 信号量用于同步
信号量的同步意味着代码的顺序执行,使用方法上可以将信号量初始化为0;如下图,执行单元A在执行代码b前,需在执行单元b执行完代码c之后。

3. 完成量用于同步
代码的同步也可以用完成量来实现,功能需求同2中信号量的同步,实现过程如下图所示。


4. 信号量VS自旋锁

信号量和自旋锁都是较为常用的互斥手段,只是适用的层次不同,信号量的实现依赖于自旋锁,为了保证信号量结构存取的原子性,在多CPU中需要用自旋锁来互斥;而信号量适用于进程间的竞争,因而这样进程上下文的切换开销很大。因此,当占用资源时间较长时适用信号量,较短时适用自旋锁。选择哪个要看临界区的性质和系统特点。
1)使用信号量的开销是进程上下文切换时间Tsw,使用自旋锁的开销是等待自旋锁Tcs,如果Tcs较小则使用自旋锁,否则使用信号量;
2)当临界区可能存在阻塞时,因为阻塞会引起进程切换,如果使用自旋锁会导致死锁,因此这是使用信号量;
3)如果共享资源是在中断或软中断下使用的,则使用自旋锁。

5. 读写信号量
    读写信号量与信号量的关系类似于读写自旋锁与自旋锁的关系;读写信号量可能引起进程阻塞,它可允许N个读执行单元同时访问共享资源,而最多只能有1个写执行单元。因此,读写信号量比信号量的粒度稍粗。
使用方法:
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. 应用实例

1) 在globalmem中因为大量涉及到cup_from_user()、cpu_to_user()容易导致阻塞的函数,因此使用信号量而不能是用自旋锁;
2)一般驱动工程师习惯将信号也放在设备结构体中。
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");



4. 总结

1)自旋锁和信号量最为常用;自旋锁适用于临界资源占用时间较短,且不存在阻塞;信号量适用于临界资源占用时间较长,可能存在阻塞的情况下;
2)原子操作只适用于整数;
3)中断屏蔽适用较少。

5. 参考文献

1) Linux设备开发驱动详解 宋宝华著
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值