Linux 自旋锁操作实验-基于正点原子IMX6ULL开发板

1 自旋锁
1.1 自旋锁简介

原子操作只能对整形变量或者位进行保护,但是,在实际的使用环境中怎么可能只有整形变量或位这么简单的临界区。举个最简单的例子,设备结构体变量就不是整型变量,我们对于结构体中成员变量的操作也要保证原子性,在线程 A 对结构体变量使用期间,应该禁止其他的线程来访问此结构体变量,这些工作原子操作都不能胜任,需要本节要讲的锁机制,在 Linux
内核中就是自旋锁。

当一个线程要访问某个共享资源的时候首先要先获取相应的锁,锁只能被一个线程持有,只要此线程不释放持有的锁,那么其他的线程就不能获取此锁。对于自旋锁而言,如果自旋锁正在被线程 A 持有,线程 B 想要获取自旋锁,那么线程 B 就会处于忙循环-旋转-等待状态,线程 B 不会进入休眠状态或者说去做其他的处理,而是会一直傻傻的在那里“转圈圈”的等待锁可用。比如现在有个公用电话亭,一次肯定只能进去一个人打电话,现在电话亭里面有人正在打电话,相当于获得了自旋锁。此时你到了电话亭门口,因为里面有人,所以你不能进去打电话,相当于没有获取自旋锁,这个时候你肯定是站在原地等待,你可能因为无聊的等待而转圈圈消遣时光,反正就是哪里也不能去,要一直等到里面的人打完电话出来。终于,里面的人打完电话出来了,相当于释放了自旋锁,这个时候你就可以使用电话亭打电话了,相当于获取到了自旋锁。

自旋锁的“自旋”也就是“原地打转”的意思,“原地打转”的目的是为了等待自旋锁可以用,可以访问共享资源。把自旋锁比作一个变量 a,变量 a=1 的时候表示共享资源可用,当 a=0的时候表示共享资源不可用。现在线程 A 要访问共享资源,发现 a=0(自旋锁被其他线程持有),那么线程 A 就会不断的查询 a 的值,直到 a=1。从这里我们可以看到自旋锁的一个缺点:那就等待自旋锁的线程会一直处于自旋状态,这样会浪费处理器时间,降低系统性能,所以自旋锁的持有时间不能太长。所以自旋锁适用于短时期的轻量级加锁,如果遇到需要长时间持有锁的场景那就需要换其他的方法了,这个我们后面会讲解。

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

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 lock; //定义自旋锁

定义好自旋锁变量以后就可以使用相应的 API 函数来操作自旋锁。

1.2 自旋锁API

自旋锁基本 API 函数:

DEFINE_SPINLOCK(spinlock_t 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) 尝试获取指定的自旋锁,如果没有获取到就返回 0
int spin_is_locked(spinlock_t *lock)检查指定的自旋锁是否被获取,如果没有被获取就返回非 0,否则返回 0

线程与中断并发访问处理 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)将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。

一般在线程中使用 spin_lock_irqsave/spin_unlock_irqrestore,在中断中使用 spin_lock/spin_unlock,示例代码如下所示:

 DEFINE_SPINLOCK(lock) /* 定义并初始化一个锁 */
 /* 线程 A */
void functionA (){
unsigned long flags; /* 中断状态 */
spin_lock_irqsave(&lock, flags) /* 获取锁 */
 /* 临界区 */
 spin_unlock_irqrestore(&lock, flags) /* 释放锁 */
 }

 /* 中断服务函数 */
void irq() {
 spin_lock(&lock) /* 获取锁 */
 /* 临界区 */
 spin_unlock(&lock) /* 释放锁 */
 }

