第22章 自旋锁死锁实验(iTOP-RK3568开发板驱动开发指南 )

news2024/11/29 16:42:02

在上一小节中,学习了内核中自旋锁的使用,而自旋锁若是使用不当就会产生死锁,在本章将会对自旋锁的特殊情况-死锁进行讲解。

22.1 自旋锁死锁

死锁是指两个或多个事物在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。当多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进,这种情况就是死锁。

自旋锁死锁发生存在两种情况:

(1)第一种情况是拥有自旋锁的进程A在内核态阻塞了,内核调度B进程,碰巧B进程也要获得自旋锁,此时B只能自旋转。而此时抢占已经关闭(在单核条件下)不会调度A进程了,B永远自旋,产生死锁,如下图(图 22-1)所示:

img

图 22-1

相应的解决办法是,在自旋锁的使用过程中要尽可能短的时间内拥有自旋锁,而且不能在临界区中调用导致线程休眠的函数。

第二种情况是进程A拥有自旋锁,中断到来,CPU执行中断函数,中断处理函数,中断处理函数需要获得自旋锁,访问共享资源,此时无法获得锁,只能自旋,从而产生死锁,如下图(图22-2)所示:

img

图 22-2

对于中断引发的死锁,最好的解决方法就是在获取锁之前关闭本地中断,Linux内核在“/include/linux/spinlock.h”文件中提供了相应的API 函数,如下(图22-3)所示:

函数描述
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)打开下半部,获取自旋锁

表 22-3

由于Linux内核运行是非常复杂的,很难确定某个时刻的中断状态,因此建议使用 spin_lock_irqsave/spin_unlock_irqrestore,因为这一组函数会保存中断状态,在释放锁的时候会恢复中断状态。

在下一小节中将进行自旋锁死锁实验,本次实验所采取的是第一种情况,即拥有自旋锁的进程A在内核态阻塞了,内核调度B进程,碰巧B进程也要获得自旋锁,依次产生死锁。

22.2 实验程序的编写

22.2.1 驱动程序编写

本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\17\module。

本章节实验以19章并发与竞争实验为基础,在open()函数中加入了自旋锁加锁,在close()函数中加入了自旋锁解锁,由于在write()函数中存在sleep()睡眠函数,所以会造成内核阻塞,睡眠期间如果使用另一个进程获取该自旋锁,就会造成死锁。

编写完成的dielock.c代码如下所示

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

static spinlock_t spinlock_test;//定义spinlock_t类型的自旋锁变量spinlock_test
static int open_test(struct inode *inode,struct file *file)
{
	//printk("\nthis is open_test \n");
	spin_lock(&spinlock_test);//自旋锁加锁
	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_unlock(&spinlock_test);//自旋锁解锁
	return 0;
}

struct chrdev_test {
       dev_t dev_num;//定义dev_t类型变量dev_num来表示设备号
       int major,minor;//定义int类型的主设备号major和次设备号minor
       struct cdev cdev_test;//定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备
       struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类
};
struct chrdev_test dev1;//创建chrdev_test类型的
struct file_operations fops_test = {
      .owner = THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
      .open = open_test,//将open字段指向open_test(...)函数
      .read = read_test,//将read字段指向read_test(...)函数
      .write = write_test,//将write字段指向write_test(...)函数
      .release = release_test,//将release字段指向release_test(...)函数
};
 
static int __init atomic_init(void)
{
	spin_lock_init(&spinlock_test);
	if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0 ){//自动获取设备号,设备名chrdev_name
		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,minor is %d\n",dev1.major,dev1.minor);
	cdev_init(&dev1.cdev_test,&fops_test);//使用cdev_init()函数初始化cdev_test结构体,并链接到fops_test结构体
	dev1.cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
	cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用cdev_add()函数进行字符设备的添加
	dev1.class_test = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_test
	device_create(dev1.class_test,0,dev1.dev_num,0,"device_test");//使device_create进行设备的创建,设备名称为device_test
	return 0;
}

