linux驱动开发-7_并发与竞争_原子操作

个人笔记

并发是指在同一时间段内,多个任务或操作在同一个系统中同时进行。这并不意味着这些任务同时发生在物理上,而是在逻辑上同时发生。在计算机系统中,通常有多个任务或操作同时执行,这样可以提高系统的吞吐量和效率。

Linux 系统是个多任务操作系统,会存在多个任务同时访问同一片内存区域,这些任务可
能会相互覆盖这段内存中的数据,造成内存数据混乱。针对这个问题必须要做处理,严重的话
可能会导致系统崩溃。现在的 Linux 系统并发产生的原因很复杂,总结一下有下面几个主要原
因:
①、多线程并发访问,Linux 是多任务(线程)的系统,所以多线程访问是最基本的原因。
②、抢占式并发访问,从 2.6 版本内核开始,Linux 内核支持抢占,也就是说调度程序可以
在任意时刻抢占正在运行的线程,从而运行其他的线程。
③、中断程序并发访问,这个无需多说,学过 STM32 的同学应该知道,硬件中断的权利可
是很大的。
④、SMP(多核)核间并发访问,现在 ARM 架构的多核 SOC 很常见,多核 CPU 存在核间并
发访问。

为了避免这一情况发生,我们就必须在编写驱动前考虑这一情况并且把数据保护起来。就有了原子操作、互斥锁、自旋锁和信号量等操作。

1.原子操作

原子操作是指在执行过程中不会被中断的操作,要么完全执行,要么完全不执行,不存在执行过程中被中断的情况。在多线程或并发编程中,原子操作是指不可分割的操作,它们能够保证在多线程环境下对共享数据的访问操作是线程安全的,不会发生竞态条件。

ATOMIC_INIT(int 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_dec_return(atomic_t *v) 从 v 减 1,并且返回 v 的值。
int atomic_inc_return(atomic_t *v) 给 v 加 1,并且返回 v 的值。
int atomic_sub_and_test(int i, atomic_t *v) 从 v 减 i,如果结果为 0 就返回真,否则返回假
int atomic_dec_and_test(atomic_t *v) 从 v 减 1,如果结果为 0 就返回真,否则返回假
int atomic_inc_and_test(atomic_t *v) 给 v 加 1,如果结果为 0 就返回真,否则返回假
int atomic_add_negative(int i, atomic_t *v) 给 v 加 i,如果结果为负就返回真,否则返回假

void set_bit(int nr, void *p) 将 p 地址的第 nr 位置 1。 
void change_bit(int nr, void *p) 将 p 地址的第 nr 位进行翻转。

int test_bit(int nr, void *p) 获取 p 地址的第 nr 位的值。

int test_and_set_bit(int nr, void *p) 将 p 地址的第 nr 位置 1,并且返回 nr 位原来的值。

int test_and_clear_bit(int nr, void *p) 将 p 地址的第 nr 位清零,并且返回 nr 位原来的值。

int test_and_change_bit(int nr, void *p) 将 p 地址的第 nr 位翻转,并且返回 nr 位原来的

2.自旋锁

自旋锁是一种线程同步的机制,用于在多线程环境中保护共享资源,确保同一时间只有一个线程能够访问共享资源。与传统的互斥锁不同,自旋锁不会使线程在无法获取锁时进入睡眠或阻塞状态,而是通过不断地循环检测锁的状态,直到获取到锁为止,因此称为“自旋”

自旋锁会一直等待进程结束会消耗大量的资源。所以只适合短时间的进程,时间长的不适合用进程锁。

在自旋锁锁上了之后,中断是可以打断的,比如某一进程正在运行,自旋锁已经锁上了,然后中断又来了,中断又开了一个锁。他们两个都锁上了。而中断会抢夺cpu的使用权,但是他又没有锁的使用权。他就会原地自旋等待某进程结束,然而进程有没有cpu的使用权,他们两个就这样一直等着,卡死在这里了。

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)将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。

DEFINE_SPINLOCK(lock) /* 定义并初始化一个锁 */
/* 线程 A */
void functionA (){
    unsigned long flags; /* 中断状态 */
    spin_lock_irqsave(&lock, flags) /* 获取锁 */    
    /* 临界区 */
    spin_unlock_irqrestore(&lock, flags) /* 释放锁 */
}
/* 中断服务函数 */
void irq() {
    spin_lock(&lock) /* 获取锁 */
    /* 临界区 */
    spin_unlock(&lock) /* 释放锁 */
}

