嵌入式linux驱动之并发
嵌入式linux驱动之内核竞态_寒听雪落的博客-CSDN博客
一, linux 系统并发产生的原因很复杂,主要下面几个原因:
1、多线程并发访问,linux 是多任务(线程)的系统,所以多线程访问是最基本的原因。
2、抢占式并发访问,从内核2.6版本开始,linux 内核支持抢占,也就是说调度程序可以在任意时刻抢占正在运行的线程,从而运行其他的线程。
3、中断程序并发访问,学过STM32应该知道,硬件中断的权利可是很大的。
4、SMP(多核)核间并发访问,现在ARM架构的多核SOC很常见,多核CPU存在核间并发访问。并发访问带来的问题就是竞争,学过FreeRTOS和UCOS的同学应该知道临界区这个概念,所谓的临界区就是共享数据段,对于临界区必须保证一次只有一个线程访问,也就是要保证临界区是原子访问的,这里的原子访问就表示这一个访问是一个步骤,不能再进行拆分。如果多个线程同时操作临界区就表示存在竞争,我们在编写驱动的时候一定要注意避免并发和防止竞争访问。我们一般在编写驱动的时候就要考虑到并发与竞争,而不是驱动都编写完了然后再处理并发与竞争。
二,驱动中原子操作函数分析
1,atomic64_t //在 type.h 头文件中,对于原子变量的结构体的定义
typedef struct
{
long counter;
}
2,atomic64_read函数
long long atomic64_read(const atomic64_t *v); //读取原子变量所使用到的函数
3,atomic64_set函数
void atomic64_set(atomic64_t *v, long long i); //原子变量设置函数,用来设置原子变量的数值
参数分析:
• *v:atomic64_t 结构体地址
• i:需要设置的值
• 返回值:空
4,atomic64_add函数
void atomic64_##op(long long a, atomic64_t *v);
含义:原子变量加法函数,作用是把指定的 atomic64_t 结构体的值加上指定的数值。这个函数用op代替了add这个函数,因为其同样也承载了减法函数的功能,使用op来复用了函数。
参数分析:
• a:指定的数
• *v:atomic64_t 结构体
• 返回值:空
5,atomic64_sub函数
void atomic64_##op(long long a, atomic64_t *v);
含义:既然有加,那就也应该有减,这是原子变量的减法函数,使用方法和加法函数一样。
参数分析:
• a:指定的数
• *v:atomic64_t 结构体
• 返回值:空
6,atomic64_inc函数
#define atomic64_inc(v) atomic64_add(1, (v)) //其中参数v是指atomic64_t 结构体
含义:原子变量自增函数,调用这个函数可让指定函数自增一。原理就是通过 atomic64_add 函数加一。
7,atomic64_dec函数
#define atomic64_dec(v) atomic64_sub(1, (v)) //其中参数v是指atomic64_t 结构体
含义:原子变量自减函数,与自增的原理一样,是通过 atomic64_sub 函数减一。
三,驱动程序源码
//添加头文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
#define ZYNQMP_GPIO_NR_GPIOS 118
#define MIO_PIN_51 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 51)
// #define MIO_PIN_38 (ARCH_NR_GPIOS - ZYNQMP_GPIO_NR_GPIOS + 38)
//设置一个设备全局变量
struct lock_device
{
dev_t devno;
struct cdev cdev;
struct class *class;
struct device *device;
atomic_t lock;
} lock_dev;
int lock_open(struct inode *inode, struct file *filp)
{
printk("-lock_open-\n");
if (!atomic_read(&lock_dev.lock)) //读取锁的状态
atomic_inc(&lock_dev.lock); //把原子变量加 1, 上锁
else
return -EBUSY; //若检测到已上锁,则返回设备忙
return 0;
}
ssize_t lock_write(struct file *flip, const char __user *buf, size_t count, loff_t *fops)
{
int flag = 0, i = 0;
flag = copy_from_user(&i, buf, count); //使用copy_from_user读取用户态发送过来的数据
printk(KERN_CRIT "flag = %d, i = %d, count = %d\n", flag, i, count);
if (flag != 0)
{
printk("Kernel receive data failed!\n");
return 1;
}
if (i == 48)
{
gpio_set_value(MIO_PIN_51, 0);
// gpio_set_value(MIO_PIN_38, 0);
}
else
{
gpio_set_value(MIO_PIN_51, 1);
// gpio_set_value(MIO_PIN_38, 1);
}
return 0;
}
int lock_close(struct inode *inode, struct file *filp)
{
printk("-lock_close-\n");
atomic_set(&lock_dev.lock, 0); //将变量设为0,意为解锁
return 0;
}
const struct file_operations lock_fops = {
.open = lock_open,
.write = lock_write,
.release = lock_close,
};
//实现装载入口函数和卸载入口函数
static __init int lock_drv_init(void)
{
int ret = 0;
printk("----^v^-----lock drv v1 init\n");
//动态申请设备号
ret = alloc_chrdev_region(&lock_dev.devno, 0, 1, "lock_device");
if (ret < 0)
{
printk("alloc_chrdev_region fail!\n");
return 0;
}
//设备初始化
cdev_init(&lock_dev.cdev, &lock_fops);
lock_dev.cdev.owner = THIS_MODULE;
//自动创建设备节点
//创建设备的类别
//参数1----设备的拥有者,当前模块,直接填THIS_MODULE
//参数2----设备类别的名字,自定义
//返回值:类别结构体指针,其实就是分配了一个结构体空间
lock_dev.class = class_create(THIS_MODULE, "lock_class");
if (IS_ERR(lock_dev.class))
{
printk("class_create fail!\n");
return 0;
}
//创建设备
//参数1----设备对应的类别
//参数2----当前设备的父类,直接填NULL
//参数3----设备节点关联的设备号
//参数4----私有数据直接填NULL
//参数5----设备节点的名字
lock_dev.device = device_create(lock_dev.class, NULL, lock_dev.devno, NULL, "lock_device");
if (IS_ERR(lock_dev.device))
{
printk("device_create fail!\n");
return 0;
}
//向系统注册一个字符设备
cdev_add(&lock_dev.cdev, lock_dev.devno, 1);
//MIO_PIN_51 38申请GPIO口
ret = gpio_request(MIO_PIN_51, "led1");
if (ret < 0)
{
printk("gpio request led1 error!\n");
return ret;
}
//GPIO口方向设置成输出
ret = gpio_direction_output(MIO_PIN_51, 1);
if (ret != 0)
{
printk("gpio direction output MIO_PIN_51 fail!\n");
}
//将原子变量置0,相当于初始化
atomic_set(&lock_dev.lock, 0);
return 0;
}
static __exit void lock_drv_exit(void)
{
printk("----^v^-----lock drv v1 exit\n");
//释放按键GPIO
gpio_free(MIO_PIN_51);
// gpio_free(MIO_PIN_38);
//注销字符设备
cdev_del(&lock_dev.cdev);
//删除设备节点
device_destroy(lock_dev.class, lock_dev.devno);
//删除设备类
class_destroy(lock_dev.class);
//注销设备号
unregister_chrdev_region(lock_dev.devno, 1);
}
//申明装载入口函数和卸载入口函数
module_init(lock_drv_init);
module_exit(lock_drv_exit);
//添加GPL协议
MODULE_LICENSE("GPL");
MODULE_AUTHOR("subomb");
四,应用程序分析
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd, ret = 0;
char *filename;
char writebuf[1] = {0};
filename = argv[1];
fd = open(filename, O_RDWR); //打开设备
if (fd < 0)
{
printf("Can't open file %s\n", filename);
return -1;
}
//通过驱动程序接口,发送指令至驱动程序,此处 0 代表灯灭,1 代表灯亮
memcpy(writebuf, argv[2], 1); //将内容拷贝到缓冲区
ret = write(fd, writebuf, 1); //写数据
if (ret < 0)
{
printf("Write file %s failed!\n", filename);
}
else
{
printf("Write file success!\n");
}
sleep(15); //线程暂停15秒,留出时间测试
printf("Finish.\n");
ret = close(fd); //关闭设备
if (ret < 0)
{
printf("Can't close file %s\n", filename);
return -1;
}
return 0;
}