IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 133.Linux并发与竞争-自旋锁 -> 正文阅读

[系统运维]133.Linux并发与竞争-自旋锁

1.前言

开发板:正点原子阿尔法开发板,本文为学习笔记

2.自旋锁简介

原子操作只能用做计数操作,保护的东西太少,只能对整形变量或者位进行保护,当在实际操作我们需要保护资源有很多,这个时候就需要用到我们的自旋锁,自锁意思是原地打转的意思,意味着某一个线程没有获取到自旋锁会原地等待,直到获取到自旋锁,才进行下一步操作,一个线程一直等待自旋锁会浪费处理器时间,降低系统性能,所以自旋锁适用于轻量级加锁

2.1 加锁步骤

1.查看锁的状态,如果锁的空闲的

2.将锁设置为当前线程持有

实际上这是存在问题,多个线程执行这两个操作会出错,所以Linux内核引入CAS函数,将上面两个步骤合成一个硬件级的指令,类似于原子操作

2.2 CAS函数

自旋锁使用CPU 提供的 CAS 函数(Compare And Swap),在用户态代码中完成加锁与解锁操作

2.3 PAUSE指令

自旋锁并不一直"忙等待",会与 CPU紧密配合 ,它通过 CPU 提供的 PAUSE 指令,减少循环等待时的耗电量;对于单核CPU,忙等待并没有意义,此时它会主动把线程休眠。

2.4 自旋锁原理

设自旋锁为变量lock,整数0表示锁为空闲状态,整数pid表示线程ID

  • CAS(lock, 0, pid) 就表示自旋锁的加锁操作
  • CAS(lock, pid,0) 则表示自旋锁的解锁操作

自旋锁伪代码:

while (true) {
//因为判断lock变量的值比CAS操作更快,所以先判断lock再调用CAS效率更高
if (lock == 0 && CAS(lock, 0, pid) == 1) 
	return;
	
if (CPU_count > 1 ) { 
	//如果是多核CPU,“忙等待”才有意义
	for (n = 1; n < 2048; n <<= 1) {
		//pause的时间,应当越来越长
		for (i = 0; i < n; i++) 
			pause();//CPU专为自旋锁设计了pause指令
	if (lock == 0 && CAS(lock, 0, pid)) 
	return;//pause后再尝试获取锁
	}
}

sched_yield();//单核CPU,或者长时间不能获取到锁,应主动休眠,让出CPU
}

3.自旋锁API

Linux内核使用结构体spinlock_t表示自旋锁,定义如下

3.1 常用API

typedef struct spinlock {
 union {
 	struct raw_spinlock rlock;

 #ifdef CONFIG_DEBUG_LOCK_ALLOC
 # define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
 	struct {
 		u8 __padding[LOCK_PADSIZE];
     	struct lockdep_map dep_map;
};
 #endif
};
} spinlock_t;

定义自旋锁

spinlock_t s_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)
  • 尝试获取一次自旋锁,获取成功返回“true”,获取失败返回false,程序继续往下执行

3.2 其他API函数

DEFINE_SPINLOCK(spinlock_t lock) //定义并初始化一个自旋锁。
int spin_is_locked(spinlock_t *lock) //检查指定的自旋锁是否被获取,如果没有被获取就
返回非 0,否则返回 0

4. 使用场景

4.1 线程间访问

以上使用的自旋锁API函数适用用SMP或者支持抢占的单CPU下线程之间的并发访问,所以被自旋锁保护的临界区不能调用引起睡眠阻塞的API函数,获取到自旋锁后自动禁止内核抢占,也就是说如果A线程中调用的导致睡眠的函数,那么线程A会放弃CPU的使用权,线程B开始运行之后,由于此时锁被被线程A持有,而且内核抢占还被禁止了,其他任务无法获取的CPU的控制权,导致线程B一直自旋,无法调度出去,此时线程A无法运行,锁无法释放,死锁产生

4.2 中断访问

前面说的是线程之间的并发访问,如果此时中断也需要访问共享资源,我们在中断中获取自旋锁时需要先禁止本地中断(也就是本 CPU 中断,对于多核 SOC来说会有多个 CPU 核),否则可能导致锁死现象的发生 )

