STM32MP157驱动开发——Linux并发与竞争(实验)


参考资料:

gpio子系统下的LED驱动

0.前言

  在上一节介绍了Linux中常用的处理并发和竞争的机制:原子操作、自旋锁、信号量和互斥体。这节就通过实验进一步了解这些机制。

一、原子操作实验

目的
使用原子操作实现对LED设备的互斥访问。本次实验在之前的gpioled驱动基础上进行修改。
步骤
1.设备树修改(与gpio驱动相同,无需修改)
2.驱动程序及测试App修改
3.编译及运行测试

驱动修改

直接将之前的驱动文件复制,并在其基础上进行修改。
①首先在LED设备结构体gpioled_dev中添加一个atomic_t类型的变量,即为设备添加一个原子变量的属性:

/*gpioled设备结构体*/
struct gpioled_dev{
    dev_t devid;            /*设备号*/
    struct cdev cdev;       /*cdev*/
    struct class *class;    /*类*/
    struct device *device;  /*设备*/
    int major;              /*主设备号*/
    int minor;              /*次设备号*/
    struct device_node *nd;    /*设备节点*/
    int led_gpio;              /*led所使用的GPIO编号*/

    atomic_t lock;			/*原子变量
};

②在led_init()函数中,对这个原子变量进行初始化,并赋值为1,表示有一个可用的LED设备:

static int __init led_init(void)
{
......
    /*初始化原子变量*/
    gpioled.lock = (atomic_t)ATOMIC_INIT(0);
    /*设置初始值为1*/
    atomic_set(&gpioled.lock, 1);
......
}

③在led_open()函数中,每次需要对设备操作之前,先调用atomic_dec_and_test()函数查看LED的状态,如果此函数的返回结果为0,表示当前有可用设备;如果小于0,则表示没有,此时需要再将原子值+1,返回LED忙状态:

static int led_open(struct inode *inode, struct file *filp)
{
    /*通过判断原子变量的值来检车LED是否被其他任务使用*/
    if(!atomic_dec_and_test(&gpioled.lock)){    /*函数部分:lock值减1,为0返回真,否则为假*/
        atomic_inc(&gpioled.lock);  /*如果小于0就加1,使其原子变量等于0*/
        return -EBUSY;  /*返回LED忙状态*/
    }

    filp->private_data = &gpioled; /* 设置私有数据 */
    return 0;
}

④led_release()函数中,增添对原子操作变量的释放步骤,也就是将该值+1,表示增加了一个可用设备:

static int led_release(struct inode *inode, struct file *filp)
{
    struct gpioled_dev *dev = filp->private_data;
    /*关闭驱动文件时释放原子变量*/
    atomic_inc(&dev->lock);

    return 0;
}

测试App修改

在关闭LED设备文件之前,使用循环模拟占用LED一段时间,这期间LED设备的原子操作变量被占用,所以其它任务无法使用LED设备。

int main(){
//......open(){...}
	while(1){
	        sleep(5);
	        cnt++;
	        printf("App running times: %d\r\n", cnt);
	        if(cnt >= 5)break;
	}
//close(){...}......
}

编译及测试

编译方式与之前的操作相同,使用Makefile编译出.ko文件,使用交叉编译工具编译测试App文件,将编译出的结果放入nfs的对应目录下,运行。
在这里插入图片描述
使用./Atomic_App /dev/gpioled 1 &命令后,开发板LED打开,'&'符号表示将这个软件在后台运行。开发板终端输出App运行次数。此时再使用./Atomic_App /dev/gpioled 0命令关闭LED时,会发现操作失败。原因是当前原子变量正在被使用,其他程序无法操作,只有等到后台程序运行结束,原子变量被释放后才可以继续操作。

二、自旋锁实验

  自旋锁要求保护的临界区尽可能短,所以在原子操作实验里,open()函数中获取,release()函数中释放的方式就不适用了。可以更改为使用一个变量来代表设备的使用情况,使用自旋锁来保护这个变量的操作即可。

驱动修改

新建8_spinlock工程,在7_atomic的基础上进行修改
①在设备结构体中添加设备状态变量dev_stat,添加自旋锁变量spinlock_t

/*gpioled设备结构体*/
struct gpioled_dev{
    dev_t devid;            /*设备号*/
    struct cdev cdev;       /*cdev*/
    struct class *class;    /*类*/
    struct device *device;  /*设备*/
    int major;              /*主设备号*/
    int minor;              /*次设备号*/
    struct device_node *nd;    /*设备节点*/
    int led_gpio;              /*led所使用的GPIO编号*/

    int dev_stats;      /*表示设备状态,0:设备未使用,>0:设备已被占用*/
    spinlock_t lock;    /*自旋锁*/
};

②在led_open()函数中,每次需要改变设备状态时进行自旋锁状态判断

static int led_open(struct inode *inode, struct file *filp)
{
    unsigned long flags;
    filp->private_data = &gpioled; /*设置私有数据 */

    spin_lock_irqsave(&gpioled.lock, flags);    /*保存中断状态,禁止本地中断,获取自旋锁*/
    if(gpioled.dev_stats){
        spin_unlock_irqrestore(&gpioled.lock, flags);   /*设备已经被使用,就将中断恢复到之前状态,激活本地终端,释放锁*/
        return -EBUSY;
    }
    gpioled.dev_stats++;        /*设备没有被占用,那么就将其标记为已打开*/
    spin_unlock_irqrestore(&gpioled.lock, flags);   /*解锁*/
    return 0;
}