3.信号量

信号量是一种用于线程同步与互斥的机制,用于控制对共享资源的访问。它是由一个整型变量和两个原子操作组成的。

相比于自旋锁,信号量可以使线程进入休眠状态,比如 A 与 B、C 合租了一套房子,这个
房子只有一个厕所,一次只能一个人使用。某一天早上 A 去上厕所了,过了一会 B 也想用厕
所,因为 A 在厕所里面,所以 B 只能等到 A 用来了才能进去。B 要么就一直在厕所门口等着,
等 A 出来,这个时候就相当于自旋锁。B 也可以告诉 A,让 A 出来以后通知他一下,然后 B 继
续回房间睡觉,这个时候相当于信号量。可以看出,使用信号量会提高处理器的使用效率,毕
竟不用一直傻乎乎的在那里“自旋”等待。但是,信号量的开销要比自旋锁大,因为信号量使
线程进入休眠状态以后会切换线程,切换线程就会有开销。总结一下信号量的特点:
①、因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场
合。
②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换
线程引起的开销要远大于信号量带来的那点优势

DEFINE_SEAMPHORE(name) 定义一个信号量,并且设置信号量的值为 1。
void sema_init(struct semaphore *sem, int val) 初始化信号量 sem,设置信号量值为 val。
void down(struct semaphore *sem)获取信号量,因为会导致休眠,因此不能在中断中使用。
int down_trylock(struct semaphore *sem);尝试获取信号量,如果能获取到信号量就获取,并且返回 0。如果不能就返回非 0,并且不会进入休眠。
int down_interruptible(struct semaphore *sem)获取信号量,和 down 类似,只是使用 down 进
入休眠状态的线程不能被信号打断。而使用此函数进入休眠以后是可以被信号打断的。
void up(struct semaphore *sem) 释放信号量

实验:

还是用点灯。

取用之前写过的 gpioled 驱动,在设备结构体中添加一个原子变量

struct gpioled_dev{
    dev_t devid;
    struct cdev cdev;
    int major;
    int minor;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int led_gpio;

    atomic_t lock; //原子操作
};

要使用这个的先在函数入口处初始化:

static int __init gpioled_init(void)
{
    int ret = 0;

    //初始化原子量
    atomic_set(&gpioled.lock, 1);
    //注册字符设备
    gpioled.major = 0;
    if(gpioled.major){
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    }else{
        ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    printk("maj:%d   min:%d\r\n", gpioled.major, gpioled.minor);
    if(ret < 0){
        goto fail_devid;
    }

    //初始化 dev
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &filp);
    //cdev 添加到设备
    ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    if(ret < 0){
        goto fail_cdev;
    }

    //创建设备节点
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if(IS_ERR(gpioled.class)){
        ret = PTR_ERR(gpioled.class);
        goto fail_class;
    }

    //创建设备
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if(IS_ERR(gpioled.device)){
        ret = PTR_ERR(gpioled.device);
        goto fail_device;
    }
    
    //获取设备节点
    gpioled.nd = of_find_node_by_path("/gpioled");
    if(gpioled.nd == NULL){
        goto fail_node;
    }
    //获取led的对应的gpio
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"led-gpios",0);
    if(gpioled.led_gpio < 0){
        printk("find errir\r\n");
        ret = -EINVAL;
        goto fail_find_node;
    }
    printk("led gpio num = %d\r\n",gpioled.led_gpio);

    //申请io 申请了才能使用
    ret = gpio_request(gpioled.led_gpio, "led-gpio");
    if(ret){
        printk("gpio_request error\r\n");
        ret = -EINVAL;
        goto fail_quest;
    }
    //使用io
    ret = gpio_direction_output(gpioled.led_gpio, 1);//设置为灭 0 亮
    if(ret){
        goto fail_io;
    }
    gpio_direction_output(gpioled.led_gpio, 0);
    return ret;

fail_io:
    gpio_free(gpioled.led_gpio);
fail_quest:
fail_find_node:
fail_node:
    device_destroy(gpioled.class, gpioled.devid);