死锁产生:

在这里插入图片描述

当线程A运行时被中断打断,中断抢走了CPU的使用权,而自旋锁又为线程A所有,中断由于没有获取到自旋锁一直等待A释放自旋锁,从而导致产生死锁,所以解决方法是A获取到自旋锁之后将本地中断关闭掉,在Linux内核中提供了相应的API函数

4.3 线程于中断并发访问的API函数

禁止本地中断,获取自旋锁

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) //定义并且初始化一个自旋锁

void funcA()
{
  	unsigned long flags;	/*中断状态*/
    spin_lock_irqsave(&lock,flags) /*获取锁*/
    ····
       ···临界区
    ····
    spin_unlock_irqrestore(&lock,flags) /*释放锁*/
}

void irq()
{
    spin_lock(&lock);
    ```
        临界区
    ····
    spin_unlock(&lock);
}

中断下半部使用自旋锁API为:

void spin_lock_bh(spinlock_t *lock)   //关闭下半部,并获取自旋锁。
void spin_unlock_bh(spinlock_t *lock) //打开下半部,并释放自旋锁

5.代码示例

编写驱动,应用程序,保证10s之内只能打开这个驱动一次,实际上自旋锁很少这样子使用,一般会保护一些临界区,这里只是为了方便总结,在open函数里面获取自旋锁,保护共享资源flags,关闭驱动之后对flags进行+1操作

驱动程序:

my_led_driver.c

#include "my_led_driver_reg.h" 

DEFINE_SPINLOCK(lock);//定义初始化一个自旋锁
int flags;

/*初始化LED*/
static void memory_map(void)
{
	IMX6U_CLK_CCGR1 			     = ioremap(CCM_CCGR1,4);
	SW_MUX_CTL_PAD_GPIO1_IO03 		 = ioremap(SW_MUX_CTL_PAD_GPIO1_IO03_BASE,4);
	IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = ioremap(SW_PAD_CTL_PAD_GPIO1_IO03_BASE,4);
	IMX6UL_GPIO1_DR                  = ioremap(GPIO1_DR,4);
	IMX6UL_GPIO1_GDIR                = ioremap(GPIO1_GDIR,4);                   
}

static void free_memory_map(void)
{
	iounmap(IMX6U_CLK_CCGR1);
	iounmap(SW_MUX_CTL_PAD_GPIO1_IO03);	
	iounmap(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03);
	iounmap(IMX6UL_GPIO1_DR);
	iounmap(IMX6UL_GPIO1_GDIR);
}

static void gpio1_clk_enable(void)
{
	u32 iClkVal;

	/*读取之前的clk值*/
	iClkVal = readl(IMX6U_CLK_CCGR1);

	/*清空27:26位,清除之前的设置*/
	iClkVal &= ~(3 << 26);

	/*设置新的值*/
	iClkVal |= (3 << 26);
	writel(iClkVal,IMX6U_CLK_CCGR1);

}

static void set_gpio1_mux_fun(void)
{
	u32 iVal;

	/*设置gpio1的复用功能,初始值都是0x0000000*/
	
	writel(5,SW_MUX_CTL_PAD_GPIO1_IO03);

	/* 3、配置 GPIO1_IO03 的 IO 属性
	 *bit 16     : 0 HYS 关闭
	 *bit [15:14]: 00 默认下拉
     *bit [13]   : 0 kepper 功能
     *bit [12]   : 1 pull/keeper 使能
	 *bit [11]   : 1 打开开路输出
     *bit [7:6]  : 10 速度 100Mhz
     *bit [5:3]  : 111 R0/7 驱动能力
     *bit [0]    : 1 高转换率
     *初始值为0x0000000,直接写
     */
	writel(0x000018b9,IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03);

	/*设置gpio1_io03为输出功能*/
	iVal = readl(IMX6UL_GPIO1_GDIR);
	iVal &=(1 << 3);
	iVal |=(1 << 3);
	writel(iVal,IMX6UL_GPIO1_GDIR);

	/*默认关闭LED,根据原理图可知低电平触发*/
	iVal = readl(IMX6UL_GPIO1_DR);
	iVal |=(1 << 3);
	writel(iVal,IMX6UL_GPIO1_DR);
		
}

void led_switch(int iLedSta)
{
	u32 iVal;
	iVal = readl(IMX6UL_GPIO1_DR);
	
	switch(iLedSta)
	{
		case LED_ON:
		{
			iVal &= ~(1 << 3);
			break;
		}
		case LED_OFF:
		{
			iVal |= (1 << 3);
			break;
		}
	default:
		{
			iVal &=~(1 << 3);
			break;
		}
	}
	
	writel(iVal,IMX6UL_GPIO1_DR);
}

//echo
static ssize_t led_enable_store(struct device *dev,struct device_attribute *attr, const char *buf, size_t len)
{
	bool iLedEnable;
	u32 iRet;

	iRet  = strtobool(buf, &iLedEnable);
	if(iRet < 0)
	{
		debug("strtobool failed\n");
		return iRet;
	}

	debug("++klz led enable is=%d\n",iLedEnable);
	led_switch(iLedEnable);
		
    return len;
}

//声明led_enable文件节点
static DEVICE_ATTR(led_enable, S_IWUSR, NULL,led_enable_store);

static const struct attribute *atk_imx6ul_led_sysfs_attrs[] = {
	&dev_attr_led_enable.attr,
	NULL,
};


static int imx6ull_led_open(struct inode *inode, struct file *file)
{
	//file->private_data = &led_device;
	
	spin_lock(&lock);
	
	if(flags)
	{
		spin_unlock(&lock);
		printk(KERN_INFO"driver is running,open failed\n");
		return -EINVAL;
	}
	else
	{
		flags++;
	}
	
	spin_unlock(&lock);
	file->private_data = &led_device;
	
	return 0;
}


static int imx6ull_led_release(struct inode *inode, struct file *file)
{
	file->private_data = &led_device;
	spin_lock(&lock);
	if(flags)
		flags--;
	
	spin_unlock(&lock);
	
	return 0;
}

static ssize_t imx6ull_led_read(struct file *file, char __user *buf, size_t cnt, loff_t * loff)
{
	return 0;
}

static ssize_t imx6ull_led_write (struct file *file, const char __user *buf, size_t cnt, loff_t *loff)
{
	u32 iRet;
	unsigned char cALedbuf[1];
	unsigned int ledSta;

	iRet = copy_from_user(cALedbuf,buf,cnt);
	if(iRet < 0)
	{
		debug("copy from user failed\n");
		return -EINVAL;
	}
	
	if(iRet < 0)
	{
		debug("imx6ull write failed\n");
		return -EINVAL;
	}

	printk(KERN_INFO"%s,cALedbuf[0]=%d\n",__FUNCTION__,cALedbuf[0]);

    ledSta = cALedbuf[0] - 48;
	led_switch(ledSta);

	return 0;

}


static struct file_operations led_device_fops = {
	.owner   = THIS_MODULE,
	.read    = imx6ull_led_read,
	.write   = imx6ull_led_write,
	.open    = imx6ull_led_open,
	.release = imx6ull_led_release,
};

static int __init led_driver_init(void)
{
	u32 iRet;

	/*1.硬件初始化:初始化内存映射,初始化硬件*/
	memory_map();
	gpio1_clk_enable();
	set_gpio1_mux_fun();

	/*2.1 之前定义了主设备号*/
	if(led_device.major)
	{
		/*选择次设备号*/
		led_device.devid = MKDEV(led_device.major,0);
		/*注册设备号*/
		iRet = register_chrdev_region(led_device.devid, DEVICE_CNT, DEVICE_NAME);
		if(iRet < 0)
		{
			debug("register_chrdev_region failed\n");
			return iRet;
		}
	}else
	{
		/*向内核申请主次设备号,DEVICE_NAME体现在/proc/devices*/
		alloc_chrdev_region(&led_device.devid, 0, DEVICE_CNT,  DEVICE_NAME); /* 申请设备号 */
		led_device.major = MAJOR(led_device.devid); /* 获取分配号的主设备号 */
		led_device.minor = MINOR(led_device.devid); /* 获取分配号的次设备号 */
	}

	led_device.cdev.owner = THIS_MODULE;
	cdev_init(&led_device.cdev,&led_device_fops);
	/*自动创建设备结点,在/dev目录下体现*/
	iRet = cdev_add(&led_device.cdev,led_device.devid,DEVICE_CNT);
	if(iRet < 0)
	{
		debug("cdev_add device failed\n");
		goto fail_cdev_add;
	}

	led_device.class = class_create(THIS_MODULE,DEVICE_NAME);
	if(IS_ERR(led_device.class))
	{
		debug("class creat failed\n");
		goto fail_class_create;
	}

	/*生成dev/DEVICE_NAME文件*/
	led_device.device = device_create(led_device.class,NULL,led_device.devid,NULL,DEVICE_NAME);
	if(IS_ERR(led_device.device))
	{
		debug("device class failed\n");
		goto fail_device_create;
	}

	/*创建led_enable结点,直接通过系统调用来操作驱动*/
	iRet = sysfs_create_files(&led_device.device->kobj,atk_imx6ul_led_sysfs_attrs);
	if(iRet)
	{
		debug("failed to create sys files\n");
		return -EINVAL;
	}
	
	debug("my led dirver init sucess\n");

	return 0;
fail_cdev_add:
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	return iRet;
fail_class_create:
	cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	return -1;
fail_device_create:
	cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	class_destroy(led_device.class);
	return -1;
	
}

static void __exit led_driver_exit(void)
{
	free_memory_map();
	cdev_del(&led_device.cdev);
	unregister_chrdev_region(led_device.devid,DEVICE_CNT);
	/*依赖于class所以先删除*/
	device_destroy(led_device.class, led_device.devid);
	class_destroy(led_device.class);

}

module_init(led_driver_init);
module_exit(led_driver_exit);

MODULE_AUTHOR("klz <1255713178@qq.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("led driver of atk imx6ull");

头文件:my_led_driver_reg.h

#ifndef MY_LED_DRIVER_REG_H
#define MY_LED_DRIVER_REG_H


#include <linux/types.h>	    /*设备号所在头文件*/
#include <linux/module.h>       /*内核模块声明的相关函数*/
#include <linux/init.h>			/*module_init和module_exit*/
#include <linux/kernel.h>		/*内核的各种函数*/
#include <asm/io.h>             /*readl.ioremap函数*/
#include <linux/kernel.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <asm/mach/map.h>
#include <linux/ide.h>

#define _DEBUG_ 
#ifdef  _DEBUG_
	#define  debug(fmt, args...)  	printk("[file = %s][fun = %s]["fmt"]\n",__FILE__,__FUNCTION__,##args)
#else
	#define  debug(fmt, args...)  	do{}while(0)
#endif


#define DEVICE_NAME "led_driver"
#define DEVICE_CNT   1
#define LED_ON		 1
#define LED_OFF      0

/*
*编写步骤:
*   0.编写驱动框架
*	1.使能GPIO的时钟
*	2.初始化 GPIO,比如输出功能、上拉、速度
*	3.设置复用
*	4.输出高低电平
*/

#define CCM_CCGR_BASE    				(0x20C4000)
#define CCM_CCGR1        				(CCM_CCGR_BASE + 0x6C)               /*时钟控制器*/      

#define SW_MUX_BASE   					(0x20E0000)
#define SW_MUX_CTL_PAD_GPIO1_IO03_BASE  (SW_MUX_BASE + 0x68)  /*io的复用控制器*/
#define SW_PAD_CTL_PAD_GPIO1_IO03_BASE  (SW_MUX_BASE + 0x2F4) /*io的速度设置,驱动能力设置,压摆率设置*/

#define GPIO_BASE       (0x209C000)
#define GPIO1_DR        (GPIO_BASE + 0x0)
#define GPIO1_GDIR      (GPIO_BASE + 0x4)
#define GPIO1_PSR       (GPIO_BASE + 0x8)
#define GPIO1_ICR1      (GPIO_BASE + 0xC)
#define GPIO1_ICR2      (GPIO_BASE + 0x10)
#define GPIO1_IMR       (GPIO_BASE + 0x14)
#define GPIO1_ISR       (GPIO_BASE + 0x18)
#define GPIO1_EDGE_SEL  (GPIO_BASE + 0x1C)


void __iomem *IMX6U_CLK_CCGR1;					        /*时钟控制寄存器 bit27:bit 26 = 1:1  gpio1_enable*/
void __iomem *SW_MUX_CTL_PAD_GPIO1_IO03;		        /*io的复用功能 bit4=1 bit3:bit0=0101*/
void __iomem *IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03;	        /*速度设置、驱动能力设置、压摆率设置*/
void __iomem *IMX6UL_GPIO1_DR;						    /*数据寄存器,置0置1*/
void __iomem *IMX6UL_GPIO1_GDIR;                        /*GPIO输入输出位设置*/

void __iomem *IMX6UL_GPIO1_PSR;                        /*只读位,读取相应的位就可获得gpio的状态,高电平还是低电平*/
void __iomem *IMX6UL_GPIO1_ICR1;                       /*中断触发位,前16位以什么方式来进行触发*/
void __iomem *IMX6UL_GPIO1_ICR2;                       /*中断触发,后16位以什么方式进行中断触发*/
void __iomem *IMX6UL_GPIO1_IMR;                        /*中断使能位*/
void __iomem *IMX6UL_GPIO1_ISR;                        /*可读取哪一个中断被触发*/
void __iomem *IMX6UL_GPIO1_EDGE_SEL;                   /*如果此位被设置,那么会双边沿触发中断*/

struct led_device_t
{
	dev_t devid;		    /*设备号*/
	struct cdev cdev;       /*cdev*/
	struct class *class;	 /*类*/
	struct device *device;	/*设备*/
	int    major;			/*主设备号*/
	int    minor;			/*次设备号*/
};

struct led_device_t led_device;

#endif

Makefile

KERNELDIR := /home/klz/linux/linux-4.1.15

CURRENT_PATH := $(shell pwd)
obj-m := my_led_driver.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
copy:
	cp *.ko /home/klz/linux/nfs/rootfs/lib/modules/4.1.15
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

sudo make之后将生成的再执行sudo make copy命令将生成的ko文件拷贝到根文件目录下

测试程序

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
    /*判断输入的命令是否合法*/
    if(argc != 3)
    {
        printf(" commend error ! \n");
        return -1;
    }

	char* filename = argv[1];
	
    /*打开文件*/   
    int fd = open(filename, O_RDWR);
    if(fd < 0)
    {
		printf("fd=%d.open file : /dev/rgb_led failed !!!\n",fd);
		return -1;
	}


    /*写入命令*/
    int error = write(fd,argv[2],sizeof(argv[2]));
    if(error < 0)
    {
        printf("write file error! \n");
        close(fd);
        /*判断是否关闭成功*/
    }


    sleep(10);  //休眠10秒

    /*关闭文件*/
    error = close(fd);
    if(error < 0)
    {
        printf("close file error! \n");
    }
    return 0;
}

生成可执行文件,且拷贝到板子上

arm-linux-gnueabihf-gcc app.c -o ledApp
sudo cp ledApp /home/klz/linux/nfs/rootfs/lib/modules/4.1.15/
chmod +x ledApp

6.测试

加载驱动

depmod
modprobe my_led_driver,ko

执行应用程序

./ledApp /dev/led_driver 1 &
10s之内运行下面指令打开驱动失败
./ledApp /dev/led_driver 0
    
驱动层打印:driver is running,open failed
应用层打印:fd=-1.open file : /dev/rgb_led failed !!!

7.小结

  • 在等待自旋锁的时候处于“自旋”状态,因此锁的持有时间不能太长,一定要短,否则的话会降低系统性能。如果临界区比较大,运行时间比较长的话要选择其他的并发处理方式,如后面要讲的信号量和互斥体。
  • 自旋锁保护的临界区不能调用导致线程休眠的函数,可能导致死锁
  • 在编写驱动程序的时候要驱动的可移植性,因此不管你用的是单核的还是多核的 SOC,都将其当做多核 SOC 来编写驱动程序。
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2021-08-11 12:50:23  更:2021-08-11 12:51:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/28 4:52:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计