Linux 驱动之并发与竞争

文章目录

    • 框架图
    • 什么是并发与竞争?
    • 如果不处理并发会发什么什么事情?
    • Linux在什么情况下会造成并发?
    • 发生并发时要保护什么?
    • 并发和竞争的处理方法
      • 原子操作
        • 什么是原子操作?
        • 原子操作的应用
        • 原子整形变量描述
        • 原子整形操作API函数(32位)
        • 原子操作举例
        • 原子位操作API函数
        • 例子
      • 自旋锁
        • 什么是自旋锁?
        • 自旋锁的API函数
        • 自旋锁的使用步骤
        • 自旋锁的注意事项
        • 自旋锁的死锁
          • 自旋锁死锁图解
        • 如何避免死锁
        • 例子
      • 信号量
        • 信号量的引入
        • 信号量的工作方式
        • 信号量的描述
        • 信号量API
        • 信号量的注意事项
        • 例子
      • 互斥锁
        • 什么是互斥锁?
        • 互斥锁的API函数
        • 互斥锁的注意事项
        • 例子

框架图

Linux 驱动之并发与竞争_第1张图片

什么是并发与竞争?

并发会造成多个程序同时访问一个共享资源,这时候由并发同时访问一个共享资源而产生的问题就是竞争。比如A和B要打电话,但是公共电话只有一部。A和B要打电话就是并发,谁先打电话就是竞争。但是电话谁都可以用,所以电话就是共享资源。

Linux是一个多任务的操作系统,并发和竞争在Linux中非常的常见。所以在编写Linux驱动的过程中就要考虑并发与竞争。否则在访问共享资源的时候容易出问题,而这些问题往往不容易排查,很难定位。

并发(concurrency):把任务在不同的时间点交给处理器进行处理。在同一时间点,任务并不会同时运行。

Linux 驱动之并发与竞争_第2张图片

并行(parallelism):把每一个任务分配给每一个处理器独立完成。在同一时间点,任务一定是同时运行。Linux 驱动之并发与竞争_第3张图片

如果不处理并发会发什么什么事情?

这里我们讨论的是内核空间中的并发,用户空间中的并发不讨论,现在有俩个相同的驱动程序A和B,这俩个驱动程序并发执行,并且他们都要修改变量c。

情况1:

程序A先运行,程序A运行完然后再运行程序B,这种情况是理想情况。程序A和程序B完美运行,没有错误。

情况2:

程序A运行了一半,内核调度让程序B执行,程序B执行完以后在回来执行程序A。但是程序A执行完以后,程序B是不是就相当于什么也没有做呢,变量c的值是程序A的值。

情况1是理想情况,但是我们根本无法预料到程序A和程序B到底是怎么运行的。如果不对共事资源进行保护。轻则程序白白运行一次,重则系统崩溃。

Linux在什么情况下会造成并发?

主要有以下几种情况:

  1. 中断程序并发访问。中断是可以随时产生的,一旦产生中断,就会放下手头的工作,去执行中断中的任务,如果在执行中断中的任务的时候修改了共享资源,就会产生并想不到的问题
  2. 抢占式并发访问。在Linux内核2.6版本以后,Linux内核支持了抢占,在支持抢占的情况下,正在执行的进程随时都有可能被抢占。
  3. 多处理器(SMP)并发访问。多核处理器之间存在核间并发访问。

发生并发时要保护什么?

进程(运行起来的程序就是进程)并发访问共享资源是不安全的,如果俩个进程同时访问空间资源,就是发生竞争。所以我们要保护共享资源,什么是共享资源呢?放在现实生活中,共享资源可以是公共电话,也可以是我们日常生活中的共享单车,共享充电宝等。这些都是共享资源。

放在代码中,共享资源就是某个整型的全局变量,或者驱动中的设备结构体。当然其他的数据也可能是共享数据,这个就要根据实际的驱动程序来实际分析了。

并发和竞争的处理方法

在编写驱动程序的时候,我们要尽量避免让驱动程序存在并发和竞争,Linux内核里面给我们提供了几种处理并发与竞争的方法,分别是:原子操作,自旋锁,信号量,互斥体

