Linux驱动开发——并发和竞态(自旋锁方式的使用③)

简介: Linux驱动开发——并发和竞态(自旋锁方式的使用③)

文章目录

解决竞态引起异常的方法之自旋锁

自旋锁特点:

自旋锁解决竞态引起异常的编程步骤

示例:利用自旋锁来解决之前案例中的代码漏洞(之前按键触发的问题)

示例二:同时间只能一个进程操作LED设备(同前一篇使用屏蔽中断类似需求)

进阶(衍生自旋锁)

衍生自旋锁的特点

利用衍生自旋锁解决竞态引起异常的编程步骤

修改LED设备同一时间仅一个进程能够操作的示例:

总结


解决竞态引起异常的方法之自旋锁

自旋锁特点:

自旋锁:


  • 能够解决多核引起的竞态问题.
  • 能够解决进程与进程之间的抢占引起的竞态问题,
  • 自旋锁无法解决中断引起的竞态问题,自旋锁保护的临界区的代码执行速度要快,更不能进行进行休眠操作,没有获取自旋锁的任务将会原地忙等待(原地空转)。

Linux内核描述自旋锁的数据类型:spinlock_t


自旋锁解决竞态引起异常的编程步骤

  • 明确驱动代码中哪些是临界区。
  • 明确驱动代码中哪些是共享资源。
  • 明确临界区中是否有休眠操作,如果有,势必不考虑此方法;如果没有,还要考虑是否有中断参与;如果有中断参与,势必不考虑,如果没有中断参与,可以考虑使用。
  • 访问临界区之前先获取自旋锁。
//定义初始化一个自旋锁对象
spinlock_t lock; //定义
spin_lock_init(&lock); //初始化
spin_lock(&lock);
//获取自旋锁,如果获取成功,立马返回即可访问临界区。
//如果获取失败,任务将在此函数中进行循环忙等待,
//直到持有自旋锁的任务释放自旋锁。


  • 任务获取自旋锁以后,即可踏踏实实的访问临界区。
  • 访问临界区之后,记得要释放自旋锁。
spin_unlock(&lock);


  • 注意:获取自旋锁和释放自旋锁务必在逻辑上成对使用。

示例:利用自旋锁来解决之前案例中的代码漏洞(之前按键触发的问题)

  • mytimer_drv.c
/*************************************************************************
  > File Name: btn_drv.c
  > Author: 
  > Mail: 
  > Created Time: 2019年12月29日 星期日 09时08分30秒
 ************************************************************************/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/irq.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/input.h>
