i.MX6ULL|并发与竞争实验

描述

常用的处理并发和竞争的机制有四种,原子操作、自旋锁、信号量和互斥体。下边就通过编写驱动来实现,展示一下相关效果。当前台的应用一直运行,控制台是不能输入指令,测试并发与竞争最好是在后台运行,而解决并非与竞争最直接的手段就是只允许一个应用去调用相关资源,这里为了好展示效果就通过任务运行来体现。下面的实验使用了新字符驱动GPIO源码,只需要复制一份即可使用。

原子操作

驱动源码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* 添加头文件 */
#include 
#include 
#include 
#include 

#define CHRDEVBASE_CNT            1       /* 设备号个数 */
#define CHRDEVBASE_NAME     "chrdevbase"   /* 名字 */

#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */

/* chrdevbase 设备结构体 */
struct newchr_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;          /* 原子变量 */
};

struct newchr_dev chrdevbase;/* 自定义字符设备 */

/*
* @description : LED 硬件初始化
* @param : 无
* @return : 无
*/
static int led_hal_init(void)
{
    int ret = 0;

    /* 设置 LED 所使用的 GPIO */
    /* 1、获取设备节点:gpioled */
    chrdevbase.nd = of_find_node_by_path("/gpioled");
    if(chrdevbase.nd == NULL) {
        printk("chrdevbase node cant not found!
");
            return -EINVAL;
        } else {
        printk("chrdevbase node has been found!
");
    }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    chrdevbase.led_gpio = of_get_named_gpio(chrdevbase.nd, "led-gpio"0);
    if(chrdevbase.led_gpio < 0) {
        printk("can't get led-gpio");
        return -EINVAL;
    }
    printk("led-gpio num = %d
", chrdevbase.led_gpio);

    /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(chrdevbase.led_gpio, 1);
    if(ret < 0) {
        printk("can't set gpio!
");
    }

    return 0;
}

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    /* 通过判断原子变量的值来检查 LED 有没有被别的应用使用 */
    if (!atomic_dec_and_test(&chrdevbase.lock)) 
    {
        atomic_inc(&chrdevbase.lock);   /* 小于 0 的话就加 1,使其原子变量等于 0 */
        return -EBUSY;  /* LED 被使用,返回忙 */
    }
    printk("[BSP]chrdevbase open!
");
    filp->private_data = &chrdevbase; /* 设置私有数据 */
    return 0;
}

/*
 * @description        : 从设备读取数据 
 * @param - filp     : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    printk("chrdevbase read!
");
    return 0;
}

/*
 * @description        : 向设备写数据 
 * @param - filp     : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    char writebuf[1];
    struct newchr_dev *dev = filp->private_data;

    /* 接收用户空间传递给内核的数据并且打印出来 */
    retvalue = copy_from_user(writebuf, buf, cnt);
    printk("[BSP]kernel recevdata data:%d!
",writebuf[0]);

    if(writebuf[0] == LEDON) { 
    gpio_set_value(dev->led_gpio, 0); /* 打开 LED 灯 */
    } else if(writebuf[0] == LEDOFF) {
    gpio_set_value(dev->led_gpio, 1); /* 关闭 LED 灯 */
    }

    // printk("chrdevbase write!
");
    return 0;
}

/*
 * @description        : 关闭/释放设备
 * @param - filp     : 要关闭的设备文件(文件描述符)
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    struct newchr_dev *dev = filp->private_data;
    /* 关闭驱动文件的时候释放原子变量 */
    atomic_inc(&dev->lock);
    printk("[BSP]release!
");
    return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,   
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};

/*
 * @description    : 驱动入口函数 
 * @param         : 无
 * @return         : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
    /* 初始化原子变量 */
    atomic_set(&chrdevbase.lock, 1);    /* 原子变量初始值为 1 */

    /* 初始化硬件 */
      led_hal_init();

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (chrdevbase.major) { /* 定义了设备号 */
        chrdevbase.devid = MKDEV(chrdevbase.major, 0);
        register_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT, CHRDEVBASE_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&chrdevbase.devid, 0, CHRDEVBASE_CNT,CHRDEVBASE_NAME); /* 申请设备号 */
        chrdevbase.major = MAJOR(chrdevbase.devid); /* 获取主设备号 */
        chrdevbase.minor = MINOR(chrdevbase.devid); /* 获取次设备号 */
    }
    printk("newcheled major=%d,minor=%d
",chrdevbase.major,chrdevbase.minor);

    /* 2、初始化 cdev */
    chrdevbase.cdev.owner = THIS_MODULE;
    cdev_init(&chrdevbase.cdev, &chrdevbase_fops);

    /* 3、添加一个 cdev */
    cdev_add(&chrdevbase.cdev, chrdevbase.devid, CHRDEVBASE_CNT);

    /* 4、创建类 */
    chrdevbase.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
    if (IS_ERR(chrdevbase.class)) {
        return PTR_ERR(chrdevbase.class);
    }

    /* 5、创建设备 */
    chrdevbase.device = device_create(chrdevbase.class, NULL,chrdevbase.devid, NULL, CHRDEVBASE_NAME);
    if (IS_ERR(chrdevbase.device)) {
        return PTR_ERR(chrdevbase.device);
    }

    return 0;
}

/*
 * @description    : 驱动出口函数
 * @param         : 无
 * @return         : 无
 */
static void __exit chrdevbase_exit(void)
{
    /* 注销字符设备 */
    cdev_del(&chrdevbase.cdev);/* 删除 cdev */
    unregister_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT);/* 注销设备号 */

    device_destroy(chrdevbase.class, chrdevbase.devid);/* 销毁设备 */
    class_destroy(chrdevbase.class);/* 销毁类 */

    printk("[BSP]chrdevbase exit!
");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

 

应用源码

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

/*
 * @description        : main主程序
 * @param - argc     : argv数组元素个数
 * @param - argv     : 具体参数
 * @return             : 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    char writebuf[100];
    unsigned char databuf[1];
    unsigned char cnt = 0;

    if(argc != 3){
        printf("[APP]Error Usage!
");
        return -1;
    }

    filename = argv[1];

    /* 打开驱动文件 */
    fd  = open(filename, O_RDWR);
    if(fd < 0){
        printf("[APP]Can't open file %s
", filename);
        return -1;
    }

    /* 把第三个参数赋值给databuf */
    databuf[0] = atoi(argv[2]);

    /* 向设备驱动写数据 */
    memcpy(writebuf, databuf, sizeof(databuf));
    retvalue = write(fd, writebuf, sizeof(databuf));
    if(retvalue < 0){
        printf("[APP]write file %s failed!
", filename);
    }

    /* 模拟占用 25S LED */
    while(1)
    {
        sleep(5);
        cnt++;
        printf("App running times:%d
", cnt);
        if(cnt >= 5break;
    }
    printf("App running finished!");

    /* 关闭设备 */
    retvalue = close(fd);
    if(retvalue < 0){
        printf("[APP]Can't close file %s
", filename);
        return -1;
    }

    return 0;
}

 

实验现象

加了原子操作后,应用程序运行时,再次触发是不能运行的,这就解决了在复杂环境下的并发和竞争的问题。注意不加“&”表示直接运行,控制台不能输入指令,加了“&”表示后台运行,可以继续输入指令。

控制台

 

套路分析

1、先在结构体定义一个变量

/* chrdevbase 设备结构体 */
struct newchr_dev{
    ......
    atomic_t lock;          /* 原子变量 */
};

 

2、在驱动入口初始化原子变量

/*
 * @description    : 驱动入口函数 
 * @param         : 无
 * @return         : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
    /* 初始化原子变量 */
    atomic_set(&chrdevbase.lock, 1);    /* 原子变量初始值为 1 */
    ......
    return 0;
}

 

3、在打开设备时判断原子变量

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    /* 通过判断原子变量的值来检查 LED 有没有被别的应用使用 */
    if (!atomic_dec_and_test(&chrdevbase.lock)) 
    {
        atomic_inc(&chrdevbase.lock);   /* 小于 0 的话就加 1,使其原子变量等于 0 */
        return -EBUSY;  /* LED 被使用,返回忙 */
    }
    ......
    return 0;
}

 

4、在释放设备时释放原子变量

/*
 * @description        : 关闭/释放设备
 * @param - filp     : 要关闭的设备文件(文件描述符)
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    struct newchr_dev *dev = filp->private_data;
    /* 关闭驱动文件的时候释放原子变量 */
    atomic_inc(&dev->lock);
    ......
    return 0;
}

 

| 自旋锁

驱动源码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* 添加头文件 */
#include 
#include 
#include 
#include 

#define CHRDEVBASE_CNT            1       /* 设备号个数 */
#define CHRDEVBASE_NAME     "chrdevbase"   /* 名字 */

#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */

/* chrdevbase 设备结构体 */
struct newchr_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;        /* 自旋锁 */
};

struct newchr_dev chrdevbase;/* 自定义字符设备 */

/*
* @description : LED 硬件初始化
* @param : 无
* @return : 无
*/
static int led_hal_init(void)
{
    int ret = 0;

    /* 设置 LED 所使用的 GPIO */
    /* 1、获取设备节点:gpioled */
    chrdevbase.nd = of_find_node_by_path("/gpioled");
    if(chrdevbase.nd == NULL) {
        printk("chrdevbase node cant not found!
");
            return -EINVAL;
        } else {
        printk("chrdevbase node has been found!
");
    }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    chrdevbase.led_gpio = of_get_named_gpio(chrdevbase.nd, "led-gpio"0);
    if(chrdevbase.led_gpio < 0) {
        printk("can't get led-gpio");
        return -EINVAL;
    }
    printk("led-gpio num = %d
", chrdevbase.led_gpio);

    /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(chrdevbase.led_gpio, 1);
    if(ret < 0) {
        printk("can't set gpio!
");
    }

    return 0;
}

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    unsigned long flags;
    filp->private_data = &chrdevbase; /* 设置私有数据 */

    spin_lock_irqsave(&chrdevbase.lock, flags); /* 上锁 */
    if (chrdevbase.dev_stats) { /* 如果设备被使用了 */
        spin_unlock_irqrestore(&chrdevbase.lock, flags); /* 解锁 */
        return -EBUSY;
    }
    chrdevbase.dev_stats++; /* 如果设备没有打开,那么就标记已经打开了 */
    spin_unlock_irqrestore(&chrdevbase.lock, flags);/* 解锁 */

    printk("[BSP]chrdevbase open!
");
    return 0;
}

/*
 * @description        : 从设备读取数据 
 * @param - filp     : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    printk("chrdevbase read!
");
    return 0;
}

/*
 * @description        : 向设备写数据 
 * @param - filp     : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    char writebuf[1];
    struct newchr_dev *dev = filp->private_data;

    /* 接收用户空间传递给内核的数据并且打印出来 */
    retvalue = copy_from_user(writebuf, buf, cnt);
    printk("[BSP]kernel recevdata data:%d!
",writebuf[0]);

    if(writebuf[0] == LEDON) { 
    gpio_set_value(dev->led_gpio, 0); /* 打开 LED 灯 */
    } else if(writebuf[0] == LEDOFF) {
    gpio_set_value(dev->led_gpio, 1); /* 关闭 LED 灯 */
    }

    // printk("chrdevbase write!
");
    return 0;
}

/*
 * @description        : 关闭/释放设备
 * @param - filp     : 要关闭的设备文件(文件描述符)
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    unsigned long flags;
    struct newchr_dev *dev = filp->private_data;
    /* 关闭驱动文件的时候将 dev_stats 减 1 */
    spin_lock_irqsave(&dev->lock, flags);   /* 上锁 */
    if (dev->dev_stats) {
        dev->dev_stats--;
    }
    spin_unlock_irqrestore(&dev->lock, flags);/* 解锁 */

    printk("[BSP]release!
");
    return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,   
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};

/*
 * @description    : 驱动入口函数 
 * @param         : 无
 * @return         : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
    /* 初始化自旋锁 */
    spin_lock_init(&chrdevbase.lock);

    /* 初始化硬件 */
      led_hal_init();

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (chrdevbase.major) { /* 定义了设备号 */
        chrdevbase.devid = MKDEV(chrdevbase.major, 0);
        register_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT, CHRDEVBASE_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&chrdevbase.devid, 0, CHRDEVBASE_CNT,CHRDEVBASE_NAME); /* 申请设备号 */
        chrdevbase.major = MAJOR(chrdevbase.devid); /* 获取主设备号 */
        chrdevbase.minor = MINOR(chrdevbase.devid); /* 获取次设备号 */
    }
    printk("newcheled major=%d,minor=%d
",chrdevbase.major,chrdevbase.minor);

    /* 2、初始化 cdev */
    chrdevbase.cdev.owner = THIS_MODULE;
    cdev_init(&chrdevbase.cdev, &chrdevbase_fops);

    /* 3、添加一个 cdev */
    cdev_add(&chrdevbase.cdev, chrdevbase.devid, CHRDEVBASE_CNT);

    /* 4、创建类 */
    chrdevbase.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
    if (IS_ERR(chrdevbase.class)) {
        return PTR_ERR(chrdevbase.class);
    }

    /* 5、创建设备 */
    chrdevbase.device = device_create(chrdevbase.class, NULL,chrdevbase.devid, NULL, CHRDEVBASE_NAME);
    if (IS_ERR(chrdevbase.device)) {
        return PTR_ERR(chrdevbase.device);
    }

    return 0;
}

/*
 * @description    : 驱动出口函数
 * @param         : 无
 * @return         : 无
 */
static void __exit chrdevbase_exit(void)
{
    /* 注销字符设备 */
    cdev_del(&chrdevbase.cdev);/* 删除 cdev */
    unregister_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT);/* 注销设备号 */

    device_destroy(chrdevbase.class, chrdevbase.devid);/* 销毁设备 */
    class_destroy(chrdevbase.class);/* 销毁类 */

    printk("[BSP]chrdevbase exit!
");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

 

应用源码

和原子操作应用源码一样,不需要修改!

实验现象

控制台

 

套路分析

自旋锁和RTOS中的临界保护有点类似,套路分析如下:

1、在结构体加入自旋锁变量和一个状态变量

/* chrdevbase 设备结构体 */
struct newchr_dev{
    ......
    int dev_stats;          /* 设备状态,0,设备未使用;>0,设备已经被使用 */
    spinlock_t lock;        /* 自旋锁 */
};

 

2、在驱动入口初始化自旋锁

/*
 * @description    : 驱动入口函数 
 * @param         : 无
 * @return         : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
    /* 初始化自旋锁 */
    spin_lock_init(&chrdevbase.lock);
    ......
    return 0;
}

 

3、在打开设备时判断设备是否被使用

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    unsigned long flags;
    filp->private_data = &chrdevbase; /* 设置私有数据 */

    spin_lock_irqsave(&chrdevbase.lock, flags); /* 上锁 */
    if (chrdevbase.dev_stats) { /* 如果设备被使用了 */
        spin_unlock_irqrestore(&chrdevbase.lock, flags); /* 解锁 */
        return -EBUSY;
    }
    chrdevbase.dev_stats++; /* 如果设备没有打开,那么就标记已经打开了 */
    spin_unlock_irqrestore(&chrdevbase.lock, flags);/* 解锁 */

    ......
    return 0;
}

 

4、在释放设备时对状态变量自减

/*
 * @description        : 关闭/释放设备
 * @param - filp     : 要关闭的设备文件(文件描述符)
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    unsigned long flags;
    struct newchr_dev *dev = filp->private_data;
    /* 关闭驱动文件的时候将 dev_stats 减 1 */
    spin_lock_irqsave(&dev->lock, flags);   /* 上锁 */
    if (dev->dev_stats) {
        dev->dev_stats--;
    }
    spin_unlock_irqrestore(&dev->lock, flags);/* 解锁 */

    printk("[BSP]release!
");
    return 0;
}

 

| 信号量

驱动源码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* 添加头文件 */
#include 
#include 
#include 
#include 

#define CHRDEVBASE_CNT            1       /* 设备号个数 */
#define CHRDEVBASE_NAME     "chrdevbase"   /* 名字 */

#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */

/* chrdevbase 设备结构体 */
struct newchr_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;   /* 信号量 */
};

struct newchr_dev chrdevbase;/* 自定义字符设备 */

/*
* @description : LED 硬件初始化
* @param : 无
* @return : 无
*/
static int led_hal_init(void)
{
    int ret = 0;

    /* 设置 LED 所使用的 GPIO */
    /* 1、获取设备节点:gpioled */
    chrdevbase.nd = of_find_node_by_path("/gpioled");
    if(chrdevbase.nd == NULL) {
        printk("chrdevbase node cant not found!
");
            return -EINVAL;
        } else {
        printk("chrdevbase node has been found!
");
    }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    chrdevbase.led_gpio = of_get_named_gpio(chrdevbase.nd, "led-gpio"0);
    if(chrdevbase.led_gpio < 0) {
        printk("can't get led-gpio");
        return -EINVAL;
    }
    printk("led-gpio num = %d
", chrdevbase.led_gpio);

    /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(chrdevbase.led_gpio, 1);
    if(ret < 0) {
        printk("can't set gpio!
");
    }

    return 0;
}

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    printk("[BSP]chrdevbase open!
");
    filp->private_data = &chrdevbase; /* 设置私有数据 */

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

/*
 * @description        : 从设备读取数据 
 * @param - filp     : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    printk("chrdevbase read!
");
    return 0;
}

/*
 * @description        : 向设备写数据 
 * @param - filp     : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    char writebuf[1];
    struct newchr_dev *dev = filp->private_data;

    /* 接收用户空间传递给内核的数据并且打印出来 */
    retvalue = copy_from_user(writebuf, buf, cnt);
    printk("[BSP]kernel recevdata data:%d!
",writebuf[0]);

    if(writebuf[0] == LEDON) { 
    gpio_set_value(dev->led_gpio, 0); /* 打开 LED 灯 */
    } else if(writebuf[0] == LEDOFF) {
    gpio_set_value(dev->led_gpio, 1); /* 关闭 LED 灯 */
    }

    // printk("chrdevbase write!
");
    return 0;
}

/*
 * @description        : 关闭/释放设备
 * @param - filp     : 要关闭的设备文件(文件描述符)
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    struct newchr_dev *dev = filp->private_data;
    up(&dev->sem);  /* 释放信号量,信号量值加 1 */
    printk("[BSP]release!
");
    return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,   
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};

/*
 * @description    : 驱动入口函数 
 * @param         : 无
 * @return         : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
    /* 初始化信号量 */
    sema_init(&chrdevbase.sem, 1);

    /* 初始化硬件 */
      led_hal_init();

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (chrdevbase.major) { /* 定义了设备号 */
        chrdevbase.devid = MKDEV(chrdevbase.major, 0);
        register_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT, CHRDEVBASE_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&chrdevbase.devid, 0, CHRDEVBASE_CNT,CHRDEVBASE_NAME); /* 申请设备号 */
        chrdevbase.major = MAJOR(chrdevbase.devid); /* 获取主设备号 */
        chrdevbase.minor = MINOR(chrdevbase.devid); /* 获取次设备号 */
    }
    printk("newcheled major=%d,minor=%d
",chrdevbase.major,chrdevbase.minor);

    /* 2、初始化 cdev */
    chrdevbase.cdev.owner = THIS_MODULE;
    cdev_init(&chrdevbase.cdev, &chrdevbase_fops);

    /* 3、添加一个 cdev */
    cdev_add(&chrdevbase.cdev, chrdevbase.devid, CHRDEVBASE_CNT);

    /* 4、创建类 */
    chrdevbase.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
    if (IS_ERR(chrdevbase.class)) {
        return PTR_ERR(chrdevbase.class);
    }

    /* 5、创建设备 */
    chrdevbase.device = device_create(chrdevbase.class, NULL,chrdevbase.devid, NULL, CHRDEVBASE_NAME);
    if (IS_ERR(chrdevbase.device)) {
        return PTR_ERR(chrdevbase.device);
    }

    return 0;
}

/*
 * @description    : 驱动出口函数
 * @param         : 无
 * @return         : 无
 */
static void __exit chrdevbase_exit(void)
{
    /* 注销字符设备 */
    cdev_del(&chrdevbase.cdev);/* 删除 cdev */
    unregister_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT);/* 注销设备号 */

    device_destroy(chrdevbase.class, chrdevbase.devid);/* 销毁设备 */
    class_destroy(chrdevbase.class);/* 销毁类 */

    printk("[BSP]chrdevbase exit!
");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

 

应用源码

和原子操作应用源码一样,不需要修改!

 

实验现象

使用信号量不会出现设备打不开的问题,它会在任务结束后再次执行!

控制台

 

套路分析

1、在结构体加入信号量

/* chrdevbase 设备结构体 */
struct newchr_dev{
    ......
    struct semaphore sem;   /* 信号量 */
};

 

2、在驱动入口初始信号量

/*
 * @description    : 驱动入口函数 
 * @param         : 无
 * @return         : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
    /* 初始化信号量 */
    sema_init(&chrdevbase.sem, 1);
    ......
    return 0;

 

3、在打开设备时获取信号量

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    printk("[BSP]chrdevbase open!
");
    filp->private_data = &chrdevbase; /* 设置私有数据 */

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

 

4、在释放设备时释放信号量

/*
 * @description        : 关闭/释放设备
 * @param - filp     : 要关闭的设备文件(文件描述符)
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    struct newchr_dev *dev = filp->private_data;
    up(&dev->sem);  /* 释放信号量,信号量值加 1 */
    printk("[BSP]release!
");
    return 0;
}

 

| 互斥体

驱动源码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/* 添加头文件 */
#include 
#include 
#include 
#include 

#define CHRDEVBASE_CNT            1       /* 设备号个数 */
#define CHRDEVBASE_NAME     "chrdevbase"   /* 名字 */

#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */

/* chrdevbase 设备结构体 */
struct newchr_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;      /* 互斥体 */
};

struct newchr_dev chrdevbase;/* 自定义字符设备 */

/*
* @description : LED 硬件初始化
* @param : 无
* @return : 无
*/
static int led_hal_init(void)
{
    int ret = 0;

    /* 设置 LED 所使用的 GPIO */
    /* 1、获取设备节点:gpioled */
    chrdevbase.nd = of_find_node_by_path("/gpioled");
    if(chrdevbase.nd == NULL) {
        printk("chrdevbase node cant not found!
");
            return -EINVAL;
        } else {
        printk("chrdevbase node has been found!
");
    }

    /* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
    chrdevbase.led_gpio = of_get_named_gpio(chrdevbase.nd, "led-gpio"0);
    if(chrdevbase.led_gpio < 0) {
        printk("can't get led-gpio");
        return -EINVAL;
    }
    printk("led-gpio num = %d
", chrdevbase.led_gpio);

    /* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
    ret = gpio_direction_output(chrdevbase.led_gpio, 1);
    if(ret < 0) {
        printk("can't set gpio!
");
    }

    return 0;
}

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    printk("[BSP]chrdevbase open!
");
    filp->private_data = &chrdevbase; /* 设置私有数据 */

    /* 获取互斥体,可以被信号打断 */
    if (mutex_lock_interruptible(&chrdevbase.lock)) {
        return -ERESTARTSYS;
    }
#if 0
    mutex_lock(&chrdevbase.lock); /* 不能被信号打断 */
#endif

    return 0;
}

/*
 * @description        : 从设备读取数据 
 * @param - filp     : 要打开的设备文件(文件描述符)
 * @param - buf     : 返回给用户空间的数据缓冲区
 * @param - cnt     : 要读取的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    printk("chrdevbase read!
");
    return 0;
}

/*
 * @description        : 向设备写数据 
 * @param - filp     : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt     : 相对于文件首地址的偏移
 * @return             : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue = 0;
    char writebuf[1];
    struct newchr_dev *dev = filp->private_data;

    /* 接收用户空间传递给内核的数据并且打印出来 */
    retvalue = copy_from_user(writebuf, buf, cnt);
    printk("[BSP]kernel recevdata data:%d!
",writebuf[0]);

    if(writebuf[0] == LEDON) { 
    gpio_set_value(dev->led_gpio, 0); /* 打开 LED 灯 */
    } else if(writebuf[0] == LEDOFF) {
    gpio_set_value(dev->led_gpio, 1); /* 关闭 LED 灯 */
    }

    // printk("chrdevbase write!
");
    return 0;
}

/*
 * @description        : 关闭/释放设备
 * @param - filp     : 要关闭的设备文件(文件描述符)
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    struct newchr_dev *dev = filp->private_data;
    /* 释放互斥锁 */
    mutex_unlock(&dev->lock);
    printk("[BSP]release!
");
    return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,   
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};

/*
 * @description    : 驱动入口函数 
 * @param         : 无
 * @return         : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
    /* 初始化互斥体 */
    mutex_init(&chrdevbase.lock);

    /* 初始化硬件 */
      led_hal_init();

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (chrdevbase.major) { /* 定义了设备号 */
        chrdevbase.devid = MKDEV(chrdevbase.major, 0);
        register_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT, CHRDEVBASE_NAME);
    } else { /* 没有定义设备号 */
        alloc_chrdev_region(&chrdevbase.devid, 0, CHRDEVBASE_CNT,CHRDEVBASE_NAME); /* 申请设备号 */
        chrdevbase.major = MAJOR(chrdevbase.devid); /* 获取主设备号 */
        chrdevbase.minor = MINOR(chrdevbase.devid); /* 获取次设备号 */
    }
    printk("newcheled major=%d,minor=%d
",chrdevbase.major,chrdevbase.minor);

    /* 2、初始化 cdev */
    chrdevbase.cdev.owner = THIS_MODULE;
    cdev_init(&chrdevbase.cdev, &chrdevbase_fops);

    /* 3、添加一个 cdev */
    cdev_add(&chrdevbase.cdev, chrdevbase.devid, CHRDEVBASE_CNT);

    /* 4、创建类 */
    chrdevbase.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
    if (IS_ERR(chrdevbase.class)) {
        return PTR_ERR(chrdevbase.class);
    }

    /* 5、创建设备 */
    chrdevbase.device = device_create(chrdevbase.class, NULL,chrdevbase.devid, NULL, CHRDEVBASE_NAME);
    if (IS_ERR(chrdevbase.device)) {
        return PTR_ERR(chrdevbase.device);
    }

    return 0;
}

/*
 * @description    : 驱动出口函数
 * @param         : 无
 * @return         : 无
 */
static void __exit chrdevbase_exit(void)
{
    /* 注销字符设备 */
    cdev_del(&chrdevbase.cdev);/* 删除 cdev */
    unregister_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT);/* 注销设备号 */

    device_destroy(chrdevbase.class, chrdevbase.devid);/* 销毁设备 */
    class_destroy(chrdevbase.class);/* 销毁类 */

    printk("[BSP]chrdevbase exit!
");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");

应用源码

和原子操作应用源码一样,不需要修改!

 

实验现象

互斥体和信号量的效果类似,也会在任务完成后再运行。

控制台

 

套路分析

1、在结构体加入互斥体

/* chrdevbase 设备结构体 */
struct newchr_dev{
    ....
    struct mutex lock;      /* 互斥体 */
};

2、在驱动入口初始化互斥体

/*
 * @description    : 驱动入口函数 
 * @param         : 无
 * @return         : 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
    /* 初始化互斥体 */
    mutex_init(&chrdevbase.lock);
    ......
    return 0;
}

3、在打开设备时获取互斥体

/*
 * @description        : 打开设备
 * @param - inode     : 传递给驱动的inode
 * @param - filp     : 设备文件,file结构体有个叫做private_data的成员变量
 *                       一般在open的时候将private_data指向设备结构体。
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
    printk("[BSP]chrdevbase open!
");
    filp->private_data = &chrdevbase; /* 设置私有数据 */

    /* 获取互斥体,可以被信号打断 */
    if (mutex_lock_interruptible(&chrdevbase.lock)) {
        return -ERESTARTSYS;
    }
#if 0
    mutex_lock(&chrdevbase.lock); /* 不能被信号打断 */
#endif

    return 0;
}

4、在释放设备时释放互斥体

/*
 * @description        : 关闭/释放设备
 * @param - filp     : 要关闭的设备文件(文件描述符)
 * @return             : 0 成功;其他 失败
 */
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    struct newchr_dev *dev = filp->private_data;
    /* 释放互斥锁 */
    mutex_unlock(&dev->lock);
    printk("[BSP]release!
");
    return 0;
}

 


打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分