【Linux】Linux驱动开发:并发与竞态(原子操作、自旋锁、信号量、互斥体)

本文详细介绍了Linux内核中四种主要的锁机制:原子操作、自旋锁、信号量和互斥体。分别阐述了它们的概念、API用法以及特点,并提供了实例代码,展示了如何在实际的设备驱动开发中使用这些锁来保护资源的并发访问。原子操作保证操作的不可分割性,自旋锁适用于短时间的临界区,信号量允许进程休眠等待,而互斥体则是一种更安全的进程同步机制。

目录

1、原子操作

1.1 原子操作的概念

1.2 原子操作的API

1.2.1 ATOMIC_INIT:定义并初始化原子变量

1.2.2 atomic_dec_and_test:上锁(减去1后和0比较)

1.2.3 atomic_inc_and_test:上锁(加上1后和0比较)

1.2.4 atomic_inc:解锁

1.2.5 atomic_dec:解锁

1.3 实例代码

2、自旋锁

2.1 自旋锁的概念

2.2 自旋锁的API

2.2.1 spinlock_t lock:定义自旋锁

2.2.2 spin_lock_init:初始化自旋锁

2.2.3 spin_lock:上锁

2.2.4 spin_unlock:解锁

2.2.5 spin_lock_irq:在中断中上锁

2.2.6 spin_unlock_irq:解锁

2.2.7 spin_lock_irqsave:嵌套锁

2.2.8 spin_unlock_irqrestore:解锁

2.3 实例代码

3、信号量

3.1 信号量的概念

3.2 信号量的API

3.2.1 struct semaphore:定义信号量

3.2.2 sema_init:初始化信号量

3.2.3 down:上锁

3.2.4 dowm_interruptible:休眠时可以被信号打断

3.2.5 dowm_killable:休眠时可以被kill掉

3.2.6 dowm_trylock:尝试获取锁,获取不到也不会休眠

3.2.7 dowm_timeout:休眠时加上超时检测

3.2.8 up:解锁

3.3 实例代码

4、互斥体

4.1 互斥体的概念

4.2 互斥体的API

4.2.1 struct mutex lock:定义互斥体

4.2.2 mutex_init:初始化互斥体

4.2.3 mutex_lock:上锁

4.2.4 mutex_trylock:尝试获取锁,获取不到也不会休眠

4.2.5 mutex_unlock:解锁

4.3 实例代码

5、额外小知识

5.1 静态初始化与动态初始化

5.2 各对象的静态初始化 


1、原子操作

1.1 原子操作的概念

        在函数操作内部保证只有一个核心在操作这个原子变量,并且对这个变量的修改通过内联汇编完成了,省去中间的环节,可以把整个操作看成一个不可被分割的整体,这就是原子操作。原子操作本质就是一个变量,没有自旋或者休眠的特点。

1.2 原子操作的API

1.2.1 ATOMIC_INIT:定义并初始化原子变量

//配合 atomic_dec_and_test 使用
atomic_t lock = ATOMIC_INIT(1);  //定义并初始化原子变量

//配合 atomic_inc_and_test 使用
atomic_t lock = ATOMIC_INIT(-1);  //定义并初始化原子变量

1.2.2 atomic_dec_and_test:上锁(减去1后和0比较)

//减去1和0比较,如果结果为0表示获取锁成功了(成功返回真),否则获取锁失败了(失败返回假)
atomic_dec_and_test(&lock);

1.2.3 atomic_inc_and_test:上锁(加上1后和0比较)

//加1和0比较,如果结果为0表示获取锁成功了(成功返回真),否则获取锁失败了(失败返回假)
atomic_inc_and_test(&lock);

1.2.4 atomic_inc:解锁

atomic_inc(&lock);

1.2.5 atomic_dec:解锁

atomic_dec(&lock);

1.3 实例代码

#define CNAME "mycdev"
struct cdev* cdev;
int major = 0;
int minor = 0;
const int count = 3;
struct class* cls;
struct device* dev;
char kbuf[128] = { 0 };

//初始化
atomic_t lock = ATOMIC_INIT(1);

int mycdev_open(struct inode* inode, struct file* file)
{
    //上锁
    if (!atomic_dec_and_test(&lock)) {
        atomic_inc(&lock);
        return -EBUSY;
    }

    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    //校验大小
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    //完成数据传输
    ret = copy_to_user(ubuf, kbuf, size);

    return size;
}
ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    //校验大小
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    //完成数据传输
    ret = copy_from_user(kbuf, ubuf, size);

    return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
    //解锁
    atomic_inc(&lock);
    return 0;
}

const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};