static void __exit atomic_exit(void)
{
	device_destroy(dev1.class_test,dev1.dev_num);//删除创建的设备
	class_destroy(dev1.class_test);//删除创建的类
	cdev_del(&dev1.cdev_test);//删除添加的字符设备cdev_test
	unregister_chrdev_region(dev1.dev_num,1);//释放字符设备所申请的设备号
	printk("module exit \n");
}
Module_init(atomic_init);
module_exit(atomic_exit)
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");

22.2.2 编写测试 APP

本实验应用程序对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\17\app。

本测试app代码和上一章节相同,需要输入两个参数,第一个参数为对应的设备节点,第二个参数为“topeet”或者“itop”,分别代表向设备写入的数据,编写完成的应用程序app.c内容如下所示:

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

由于本次测试的CPU为多核心CPU,其他核心仍旧可以调度其他进程,所以需要多次使用taskset函数指定CPU进行进程的运行,以此来产生死锁,在与app.c同级目录下创建名为app.sh的脚本文件,脚本内容如下所示:

#!/bin/bash
taskset -c 0 ./app /dev/device_test topeet &
taskset -c 1 ./app /dev/device_test topeet &
taskset -c 2 ./app /dev/device_test topeet &
taskset -c 3 ./app /dev/device_test topeet &
taskset -c 0 ./app /dev/device_test topeet &
taskset -c 1 ./app /dev/device_test topeet &
taskset -c 2 ./app /dev/device_test topeet &

保存退出之后,需要使用以下命令赋予脚本可执行权限,如下图(图22-4)所示:

chmod 777 app.sh

img

图 22-4

至此测试程序app.c和运行脚本app.sh就编写完成了。

22.3 运行测试

22.3.1 编译驱动程序

在上一小节中的dielock.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += dielock.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
    make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
    make -C $(KDIR) M=$(PWD) clean    #make clean操作

对于Makefile的内容注释已在上图添加,保存退出之后,来到存放dielock.c和Makefile文件目录下,如下图(图22-5)所示:

img

图 22-5

然后使用命令“make”进行驱动的编译,编译完成如下图(图22-6)所示:

img

图 22-6

编译完生成dielock.ko目标文件,如下图(图22-7)所示:

img

图 22-7

至此驱动模块就编译成功了,下面进行应用程序的编译。

22.3.2 编译应用程序

来到应用程序app.c文件的存放路径如下图(图22-8)所示:

img

图 22-8

然后使用以下命令对app.c进行交叉编译,编译完成如下图(图22-9)所示:

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

img

图 22-9

生成的app文件就是之后放在开发板上运行的可执行文件,至此应用程序的编译就完成了。

22.3.3 运行测试

开发板启动之后,使用以下命令进行驱动模块的加载,如下图(图22-10)所示:

insmod dielock.ko

img

图 22-10

可以看到申请的主设备号和次设备号就被打印了出来,然后使用以下代码对自动生成的设备节点device_test进行查看,如下图(图22-11)所示:

 ls /dev/device_test

img

图 22-11

可以看到device_test节点已经被自动创建了,然后使用以下命令运行app.sh脚本,该脚本会指定CPU在加锁之后进入内核休眠状态,如下图(图22-12)所示:

./app.sh

img

图 22-12

​ 在指令输入之后,串口终端无法输入,引发了死锁,进而造成了系统崩溃,所以在编写驱动的过程中,要尽可能的避免死锁的出现。

至此,自旋锁死锁驱动实验就完成了。

【最新驱动资料(文档+例程)】

链接 https://pan.baidu.com/s/1M4smUG2vw_hnn0Hye-tkog

提取码:hbh6

【B 站配套视频】

https://b23.tv/XqYa6Hm

【RK3568 购买链接】

https://item.taobao.com/item.htm?spm=a1z10.5-c-s.w4002-2245