2 自旋锁实验
上一个实验我们使用原子变量实现了一次只能有一个应用程序访问 LED 灯,本节我们使用自旋锁来实现此功能。在使用自旋锁之前,先回顾一下自旋锁的使用注意事项:
①、自旋锁保护的临界区要尽可能的短,因此在 open 函数中申请自旋锁,然后在 release 函数中释放自旋锁的方法就不可取。我们可以使用一个变量来表示设备的使用情况,如果设备被使用了那么变量就加一,设备被释放以后变量就减 1,我们只需要使用自旋锁保护这个变量即可。
②、考虑驱动的兼容性,合理的选择 API 函数。
综上所述,在本节例程中,我们通过定义一个变量 dev_stats 表示设备的使用情况,dev_stats为 0 的时候表示设备没有被使用,dev_stats 大于 0 的时候表示设备被使用。驱动 open 函数中先判断 dev_stats 是否为 0,也就是判断设备是否可用,如果为 0 的话就使用设备,并且将 dev_stats加 1,表示设备被使用了。使用完以后在 release 函数中将 dev_stats 减 1,表示设备没有被使用了。因此真正实现设备互斥访问的是变量 dev_stats,但是我们要使用自旋锁对 dev_stats 来做保护。

2.1 实验程序编写
1、本次实验在原子操作实验基础上完成,新建工程,spinlock.c驱动文件内容如下:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/atomic.h>

#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"
#define LEDOFF 0
#define LEDON 1

/*gpioled 设备结构体*/
struct gpioled_dev
{
    dev_t devid;
    int major;
    int minor;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    int led_gpio;

    int dev_status;  /*0表示设备可以使用,1表示不可使用*/
    spinlock_t lock; /* 自旋锁 */
};

struct gpioled_dev gpioled; /*LED*/

static int led_open(struct inode *inode, struct file *filp)
{
    unsigned long irqflag;

    filp->private_data = &gpioled;
    //spin_lock(&gpioled.lock);
    spin_lock_irqsave(&gpioled.lock, irqflag); /* 上锁 */

    if (gpioled.dev_status)
    { /* 如果设备被使用了 */
        //spin_unlock(&gpioled.lock);
        spin_unlock_irqrestore(&gpioled.lock, irqflag); /* 解锁 */
        return -EBUSY;
    }
    gpioled.dev_status++; /* 如果设备没有打开,标记被使用*/

    //spin_unlock(&gpioled.lock);
    spin_unlock_irqrestore(&gpioled.lock, irqflag); /* 解锁 */
    return 0;
}

static int led_release(struct inode *inode, struct file *filp)
{
    unsigned long irqflag;
    struct gpioled_dev *dev = filp->private_data;

    /* 关闭驱动文件的时候将 dev_stats 减 1 */
    //spin_lock(&dev->lock);
    spin_lock_irqsave(&dev->lock, irqflag); /* 上锁 */
    if (dev->dev_status)
    {
        dev->dev_status--; /*标记驱动可以使用*/
    }
    //spin_unlock(&dev->lock);
    spin_unlock_irqrestore(&dev->lock, irqflag); /* 解锁 */
    return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf,
                         size_t count, loff_t *ppos)
{
    int ret;
    unsigned char databuf[1];
    struct gpioled_dev *dev = filp->private_data;

    ret = copy_from_user(databuf, buf, count);
    if (ret < 0)
    {
        return -EINVAL;
    }

    if (databuf[0] == LEDON)
    {
        gpio_set_value(dev->led_gpio, 0);
    }
    else if (databuf[0] == LEDOFF)
    {
        gpio_set_value(dev->led_gpio, 1);
    }
    return 0;
}

/*操作集*/
static const struct file_operations led_fops = {
    .owner = THIS_MODULE,
    .write = led_write,
    .open = led_open,
    .release = led_release,
};

