Linux 驱动之并发与竞争

这篇具有很好参考价值的文章主要介绍了Linux 驱动之并发与竞争。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

框架图

Linux 驱动之并发与竞争

什么是并发与竞争?

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

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

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

Linux 驱动之并发与竞争

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

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

这里我们讨论的是内核空间中的并发,用户空间中的并发不讨论,现在有俩个相同的驱动程序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 <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>

#include <asm/atomic.h>

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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

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 驱动之并发与竞争

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

usr.c

#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>

#include <asm/atomic.h>

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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

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 <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>

#include <asm/atomic.h>
#include <linux/semaphore.h>
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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

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 <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>

#include <asm/atomic.h>

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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

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文章来源地址https://www.toymoban.com/news/detail-423717.html

到了这里,关于Linux 驱动之并发与竞争的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【嵌入式环境下linux内核及驱动学习笔记-(5-驱动的并发控制机制)】

    在讨论并发前,先要了解以下几个概念:执行流,上下文,共享与临界等。 什么叫执行流: 【执行流】:有开始有结束总体顺序执行的一段代码 又称 上下文 。 上下文分类: 【任务上下文】:普通的,具有五种状态(就绪态、运行态、睡眠态、暂停态、僵死态),可被阻塞

    2023年04月21日
    浏览(50)
  • linux内核网络驱动框架(linux驱动开发篇)

    网络驱动的核心: 1、就是初始化 net_device 结构体中的各个成员变量, 2、然后将初始化完成以后的 net_device 注册到 Linux 内核中 1、网络设备(用net_device结构体) 2、网络设备的操作集( net_device_ops结构体 ) 3、sk_buff结构体 网络是分层的,对于应用层而言不用关系具体的底层是

    2023年04月08日
    浏览(80)
  • 【Linux驱动】Linux--V4L2视频驱动框架

    v4l2驱动框架主要的对象有video_device、v4l2_device、v4l2_subdev、videobuf video_device 一个字符设备,为用户空间提供设备节点(/dev/videox),提供系统调用的相关操作(open、ioctl…) v4l2_device 嵌入到video_device中,表示一个v4l2设备的实例 v4l2_subdev 依附在v4l2_device之下,并表示一个v4l2设备的子

    2024年02月14日
    浏览(32)
  • Linux驱动框架

    #include linux/module.h #include linux/kernel.h #include linux/fs.h #include linux/init.h #include linux/delay.h #include asm/uaccess.h #include asm/irq.h #include asm/io.h #include asm/arch/regs-gpio.h #include asm/hardware.h static struct class *firstdrv_class; static struct class_device    *firstdrv_class_dev; static int first_drv_open(struct inode *in

    2023年04月20日
    浏览(30)
  • 【Linux网络编程】高并发服务器框架 线程池介绍+线程池封装

    前言 一、线程池介绍 💻线程池基本概念 💻线程池组成部分 💻线程池工作原理  二、线程池代码封装 🌈main.cpp 🌈ThreadPool.h 🌈ThreadPool.cpp 🌈ChildTask.h  🌈ChildTask.cpp 🌈BaseTask.h 🌈BaseTask.cpp 三、测试效果 四、总结 📌创建线程池的好处 本文主要学习 Linux内核编程 ,结合

    2024年01月16日
    浏览(95)
  • Linux内核驱动 --- CCF框架 provider驱动的编写

    复制上节内容中对Provider驱动编写流程的总结: 1)分析硬件的clock tree,按照上面所描述的分类,将这些clock分类。 2)将clock tree在DTS中描述出来,需要注意以下几2点: 3)对于不能由clock framework core处理的clock,需要在driver中使用struct of_device_id进行匹配,并在初始化时,调用

    2024年02月07日
    浏览(48)
  • Linux 网络驱动-MAC、PHY层驱动框架(三)

       I.MX6ULL 有两个 10/100M 的网络 MAC 外设,因此 I.MX6ULL 网络驱动主要就是这两个网络 MAC 外设的驱动。这两个外设的驱动都是一样的,我们分析其 中一个就行了,首先肯定是设备树, NXP 的 I.MX 系 列 SOC 网 络 绑 定 文 档 为 Documentation/devicetree/bindings/net/fsl-fec.txt,此绑定文档描

    2024年02月09日
    浏览(39)
  • 【Linux】驱动学习,先啃框架

    目录 前言: 一、驱动设计 (1)面向对象: (2)分层: (3)分离: 二、驱动框架 (1)传统框架  (2)总线设备驱动框架: (3)设备树 经典环节: 我一直深信,带着问题思考和实践,能够更容易理解并学习到 。 (1)驱动设计的核心思想 面向对象 分层 分离 (2)驱动

    2024年02月05日
    浏览(29)
  • Linux下的FrameBuffer驱动框架

    一、RGB LCD经典显示器件介绍 : 1、LCD屏幕的重要属性参数 : ① 分辨率 :也就是屏幕上的像素点的个数; ② 像素格式 :即单个像素点RGB三种颜色的表达方式,包括RGB888、ARGB8888和RGB565等。 ③ LCD屏幕硬件接口 : 这里指的是RGB LCD排线接口 , 如下图所示 : R[7:0]、G[7:0]和B[

    2024年01月16日
    浏览(35)
  • Linux -- 字符设备驱动--LED的驱动开发(初级框架)

    看原理图确定引脚,确定引脚输出什么电平才能点亮 / 熄灭 LED 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是? 编写驱动:先写框架,再写硬件操作的代码 注意 :在芯片手册中确定的寄存器地址被称为 物理地址 ,在 Linux 内核中无法直接使用。 需要使

    2024年04月28日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包