驱动-自旋锁

前面原子操作进行了讲解, 并使用原子整形操作对并发与竞争实验进行了改进,但是原子操作只能对整形变量或者位进行保护, 而对于结构体或者其他类型的共享资源, 原子操作就力不从心了, 这时候就轮到自旋锁的出场了。

两个 app 应用程序之间对共享资源的竞争访问引起了数据传输错误, 而在 Linux 内核中, 提供了四种处理并发与竞争的常见方法:
分别是原子操作、 自旋锁、 信号量、 互斥体, 这里了解下原子操作


自旋锁概念

自旋锁是一种低级的同步原语,用于在多处理器系统中保护共享资源。与互斥锁不同,当一个线程尝试获取已被占用的自旋锁时,它不会阻塞或睡眠,而是会在一个循环中不断检查锁的状态(即"自旋"),直到锁变为可用状态。

工作原理

  • 尝试获取锁:线程尝试通过原子操作获取锁

  • 锁可用:获取成功,线程继续执行临界区代码

  • 锁被占用:线程进入忙等待循环,不断检查锁状态

  • 锁释放:当锁持有者释放锁后,等待线程可以获取锁

特点

  • 忙等待:不放弃CPU,持续检查锁状态

  • 短临界区:适合保护执行时间很短的代码段

  • 无上下文切换:避免了线程切换的开销

  • 多处理器有效:在单处理器系统上可能浪费CPU周期

常用API

#include <linux/spinlock.h>

// 定义和初始化
spinlock_t my_lock;
spin_lock_init(&my_lock);

// 获取锁
spin_lock(&my_lock);       // 获取锁,禁用本地 CPU 中断
spin_lock_irq(&my_lock);   // 获取锁并禁用硬件中断
spin_lock_irqsave(&my_lock, flags); // 保存中断状态并获取锁

// 释放锁
spin_unlock(&my_lock);     // 释放锁
spin_unlock_irq(&my_lock); // 释放锁并恢复中断
spin_unlock_irqrestore(&my_lock, flags); // 释放锁并恢复之前的中断状态

// 尝试获取锁
int spin_trylock(&my_lock); // 非阻塞尝试获取锁,成功返回非零

参考资料

接下来还是以前面字符设备 动态参数传递实验为基础,打开访问字符设备实验。 所以以前知识点 建议了解
在字符设备这块内容,所有知识点都是串联起来的,需要整体来理解,缺一不可,建议多了解一下基础知识
驱动-申请字符设备号
驱动-注册字符设备
驱动-创建设备节点
驱动-字符设备驱动框架
驱动-杂项设备
驱动-内核空间和用户空间数据交换
驱动-文件私有数据
Linux驱动之 原子操作
Linux驱动—原子操作

驱动-原子操作实验

实验源码 spinlock.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include <linux/errno.h>

 
static spinlock_t spinlock_test;//定义spinlock_t类型自旋变量
static  int flag=1;
 static int open_test(struct  inode  *inode,struct file *file)
 {

    spin_lock(&spinlock_test); //自旋枷锁
    if(flag!=1){  //判断标志位flag 的值是否等于1 
        spin_unlock(&spinlock_test); 
        return -EBUSY;
    }
    flag=0; //将标志位的值设置为0
    spin_unlock(&spinlock_test);   //自旋锁解锁
    printk("\n this is open_test \n");
    return 0;
 };
 

static ssize_t read_test(struct file *file,char __user *ubuf,size_t len,loff_t *off)
{
	int ret;
	char kbuf[10] = "topeet";//定义char类型字符串变量kbuf
	printk("\nthis is read_test \n");
	ret = copy_to_user(ubuf,kbuf,strlen(kbuf));//使用copy_to_user接收用户空间传递的数据
	if (ret != 0){
		printk("copy_to_user is error \n");
	}
	printk("copy_to_user is ok \n");
	return 0;
}
static char kbuf[10] = {0};//定义char类型字符串全局变量kbuf
static ssize_t write_test(struct file *file,const char __user *ubuf,size_t len,loff_t *off)
{
	int ret;
	ret = copy_from_user(kbuf,ubuf,len);//使用copy_from_user接收用户空间传递的数据
	if (ret != 0){
		printk("copy_from_user is error\n");
	}
	if(strcmp(kbuf,"topeet") == 0 ){//如果传递的kbuf是topeet就睡眠四秒钟
		ssleep(4);
	}
	else if(strcmp(kbuf,"itop") == 0){//如果传递的kbuf是itop就睡眠两秒钟
		ssleep(2);
	}
	printk("copy_from_user buf is %s \n",kbuf);
	return 0;
}
static int release_test(struct inode *inode,struct file *file)
{
	//printk("\nthis is release_test \n");
    spin_lock(&spinlock_test);//自旋锁加锁
	flag = 1;
	spin_unlock(&spinlock_test);//自旋锁解锁	
    return 0;
}