/*驱动入口函数*/
static int __init led_init(void)
{
    int ret = 0;

    /*1, 初始化自旋锁*/
    spin_lock_init(&gpioled.lock);
    gpioled.dev_status = 0;

    /*1,注册字符设备驱动*/
    gpioled.major = 0;
    if (gpioled.major)
    { /*给定主设备号*/
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT, "GPIOLED_NAME");
    }
    else
    {
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, "GPIOLED_NAME");
        gpioled.major = MAJOR(gpioled.devid);
        gpioled.minor = MINOR(gpioled.devid);
    }
    printk("gpioled major =%d, minor =%d \r\n", gpioled.major, gpioled.minor);

    /*2,初始化cdev*/
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &led_fops);

    /*3,添加cdev*/
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

    /*4,创建类*/
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class))
    {
        return PTR_ERR(gpioled.class);
    }
    /*5,创建设备*/
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device))
    {
        return PTR_ERR(gpioled.device);
    }

    /*1,获取设备节点*/
    gpioled.nd = of_find_node_by_path("/gpioled");
    if (gpioled.nd == NULL)
    {
        ret = -EINVAL;
        goto fail_findnode;
    }

    /*2,获取LED所对应的GPIO*/
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
    if (gpioled.led_gpio < 0)
    {
        printk("can't find led gpio\r\n");
        ret = -EINVAL;
        goto fail_findnode;
    }
    printk("led gpio num =%d\r\n", gpioled.led_gpio);

    /*3,申请IO*/
    ret = gpio_request(gpioled.led_gpio, "led-gpio");
    if (ret)
    {
        printk("Failed to request the led gpio\r\n");
        ret = -EINVAL;
        goto fail_findnode;
    }

    /*4,使用IO,设置为输出*/
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if (ret)
    {
        goto fail_setoutput;
    }

    /*5,输出低电平,点亮led灯*/
    gpio_set_value(gpioled.led_gpio, 0);

    return 0;

fail_setoutput:
    gpio_free(gpioled.led_gpio);
fail_findnode:

    return ret;
}

/*驱动出口函数*/
static void __exit led_exit(void)
{
    /*关灯*/
    gpio_set_value(gpioled.led_gpio, 1);

    /*注销字符设备驱动*/
    cdev_del(&gpioled.cdev);
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);

    /*释放IO*/
    gpio_free(gpioled.led_gpio);
}

module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("supersmart");

第一步:驱动入口函数 led_init 中调用 spin_lock_init 函数初始化自旋锁。
第二步:在open函数中使用自旋锁实现对设备的互斥访问。
第三步:在 release 函数中将 dev_stats 减 1,表示设备被释放了,可以被其他的应用程序使用。将 dev_stats 减 1 的时候需要自旋锁对其进行保护。

2、测试APP内容如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

/*
 *argc:应用程序参数个数
 * argv[]:具体的参数内容,字符串形式
 * ./spinlockAPP <filename> <0:1> 0 关灯,1 开灯
 * ./spinlockAPP /dev/gpioled 0 关灯
 * ./spinlockAPP /dev/gpioled 1 开灯
 */

#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];
    unsigned char cnt;

    if (argc != 3)
    {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    fd = open(filename, O_RDWR);
    if (fd < 0)
    {
        printf("file %s open failed!\r\n", filename);
        return -1;
    }

    databuf[0] = atoi(argv[2]); /*将字符转化为数字*/
    retvalue = write(fd, databuf, sizeof(databuf));
    if (retvalue < 0)
    {
        printf("LED Control Failed ! \r\n");
        close(fd);
        return -1;
    }
    
    /*模拟应用占用驱动25s*/
    while(1)
    {
        sleep(5);
        cnt++;
        printf("App runing times:%d\r\n",cnt);
        if(cnt>=5) break;
    }
    printf("App runing finish!\r\n");
    close(fd);
    return 0;
}

3 运行测试
1、编译驱动程序(略)
2、编译测试 APP (略)
3、运行测试
加载驱动
在这里插入图片描述

先执行./spinlockApp /dev/gpioled 1& 命令,让 spinlockAPP 软件模拟占用 25S 的 LED 灯。

再执行./spinlockApp /dev/gpioled 0 命令,驱动正常工作的话并不会马上关闭 LED 灯,会提示你“file /dev/gpioled open failed!”,必须等待第一个 spinlockApp 软件运行完成(25S 计时结束)才可以再次操作 LED 灯。
在这里插入图片描述
卸载驱动
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值