struct btn_resource{
    int gpio; //按键对应的GPIO
    char *name;//gpio Name
    int code;//按键值
};
struct led_resource{
    int gpio;
    char *name;
};
//定义吃时候按键对应的硬件信息
static struct btn_resource btn_info[] = {
    {
        .gpio = PAD_GPIO_A + 28,
        .name = "KEY_UP",
        .code = KEY_UP
    },
    {
        .gpio = PAD_GPIO_B + 9,
        .name = "KEY_DOWN",
        .code = KEY_DOWN
    }
};
static struct led_resource led_info[] ={
    {
        .gpio = PAD_GPIO_C + 12,
        .name = "LED1"
    },
    {
        .gpio = PAD_GPIO_C + 7,
        .name = "LED2"
    },
    {
        .gpio = PAD_GPIO_C + 11,
        .name = "LED3"
    },
    {
        .gpio = PAD_GPIO_B + 26,
        .name = "LED4"
    }
};
//记录按键的硬件信息
static struct btn_resource *pdata;
//定义定时器对象
static struct timer_list mytimer;
//定义定时器时间
static int mytimer_value = 5;
static int led_cmd_flag = 0;
//tasklet process mytimer_function led opts
static void timer_tasklet_function(unsigned long data)
{
    //open led or close led
    int i = 0;
    int *p_data = data;
    printk("%s : current timer value = %d, state = %d\n", __func__, mytimer_value, *p_data);
    if(*p_data)
    {
        *p_data = 0;
        for(i = 0; i < ARRAY_SIZE(led_info); i++)
        {
            gpio_set_value(led_info[i].gpio, 0);
        }
    }
    else
    {
        *p_data = 1;
        for(i = 0; i < ARRAY_SIZE(led_info); i++)
        {
            gpio_set_value(led_info[i].gpio, 1);
        }
    }
    printk("tasklet process : %s \n", __func__);
}
//定义初始化一个tasklet对象
static DECLARE_TASKLET(mytimer_tasklet, timer_tasklet_function, (unsigned long)&led_cmd_flag);
//定义初始化一个自旋锁对象
static spinlock_t mylock;
//定时器处理函数
//data = (unsigned long)&g_data
static void mytimer_function(unsigned long data)
{
    //登记底半部tasklet处理函数
    tasklet_schedule(&mytimer_tasklet);
    //add_timer(&mytimer);
    mod_timer(&mytimer, jiffies + mytimer_value * HZ);
    printk("schedule timer top process : %s \n", __func__);
}
//工作队列方式处理定时器延长或缩短
//work = &btn_work
static void btn_work_function(struct work_struct *work)
{
    switch(pdata->code)
    {
        case KEY_UP:
            //add expir timeout
            spin_lock(&mylock);
            if(mytimer_value <= 10)
                mytimer_value++;
    spin_unlock(&mylock);
            break;
        case KEY_DOWN:
            //submit expir timeout
            spin_lock(&mylock);
            if(mytimer_value > 0)
                mytimer_value--;
    spin_unlock(&mylock);
            break;
        default:
            printk("dev->code is not impire.\n");
    }
    printk("btn work function : %s \n", __func__);
}
//定义工作队列
static struct work_struct btn_work;
//中断处理函数
//不同的按键触发irq不同,KEY_UP对应irq = gpio_to_irq(GPIOA28)
//响应的参数不同,dev = &btn_info[0]或者dev = &btn_info[1]
static irqreturn_t button_isr(int irq, void *dev)
{
    //获取当前硬件信息
    pdata = (struct btn_resource *)dev;
    //登记工作队列
    schedule_work(&btn_work);
    //验证顶半部先执行
    printk("top func : %s mytimer_value = %d\n", __func__, mytimer_value);
    return IRQ_HANDLED;//返回执行成功
}
static int mytimer_init(void)
{
    int i;
  //初始化自旋锁
  spin_lock_init(&mylock);
    //吃时候定时器对象
    init_timer(&mytimer);
    //指定定时器超时时间
    mytimer.expires = jiffies + 5 * HZ;
    mytimer.function = mytimer_function;
    //指定参数
    mytimer.data = (unsigned long)&led_cmd_flag;
    add_timer(&mytimer);
    for(i = 0; i < ARRAY_SIZE(btn_info); i++)
    {
        int irq = gpio_to_irq(btn_info[i].gpio);
        gpio_request(btn_info[i].gpio, btn_info[i].name);
        //IRQF_TRIGGER_FALLING
        //IRQF_TRIGGER_RISING
        request_irq(irq, button_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, btn_info[i].name, &btn_info[i]);
    }
    for(i = 0; i < ARRAY_SIZE(led_info); i++)
    {
        gpio_request(led_info[i].gpio, led_info[i].name);
        gpio_direction_output(led_info[i].gpio, 0);
    }
    printk("led btn timer init...\n");
    //初始化工作队列
    INIT_WORK(&btn_work, btn_work_function);
    return 0;
}
static void mytimer_exit(void)
{
    int i;
    del_timer(&mytimer);
    for(i =0; i < ARRAY_SIZE(btn_info); i++)
    {
        int irq = gpio_to_irq(btn_info[i].gpio);
        gpio_free(btn_info[i].gpio);
        free_irq(irq, &btn_info[i]);
    }
    for(i = 0; i < ARRAY_SIZE(led_info); i++)
    {
        gpio_set_value(led_info[i].gpio, 1);
        gpio_free(led_info[i].gpio);
    }
    printk("led btn timer exit...\n");
}
module_init(mytimer_init);
module_exit(mytimer_exit);
MODULE_LICENSE("GPL");


  • Makefile