static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();

    // 2.对象初始化
    cdev_init(cdev, &fops);

    // 3.申请设备号
    if (major > 0) {
        ret = register_chrdev_region(MKDEV(major, minor), count, CNAME);
    } else if (major == 0) {
        ret = alloc_chrdev_region(&devno, minor, count, CNAME);
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), count);

    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);

    for (i = 0; i < count; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "mycdev%d", i);
    }

    return 0; 
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < count; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), count);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);

2、自旋锁

2.1 自旋锁的概念

        内核当发生访问资源冲突的时候,可以有两种锁的解决方案选择:一个是原地等待,一个是挂起当前进程,调度其他进程执行(睡眠)。

         Spinlock 是内核中提供的一种比较常见的锁机制,自旋锁是“原地等待”的方式解决资源冲突的,即,一个线程获取了一个自旋锁后,另外一个线程期望获取该自旋锁,获取不到,只能够原地“打转”(忙等待)。

        自旋锁不会使线程状态发生切换一直处于用户态,即线程一直都是 active 的,不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。

自旋锁的特点:

1、忙等的过程是需要消耗cpu资源的

2、自旋锁保护的临界要尽可能的短,在临界区中不能够有延时,耗时,甚至休眠的动作。

另外在临界区中也不能够有copy_to_user/copy_form_user的调用

3、自旋锁会产生死锁(在同一个进程中多次获取同一把未解锁的锁)

4、自旋锁可以工作中断上下文

5、自旋锁在上锁前会关闭抢占

2.2 自旋锁的API

2.2.1 spinlock_t lock:定义自旋锁

spinlock_t lock;

2.2.2 spin_lock_init:初始化自旋锁

void spin_lock_init(spinlock_t *lock);

2.2.3 spin_lock:上锁

//会关闭抢占,但是不会关闭中断
void spin_lock(spinlock_t *lock);

2.2.4 spin_unlock:解锁

void spin_unlock(spinlock_t *lock);

2.2.5 spin_lock_irq:在中断中上锁

//既会关闭抢占,也会关闭中断,关闭当前核的中断
 void spin_lock_irq(spinlock_t *lock);

2.2.6 spin_unlock_irq:解锁

void spin_unlock_irq(spinlock_t *lock);

2.2.7 spin_lock_irqsave:嵌套锁

//在使用前保存中断的状态,如果之前中断是关闭的,解锁后中断也是关闭的,
//如果之前中断是打开的,解锁后中断还是打开的
//即如果在中断中,需要嵌套使用自旋锁,就用这个
void spin_lock_irqsave(spinlock_t *lock, unsigned long f) //嵌套的时候使用

2.2.8 spin_unlock_irqrestore:解锁

void spin_unlock_irqrestore(spinlock_t *lock, unsigned long f);

2.3 实例代码

#define CNAME "mycdev"
struct cdev *cdev;
int major = 0;
int minor = 0;
const int count=3;
struct class *cls;
struct device *dev;
char kbuf[128] = {0};
//1.定义自旋锁
spinlock_t lock;
int flags=0;

int mycdev_open(struct inode*inode, struct file*file)
{
    //上锁
    spin_lock(&lock);
    if(flags != 0){        
        //解锁
        spin_unlock(&lock);
        return -EBUSY;
    }
    flags=1;
    //解锁
    spin_unlock(&lock);

    return 0;
}
ssize_t mycdev_read(struct file*file, 
    char __user*ubuf, size_t size, loff_t*offs)
{
    int ret;
    //校验大小
    if(size>sizeof(kbuf)) size = sizeof(kbuf);
    //完成数据传输
    ret = copy_to_user(ubuf,kbuf,size);
    return size;
}
ssize_t mycdev_write(struct file*file, 
    const char __user*ubuf, size_t size, loff_t*offs)
{
    int ret;
    //校验大小
    if(size>sizeof(kbuf)) size = sizeof(kbuf);
    //完成数据传输
    ret = copy_from_user(kbuf,ubuf,size);
    return size;
}
int mycdev_close(struct inode*inode, struct file*file)
{
    //上锁
    spin_lock(&lock);
    flags=0;
    //解锁
    spin_unlock(&lock);
    return 0;
}

const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};