struct chrdev_test
{
   dev_t  dev_num;  //定义dev_t类型变量来表示设备号
   int major,minor; //定义int 类型的主设备号和次设备号
   struct cdev cdev_test;   //定义字符设备
   struct class *class_test;   //定义结构体变量class 类
};

struct chrdev_test dev1; //创建chardev_test类型结构体变量




static struct file_operations fops_test = {
	.owner=THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
    .open = open_test,//将open字段指向chrdev_open(...)函数
    .read = read_test,//将open字段指向chrdev_read(...)函数
    .write = write_test,//将open字段指向chrdev_write(...)函数
    .release = release_test,//将open字段指向chrdev_release(...)函数
};//定义file_operations结构体类型的变量cdev_test_ops




static int __init chrdev_fops_init(void)//驱动入口函数
{

     
    if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0){
            printk("alloc_chrdev_region is error\n");
    }   
        printk("alloc_chrdev_region is ok\n");
        dev1.major=MAJOR(dev1.dev_num);//通过MAJOR()函数进行主设备号获取
        dev1.minor=MINOR(dev1.dev_num);//通过MINOR()函数进行次设备号获取
        printk("major is %d\n",dev1.major);
        printk("minor is %d\n",dev1.minor);
 
        ////使用cdev_init()函数初始化cdev_test结构体,并链接到cdev_test_ops结构体
        cdev_init(&dev1.cdev_test,&fops_test);
        dev1.cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块 

        cdev_add(&dev1.cdev_test,dev1.dev_num,1);
    printk("cdev_add is ok\n");
    dev1.class_test  = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_test
    device_create(dev1.class_test,NULL,dev1.dev_num,NULL,"device_test");//使用device_create进行设备的创建,设备名称为device_test

    return 0;
}
static void __exit chrdev_fops_exit(void)//驱动出口函数
{
    cdev_del(&dev1.cdev_test);//使用cdev_del()函数进行字符设备的删除
    unregister_chrdev_region(dev1.dev_num,1);//释放字符驱动设备号 
    device_destroy(dev1.class_test,dev1.dev_num);//删除创建的设备
    class_destroy(dev1.class_test);//删除创建的类
    printk("module exit \n");

}
module_init(chrdev_fops_init);//注册入口函数
module_exit(chrdev_fops_exit);//注册出口函数
MODULE_LICENSE("GPL v2");//同意GPL开源协议
MODULE_AUTHOR("wang fang chen "); //作者信息

部分源码解读

  • 上面测试源码demo,完全还是基于之前字符设备的一套,和 原子操作实验代码完全一样,唯一区别是没有用原子操作来防止竞争,用了自旋锁来防止资源竞争
  • 可以简要看一下 自旋锁这里是一堆一堆出现的。 spin_lock(&spinlock_test); //自旋枷锁 spin_unlock(&spinlock_test); //自旋锁解锁
    就是枷锁 设置flag,然后解锁。 恢复flag 状态时候 就枷锁->设置flag 的值->解锁。

Makefile 编译文件

#!/bin/bash
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
obj-m += spinlock.o
KDIR :=/home/wfc123/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
	make -C $(KDIR) M=$(PWD) modules

clean:
	make -C $(KDIR) M=$(PWD) clean


测试程序 app.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
     
int main(int argc,char *argv[])
{
    int fd;//定义int类型的文件描述符
    char str1[10]={0};//定义读取缓冲区str1
    fd=open(argv[1],O_RDWR,0666);//调用open函数,打开输入的第一个参数文件,权限为可读可写
    //fd=open("/dev/device_test",O_RDWR,0666);//调用open函数,打开输入的第一个参数文件,权限为可读可写
    if(fd<0){
        printf("open is error\n");
        return -1;
    }
    printf("open is ok\n");
    if(strcmp(argv[2],"topeet")==0){
     write(fd,"topeet",sizeof(str1)); 
    }else if(strcmp(argv[2],"itop")==0)
    {
             write(fd,"itop",sizeof(str1)); 
    }
    

    close(fd);//调用close函数,对取消文件描述符到文件的映射
    return 0;
}


编译 测试程序 app


aarch64-linux-gnu-gcc -o app app.c -static

加载驱动 insmod

执行命令,测试结果如下:
在这里插入图片描述

查看 dev 下生成的字符设备

ls /dev/device_test
[root@topeet:/mnt/sdcard]# ls /dev/device_test
/dev/device_test
[root@topeet:/mnt/sdcard]#

./app 执行程序 验证

root@topeet:/mnt/sdcard]# ./app /dev/device_test topeet &
de[root@topeet:/mnt/sdcard]# ./app /dev/device_test itopopen is ok