4222659486)]

图 22-12

​ 在指令输入之后,串口终端无法输入,引发了死锁,进而造成了系统崩溃,所以在编写驱动的过程中,要尽可能的避免死锁的出现。

至此,自旋锁死锁驱动实验就完成了。

【最新驱动资料(文档+例程)】

链接 https://pan.baidu.com/s/1M4smUG2vw_hnn0Hye-tkog

提取码:hbh6

【B 站配套视频】

https://b23.tv/XqYa6Hm

【RK3568 购买链接】

https://item.taobao.com/item.htm?spm=a1z10.5-c-s.w4002-2245

2452613.11.2fec74a6elWNeA&id=669939423234

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/991935.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Docker】容器化应用程序的配置管理策略与实践

一、引言 1.1 Docker的背景和优势 Docker是一种开源的容器化平台&#xff0c;简化应用程序的打包、交付和运行过程。基于Linux容器技术&#xff0c;通过提供一个轻量级、可移植和自包含的容器来实现应用程序的隔离和部署。 在传统的应用程序开发和部署中&#xff0c;往往需要…

九科-模块化-创建目录_如果不存在

python代码 # 创建目录_如果不存在 def create_directory_if_not_exists(dir_path):# 如果目录不存在if not os.path.exists(dir_path):# 创建目录os.makedirs(dir_path) 九科组件模块 总图 查询目录是否存在 IF判断目录是否存在 如果目录不存在&#xff0c;创建目录

syn洪流原理

TCP三次握手 建立连接发送或回应第一次握手客户端发送报文&#xff0c;标志位为SYN&#xff08;seqa&#xff09;第二次握手服务器发送报文&#xff0c;标志位为SYN&#xff0c;ACK&#xff08;seqb,acka1&#xff09;第三次握手客户端回应服务器报文&#xff0c;标志位为ACK&…

Java 设置免登录请求接口被拦截问题

1、在设置免登录时&#xff0c;前端将请求的路由添加到白名单后&#xff0c;请求接口还是被拦截到了&#xff0c;将请求接口也设置后还是会被拦截跳转到登录页面 通过JAVA 注解 Anonymous 进行设置匿名访问就可以了

Docker 的常用命令

0 基本命令 概述 [root192 home]# docker --helpUsage: docker [OPTIONS] COMMANDA self-sufficient runtime for containersOptions:--config string Location of client configfiles (default "/root/.docker")-c, --context string Name of the context…

js中如何判断一个对象是否为空对象?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 使用 Object.keys()⭐ 使用 for...in 循环⭐ 使用 JSON.stringify()⭐ 使用 ES6 的 Object.getOwnPropertyNames()⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带…

windows本地认证

来自帅气学弟得经验 阅读流程 windows系统认证包括**本地交互式认证 **和 网络认证 交互式登录&#xff1a;向本地计算机或域账户确认用户的身份 网络登录&#xff1a;对用户尝试访问的网络服务或资源提供用户认证 若是本地用户本地认证需要了解**windows密码 **&#xff0c;…

【C++心愿便利店】No.5---构造函数和析构函数

文章目录 前言一、类的6个默认成员函数二、构造函数三、析构函数 前言 &#x1f467;个人主页&#xff1a;小沈YO. &#x1f61a;小编介绍&#xff1a;欢迎来到我的乱七八糟小星球&#x1f31d; &#x1f4cb;专栏&#xff1a;C 心愿便利店 &#x1f511;本章内容&#xff1a;类…

通过curl命令分析http接口请求各阶段的耗时等

目录 一、介绍二、功能1、-v 输出请求 响应头状态码 响应文本等信息2、-x 测试代理ip是否能在该网站使用3、-w 额外输出查看接口请求响应的消耗时间4、-o 将响应结果存储到文件里面5、-X post请求测试 (没测成功用的不多) 一、介绍 Curl是一个用于发送和接收请求的命令行工具和…