原子操作

什么是原子操作?

原子操作中的”原子”指的是化学反应中最小的微粒。在Linux上用原子形容一个操作或者一个函数是最小执行单位,是不可以被打断的。所以原子操作指的是该操作在执行完之前不会被任何事物打断。

原子操作的应用

原子操作一般用于整形变量或者位的保护。比我,我们定义一个变量a,如果程序A正在给变量a赋值,此时程序B也要来操作变量a,这时候就发生了并发与竞争。程序A的操作就有可能会被程序B打断。如果我们使用原子操作对变量a进行保护,就可以避免这种问题。

原子整形变量描述

Linux中定义了一个叫做atomic_tatomic64_t的结构体来描述原子变量,其中atomic_t是用到32位系统中,atomic64_t是用在64位系统中。代码如下所示:

typedef struct {
    int counter;
} atomic_t;
#ifdef CONFIG_64BIT
typedef struct {
    long counter;
} atomic64_t;
#endif

原子整形操作API函数(32位)

64位的就是将下面所有atomic字眼改成atomic64

头文件linux/atomic.h,asm/atomic.h

原子整数操作 描述
ATOMIC_INIT(int i) 在声明一个atomic_t变量时,将它初始化为i
int atomic_read(atomic_t*v) 原子地读取整数变量v
void atomic_set(atomic_t *v, int i) 原子地设置v值为i
void atomic_add(int i, atomic_t* v) 原子地给v加i
void atomic_sub(int i,atomic_t * v) 原子地从v减i
void atomic_inc(atomic_t* v) 原子地给v加1
void atomic_dec(atomic_t* v) 原子地从v减1
int atomic_sub_and_test(int i,atomic_t* v) 原子地从v减i,如果结果等于o,返回真:否则返回假
int atomic_add_negative(int i,atomic_t* v) 原子地给v加i,如果结果是负数,返回真;否则返回假
int atomic_add_return(int i, atomic_t* v) 原子地给v加i,且返回结果
int atomic_sub_return(int i, atomic_t* v) 原子地从v减i,且返回结果
int atomic_inc_return(int i, atomic_t* v) 原子地给v加1,且返回结果
int atomic_dec_return(int i, atomic_t* v) 原子地从v减1。且返回结果
int atomic_dec_and_test(atomic_t* v) 原子地从v减1,如果结果等于0,返回真:否则返回假
int atomic_inc_and_test(atomic_t *v) 原子地给v加1,如果结果等于0,返回真;否则返回假

原子操作举例

atomic64_t v=ATOMIC64_INIT(O);//定义并初始化原子变量v=o
atomic64_set(&v,1);//设置v=1
atomic64_read(&v);//读取v的值,此时v的值是1
atomic64_inc(&v);//v的值加1,此时v的值是2

原子位操作API函数

原子位操作API函数
除原子整数操作外,内核也提供了一组针对位的操作函数这些位操作函数是对普通的内存地址进行操作的,参数是一个指针和一个位号。

原子位数操作 描述
void set_bit(int nrvoid* addr) 原子地设置addr 所指对象的第 nr 位
void clear_bit(int nr,void* addr) 原子地清空 addr 所指对象的第 nr 位
void change bti(int nrvoid* addr) 原子地翻转addr 所指对象的第 nr位
int test_and _set bit(int nrvoid *addr) 原子地设置addr 所指对象的第 nr位,并返回原先的值
int test and clear _bit(int nr,void *addr) 原子地清空 addr 所指对象的第 nr位,并返回原先的值
int test and change bit(int nr,void *addr) 原子地翻转 addr 所指对象的第 nr位,并返回原先的值
int test bit(int nrvoid* addr) 原子地返回addr 所指对象的第 nr位

例子

usr.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 

struct device_test
{
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
};

struct device_test dev1;

static atomic64_t v = ATOMIC64_INIT(1);

static int cdev_test_open(struct inode *inode, struct file *file)
{
    if(!atomic64_dec_and_test(&v)){
        atomic64_add(1, &v);
        return -EBUSY;
    }
    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_to_user(buf, dev->kbuf, strlen(dev->kbuf)) != 0)
    {
        printk("copy_to_user is error");
        return -EFAULT;
    }
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_from_user(dev->kbuf, buf, size) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    printk("kbuf is %s\n", dev->kbuf);
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    atomic64_inc(&v);
    return 0;
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

