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) {
if (lock == 0 && CAS(lock, 0, pid) == 1)
return;
if (CPU_count > 1 ) {
for (n = 1; n < 2048; n <<= 1) {
for (i = 0; i < n; i++)
pause();
if (lock == 0 && CAS(lock, 0, pid))
return;
}
}
sched_yield();
}
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;
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;
iClkVal = readl(IMX6U_CLK_CCGR1);
iClkVal &= ~(3 << 26);
iClkVal |= (3 << 26);
writel(iClkVal,IMX6U_CLK_CCGR1);
}
static void set_gpio1_mux_fun(void)
{
u32 iVal;
writel(5,SW_MUX_CTL_PAD_GPIO1_IO03);
writel(0x000018b9,IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03);
iVal = readl(IMX6UL_GPIO1_GDIR);
iVal &=(1 << 3);
iVal |=(1 << 3);
writel(iVal,IMX6UL_GPIO1_GDIR);
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);
}
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;
}
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)
{
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;
memory_map();
gpio1_clk_enable();
set_gpio1_mux_fun();
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
{
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);
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;
}
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;
}
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);
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>
#include <linux/kernel.h>
#include <asm/io.h>
#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
#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)
#define SW_PAD_CTL_PAD_GPIO1_IO03_BASE (SW_MUX_BASE + 0x2F4)
#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;
void __iomem *SW_MUX_CTL_PAD_GPIO1_IO03;
void __iomem *IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03;
void __iomem *IMX6UL_GPIO1_DR;
void __iomem *IMX6UL_GPIO1_GDIR;
void __iomem *IMX6UL_GPIO1_PSR;
void __iomem *IMX6UL_GPIO1_ICR1;
void __iomem *IMX6UL_GPIO1_ICR2;
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;
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);
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 来编写驱动程序。
|