Linux驱动之 内核最常见的自旋锁及死锁示例

Linux驱动 之自旋锁、死锁学习记录:

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

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

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

非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换
(线程被阻塞后便进入内核(Linux )调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)。

自旋锁的使用
在linux kernel的实现中,经常会遇到这样的场景:共享数据被中断上下文和进程上下文访问,该如何保护呢?
如果只有进程上下文的访问,那么可以考虑使用 semaphore或者mutex的锁机制,
但是现在中断上下文也掺和进来,那些可以导致睡眠的 lock就不能使用了,这时候,可以考虑使用spinlock。

在中断上下文,是不允许睡眠的,所以,这里需要的是一个不会导致睡眠的锁——spinlock。
换言之,中断上下文要用锁,首选 spinlock

使用步骤:
我们要访问临界资源需要首先申请自旋锁
获取不到锁就自旋,如果能获得锁就进入临界区
当自旋锁释放后,自旋在这个锁的任务即可获得锁并进入临界区,退出临界区的任务必须释放自旋锁
内核中的spinlock_t的数据类型定义如下
typedef struct spinlock {
   struct raw_spinlock rlock;  
} spinlock_t;
typedef struct raw_spinlock {
   arch_spinlock_t raw_lock;
} raw_spinlock_t;

自旋锁定义、初始化
spinlock_t lock;

spin_lock_init (&lock);  
DEFINE_SPINLOCK(lock); 
自旋锁的申请
spin_lock(lock); //成功获得自旋锁立即返回,否则自旋在那里直到该自旋锁的保持者释放
spin_trylock(lock); //成功获得自旋锁立即返回真,否则返回假,而不是像上一个那样"在原地打转"
、、、、、 //临界区操作
自旋锁的释放
spin_unlock(lock);//释放自旋锁
自旋锁死锁的2种情况

1)拥有自旋锁的进程A在内核态阻塞了,内核调度B进程,碰巧B进程也要获得自旋锁,此时B只能自旋转。而此时抢占已经关闭,不会调度A讲程了,B永远自旋产生死锁。
2)进程A拥有自旋锁,中断到来,CPU执行中断函数,中断处理函数,中断处理函数需要获得自旋锁,访问共享资源,此时无法获得锁,也只能自旋,产生死锁。
B站:一口Linux:
截图
针对单CPU,拥有自旋锁的任务不应该调度会引起休眠的函数,否则会导致死锁。
查看CPU核数:ps -et | grep softirq
在这里插入图片描述
虚拟机重新设置一下处理器数量,就好:

cxx@ubuntu16:~$ ps -ef | grep softirq
root          7      2  0 10:32 ?        00:00:00 [ksoftirqd/0] 
cxx        2450   2404  0 10:34 pts/18   00:00:00 grep --color=auto softirq
cxx@ubuntu16:~$ 
cdev_hello.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/spinlock.h>
#define NEWCHRDEV_CNT	1			/* 设备号个数 */
#define NEWCHRDEV_NAME	"hello"		/* 名字 */

static spinlock_t lock; //定义
static int flage = 1;

/* newchrdev设备结构体 */
struct newchr_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct device *device;	 /* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
};
struct newchr_dev chr_hello;	/* 设备hello */
 
/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
 #define DEAD 1
static int hello_open (struct inode *inode, struct file *filep)
{
	printk("hello_open()\n");
	spin_lock(&lock);  //锁
	printk("hello_open() spin_lock \n"); 
    if(flage != 1){
         spin_unlock(&lock);
         return -EBUSY;
    }
    flage =0;
    #if DEAD
    #elif
    spin_unlock(&lock);
	printk("hello_open() spin_unlock \n");
    #endif

	return 0;
}
/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int hello_release (struct inode *inode, struct file *filep)
{
	printk("hello_release()\n");
	flage = 1;
    #if DEAD
    spin_unlock(&lock);
	printk("hello_release() spin_unlock \n");
    #endif
	return 0;
}

/* 设备操作函数 */
static struct file_operations chr_hello_ops = {
	.owner = THIS_MODULE,	
	.open = hello_open,
	.release = hello_release,
};