③在led_release()函数中,对设备状态进行释放,同时也需要对自旋锁状态进行判断

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

    /*关闭驱动文件时,将dev_stat减1*/
    spin_lock_irqsave(&dev->lock, flags);   /*上锁*/
    if(dev->dev_stats){
        dev->dev_stats--;
    }
    spin_unlock_irqrestore(&dev->lock, flags);  /*上锁*/
    return 0;
}

④在led_init()函数中对自旋锁进行初始化:spin_lock_init(&gpioled.lock);

编译及运行测试

可使用原子操作中的程序进行测试,将本节的驱动编译后运行
在这里插入图片描述

三、信号量实验

信号量可以导致休眠,因此信号量保护的临界区没有运行时间限制,可以在驱动的 open 函数申请信号量,然后在release 函数中释放信号量。但是信号量不能用在中断中,所以不会在中断中使用信号量。

驱动修改

新建9_semaphore工程,在7_atomic的基础上进行修改
①使用信号量必须添加<linux/semaphore.h>头文件,然后在设备结构体中添加一个信号量成员变量 sem

/*gpioled设备结构体*/
struct gpioled_dev{
    dev_t devid;            /*设备号*/
    struct cdev cdev;       /*cdev*/
    struct class *class;    /*类*/
    struct device *device;  /*设备*/
    int major;              /*主设备号*/
    int minor;              /*次设备号*/
    struct device_node *nd;    /*设备节点*/
    int led_gpio;              /*led所使用的GPIO编号*/

    struct semaphore sem;   /*信号量*/
};

②在led_open()函数和led_release()函数中,添加对信号量的可用判断

static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &gpioled; /*设置私有数据 */

    /*获取信号量*/
    if(down_interruptible(&gpioled.sem)){   /*获取信号量,进入休眠状态的进程可以被信号打断,这时候count为0*/
        return -ERESTARTSYS;
    }
#if 0
    down(&gpioled.sem);     /*不能被信号打断*/
#endif
    return 0;
}

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

    up(&dev->sem);  /*释放信号量,信号量count加1*/
    return 0;
}

当信号量值大于1,设备可用,否则不可用,程序进入休眠。获取信号量的方式可以用能被信号打断的方式,也可以不被信号打断,等待内核调度唤醒。
③在led_init()函数中将信号量初始化为1

sema_init(&gpioled.sem, 1);

编译及运行测试同上,这里不再赘述。

四、互斥体实验

  最适合这种程序互斥的使用方式,其实应该是互斥体。其他方式的使用方式与互斥体的实现机制相似。
驱动的修改与前面的几种相似,都是添加设备的对应属性,这里为添加互斥体变量:

/*gpioled设备结构体*/
struct gpioled_dev{
    dev_t devid;            /*设备号*/
    struct cdev cdev;       /*cdev*/
    struct class *class;    /*类*/
    struct device *device;  /*设备*/
    int major;              /*主设备号*/
    int minor;              /*次设备号*/
    struct device_node *nd;    /*设备节点*/
    int led_gpio;              /*led所使用的GPIO编号*/

    struct mutex lock;      /*互斥体变量*/
};

初始化:mutex_init(&gpioled.lock);
然后修改led_open()和led_release()函数的相应判断:

static int led_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &gpioled; /*设置私有数据 */

    /*获取信号量*/
    if(mutex_lock_interruptible(&gpioled.lock)){   /*获取信号量,进入休眠状态的进程可以被信号打断,这时候count为0*/
        return -ERESTARTSYS;
    }
#if 0
    mutex_lock(&gpioled.lock);     /*不能被信号打断*/
#endif
    return 0;
}

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

    mutex_unlock(&dev->lock);  /*释放信号量,信号量count加1*/
    return 0;
}

然后进行编译测试。

五、总结

  本节主要对四种处理并发和竞争的机制进行操作实验,以实现LED驱动文件的单一程序访问为目的,分别使用四种方式进行实现。
  在实验过程中,主要应该针对这四种不同措施的不同特点进行程序的逻辑设计。其中包括:①自旋锁要求保护的临界区尽可能短,所以设计一个LED的状态变量,由自旋锁对状态值的改变进行保护;②原子操作只能对整型变量或者位进行保护,所以无法让其保护事件性质(表现为结构体类型)的属性;③自旋锁可以在中断中使用,但加锁解锁前需要对中断环境进行存档和读档,并且需要操作本地中断;④信号量不能用于中断中,且开销比自旋锁大,但可以允许线程进入休眠状态,之后再由信号量唤醒,适用于资源占用比较久的场合;⑤当信号量的值为1时(可以等价于只有一个可用资源),此时信号量就退化成(或等价于)互斥体,(并不完全相等,信号量可能会引起优先级翻转问题)。互斥体同样不能用于中断,但是可以允许程序休眠,且可以递归上锁解锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值