fail_device:
    class_destroy(gpioled.class);
fail_class:
    cdev_del(&gpioled.cdev);
fail_cdev:
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
fail_devid:
    return ret;
}

在函数被调用的时候,我们就得把这个锁给打开。

static int gpioled_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &gpioled;
    //判断lock
    if(!atomic_dec_and_test(&gpioled.lock)){
        atomic_inc(&gpioled.lock);
        return -EBUSY;
    }
#if 0
    if(atomic_read(&gpioled.lock) <= 0){
        return -EBUSY;
    }else{
        atomic_dec(&gpioled.lock);
    }
#endif
    return 0;

}

在函数结束时,我们就要释放锁。

static int gpioled_release(struct inode *inode, struct file *filp)
{
    struct gpioled_dev *dev = filp->private_data;
    atomic_inc(&dev->lock); //加一释放驱动

    return 0;
}

为了明显观测到这一现象,我们需要将程序空转个几秒,不然程序一下就执行完了看不到现象。

atomicApp.c                       使用 arm-linux-guneabihf-gcc atomicApp.c -o atomicApp编译

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define LEDOFF  0
#define LEDON   1

int main(int argc,char *argv[])
{
    int cnt;
    int fd;
    int ret;
    char *filename;
    unsigned char databuff[1];
    if(argc != 3){
        printf("data deficiency\r\n");
    }
    filename = argv[1];

    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("file open errer\r\n");
        return -1;
    }
    databuff[0] = atoi(argv[2]);

    ret = write(fd,databuff,sizeof(databuff));
    if(ret < 0){
        printf("control failed\r\n");
        close(fd);
        return -1;
    }
    while(1){
        sleep(5);
        cnt++;
        printf("APP RUNING TIME:%d\r\n",cnt);
        if(cnt >= 5){
            break;
        }
    }
    printf("APP OVER\r\n");
    close(fd);
    return 0;
}

效果:

先 depmod 刷新,然后modprobe atomic.ko 注册驱动

执行atomicApp文件 1 表示关灯 & 表示在后台运行。

这里调用 函数一次后 在25秒的运行时间内不可以再次调用,文件打开失败。

接着是自旋锁的:(互斥体,信号量都在里面)

驱动文件

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/atomic.h>

#define GPIOLED_CNT     1
#define GPIOLED_NAME    "gpioled"

#define LEDOFF          1
#define LEDON           0

//gpio 设备结构体
struct gpioled_dev{
    dev_t devid;
    struct cdev cdev;
    int major;
    int minor;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int led_gpio;
/*
    int dev_status; //0 表示设备可以使用 大于 1 表示不可以使用 自旋锁
    spinlock_t lock;
*/
 /*   //信号量
    struct semaphore sem;
*/
    //互斥体
    struct mutex lock;
};

struct gpioled_dev gpioled;

static int gpioled_open(struct inode *inode, struct file *filp)
{
 //   unsigned long irqflag;
    filp->private_data = &gpioled;
 /*   //加锁保护变量
    //spin_lock(&gpioled.lock);
    spin_lock_irqsave(&gpioled.lock, irqflag);
    if(gpioled.dev_status){//驱动不能使用
        spin_unlock(&gpioled.lock);
        return -EBUSY;
    }
    gpioled.dev_status++;//表示不能使用
   // spin_unlock(&gpioled.lock);
    spin_unlock_irqrestore(&gpioled.lock, irqflag);
*/

/*
    down(&gpioled.sem);//获取信号量
*/

    mutex_lock(&gpioled.lock);
    return 0;

}

static int gpioled_release(struct inode *inode, struct file *filp)
{
//   unsigned long irqflag;
    struct gpioled_dev *dev = filp->private_data;
/*
    //spin_lock(&dev->lock);
    spin_lock_irqsave(&dev->lock, irqflag);
    if(dev->dev_status){
        dev->dev_status--;//标记驱动可以使用
    }
    //spin_unlock(&dev->lock);
    spin_unlock_irqrestore(&dev->lock, irqflag);
*/

/*
    up(&dev->sem);
*/

    mutex_unlock(&dev->lock);
    return 0;
}