static int __init mycdev_init(void)
{
    int ret,i;
    dev_t devno;
    //1.分配对象
    cdev = cdev_alloc();

    //2.对象初始化
    cdev_init(cdev,&fops);

    //3.申请设备号
    if(major > 0){
        ret = register_chrdev_region(MKDEV(major,minor),count,CNAME);
    }else if(major == 0){
        ret = alloc_chrdev_region(&devno,minor,count,CNAME);
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    //4.注册
    ret = cdev_add(cdev,MKDEV(major,minor),count);

    //5.自动创建设备节点
    cls = class_create(THIS_MODULE,CNAME);

    for(i=0;i<count;i++){
        dev = device_create(cls,NULL,MKDEV(major,i),NULL,"mycdev%d",i);
    }

    //2.初始化自旋锁
    spin_lock_init(&lock);

    return 0; 
}
static void __exit mycdev_exit(void)
{
    int i;
    for(i=0;i<count;i++){
        device_destroy(cls,MKDEV(major,i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major,minor),count);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);

3、信号量

3.1 信号量的概念

        信号量是一个计数器,它用来记录对共享的存取状况,信号量是一种同步的机制,只有val初始化为1的时候才有互斥的效果。

信号量的特点:

1、信号量在获取不到资源的时候休眠

2、信号量保护的临界区可以很大,里面可以有延时,耗时,甚至休眠的操作

3、信号量不会产生死锁

4、信号量工作于进程上下文

5、信号量不会关闭抢占

3.2 信号量的API

3.2.1 struct semaphore:定义信号量

struct semaphore lock;

3.2.2 sema_init:初始化信号量

//初始化信号量,只有val初始化为1的时候才有互斥的效果。
//如果val初始化为0,同步机制
void sema_init(struct semaphore *sem, int val);

3.2.3 down:上锁

void down(struct semaphore *sem);

3.2.4 dowm_interruptible:休眠时可以被信号打断

int  down_interruptible(struct semaphore *sem);

3.2.5 dowm_killable:休眠时可以被kill掉

int  down_killable(struct semaphore *sem); 

3.2.6 dowm_trylock:尝试获取锁,获取不到也不会休眠

//获取到锁的时候返回0,失败返回1,即使没有资源也不会休眠
int  down_trylock(struct semaphore *sem);

3.2.7 dowm_timeout:休眠时加上超时检测

//如果在超时时间到了还没有获取到信号量,返回-ETIME错误码,否则返回0
int  down_timeout(struct semaphore *sem, long jiffies);

3.2.8 up:解锁

void up(struct semaphore *sem);

3.3 实例代码

#define CNAME "mycdev"
struct cdev *cdev;
int major = 0;
int minor = 0;
const int count=3;
struct class *cls;
struct device *dev;
char kbuf[128] = {0};

struct semaphore lock;

int mycdev_open(struct inode*inode, struct file*file)
{
    //上锁
    if(down_trylock(&lock)){
        return -EBUSY;
    }

    return 0;
}
ssize_t mycdev_read(struct file*file, 
    char __user*ubuf, size_t size, loff_t*offs)
{
    int ret;
    //校验大小
    if(size>sizeof(kbuf)) size = sizeof(kbuf);
    //完成数据传输
    ret = copy_to_user(ubuf,kbuf,size);
    return size;
}
ssize_t mycdev_write(struct file*file, 
    const char __user*ubuf, size_t size, loff_t*offs)
{
    int ret;
    //校验大小
    if(size>sizeof(kbuf)) size = sizeof(kbuf);
    //完成数据传输
    ret = copy_from_user(kbuf,ubuf,size);
    return size;
}
int mycdev_close(struct inode*inode, struct file*file)
{
    //解锁
    up(&lock);
    return 0;
}

const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};

static int __init mycdev_init(void)
{
    int ret,i;
    dev_t devno;
    //1.分配对象
    cdev = cdev_alloc();

    //2.对象初始化
    cdev_init(cdev,&fops);

    //3.申请设备号
    if(major > 0){
        ret = register_chrdev_region(MKDEV(major,minor),count,CNAME);
    }else if(major == 0){
        ret = alloc_chrdev_region(&devno,minor,count,CNAME);
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    //4.注册
    ret = cdev_add(cdev,MKDEV(major,minor),count);

    //5.自动创建设备节点
    cls = class_create(THIS_MODULE,CNAME);

    for(i=0;i<count;i++){
        dev = device_create(cls,NULL,MKDEV(major,i),NULL,"mycdev%d",i);
    }
    //初始化信号量
    sema_init(&lock,1);

    return 0;
}
static void __exit mycdev_exit(void)
{
    int i;
    for(i=0;i<count;i++){
        device_destroy(cls,MKDEV(major,i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major,minor),count);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);

4、互斥体

4.1 互斥体的概念

        在一个进程获取到互斥体的时候,另外一个进程也想获取这个互斥体此时,后一个进程处于休眠状态。

互斥体与信号量的区别:

        信号量是在并行处理环境中对多个处理器访问某个公共资源进行保护的机制, mutex 用于互斥操作。mutex 的语义相对于信号量要简单轻便一些,在锁争用激烈的测试场景下, mutex 比信号量执行速度更快,可扩展性更好,另外 mutex 数据结构的定义比信号量小。

互斥体的特点:

1、互斥体在获取不到资源的时候休眠

2、互斥体保护的临界区可以很大,里面可以有延时,耗时,甚至休眠的操作

3、互斥体不会产生死锁

4、互斥体工作于进程上下文

5、互斥体不会关闭抢占

6、互斥体在进入休眠前会稍微等一会,在对临界区比较小的临界资源保护的时候,互斥体的效率比信号量的高

互斥体使用的注意事项:

1、同一时刻只有一个线程可以持有 mutex 。
2、只有锁持有者可以解锁,不能在一个进程中持有 mutex ,在另外一个进程中释放他。
3、不允许递归地加锁和解锁。
4、当进程持有 mutex 时,进程不可以退出。
5、mutex 可以睡眠,所以不允许在中断处理程序或者中断下半部中使用,例如 tasklet 、定时器等。

4.2 互斥体的API

4.2.1 struct mutex lock:定义互斥体

struct mutex lock;

4.2.2 mutex_init:初始化互斥体

mutex_init(&lock);

4.2.3 mutex_lock:上锁

void mutex_lock(struct mutex *lock);

4.2.4 mutex_trylock:尝试获取锁,获取不到也不会休眠

//尝试获取互斥体,成功返回1,失败返回0
int mutex_trylock(struct mutex *lock); 

4.2.5 mutex_unlock:解锁

void mutex_unlock(struct mutex *lock);

4.3 实例代码

#define CNAME "mycdev"
struct cdev *cdev;
int major = 0;
int minor = 0;
const int count=3;
struct class *cls;
struct device *dev;
char kbuf[128] = {0};

struct mutex lock;

int mycdev_open(struct inode*inode, struct file*file)
{
    //上锁
    if(!mutex_trylock(&lock)){
        return -EBUSY;
    }

    return 0;
}
ssize_t mycdev_read(struct file*file, 
    char __user*ubuf, size_t size, loff_t*offs)
{
    int ret;
    //校验大小
    if(size>sizeof(kbuf)) size = sizeof(kbuf);
    //完成数据传输
    ret = copy_to_user(ubuf,kbuf,size);
    return size;
}
ssize_t mycdev_write(struct file*file, 
    const char __user*ubuf, size_t size, loff_t*offs)
{
    int ret;
    //校验大小
    if(size>sizeof(kbuf)) size = sizeof(kbuf);
    //完成数据传输
    ret = copy_from_user(kbuf,ubuf,size);
    return size;
}
int mycdev_close(struct inode*inode, struct file*file)
{
    解锁
    mutex_unlock(&lock);
    return 0;
}

const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};

static int __init mycdev_init(void)
{
    int ret,i;
    dev_t devno;
    //1.分配对象
    cdev = cdev_alloc();

    //2.对象初始化
    cdev_init(cdev,&fops);

    //3.申请设备号
    if(major > 0){
        ret = register_chrdev_region(MKDEV(major,minor),count,CNAME);
    }else if(major == 0){
        ret = alloc_chrdev_region(&devno,minor,count,CNAME);
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    //4.注册
    ret = cdev_add(cdev,MKDEV(major,minor),count);

    //5.自动创建设备节点
    cls = class_create(THIS_MODULE,CNAME);
    for(i=0;i<count;i++){
        dev = device_create(cls,NULL,MKDEV(major,i),NULL,"mycdev%d",i);
    }
    //初始化互斥体
    mutex_init(&lock);

    return 0;
}
static void __exit mycdev_exit(void)
{
    int i;
    for(i=0;i<count;i++){
        device_destroy(cls,MKDEV(major,i));
    }
    class_destroy(cls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major,minor),count);
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);

5、额外小知识

5.1 静态初始化与动态初始化

静态初始化:

        1、静态初始化互斥体时,在编译时就已经完成了初始化,不需要在运行时再进行初始化。

        2、这种方式下,互斥体的生命周期与程序的生命周期相同,无需手动释放。

        3、静态初始化的互斥体通常在全局范围内声明和使用。

动态初始化:

        1、动态初始化互斥体时,需要在运行时显式地初始化互斥体。

        2、在动态初始化的情况下,互斥体的生命周期需要手动管理,包括创建和销毁。

        3、动态初始化的互斥体通常在局部范围内声明和使用,以便于管理其生命周期。

5.2 各对象的静态初始化 

自旋锁;

        DEFINE_SPINLOCK(lock)

互斥体:

        DEFINE_MUTEX(mutexname)

信号量:

        DEFINE_SEMAPHORE(name)

读写信号量:

        DECLARE_RWSEM(name)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值