static int usr_init(void)
{
    int ret;
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if (IS_ERR(dev1.class))
    {
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if (IS_ERR(dev1.device))
    {
        goto err_class_device;
    }

    return 0;
err_class_device:
    class_destroy(dev1.class);

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

app.c(gcc app.c -o a.out)

#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
    int fd;
    char buf1[32] = {};
    char buf2[32] = {"nihao"};
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    sleep(5);
    close(fd);
    return 0;
}

输出:

fengzc@ubuntu:~/study/drivers_test/11_atomic$ sudo insmod usr.ko

fengzc@ubuntu:~/study/drivers_test/11_atomic$ cp ./a.out ./b.out

fengzc@ubuntu:~/study/drivers_test/11_atomic$ sudo ./a.out &
[1] 16630
fengzc@ubuntu:~/study/drivers_test/11_atomic$ sudo ./b.out
open error
: Device or resource busy

自旋锁

什么是自旋锁?

自旋锁是为了实现保护共享资源提出的一种锁机制,也是内核中比较常见的锁机制。自旋锁是以“原地等待”的方式解决资源冲突。即当线程A获取到自旋锁以后,此时线程B也想获取到自旋锁。但是线程B获取不到,只能“原地打转”(仍然占用CPU,不会休眠),不断尝试获取自旋锁,直到获取成功,然后才退出循环。

Linux内核使用结构体spinlock_t来描述自旋锁,结构体定义如下:

typedef struct spinlock {
    union {
        struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
#define LOCK_PADSIZE (offsetof(struct raw spinlock, dep_map)
            struct {
                u8 __padding[LOCK_PADSIZE];
                struct lockdep_map dep_map;
            }
#endif
        }; 
}spinlock_t;

自旋锁的API函数

函数 描述
DEFINE_SPINLOCK(spinlock_t lock) 定义并初始化一个变量
int spin_lock_init(spinlock_t* lock) 初始化自旋锁
void spin_lock(spinlock_t * lock) 获取自旋锁,也叫做加锁
void spin_unlock(spinlock_t * lock) 释放自旋锁,也叫做解锁
int spin_trylock(spinlock_t * lock) 尝试获取自旋锁,如果没有获取到就返回0
int spin_is_locked(spinlock_t * lock) 检查自旋锁是否被获取,如果没有被获取就返回非 0,否则返回 0
void spin_lock_irq(spinlock_t * lock) 关闭中断并获取自旋锁
void spin_unlock_irq(spinlock_t * lock) 打开中断并释放自旋锁
void spin_lock_irqsave(spinlock_t * lock, unsigned long flags) 保存中断状态,关闭中断并获取自旋锁
void spin_unlock_irqrestore(spinlock_t * lock, unsigned long flags) 恢复之前保存的中断状态,打开中断并释放自旋锁
void spin_lock_bh(spinlock_t * lock) 关闭下半部,获取自旋锁
void spin_unlock_bh(spinlock_t * lock) 打开下半部,获取自旋锁

注意:其中,spin_lock_irqspin_lock_irqsave是防止临界区被系统中断使用的

自旋锁的使用步骤

  1. 在访问临界资源的时候先申请自旋锁

  2. 获取到自旋锁以后就进入临界区,获取不到自旋锁就“原地等待“

  3. 退出临界区的时候要释放自旋锁

自旋锁的注意事项

  1. 由于自旋锁会“原地等待”,因为“原地等待”会继续占用CPU,会消耗CPU资源。所以锁的时间不能太长。也就是临界区的代码不能太多。

  2. 在自旋锁保护的临界区里面不能调用可能会导致线程休眠的函数,否则可能会发生死锁。

  3. 自旋锁一般是用在多核的SOC上。

自旋锁的死锁

​ 在多核CPU或者支持抢占的单核CPU中。被自旋锁保护的临界区不能调用任何能够引起睡眠或者阻塞的函数,否则可能会发生死锁。

​ 使用自旋锁会禁止抢占。比如在单核CPU下,A进程获取到自旋锁以后暂时关闭内核抢占,如果A进程此时进入了休眠状态(放弃了CPU的使用权),B进程此时也想获取到自旋锁,但是此时白旋锁被进程A持有,而且此时CPU的抢占被禁止了。因为是单核,进程B就无法被调度出去,只能在”原地旋转”等在锁被A释放。但是进程A无法运行,锁也就无法释放。死锁就发生了。

​ 多核CPU不会发生上面的情况。因为其他的核会调度其他进程。

​ 当进程A获取到自旋锁以后,如果产生了中断,并且在中断里面也要访问共享资源(中断里面可以用自旋锁),此时中断里面无法获取到自旋锁,只能“原地旋转”,产生死锁。为了避免这种情况发生,可以使用spin_lock_irqsaveAPI来禁止中断并获取自旋锁

自旋锁死锁图解

Linux 驱动之并发与竞争_第4张图片

如何避免死锁

  1. 如果中断服务函数里面要使用自旋锁,需要在驱动程序中使用spin_lock_irqsavespin_unlock_irqrestore函数来申请自旋锁,防止在执行临界区里面的代码时被中断打断。
  2. 避免在一个函数里面多次获取自旋锁
  3. 临界区的代码不能太久

例子

usr.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 

struct device_test
{
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
};

struct device_test dev1;

static spinlock_t spinlock;
static int flag = 1;

static int cdev_test_open(struct inode *inode, struct file *file)
{
    spin_lock(&spinlock);
    if(flag != 1){
        spin_unlock(&spinlock);
        return -EBUSY;
    }
    flag = 0;
    spin_unlock(&spinlock);
    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_to_user(buf, dev->kbuf, strlen(dev->kbuf)) != 0)
    {
        printk("copy_to_user is error");
        return -EFAULT;
    }
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_from_user(dev->kbuf, buf, size) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    printk("kbuf is %s\n", dev->kbuf);
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    spin_lock(&spinlock);
    flag = 1;
    spin_unlock(&spinlock);
    
    return 0;
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

static int usr_init(void)
{
    int ret;
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if (IS_ERR(dev1.class))
    {
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if (IS_ERR(dev1.device))
    {
        goto err_class_device;
    }

    return 0;
err_class_device:
    class_destroy(dev1.class);

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

app.c(gcc app.c -o a.out)

#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
    int fd;
    char buf1[32] = {};
    char buf2[32] = {"nihao"};
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    sleep(5);
    close(fd);
    return 0;
}

输出:

fengzc@ubuntu:~/study/drivers_test/12_spinlock$ sudo insmod usr.ko
fengzc@ubuntu:~/study/drivers_test/12_spinlock$ sudo ./a.out &
[1] 19119
fengzc@ubuntu:~/study/drivers_test/12_spinlock$ sudo ./b.out
open error
: Device or resource busy

信号量

信号量的引入

自旋锁是通过“原地等待”的方式来处理并发与竞争的,所以被保护的临界区不能太长,以免造成对CPU资源的浪费。但是有些情况我们必不可免要长时间对一些资源进行保护。这时候就可以使用信号量。

什么是信号量呢?

举个例子,现在有一个电话亭,里面只有一个公共电话。某天A去打电话,恰好过了一会B也来了要打电话。但是此时A在打电话,所以B就只能等A打完电话才可以打。如果是自旋锁的,B就要一直等着A打完。但是A的事情很重要,需要打很长时间电话。这时候自旋锁就不合适了。那A是不是也可以告诉B,你先去休息一会,等我打完告诉你,你再来打电话。这个就是信号量。信号量会引起调用者睡眠,所以信号量也叫睡眠锁。

信号量的工作方式

信号量的本质是一个全局变量。信号量的值可以根据实际情况来自行设置(取值范围大于等于0,当有线程来访问资源时,信号量执行“减一”操作,访问完以后,在执行“加一”操作。比如一个屋子有5把钥匙,这5把钥匙就是信号量的值,也就是说有5个人可以进到这个屋子(允许多个线程同时访问共享资源)。当某个人想进屋子的时候,就要先拿一把钥匙,此时信号量的值“减一”。直到这5把钥匙全部拿走以后,这个屋子别人就进不去了。如果有人从屋子里面出来,还回去一把钥匙,此时信号量的值“加一”,那就又可以进去一个人。

信号量的描述

Linux内核使用结构体semaphore来表示信号量,定义在semaphore.h文件当中,如下所示:

struct semaphore {
    raw_spinlock_t     lock;
    unsigned int       count;
    struct list_head   wait list;
}

信号量API

函数 描述
DEFINE_SEAMPHORE(name) 定义信号量,并设置信号量的值为 1
void sema_init(struct semaphore *sem, int val) 初始化信号量 sem 并设置信号的值为 val
void down(struct semaphore *sem) 获取信号量。不能被信号打断,如 ctrl+c
int down_interruptible(struct semaphore *sem) 获取信号量。能被信号打断,如 ctrl+c
void up(struct semaphore *sem) 释放信号量
int down_trylock(struct semaphore *sem 尝试获取信号量,如果获取到信号量就返回0,获取不到就返回非 0

信号量的注意事项

  1. 信号量的值不能小于0

  2. 访问共享资源时,信号量执行“减一”操作,访问完成后在执行“加一”操作

  3. 当信号量的值为0时,想访问共享资源的线程必须等待,直到信号量大于0时,等待的线程才可以访

  4. 因为信号量会引起休眠,所以中断里面不能用信号量

  5. 共享资源持有时间比较长,一般用信号量而不用自旋锁

  6. 在同时使用信号量和自旋锁的时候,要先获取信号量,在使用自旋锁。因为信号量会导致睡眠

例子

usr.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
struct device_test
{
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
};

struct device_test dev1;

static struct semaphore semlock;

static int cdev_test_open(struct inode *inode, struct file *file)
{
#if 0
     down(&semlock);
#else
    if (down_interruptible(&semlock))
    {
        return -EBUSY;
    }
#endif    
    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_to_user(buf, dev->kbuf, strlen(dev->kbuf)) != 0)
    {
        printk("copy_to_user is error");
        return -EFAULT;
    }
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_from_user(dev->kbuf, buf, size) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    printk("kbuf is %s\n", dev->kbuf);
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    up(&semlock);

    return 0;
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

static int usr_init(void)
{
    int ret;
    sema_init(&semlock, 1);
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if (IS_ERR(dev1.class))
    {
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if (IS_ERR(dev1.device))
    {
        goto err_class_device;
    }

    return 0;
err_class_device:
    class_destroy(dev1.class);

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

app.c(gcc app.c -o a.out)

#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
    int fd;
    char buf1[32] = {};
    char buf2[32] = {"nihao"};
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    printf("open ok!\n");
    sleep(10);
    close(fd);
    printf("close ok!\n");
    return 0;
}

输出:

fengzc@ubuntu:~/study/drivers_test/13_sem$ sudo insmod usr.ko
fengzc@ubuntu:~/study/drivers_test/13_sem$ sudo ./a.out &
[1] 21355
fengzc@ubuntu:~/study/drivers_test/13_sem$ open ok!
fengzc@ubuntu:~/study/drivers_test/13_sem$ sudo ./b.out
close ok!
open ok!
close ok!

互斥锁

什么是互斥锁?

同一个资源同一个时间只有一个访问者在进行访问,其他的访问者访问结束以后才可以访问这个资源。这就是互斥
互斥锁和信号量值为1的情况很类似,但是互斥锁更简洁,更高效,互斥锁会引起休。不过在使用中需要注意的事项也就更多。

Linux内核使用结构体mutex来描述互斥锁,结构体定义如下:

struct mutex{
    atomic_long_t       owner;
    spinlock_t          wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
    struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
    struct list_head wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
    void                *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
    struct lockdep_map  dep_map ;
#endif
};

互斥锁的API函数

函数 描述
DEFINE_MUTEX(name) 定义并初始化一个互斥锁
void mutex_init(mutex *lock) 初始化互斥锁
void mutex_lock(struct mutex *lock) 上锁,如果不可以用则睡眠
void mutex_unlock(struct mutex *lock) 解锁
int mutex_is_locked(struct mutex *lock) 如果锁已经被使用则返回1,否则返回0

互斥锁的注意事项

  1. 互斥锁会导致休眠,所以在中断里面不能用互斥锁。
  2. 同一时刻只能有一个线程持有互斥锁,并且只有持有者可以解锁。
  3. 不允许递归上锁和解锁。
  4. 一般情况下我们优先考虑互斥锁,不考虑信号量,因为互斥锁更简洁,更高效

例子

usr.c

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 

struct device_test
{
    dev_t dev_num;
    struct cdev cdev_test;

    int major;
    int minor;

    struct class *class;
    struct device *device;

    char kbuf[32];
};

struct device_test dev1;

static struct mutex mutex;
static int flag = 1;

static int cdev_test_open(struct inode *inode, struct file *file)
{
    mutex_lock(&mutex);
    if(flag != 1){
        mutex_unlock(&mutex);
        return -EBUSY;
    }
    flag = 0;
    mutex_unlock(&mutex);
    file->private_data = &dev1;
    printk("This is a cdev_test_open\n");
    return 0;
}

static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_to_user(buf, dev->kbuf, strlen(dev->kbuf)) != 0)
    {
        printk("copy_to_user is error");
        return -EFAULT;
    }
    return 0;
}

static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
    struct device_test *dev = (struct device_test *)file->private_data;
    if (copy_from_user(dev->kbuf, buf, size) != 0)
    {
        printk("copy_from_user is error");
        return -EFAULT;
    }
    printk("kbuf is %s\n", dev->kbuf);
    return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
    flag = 1;  
    return 0;
}

struct file_operations cdev_test_ops = {
    .owner = THIS_MODULE,
    .open = cdev_test_open,
    .read = cdev_test_read,
    .write = cdev_test_write,
    .release = cdev_test_release,
};

static int usr_init(void)
{
    int ret;
    mutex_init(&mutex);
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name");
    if (ret < 0)
    {
        goto err_chrdev;
        printk("alloc_chrdev_region failed\n");
    }
    printk("alloc_chrdev_region succeed\n");

    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk("major is %d\n", dev1.major);
    printk("minor is %d\n", dev1.minor);

    dev1.cdev_test.owner = THIS_MODULE;
    cdev_init(&dev1.cdev_test, &cdev_test_ops);
    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
    if (ret < 0)
    {
        goto err_chr_add;
    }

    dev1.class = class_create(THIS_MODULE, "test");
    if (IS_ERR(dev1.class))
    {
        goto err_class_create;
    }
    device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
    if (IS_ERR(dev1.device))
    {
        goto err_class_device;
    }

    return 0;
err_class_device:
    class_destroy(dev1.class);

err_class_create:
    cdev_del(&dev1.cdev_test);

err_chr_add:
    unregister_chrdev_region(dev1.dev_num, 1);

err_chrdev:
    return ret;
}

static void usr_exit(void)
{
    unregister_chrdev_region(dev1.dev_num, 1);
    cdev_del(&dev1.cdev_test);

    device_destroy(dev1.class, dev1.dev_num);
    class_destroy(dev1.class);
    printk("bye bye\n");
}

module_init(usr_init);
module_exit(usr_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fengzc");
MODULE_VERSION("v1.0");

app.c(gcc app.c -o a.out)

#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
    int fd;
    char buf1[32] = {};
    char buf2[32] = {"nihao"};
    fd = open("/dev/test", O_RDWR); // 打开设备节点
    if (fd < 0)
    {
        perror("open error \n");
        return fd;
    }
    sleep(5);
    close(fd);
    return 0;
}

输出:

fengzc@ubuntu:~/study/drivers_test/15_mutex$ sudo insmod usr.ko
fengzc@ubuntu:~/study/drivers_test/15_mutex$ cp ./a.out ./b.out
fengzc@ubuntu:~/study/drivers_test/15_mutex$ (sudo ./a.out &);sudo ./b.out
open error
: Device or resource busy

你可能感兴趣的:(linux,linux,驱动开发)