TCP/IP传输协议学习

了解完整的通信过程 1.发送方源终端设备的应用创建数据。 2.当数据在源终端设备中沿协议栈向下传递&#xff0c;对其分段和封装。 3.在协议栈网络接入层的介质上生成数据。 4.通过由介质和任意中间设备组成的网际层网络传输数据。 5.在目的终端设备中沿协议栈向上传递时对其…

用队列实现栈(C语言版本)

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;强烈推荐优质专栏: &#x1f354;&#x1f35f;&#x1f32f;C的世界(持续更新中) &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;…

ArcGIS 10.5安装教程!

软件介绍&#xff1a; ArcGIS Desktop 10.5中文特别版是一款功能强大的GSI专业电子地图信息编辑和开发软件&#xff0c;ArcGIS Desktop 包括两种可实现制图和可视化的主要应用程序&#xff0c;即 ArcMap 和 ArcGIS Pro。ArcMap 是用于在 ArcGIS Desktop 中进行制图、编辑、分析…

滴滴:二季度中国出行营收同比增长57%,6月日均单量超3000万单

9月9日&#xff0c;滴滴在其官网发布2023年第二季度业绩报告&#xff0c;二季度滴滴实现总收入488亿元&#xff0c;同比增长52.6%&#xff1b;归属于滴滴普通股股东的净亏损为3亿元&#xff0c;经调整EBITA亏损1000万元。 分业务来看&#xff0c;二季度滴滴中国出行&#xff0…

北京运营《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书

北京运营《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书

当所有行业都在数字化转型时,企咨行业如何快速“破局”

党的二十大报告指出&#xff0c;“加快发展数字经济&#xff0c;促进数字经济和实体经济深度融合&#xff0c;打造具有国际竞争力的数字产业集群。”随着新一轮科技革命和产业变革深入发展&#xff0c;数字化转型已经不是一道“选择题”&#xff0c;而是一堂“必修课”。 数字…

国产触控笔哪个牌子好?开学平价好用触控笔排行榜

很多学生在新学期的时候&#xff0c;都会用到电容笔&#xff0c;这说明电容笔的重要性。苹果推出了ipad专用的电容笔后&#xff0c;这种电容笔便成为了市场上最热门的产品&#xff0c;只是因为Apple Pencil的价格过于昂贵&#xff0c;所以很多人并没有购买得起。所以&#xff0…

一文读懂官方给出torch.nn.RNN API的参数及手写RNN API复现

理论部分 官方给出的文档解释&#xff1a; 计算公式&#xff1a; 该公式对应的结构框图&#xff1a; 其中 xt 表示当前 t 时刻的输入&#xff0c;Wih表示 “输入层” 到 “隐藏层” 的权重矩阵。即 Wih 会将输入映射至隐藏层。bih表示输入层到隐藏层的偏置&#xff0c;ht-1表…

【我的第一千篇文章】

作为一名Java开发者&#xff0c;我很自豪地宣布&#xff0c;这里是我输出的第一千篇文章。在过去的六年里&#xff0c;我一直坚持每月输出优质内容&#xff0c;并将其分享给了全世界的读者们。这一千篇文章中&#xff0c;有很多关于Java编程的技巧、经验分享、优秀实践示例、案…

16 “count(*)“ 和 “count(1)“ 和 “count(field1)“ 的差异

前言 经常会有面试题看到这样的问题 “ select count(*) ”, “ select count(field1) ”, “ select count(1) ” 的效率差异啥的 然后 我们这里 就来探索一下 这个问题 我们这里从比较复杂的 select count(field1) 开始看, 因为 较为复杂的处理过程 会留一下一些关键的调试…

C4D国潮场景3D模型合集

110个国潮场景3D模型&#xff0c;C4D源文件&#xff0c;部分效果图如下&#xff1a; 微信扫描下方二维码 回复关键字获取 100004