深入浅出Linux设备驱动编程--设备驱动中的并发控制

简介:
4.设备驱动中的并发控制
在驱动程序中,当多个线程同时访问相同的资源时(驱动程序中的全局变量是一种典型的共享资源),可能会引发“竞态”,因此我们必须对共享资源进行并发控制。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)
{
 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 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”




 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120100,如需转载请自行联系原作者

相关文章
|
2月前
|
Shell Linux
Linux shell编程学习笔记30:打造彩色的选项菜单
Linux shell编程学习笔记30:打造彩色的选项菜单
|
9天前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
28 5
|
11天前
|
Linux 数据库
Linux内核中的锁机制:保障并发操作的数据一致性####
【10月更文挑战第29天】 在多线程编程中,确保数据一致性和防止竞争条件是至关重要的。本文将深入探讨Linux操作系统中实现的几种关键锁机制,包括自旋锁、互斥锁和读写锁等。通过分析这些锁的设计原理和使用场景,帮助读者理解如何在实际应用中选择合适的锁机制以优化系统性能和稳定性。 ####
29 6
|
22天前
|
运维 监控 Shell
深入理解Linux系统下的Shell脚本编程
【10月更文挑战第24天】本文将深入浅出地介绍Linux系统中Shell脚本的基础知识和实用技巧,帮助读者从零开始学习编写Shell脚本。通过本文的学习,你将能够掌握Shell脚本的基本语法、变量使用、流程控制以及函数定义等核心概念,并学会如何将这些知识应用于实际问题解决中。文章还将展示几个实用的Shell脚本例子,以加深对知识点的理解和应用。无论你是运维人员还是软件开发者,这篇文章都将为你提供强大的Linux自动化工具。
|
2月前
|
Shell Linux
Linux shell编程学习笔记82:w命令——一览无余
Linux shell编程学习笔记82:w命令——一览无余
|
2月前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
97 6
|
2月前
|
Linux Shell
Linux系统编程:掌握popen函数的使用
记得在使用完 `popen`打开的流后,总是使用 `pclose`来正确关闭它,并回收资源。这种做法符合良好的编程习惯,有助于保持程序的健壮性和稳定性。
140 3
|
2月前
|
Shell Linux Python
python执行linux系统命令的几种方法(python3经典编程案例)
文章介绍了多种使用Python执行Linux系统命令的方法,包括使用os模块的不同函数以及subprocess模块来调用shell命令并处理其输出。
37 0
|
3月前
|
项目管理 敏捷开发 开发框架
敏捷与瀑布的对决:解析Xamarin项目管理中如何运用敏捷方法提升开发效率并应对市场变化
【8月更文挑战第31天】在数字化时代,项目管理对软件开发至关重要,尤其是在跨平台框架 Xamarin 中。本文《Xamarin 项目管理:敏捷方法的应用》通过对比传统瀑布方法与敏捷方法,揭示敏捷在 Xamarin 项目中的优势。瀑布方法按线性顺序推进,适用于需求固定的小型项目;而敏捷方法如 Scrum 则强调迭代和增量开发,更适合需求多变、竞争激烈的环境。通过详细分析两种方法在 Xamarin 项目中的实际应用,本文展示了敏捷方法如何提高灵活性、适应性和开发效率,使其成为 Xamarin 项目成功的利器。
53 1
|
3月前
|
安全 Linux 开发工具
探索Linux操作系统:从命令行到脚本编程
【8月更文挑战第31天】在这篇文章中,我们将一起潜入Linux操作系统的海洋,从最基础的命令行操作开始,逐步深入到编写实用的脚本。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和实用技能。我们将通过实际代码示例,展示如何在日常工作中利用Linux的强大功能来简化任务和提高效率。准备好了吗?让我们一起开启这段旅程,探索Linux的奥秘吧!