obj-m += mytimer_drv.o
all:
  make -C /opt/kernel SUBDIRS=$(PWD) modules
clean:
  make -C /opt/kernel SUBDIRS=$(PWD) clean


  • 执行结果


2019123122184129.png

2019123122184129.png

示例二:同时间只能一个进程操作LED设备(同前一篇使用屏蔽中断类似需求)

  • led_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
//共享资源
static int open_cnt = 1; //记录LED打开的状态开关
//定义自旋锁对象
static spinlock_t lock;
static int led_open(struct inode *inode, 
                        struct file *file)
{
    //获取自旋锁
    spin_lock(&lock);
    //临界区
    if(--open_cnt != 0) {
        printk("设备已被打开!\n");
        open_cnt++;
        //释放自旋锁
        spin_unlock(&lock);
        return -EBUSY;//返回设备忙错误码
    }
    //释放自旋锁
    spin_unlock(&lock);
    printk("设备打开成功!\n");
    return 0; //open返回成功
}
static int led_close(struct inode *inode, 
                        struct file *file)
{
    //获取自旋锁
    spin_lock(&lock);
    //临界区
    open_cnt++;
    //释放自旋锁
    spin_unlock(&lock);
    return 0;
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_close
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "myled",
    .fops = &led_fops
};
static int led_init(void)
{
    misc_register(&led_misc);
    //初始化自旋锁对象
    spin_lock_init(&lock);
    return 0;
}
static void led_exit(void)
{
    misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");


  • led_test.c
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main(void)
{
    int fd;
    fd = open("/dev/myled", O_RDWR);
    if (fd < 0) {
        printf("打开设备失败!\n");
        return -1;
    }
    sleep(100000000);
    close(fd);
    return 0;
}


  • Makefile
obj-m += led_drv.o
all:
  make -C /opt/kernel SUBDIRS=$(PWD) modules
clean:
  make -C /opt/kernel SUBDIRS=$(PWD) clean


  • 执行结果

20191231222752352.png

进阶(衍生自旋锁)

衍生自旋锁的特点

衍生自旋锁其实就是结合目前介绍的两种解决竞态问题的方法同时使用,即衍生自旋锁 = 屏蔽中断 + 自旋锁。

所以衍生自旋锁能够解决所有的竞态引起的异常问题。

衍生自旋锁保护的临界区的代码执行速度也必须要快且不能有休眠操作,因为在这段被保护的代码不仅屏蔽了中断而且还上了锁。当然同自旋锁一样,没有获取到自旋锁的任务将会原地忙等待(原地空转)。


Linux内核描述衍生自旋锁的数据类型其实也是:spinlock_t,区别就是在上锁的地方使用的不是之前的spin_lock接口而是上锁屏蔽中断接口:spin_lock_irqsave.


利用衍生自旋锁解决竞态引起异常的编程步骤

  1. 明确驱动代码中哪些是临界区。
  2. 明确驱动代码中哪些是共享资源。
  3. 明确临界区中是否有休眠操作,如果有,则不考虑使用衍生自旋锁,如果没有则可以考虑使用。
  4. 访问临界区之前需要获取自旋锁,方法如下:
//定义初始化一个衍生自旋锁对象
spinlock_t lock; //定义
spin_lock_init(&lock); //初始化
unsigned long flags;
spin_lock_irqsave(&lock, flags);
//屏蔽中断,获取衍生自旋锁,如果获取成功,立马返回即可访问临界区,如果获取失败,任务将在此函数中进行循环忙等待直到持有自旋锁的任务释放自旋锁

  1. 任务获取衍生自旋锁后,即可以踏踏实实的访问临界区。
  2. 访问临界区之后,记得要释放衍生自旋锁,恢复中断。
spin_unlock_irqrestore(&lock);

  1. 注意:获取衍生自旋锁和释放衍生自旋锁务必在逻辑上成对使用。

修改LED设备同一时间仅一个进程能够操作的示例:

  • led_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
//共享资源
static int open_cnt = 1; //记录LED打开的状态开关
//定义自旋锁对象
static spinlock_t lock;
static int led_open(struct inode *inode, 
                        struct file *file)
{
    unsigned long flags;
    //屏蔽中断,获取衍生自旋锁
    spin_lock_irqsave(&lock, flags);
    //临界区
    if(--open_cnt != 0) {
        printk("设备已被打开!\n");
        open_cnt++;
        //释放自旋锁,恢复中断
        spin_unlock_irqrestore(&lock, flags);
        return -EBUSY;//返回设备忙错误码
    }
    //释放自旋锁,恢复中断
    spin_unlock_irqrestore(&lock, flags);
    printk("设备打开成功!\n");
    return 0; //open返回成功
}
static int led_close(struct inode *inode, 
                        struct file *file)
{
    unsigned long flags;
    //屏蔽中断,获取衍生自旋锁
    spin_lock_irqsave(&lock, flags);
    //临界区
    open_cnt++;
    //释放自旋锁,恢复中断
    spin_unlock_irqrestore(&lock, flags);
    return 0;
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_close
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "myled",
    .fops = &led_fops
};
static int led_init(void)
{
    misc_register(&led_misc);
    //初始化自旋锁对象
    spin_lock_init(&lock);
    return 0;
}
static void led_exit(void)
{
    misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");


  • led_test.c(同上,不详写了)

  • Makefile(同上,不详写了)

  • 执行结果:


20191231225017443.png

20191231225017443.png

总结

本篇介绍了Linux内核解决竞态引起的异常处理中的自旋锁方法,但是由于自旋锁不能够避免中断引起的竞态问题,所以内核给出了衍生自旋锁的解决方法,也就是将屏蔽中断和自旋锁组合起来,这样就可以解决多核、中断、多任务等各种情况下的竞态问题了。(在被保护的临界区内还是不能有休眠操作),所以当我们在面对全局变量、共享资源等情况下首先应该优先采取的方案也就是目前看到的最好的方法——衍生自旋锁。


  • 当然,后面还会提到剩下的两种方法——信号量和原子操作。


相关文章
|
1月前
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
45 5
|
1月前
|
Linux 数据库
Linux内核中的锁机制:保障并发操作的数据一致性####
【10月更文挑战第29天】 在多线程编程中,确保数据一致性和防止竞争条件是至关重要的。本文将深入探讨Linux操作系统中实现的几种关键锁机制,包括自旋锁、互斥锁和读写锁等。通过分析这些锁的设计原理和使用场景,帮助读者理解如何在实际应用中选择合适的锁机制以优化系统性能和稳定性。 ####
61 6
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
114 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
3月前
|
存储 Linux 开发工具
如何进行Linux内核开发【ChatGPT】
如何进行Linux内核开发【ChatGPT】
|
4月前
|
Java Linux API
Linux设备驱动开发详解2
Linux设备驱动开发详解
56 6
|
4月前
|
消息中间件 算法 Unix
Linux设备驱动开发详解1
Linux设备驱动开发详解
61 5
|
4月前
|
移动开发 监控 网络协议
在Linux中,如何查看 http 的并发请求数与其 TCP 连接状态?
在Linux中,如何查看 http 的并发请求数与其 TCP 连接状态?
|
4月前
|
网络协议 Linux
在Linux中,如何查看 http 的并发请求数与其 TCP 连接状态?
在Linux中,如何查看 http 的并发请求数与其 TCP 连接状态?
|
1月前
|
Linux 网络安全 数据安全/隐私保护
Linux 超级强大的十六进制 dump 工具:XXD 命令,我教你应该如何使用!
在 Linux 系统中,xxd 命令是一个强大的十六进制 dump 工具,可以将文件或数据以十六进制和 ASCII 字符形式显示,帮助用户深入了解和分析数据。本文详细介绍了 xxd 命令的基本用法、高级功能及实际应用案例,包括查看文件内容、指定输出格式、写入文件、数据比较、数据提取、数据转换和数据加密解密等。通过掌握这些技巧,用户可以更高效地处理各种数据问题。
106 8
|
1月前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
338 6