4.设备驱动中的并发控制

来源:互联网 发布:电子竞技 奥运会 知乎 编辑:程序博客网 时间:2024/05/21 19:29

 设备驱动中的并发控制

        在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型
的共享资源),可能会引发“竞态”,因此我们必须对共享资源进行并发控制。Linux 内核中
解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。
       自旋锁与信号量“类似而不类”,类似说的是它们功能上的相似性,“不类”指代它们在
本质和实现机理上完全不一样,不属于一类。
       自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环
查看是否该自旋锁的保持者已经释放了锁,“自旋”就是“在原地打转”。而信号量则引起调
用者睡眠,它把进程从运行队列上拖出去,除非获得锁。这就是它们的“不类”。
但是,无论是信号量,还是自旋锁,在任何时刻,最多只能有一个保持者,即在任何时
刻最多只能有一个执行单元获得锁。这就是它们的“类似”。
       鉴于自旋锁与信号量的上述特点,一般而言,自旋锁适合于保持时间非常短的情况
可以在任何上下文使用
信号量适合于保持时间较长的情况,会只能在进程上下文使用。如
果被保护的共享资源只在进程上下文访问,则可以以信号量来保护该共享资源,如果对共享
资源的访问时间非常短,自旋锁也是好的选择。但是,如果被保护的共享资源需要在中断上
下文访问(包括底半部即中断处理句柄和顶半部即软中断),就必须使用自旋锁。

       与信号量相关的 API 主要有:
定义信号量
struct semaphore sem;
初始化信号量
void sema_init (struct semaphore *sem, int val);
该函数初始化信号量,并设置信号量sem 的值为val
void init_MUTEX (struct semaphore *sem);
该函数用于初始化一个互斥锁,即它把信号量sem 的值设置为1,等同于sema_init (struct
semaphore *sem, 1);
void init_MUTEX_LOCKED (struct semaphore *sem);
该函数也用于初始化一个互斥锁,但它把信号量sem 的值设置为0,等同于sema_init
(struct semaphore *sem, 0);
获得信号量
void down(struct semaphore * sem);
该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文使用;
int down_interruptible(struct semaphore * sem);
该函数功能与down 类似,不同之处为,down 不能被信号打断,但down_interruptible
能被信号打断;

int down_trylock(struct semaphore * sem);
该函数尝试获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,
返回非0 值。它不会导致调用者睡眠,可以在中断上下文使用。
释放信号量
void up(struct semaphore * sem);
该函数释放信号量sem,唤醒等待者。
与自旋锁相关的 API 主要有:
定义自旋锁
spinlock_t spin;

初始化自旋锁
spin_lock_init(lock)
该宏用于动态初始化自旋锁lock
获得自旋锁
spin_lock(lock)
该宏用于获得自旋锁lock,如果能够立即获得锁,它就马上返回,否则,它将自旋在那
里,直到该自旋锁的保持者释放;
spin_trylock(lock)
该宏尝试获得自旋锁lock,如果能立即获得锁,它获得锁并返回真,否则立即返回假,
实际上不再“在原地打转”;
释放自旋锁
spin_unlock(lock)
该宏释放自旋锁lock,它与spin_trylock 或spin_lock 配对使用;
除此之外,还有一组自旋锁使用于中断情况下的 API。
下面进入对并发控制的实战。首先,在 globalvar 的驱动程序中,我们可以通过信号量
来控制对int global_var 的并发访问,下面给出源代码:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <asm/semaphore.h>
MODULE_LICENSE("GPL");
#define MAJOR_NUM 254
static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);
static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);
struct file_operations globalvar_fops =
{
        read: globalvar_read, write: globalvar_write,
};
static int global_var = 0;

static struct semaphore sem;
static int __init globalvar_init(void)
{

     int ret;
     ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);
     if (ret)
     {
           printk("globalvar register failure");
     }

     else
     {
        printk("globalvar register success");
        init_MUTEX(&sem);
     }
     return ret;
}

static void __exit globalvar_exit(void)
{
    int ret;
    ret = unregister_chrdev(MAJOR_NUM, "globalvar");
   if (ret)
   {
       printk("globalvar unregister failure");
   }
   else
   {
      printk("globalvar unregister success");
   }
}
static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
   //获得信号量
   if (down_interruptible(&sem))
   {
      return - ERESTARTSYS;
   }
   //将global_var 从内核空间复制到用户空间
   if (copy_to_user(buf, &global_var, sizeof(int)))
   {
      up(&sem);
      return - EFAULT;
   }
   //释放信号量
   up(&sem);
   return sizeof(int);
}
ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t

*off)
{
   //获得信号量
   if (down_interruptible(&sem))
   {
     return - ERESTARTSYS;
   }
   //将用户空间的数据复制到内核空间的global_var
   if (copy_from_user(&global_var, buf, sizeof(int)))
   {
      up(&sem);
      return - EFAULT;
   }
   //释放信号量
   up(&sem);
   return sizeof(int);
}
module_init(globalvar_init);
module_exit(globalvar_exit);

接下来,我们给globalvar 的驱动程序增加open()和release()函数,并在其中借助自旋锁
来保护对全局变量int globalvar_count(记录打开设备的进程数)的访问来实现设备只能被
一个进程打开(必须确保globalvar_count 最多只能为1):
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <asm/semaphore.h>
MODULE_LICENSE("GPL");
#define MAJOR_NUM 254
static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);
static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);
static int globalvar_open(struct inode *inode, struct file *filp);
static int globalvar_release(struct inode *inode, struct file *filp);
struct file_operations globalvar_fops =
{

   read: globalvar_read, write: globalvar_write, open: globalvar_open, release:
   globalvar_release,
};
static int global_var = 0;
static int globalvar_count = 0;
static struct semaphore sem;
static spinlock_t spin = SPIN_LOCK_UNLOCKED;
static int __init globalvar_init(void)
{
i   nt ret;
ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);
if (ret)
{
printk("globalvar register failure");
}
else
{
printk("globalvar register success");

init_MUTEX(&sem);
}
return ret;
}
static void __exit globalvar_exit(void)
{
int ret;
ret = unregister_chrdev(MAJOR_NUM, "globalvar");
if (ret)
{
printk("globalvar unregister failure");
}
else
{
printk("globalvar unregister success");
}
}

static int globalvar_open(struct inode *inode, struct file *filp)
{
//获得自选锁

spin_lock(&spin);
//临界资源访问
if (globalvar_count)
{
spin_unlock(&spin);
return - EBUSY;
}
globalvar_count++;
//释放自选锁
spin_unlock(&spin);
return 0;
}
static int globalvar_release(struct inode *inode, struct file *filp)
{
globalvar_count--;
return 0;
}

static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t
*off)
{
if (down_interruptible(&sem))
{
return - ERESTARTSYS;
}
if (copy_to_user(buf, &global_var, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
up(&sem);
return sizeof(int);
}

static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len,
loff_t *off)
{
if (down_interruptible(&sem))
{
return - ERESTARTSYS;

}
if (copy_from_user(&global_var, buf, sizeof(int)))
{
up(&sem);
return - EFAULT;
}
up(&sem);
return sizeof(int);
}
module_init(globalvar_init);
module_exit(globalvar_exit);

为了上述驱动程序的效果,我们启动两个进程分别打开/dev/globalvar。在两个终端中调
用./globalvartest.o 测试程序,当一个进程打开/dev/globalvar 后,另外一个进程将打开失败,
输出“device open failure”,如下图:

 

原创粉丝点击