/* 
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int hello_init(void)
{
	int result = 0;
	printk("chrdev_hello init!\r \n");
	/* 注册字符设备驱动 */
	/* 1、创建设备号 */
	if (chr_hello.major) {		/*  定义了设备号 */
		chr_hello.devid = MKDEV(chr_hello.major, 0);
		/* 据定义设备号申请注册 */
		result = register_chrdev_region(chr_hello.devid, NEWCHRDEV_CNT, NEWCHRDEV_NAME);
		if(result < 0){
			printk("register_chrdev fail \n");  
			goto out_err_1;
		}
	} else {			/* 没有定义设备号,自动分配*/
		result = alloc_chrdev_region(&chr_hello.devid, 0, NEWCHRDEV_CNT, NEWCHRDEV_NAME);	/* 申请设备号 */
		if(result < 0){
			printk("alloc_chrdev_region fail \n"); //自动分配设备号错误
			goto out_err_1;
		}
		chr_hello.major = MAJOR(chr_hello.devid);	/* MAJOR宏获取分配号的主设备号 */
		chr_hello.minor = MINOR(chr_hello.devid);	/* MINOR宏获取分配号的次设备号 */
	}
	printk("chr_hello major=%d,minor=%d\r\n",chr_hello.major, chr_hello.minor);	
	
	/* 2、初始化cdev */
	chr_hello.cdev.owner = THIS_MODULE;
	cdev_init(&chr_hello.cdev, &chr_hello_ops);
	/* 3、添加一个cdev */
	cdev_add(&chr_hello.cdev, chr_hello.devid, NEWCHRDEV_CNT);

	/* 4、创建类 */
	chr_hello.class = class_create(THIS_MODULE, NEWCHRDEV_NAME);
	if (IS_ERR(chr_hello.class)) {
		printk(KERN_ERR "class_create() failed\n");
		result = PTR_ERR(chr_hello.class);
		goto out_err_2;
	}

	/* 5、创建设备 */
	chr_hello.device = device_create(chr_hello.class, NULL, chr_hello.devid, NULL, NEWCHRDEV_NAME);
	if (IS_ERR(chr_hello.device)) {
		printk(KERN_ERR "device_create() failed\n");
		result = PTR_ERR(chr_hello.device);
		goto out_err_3;
	}
	spin_lock_init(&lock); 		//自旋锁初始化
	return result; 
//释放已申请的资源返回
out_err_3:
	device_destroy(chr_hello.class, chr_hello.devid); /*  删除device */	
out_err_2:
	class_destroy(chr_hello.class);  /*  删除class */
	unregister_chrdev_region(chr_hello.devid, NEWCHRDEV_CNT); /* 注销设备号 */
	cdev_del(&chr_hello.cdev);/*  删除cdev */
out_err_1:
	return 	result; 
}
/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void hello_exit(void)
{
	printk("chrdev_hello exit!\r \n");
	/* 注销字符设备驱动 */
	device_destroy(chr_hello.class, chr_hello.devid); /*  删除device */
	class_destroy(chr_hello.class);  /*  删除class */
	unregister_chrdev_region(chr_hello.devid, NEWCHRDEV_CNT); /* 注销设备号 */
	cdev_del(&chr_hello.cdev);/*  删除cdev */
	return;
}
//modinfo  name.ko
MODULE_LICENSE("GPL"); //遵循GPL协议
MODULE_AUTHOR("CJX");
MODULE_DESCRIPTION("Just for Demon");

module_init(hello_init);
module_exit(hello_exit);
//cat proc/devices
app.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main()
{
	char *filename = "/dev/hello"; 	
	int fd = open(filename,O_RDWR);
	if(fd<0){
		perror("open fail \n");
		return -1;
	}
	printf("open file %s success!\r\n", filename);

    sleep(20);
	 
	close(fd);
	
	return 0;
}
直接宕机:在这里插入图片描述尝试把spin_lock(lock);换成 spin_trylock(lock); 即可

如何避免死锁

1.如果中断处理函数中也要获得自旋锁,那么驱动程序需要在拥有自旋锁时禁止中断
2.自旋锁必须在可能的最短时间内拥有
3.避免某个获得锁的函数调用其他同样试图获取这个锁的函数,否则代码就会死锁;

不论是信号量还是自旋锁,都不允许锁拥有者第二次获得这个锁,如果试图这么做,系统将挂起

4.锁的顺序规则:
		a)按同样的顺序获得锁;
		b)如果必须获得一个局部锁和一个属于内核更中心位置的锁,则应该首先获取自己的局部锁;
		c)如果我们拥有信号量和自旋锁的组合,则必须首先获得信号量;在拥有自旋锁时调用down(可导致休眠)是个严重的错误的。

自旋锁与信号量应用场合:

信号量是进程级的,用于多个进程之间对资源的互斥。如果竞争失败,会发生进程上下文切换,因为进程上下文切换的开销比较大,因此,只有当进程占用资源时间较长时,选用信号量才是较好的选择。

所要保护的临界资源访问时间比较短时,用自旋锁是非常方便的,它不会引起进程睡眠而导致上下文切换。
如果访问临界资源的时间较长,则选用信号量,否则选用自旋锁。

信号量所保护的临界资源区可包含可能引起阻塞的代码,而自旋锁则绝对要避免这样的代码,阻塞意味着需要进程上下文切换,如果进程被切换出去,这个时候如果另外一个进程想获得自旋锁的话,会引起死锁。

信号量存在于进程上下文,因此,如果被保护的资源需要在中断或者软终端情况下使用,则只能选择自旋锁。

参考学习视频:B站 一口Linux: https://space.bilibili.com/661326452/

  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

编程一时爽Cxx

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

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

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

打赏作者

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

抵扣说明:

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

余额充值