open is error

这个就验证了 自旋锁 起作用了的,保护了 变量 flag

总结

这里简单介绍了自旋锁的应用场景,需要了解原理和使用api 即可。

资源下载链接为: https://pan.quark.cn/s/22ca96b7bd39 在当今的软件开发领域,自动化构建与发布是提升开发效率和项目质量的关键环节。Jenkins Pipeline作为一种强大的自动化工具,能够有效助力Java项目的快速构建、测试及部署。本文将详细介绍如何利用Jenkins Pipeline实现Java项目的自动化构建与发布。 Jenkins Pipeline简介 Jenkins Pipeline是运行在Jenkins上的一套工作流框架,它将原本分散在单个或多个节点上独立运行的任务串联起来,实现复杂流程的编排与可视化。它是Jenkins 2.X的核心特性之一,推动了Jenkins从持续集成(CI)向持续交付(CD)及DevOps的转变。 创建Pipeline项目 要使用Jenkins Pipeline自动化构建发布Java项目,首先需要创建Pipeline项目。具体步骤如下: 登录Jenkins,点击“新建项”,选择“Pipeline”。 输入项目名称和描述,点击“确定”。 在Pipeline脚本中定义项目字典、发版脚本和预发布脚本。 编写Pipeline脚本 Pipeline脚本是Jenkins Pipeline的核心,用于定义自动化构建和发布的流程。以下是一个简单的Pipeline脚本示例: 在上述脚本中,定义了四个阶段:Checkout、Build、Push package和Deploy/Rollback。每个阶段都可以根据实际需求进行配置和调整。 通过Jenkins Pipeline自动化构建发布Java项目,可以显著提升开发效率和项目质量。借助Pipeline,我们能够轻松实现自动化构建、测试和部署,从而提高项目的整体质量和可靠性。
### Linux驱动程序中自旋锁的使用方法及注意事项 #### 自旋锁的基本概念 自旋锁是一种用于多处理器环境下的同步原语,其主要作用是在多个CPU核心之间提供互斥访问共享资源的能力。当一个线程尝试获取已被占用的自旋锁时,该线程不会进入休眠状态而是不断循环检查直到获得锁为止[^1]。 #### 工作原理 一旦某个CPU获得了自旋锁,则其他试图获取同一把锁的CPU将会不停地执行空转操作(即“旋转”),直至前一个持有者释放了这把锁。这种方式适用于预期等待时间非常短暂的情况,在这种情况下让CPU保持忙碌可能比将其置于睡眠更有效率[^2]。 #### API详解 以下是几个常用的与自旋锁有关的操作函数: - `spin_lock_init(&my_lock);` 初始化一个新的静态定义好的自旋锁变量。 - `spin_lock(&my_lock);` 获取指定名称为`&my_lock` 的自旋锁;如果此时已经有另一个CPU正在占有此锁,则当前调用方会持续轮询直到成功取得锁定权。 - `spin_unlock(&my_lock);` 解除对给定名为`&my_lock` 的自旋锁的所有权,并允许任何等待中的其他CPU去竞争这个锁。 - `spin_trylock(&my_lock);` 尝试不阻塞地获取自旋锁,返回值表示是否成功拿到了锁。 - `spin_is_locked(&my_lock);` 判断某特定自旋锁现在是不是已经被加锁的状态。 ```c // 定义并初始化一个自旋锁 spinlock_t my_lock; spin_lock_init(&my_lock); void critical_section(void){ unsigned long flags; spin_lock_irqsave(&my_lock, flags); // 执行临界区内代码 spin_unlock_irqrestore(&my_lock, flags); } ``` 上述例子展示了如何在一个中断安全的方式下调用自旋锁来保护一段临界区内的代码。这里使用了两个特殊的宏——`spin_lock_irqsave()` 和 `spin_unlock_irqrestore()`, 这样做可以在禁用本地中断的同时确保即使发生异常情况也能恢复原始中断设置[^3]。 #### 注意事项 - **不可长时间持有**:由于自旋锁会导致未获锁的任务一直运行而浪费CPU周期,因此只应在预计很快就能完成的小范围代码片段里使用它们。 - **避免死锁风险**:设计应用程序逻辑时要特别小心处理嵌套或交叉请求不同类型的锁可能导致死锁的情形。 - **考虑优先级反转问题**:高优先级任务可能会因为低优先级任务持有了所需资源而被延迟执行,所以应该尽量减少在实时性要求高的路径上调用自旋锁的机会。 - **不在进程上下文中使用**:对于那些能够主动挂起自身的实体来说不应该依赖于自旋锁来进行同步控制,比如普通的用户空间程序或是某些特殊条件下工作的内核模块部分[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

野火少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值