static ssize_t gpioled_write(struct file *filp, const  char __user *buf, size_t count, loff_t *ppos)
{
    int ret;
    unsigned char data[1];
    struct gpioled_dev *dev = filp->private_data;
    ret = copy_from_user(data, buf, count);
    if(ret < 0){
        printk("write error\r\n");
        return ret;
    }
    if(data[0] == LEDON){
        gpio_set_value(dev->led_gpio, LEDON);
    }else if(data[0] == LEDOFF){
        gpio_set_value(dev->led_gpio, LEDOFF);
    }

    return 0;
}

static const struct file_operations filp = {
    .owner = THIS_MODULE,
    .open = gpioled_open,
    .release = gpioled_release,
    .write = gpioled_write,
};
//入口出口
static int __init gpioled_init(void)
{
    int ret = 0;
//  初始化互斥体
    mutex_init(&gpioled.lock);

/*  //初始化信号量
    sema_init(&gpioled.sem, 1);
*/

    //初始化自旋锁
 //   spin_lock_init(&gpioled.lock);
//    gpioled.dev_status = 0;
    //注册字符设备
    gpioled.major = 0;
    if(gpioled.major){
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    }else{
        ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    printk("maj:%d   min:%d\r\n", gpioled.major, gpioled.minor);
    if(ret < 0){
        goto fail_devid;
    }

    //初始化 dev
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &filp);
    //cdev 添加到设备
    ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    if(ret < 0){
        goto fail_cdev;
    }

    //创建设备节点
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if(IS_ERR(gpioled.class)){
        ret = PTR_ERR(gpioled.class);
        goto fail_class;
    }

    //创建设备
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if(IS_ERR(gpioled.device)){
        ret = PTR_ERR(gpioled.device);
        goto fail_device;
    }
    
    //获取设备节点
    gpioled.nd = of_find_node_by_path("/gpioled");
    if(gpioled.nd == NULL){
        goto fail_node;
    }
    //获取led的对应的gpio
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"led-gpios",0);
    if(gpioled.led_gpio < 0){
        printk("find errir\r\n");
        ret = -EINVAL;
        goto fail_find_node;
    }
    printk("led gpio num = %d\r\n",gpioled.led_gpio);

    //申请io 申请了才能使用
    ret = gpio_request(gpioled.led_gpio, "led-gpio");
    if(ret){
        printk("gpio_request error\r\n");
        ret = -EINVAL;
        goto fail_quest;
    }
    //使用io
    ret = gpio_direction_output(gpioled.led_gpio, 1);//设置为灭 0 亮
    if(ret){
        goto fail_io;
    }
    gpio_direction_output(gpioled.led_gpio, 0);
    return ret;

fail_io:
    gpio_free(gpioled.led_gpio);
fail_quest:
fail_find_node:
fail_node:
    device_destroy(gpioled.class, gpioled.devid);
fail_device:
    class_destroy(gpioled.class);
fail_class:
    cdev_del(&gpioled.cdev);
fail_cdev:
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
fail_devid:
    return ret;
}

static void __exit gpioled_exit(void)
{
    gpio_direction_output(gpioled.led_gpio, 1);
    //注销字符驱动
    cdev_del(&gpioled.cdev);
    //删除设备号
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
    //摧毁驱动
    device_destroy(gpioled.class, gpioled.devid);
    //摧毁类
    class_destroy(gpioled.class);
    //销毁io
    gpio_free(gpioled.led_gpio);
}
//设备注册与卸载
module_init(gpioled_init);
module_exit(gpioled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lakzhu");

软件:spinlockApp.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define LEDOFF  0
#define LEDON   1

int main(int argc,char *argv[])
{
    int cnt;
    int fd;
    int ret;
    char *filename;
    unsigned char databuff[1];
    if(argc != 3){
        printf("data deficiency\r\n");
    }
    filename = argv[1];

    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("file open errer\r\n");
        return -1;
    }
    databuff[0] = atoi(argv[2]);

    ret = write(fd,databuff,sizeof(databuff));
    if(ret < 0){
        printf("control failed\r\n");
        close(fd);
        return -1;
    }
    while(1){
        sleep(5);
        cnt++;
        printf("APP RUNING TIME:%d\r\n",cnt);
        if(cnt >= 5){
            break;
        }
    }
    printf("APP OVER\r\n");
    close(fd);
    return 0;
}

他们都能够让程序自动排队,一个执行完另外一个就可以接着运行。

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值