【Linux驱动开发】并发控制机制:原子操作、自旋锁、信号量、互斥锁详解

并发控制机制

首先我们来了解一下 “操作系统的并发性” 这个概念:

操作系统的并发性(concurrence):指的是两个或者两个以上事件在同一时间间隔内发生,即这个设备一会执行这个事件一会执行那个事件,多个事件共同使用一个设备。

操作系统的并发性指它应该具有处理和调度多个程序同时执行的能力。

接下来,我们认识一下在Linux中,并发与竞争的几个重要概念:

竞态:多任务并行执行时,如果在一个时刻同时操作同一个资源,会引起资源的错乱,这种错乱情形被称为竞态

共享资源:可能会被多个任务同时使用的资源

临界区:操作共享资源的代码段

         如果多个进程/线程同时操作临界区就会出现竞争,而在我们编写驱动的过程中一定要避免这种情况发生。

        Linux提供了并发控制机制,来防止竞态的出现,避免在同一时刻使用共享资源 。

我们一定要注意在开始编写驱动就要考虑并发与竞争问题,而不是等到驱动都写完了然后再去处理并发与竞争。为了方便理解,在这里我用Linux下的并发控制机制来操作LED灯。

目录

原子变量 / 原子操作

原理介绍

示例代码

自旋锁

原理介绍

示例代码

信号量

原理介绍

示例代码

互斥锁 / 互斥体

原理介绍

示例代码


原子变量 / 原子操作

原理介绍

        原子是化学反应不可再分的基本微粒,这里的原子访问就表示这一个访问是一个步骤,不能再进行拆分。

        缺点:原子操作只能对整形变量或者位进行保护。

        #include <asm/atomic.h>

        原子变量:atomic_t ,不可被打断的特殊整型变量

原子操作API函数

a.设置原子量的值
void atomic_set(atomic_t *v,int i);	//设置原子量的值为i
atomic_t v = ATOMIC_INIT(0);	//定义原子变量v并初始化为0
b.获取原子量的值
atomic_read(atomic_t *v); 		//返回原子量的值
c.原子变量加减
void atomic_add(int i,atomic_t *v);//原子变量增加i
void atomic_sub(int i,atomic_t *v);//原子变量减少i
d.原子变量自增自减
void atomic_inc(atomic_t *v);//原子变量增加1
void atomic_dec(atomic_t *v);//原子变量减少1
e.操作并测试:运算后结果为0则返回真,否则返回假
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i,atomic_t *v);

 原子位操作API函数

a.设置位
void set_bit(nr, void *addr);		//设置addr的第nr位为1
b.清除位
void clear_bit(nr , void *addr);	//清除addr的第nr位为0
c.改变位
void change_bit(nr , void *addr);	//改变addr的第nr位为1
d.测试位
void test_bit(nr , void *addr);		//测试addr的第nr位是否为1

适用场合:共享资源为单个整型变量的互斥场合

示例代码

atomic.c

/*
 * @Author: imysy_22
 * @Description: 并发控制LED之原子变量
 * @Date: 2022-10-18 23:12:03
 * @LastEditTime: 2022-10-19 20:04:17
 * @FilePath: /undefined/home/imysy22/IMX6U/rootfs/home/root/MyDrivers/2_led_chrdev/pinctrl_gpio_version/atomic/led.c
 */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <asm/atomic.h>

#define DevName "gpioled"

struct gpioled_dev {
    int major;
    int minor;
    int led_gpio;   /* led所使用的gpio编号 */
    dev_t devno;

    struct cdev mdev;

    struct class *pcls;
    struct device *pdev;

    struct device_node *pnd;
    struct property *proper;

    atomic_t lock;
};

struct gpioled_dev *pgmydev = NULL;

static void led_on(struct file *pfile);
static void led_off(struct file *pfile);

/**
 * @description: 
 * @param {inode} *pnode
 * @param {file} *pfile     设备文件,file结构体有个叫做private_data的成员变量,一般在open的时候将private_data指向设备结构体。
 * @return {*}
 */
static int chr_led_open(struct inode *pnode,struct file *pfile)
{
    pfile->private_data = (void *)(container_of(pnode->i_cdev, struct gpioled_dev, mdev));

    //struct gpioled_dev *pmydev = pfile->private_data;

    if(!atomic_dec_and_test(&pgmydev->lock))  /* 若返回值为假,则代表已经有别的应用在使用这个设备了 */
    {
        atomic_inc(&pgmydev->lock);  /* 判断的时候自减1要加回来 */
        printk("[error]: The device has been opened.\n");
        return -1;
    }

    return 0;
}

static int chr_led_release(struct inode *pnode,struct file *pfile)
{
    struct gpioled_dev *pmydev = pfile->private_data;

    atomic_set(&pmydev->lock, 1);  /* 原子变量在设备使用完后恢复初始值1 */

    //printk("Character device(%d:%d) removed successfully.\n", pmydev->major, pmydev->minor);    
    return 0;
}

static ssize_t chr_led_write(struct file *pfile,const char __user *puser,size_t size,loff_t *p_pos)
{
    int ret = 0;
    char buf[1] = {0};
    unsigned char led_state;

    if((ret = copy_from_user(buf, puser, size)))
    {
        printk("copy_from_user failed...\n");
        return -1;
    }

    led_state = buf[0];

    if(led_state == 0)
    {
        led_on(pfile);   //低电平开灯
    }

    if(led_state == 1)
    {
        led_off(pfile);  //高电平关灯
    }

    return 1;
}


struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = chr_led_open,
    .release = chr_led_release,
    .write = chr_led_write,
};

static int __init chr_led_init(void)
{
    int ret = 0;
    
    pgmydev = (struct gpioled_dev *)kmalloc(sizeof(struct gpioled_dev), GFP_KERNEL);
    if(NULL == pgmydev)
    {
        printk("kmalloc failed\n");
        return -1;
    }
    memset(pgmydev, 0, sizeof(struct gpioled_dev));

    atomic_set(&pgmydev->lock, 1);

    /* 
        获取设备树中的属性数据 
            前面操作设备树节点这几步操作和设备树版本的不一样
    */
    
    /* 1、获取设备节点:gpioled */
    if((pgmydev->pnd = of_find_node_by_path("/gpioled")) == NULL)
    {
        printk("of_find_node_by_path failed...\n");
        return -EINVAL;
    }

    /* 2、获取gpio子系统中设备树中的gpio的电气属性,并得到Led所使用的GPIO编号 */
    if((pgmydev->led_gpio = of_get_named_gpio(pgmydev->pnd, "led-gpio", 0)) < 0)
    {
        printk("of_get_named_gpio failed...\n");
        return -EINVAL;
    }

    printk("led-gpio num = %d\n", pgmydev->led_gpio);

    /* 3、设置GPIO1_IO03为输出,并输出高电平,默认熄灭led灯 */
    if((ret = gpio_direction_output(pgmydev->led_gpio, 1)) < 0)
    {
        printk("gpio_direction_output failed...\n");
        return -EINVAL;
    }

    printk("**LED0 initialzation successed***\n");
    
    /* 注册设备流程和设备树版本的LED一样 */

    pgmydev->major = 14;
    pgmydev->minor = 0;
    pgmydev->devno = MKDEV(pgmydev->major, pgmydev->minor);

    ret = register_chrdev_region(pgmydev->devno, 1, DevName);
    if(ret)
    {
        ret = alloc_chrdev_region(&pgmydev->devno, pgmydev->minor, 1, DevName);
        if(ret)
        {
            printk("register failed...\n");
            return -1;
        }

        pgmydev->major = MAJOR(pgmydev->devno);
        pgmydev->minor = MINOR(pgmydev->devno);
    }

    cdev_init(&pgmydev->mdev, &myops);

    pgmydev->mdev.owner = THIS_MODULE;
    cdev_add(&pgmydev->mdev, pgmydev->devno, 1);

    pgmydev->pcls = class_create(THIS_MODULE, DevName);
    if(IS_ERR(pgmydev->pcls))
    {
        printk("class_create failed...\n");
        cdev_del(&pgmydev->mdev);
        unregister_chrdev_region(pgmydev->devno, 1);
        
        return -1;
    }

    pgmydev->pdev = device_create(pgmydev->pcls, NULL, pgmydev->devno, NULL, DevName);
    if(pgmydev->pdev == NULL)
    {
        printk("device_create failed...\n");
        class_destroy(pgmydev->pcls);
        cdev_del(&pgmydev->mdev);
        unregister_chrdev_region(pgmydev->devno, 1);
        
        return -1;
    }

    printk("New character device(%d:%d) added successfully.\n", pgmydev->major, pgmydev->minor);    

    return 0;
}

static void __exit chr_led_exit(void)
{
    printk("Character device(%d:%d) removing.\n", pgmydev->major, pgmydev->minor);
    device_destroy(pgmydev->pcls, pgmydev->devno);
    class_destroy(pgmydev->pcls);

    cdev_del(&pgmydev->mdev); 

    unregister_chrdev_region(pgmydev->devno, 1);
    printk("Removed successfully.\n");
}

static void led_on(struct file *pfile)
{
    struct gpioled_dev *pmydev = pfile->private_data;

    gpio_set_value(pmydev->led_gpio, 0);

}

static void led_off(struct file *pfile)
{
    struct gpioled_dev *pmydev = pfile->private_data;

    gpio_set_value(pmydev->led_gpio, 1);
}

module_init(chr_led_init);
module_exit(chr_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("imysy_22-2022.10.16");

自旋锁

原理介绍

        自旋锁的“自旋”也就是“原地打转”的意思,“原地打转”的目的是为了等待自旋锁可以用,可以访问共享资源。

        自旋锁是基于忙等待的并发控制机制。

        缺点:自旋锁的持有时间不能太长,否则会一直浪费CPU的时间,降低系统性能。

    #include <linux/spinlock.h>
    定义spinlock_t类型的变量lock
    spin_lock_init(&lock)后才能正常使用spinlock


    spin_lock(&lock);
    临界区
    spin_unlock(&lock);

       

自旋锁API函数

a.定义自旋锁
spinlock_t  lock;
b.初始化自旋锁
spin_lock_init(spinlock_t *);
c.获得自旋锁
spin_lock(spinlock_t *);	//成功获得自旋锁立即返回,否则自旋在那里直到该自旋锁的保持者释放
spin_trylock(spinlock_t *);	//成功获得自旋锁立即返回真,否则返回假,而不是像上一个那样"在原地打转”
d.释放自旋锁
spin_unlock(spinlock_t *);

适用场合:

  1. 异常上下文之间或异常上下文与任务上下文之间共享资源时

  2. 任务上下文之间且临界区执行时间很短时

  3. 互斥问题

示例代码

spinlock.c

/*
 * @Author: imysy_22
 * @Description: 并发控制LED之自旋锁
 * @Date: 2022-10-18 23:12:03
 * @LastEditTime: 2022-10-19 20:24:56
 * @FilePath: /undefined/home/imysy22/IMX6U/rootfs/home/root/MyDrivers/2_led_chrdev/pinctrl_gpio_version/Concurrency_and_Competition/spinlock.c
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/mm.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <asm/io.h>

#define DevName "gpioled"

struct gpioled_dev {
    int major;
    int minor;
    int led_gpio;   /* led所使用的gpio编号 */
    dev_t devno;

    struct cdev mdev;

    struct class *pcls;
    struct device *pdev;

    struct device_node *pnd;
    struct property *proper;

    int dev_stats;  /* 设备状态,0-设备未使用;1-设备在使用中 */
    spinlock_t lock;
};

struct gpioled_dev *pgmydev = NULL;

static void led_on(struct file *pfile);
static void led_off(struct file *pfile);

/**
 * @description: 
 * @param {inode} *pnode
 * @param {file} *pfile     设备文件,file结构体有个叫做private_data的成员变量,一般在open的时候将private_data指向设备结构体。
 * @return {*}
 */
static int chr_led_open(struct inode *pnode,struct file *pfile)
{
    unsigned long flags;
    pfile->private_data = (void *) (container_of(pnode->i_cdev, struct gpioled_dev, mdev));
    struct gpioled_dev *pmydev = pfile->private_data;

    /* 上自旋锁,并保存中断状态然后屏蔽所有中断 */
    spin_lock_irqsave(&pmydev->lock, flags);

    if(pmydev->dev_stats)  /* 判断设备是否在使用中 */
    {
        spin_unlock_irqrestore(&pmydev->lock, flags);
        return -1;
    }

    pmydev->dev_stats = 1;
    spin_unlock_irqrestore(&pmydev->lock, flags);

    return 0;
}

static int chr_led_release(struct inode *pnode,struct file *pfile)
{
    struct gpioled_dev *pmydev = pfile->private_data;
    unsigned long flags;

    spin_lock_irqsave(&pmydev->lock, flags);
    if(pgmydev->dev_stats)
        pgmydev->dev_stats = 0;
    spin_unlock_irqrestore(&pmydev->lock, flags);


    //printk("Character device(%d:%d) removed successfully.\n", pmydev->major, pmydev->minor);    
    return 0;
}

static ssize_t chr_led_write(struct file *pfile,const char __user *puser,size_t size,loff_t *p_pos)
{
    int ret = 0;
    char buf[1] = {0};
    unsigned char led_state;

    if((ret = copy_from_user(buf, puser, size)))
    {
        printk("copy_from_user failed...\n");
        return -1;
    }

    led_state = buf[0];

    if(led_state == 0)
    {
        led_on(pfile);   //低电平开灯
    }

    if(led_state == 1)
    {
        led_off(pfile);  //高电平关灯
    }

    return 1;
}


struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = chr_led_open,
    .release = chr_led_release,
    .write = chr_led_write,
};

static int __init chr_led_init(void)
{
    int ret = 0;
    
    pgmydev = (struct gpioled_dev *)kmalloc(sizeof(struct gpioled_dev), GFP_KERNEL);
    if(NULL == pgmydev)
    {
        printk("kmalloc failed\n");
        return -1;
    }
    memset(pgmydev, 0, sizeof(struct gpioled_dev));

    spin_lock_init(&pgmydev->lock);

    /* 
        获取设备树中的属性数据 
            前面操作设备树节点这几步操作和设备树版本的不一样
    */
    
    /* 1、获取设备节点:gpioled */
    if((pgmydev->pnd = of_find_node_by_path("/gpioled")) == NULL)
    {
        printk("of_find_node_by_path failed...\n");
        return -EINVAL;
    }

    /* 2、获取gpio子系统中设备树中的gpio的电气属性,并得到Led所使用的GPIO编号 */
    if((pgmydev->led_gpio = of_get_named_gpio(pgmydev->pnd, "led-gpio", 0)) < 0)
    {
        printk("of_get_named_gpio failed...\n");
        return -EINVAL;
    }

    printk("led-gpio num = %d\n", pgmydev->led_gpio);

    /* 3、设置GPIO1_IO03为输出,并输出高电平,默认熄灭led灯 */
    if((ret = gpio_direction_output(pgmydev->led_gpio, 1)) < 0)
    {
        printk("gpio_direction_output failed...\n");
        return -EINVAL;
    }

    printk("**LED0 initialzation successed***\n");
    
    /* 注册设备流程和设备树版本的LED一样 */

    pgmydev->major = 14;
    pgmydev->minor = 0;
    pgmydev->devno = MKDEV(pgmydev->major, pgmydev->minor);

    ret = register_chrdev_region(pgmydev->devno, 1, DevName);
    if(ret)
    {
        ret = alloc_chrdev_region(&pgmydev->devno, pgmydev->minor, 1, DevName);
        if(ret)
        {
            printk("register failed...\n");
            return -1;
        }

        pgmydev->major = MAJOR(pgmydev->devno);
        pgmydev->minor = MINOR(pgmydev->devno);
    }

    cdev_init(&pgmydev->mdev, &myops);

    pgmydev->mdev.owner = THIS_MODULE;
    cdev_add(&pgmydev->mdev, pgmydev->devno, 1);

    pgmydev->pcls = class_create(THIS_MODULE, DevName);
    if(IS_ERR(pgmydev->pcls))
    {
        printk("class_create failed...\n");
        cdev_del(&pgmydev->mdev);
        unregister_chrdev_region(pgmydev->devno, 1);
        
        return -1;
    }

    pgmydev->pdev = device_create(pgmydev->pcls, NULL, pgmydev->devno, NULL, DevName);
    if(pgmydev->pdev == NULL)
    {
        printk("device_create failed...\n");
        class_destroy(pgmydev->pcls);
        cdev_del(&pgmydev->mdev);
        unregister_chrdev_region(pgmydev->devno, 1);
        
        return -1;
    }

    printk("New character device(%d:%d) added successfully.\n", pgmydev->major, pgmydev->minor);    

    return 0;
}

static void __exit chr_led_exit(void)
{
    printk("Character device(%d:%d) removing.\n", pgmydev->major, pgmydev->minor);
    device_destroy(pgmydev->pcls, pgmydev->devno);
    class_destroy(pgmydev->pcls);

    cdev_del(&pgmydev->mdev); 

    unregister_chrdev_region(pgmydev->devno, 1);
    printk("Removed successfully.\n");
}

static void led_on(struct file *pfile)
{
    struct gpioled_dev *pmydev = pfile->private_data;

    gpio_set_value(pmydev->led_gpio, 0);

}

static void led_off(struct file *pfile)
{
    struct gpioled_dev *pmydev = pfile->private_data;

    gpio_set_value(pmydev->led_gpio, 1);
}

module_init(chr_led_init);
module_exit(chr_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("imysy_22-2022.10.16");

信号量

原理介绍

        信号量类似于停车场里指示停车位的计数器。使用信号量会提高处理器的使用效率,毕竞不用一直傻乎乎的在那里“自旋”等待。但是信号量有开销。

        信号量是基于阻塞的并发控制机制。

信号量的特点::

①、因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换线程引起的开销要远大于信号量带来的那点优势。
 

#include <linux/semaphore.h>       

信号量API函数

a.定义信号量
struct semaphore sem;
b.初始化信号量
void sema_init(struct semaphore *sem, int val);
c.获得信号量P
int down(struct semaphore *sem);//深度睡眠,不能在中断中使用
int down_interruptible(struct semaphore *sem);//浅度睡眠,能在中断中使用
d.释放信号量V
void up(struct semaphore *sem);

适用场合:任务上下文之间且临界区执行时间较长时的互斥或同步问题

示例代码

semaphore.c

/*
 * @Author: imysy_22
 * @Description: 并发控制LED之信号量
 * @Date: 2022-10-18 23:12:03
 * @LastEditTime: 2022-10-19 20:54:28
 * @FilePath: /undefined/home/imysy22/IMX6U/rootfs/home/root/MyDrivers/2_led_chrdev/pinctrl_gpio_version/Concurrency_and_Competition/semaphore.c
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/semaphore.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/mm.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <asm/io.h>


#define DevName "gpioled"

struct gpioled_dev {
    int major;
    int minor;
    int led_gpio;   /* led所使用的gpio编号 */
    dev_t devno;

    struct cdev mdev;

    struct class *pcls;
    struct device *pdev;

    struct device_node *pnd;
    struct property *proper;

    struct semaphore sem;
};

struct gpioled_dev *pgmydev = NULL;

static void led_on(struct file *pfile);
static void led_off(struct file *pfile);

/**
 * @description: 
 * @param {inode} *pnode
 * @param {file} *pfile     设备文件,file结构体有个叫做private_data的成员变量,一般在open的时候将private_data指向设备结构体。
 * @return {*}
 */
static int chr_led_open(struct inode *pnode,struct file *pfile)
{
    pfile->private_data = (void *) (container_of(pnode->i_cdev, struct gpioled_dev, mdev));

    struct gpioled_dev *pmydev = pfile->private_data;

    /* 获取信号量,进入休眠状态的进程可以被信号打断 */
    if(down_interruptible(&pmydev->sem))
    {
        /* 返回非零就是设备在被别的应用程序使用 */
        return -1;
    }

    //down(&pmydev->sem);

    return 0;
}

static int chr_led_release(struct inode *pnode,struct file *pfile)
{
    struct gpioled_dev *pmydev = pfile->private_data;

    up(&pmydev->sem);
    //printk("Character device(%d:%d) removed successfully.\n", pmydev->major, pmydev->minor);    
    return 0;
}

static ssize_t chr_led_write(struct file *pfile,const char __user *puser,size_t size,loff_t *p_pos)
{
    int ret = 0;
    char buf[1] = {0};
    unsigned char led_state;

    if((ret = copy_from_user(buf, puser, size)))
    {
        printk("copy_from_user failed...\n");
        return -1;
    }

    led_state = buf[0];

    if(led_state == 0)
    {
        led_on(pfile);   //低电平开灯
    }

    if(led_state == 1)
    {
        led_off(pfile);  //高电平关灯
    }

    return 1;
}


struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = chr_led_open,
    .release = chr_led_release,
    .write = chr_led_write,
};

static int __init chr_led_init(void)
{
    int ret = 0;
    
    pgmydev = (struct gpioled_dev *)kmalloc(sizeof(struct gpioled_dev), GFP_KERNEL);
    if(NULL == pgmydev)
    {
        printk("kmalloc failed\n");
        return -1;
    }
    memset(pgmydev, 0, sizeof(struct gpioled_dev));

    sema_init(&pgmydev->sem, 1);

    /* 
        获取设备树中的属性数据 
            前面操作设备树节点这几步操作和设备树版本的不一样
    */
    
    /* 1、获取设备节点:gpioled */
    if((pgmydev->pnd = of_find_node_by_path("/gpioled")) == NULL)
    {
        printk("of_find_node_by_path failed...\n");
        return -EINVAL;
    }

    /* 2、获取gpio子系统中设备树中的gpio的电气属性,并得到Led所使用的GPIO编号 */
    if((pgmydev->led_gpio = of_get_named_gpio(pgmydev->pnd, "led-gpio", 0)) < 0)
    {
        printk("of_get_named_gpio failed...\n");
        return -EINVAL;
    }

    printk("led-gpio num = %d\n", pgmydev->led_gpio);

    /* 3、设置GPIO1_IO03为输出,并输出高电平,默认熄灭led灯 */
    if((ret = gpio_direction_output(pgmydev->led_gpio, 1)) < 0)
    {
        printk("gpio_direction_output failed...\n");
        return -EINVAL;
    }

    printk("**LED0 initialzation successed***\n");
    
    /* 注册设备流程和设备树版本的LED一样 */

    pgmydev->major = 14;
    pgmydev->minor = 0;
    pgmydev->devno = MKDEV(pgmydev->major, pgmydev->minor);

    ret = register_chrdev_region(pgmydev->devno, 1, DevName);
    if(ret)
    {
        ret = alloc_chrdev_region(&pgmydev->devno, pgmydev->minor, 1, DevName);
        if(ret)
        {
            printk("register failed...\n");
            return -1;
        }

        pgmydev->major = MAJOR(pgmydev->devno);
        pgmydev->minor = MINOR(pgmydev->devno);
    }

    cdev_init(&pgmydev->mdev, &myops);

    pgmydev->mdev.owner = THIS_MODULE;
    cdev_add(&pgmydev->mdev, pgmydev->devno, 1);

    pgmydev->pcls = class_create(THIS_MODULE, DevName);
    if(IS_ERR(pgmydev->pcls))
    {
        printk("class_create failed...\n");
        cdev_del(&pgmydev->mdev);
        unregister_chrdev_region(pgmydev->devno, 1);
        
        return -1;
    }

    pgmydev->pdev = device_create(pgmydev->pcls, NULL, pgmydev->devno, NULL, DevName);
    if(pgmydev->pdev == NULL)
    {
        printk("device_create failed...\n");
        class_destroy(pgmydev->pcls);
        cdev_del(&pgmydev->mdev);
        unregister_chrdev_region(pgmydev->devno, 1);
        
        return -1;
    }

    printk("New character device(%d:%d) added successfully.\n", pgmydev->major, pgmydev->minor);    

    return 0;
}

static void __exit chr_led_exit(void)
{
    printk("Character device(%d:%d) removing.\n", pgmydev->major, pgmydev->minor);
    device_destroy(pgmydev->pcls, pgmydev->devno);
    class_destroy(pgmydev->pcls);

    cdev_del(&pgmydev->mdev); 

    unregister_chrdev_region(pgmydev->devno, 1);
    printk("Removed successfully.\n");
}

static void led_on(struct file *pfile)
{
    struct gpioled_dev *pmydev = pfile->private_data;

    gpio_set_value(pmydev->led_gpio, 0);

}

static void led_off(struct file *pfile)
{
    struct gpioled_dev *pmydev = pfile->private_data;

    gpio_set_value(pmydev->led_gpio, 1);
}

module_init(chr_led_init);
module_exit(chr_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("imysy_22-2022.10.16");

互斥锁 / 互斥体

原理介绍

        将信号量的值设置为1就可以使用信号量进行互斥访问了,虽然可以通过信号量实现互斥,但是Linux提供了一个比信号量更专业的机制来进行互斥,它就是互斥锁—mutex。互斥访问表示一次只有一个线程可以访问共享资源。

        互斥锁是基于阻塞的互斥机制。

互斥锁的特点::

①、mutex可以导致休眠,因此不能在中断中使用mutex,中断中只能使用自旋锁。②、和信号量一样,mutex保护的临界区可以调用引起阻塞的API函数。
③、因为一次只有一个线程可以持有mutex,因此,必须由mutex 的持有者释放mutex。并且 mutex不能递归上锁和解锁。
 

#include <linux/mutex.h>

互斥锁API函数

a.初始化
struct mutex  my_mutex;
mutex_init(&my_mutex);
b.获取互斥体
void  mutex_lock(struct mutex *lock);
c.释放互斥体
void mutex_unlock(struct mutex *lock);

适用场合:任务上下文之间且临界区执行时间较长时的互斥问题

示例代码

mutex.c

/*
 * @Author: imysy_22
 * @Description: 并发控制LED互斥锁
 * @Date: 2022-10-18 23:12:03
 * @LastEditTime: 2022-10-19 22:01:43
 * @FilePath: /undefined/home/imysy22/IMX6U/rootfs/home/root/MyDrivers/2_led_chrdev/pinctrl_gpio_version/Concurrency_and_Competition/mutex.c
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/mutex.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/mm.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <asm/io.h>

#define DevName "gpioled"

struct gpioled_dev {
    int major;
    int minor;
    int led_gpio;   /* led所使用的gpio编号 */
    dev_t devno;

    struct cdev mdev;

    struct class *pcls;
    struct device *pdev;

    struct device_node *pnd;
    struct property *proper;

    struct mutex lock;
};

struct gpioled_dev *pgmydev = NULL;

static void led_on(struct file *pfile);
static void led_off(struct file *pfile);

/**
 * @description: 
 * @param {inode} *pnode
 * @param {file} *pfile     设备文件,file结构体有个叫做private_data的成员变量,一般在open的时候将private_data指向设备结构体。
 * @return {*}
 */
static int chr_led_open(struct inode *pnode,struct file *pfile)
{
    pfile->private_data = (void *) (container_of(pnode->i_cdev, struct gpioled_dev, mdev));

    struct gpioled_dev *pmydev = pfile->private_data;

    /* 获得互斥锁,可以被信号打断 */
    if(mutex_lock_interruptible(&pmydev->lock))
    {
        return -1;
    }

    return 0;
}

static int chr_led_release(struct inode *pnode,struct file *pfile)
{
    struct gpioled_dev *pmydev = pfile->private_data;

    mutex_unlock(&pmydev->lock);
    //printk("Character device(%d:%d) removed successfully.\n", pmydev->major, pmydev->minor);    
    return 0;
}

static ssize_t chr_led_write(struct file *pfile,const char __user *puser,size_t size,loff_t *p_pos)
{
    int ret = 0;
    char buf[1] = {0};
    unsigned char led_state;

    if((ret = copy_from_user(buf, puser, size)))
    {
        printk("copy_from_user failed...\n");
        return -1;
    }

    led_state = buf[0];

    if(led_state == 0)
    {
        led_on(pfile);   //低电平开灯
    }

    if(led_state == 1)
    {
        led_off(pfile);  //高电平关灯
    }

    return 1;
}


struct file_operations myops = {
    .owner = THIS_MODULE,
    .open = chr_led_open,
    .release = chr_led_release,
    .write = chr_led_write,
};

static int __init chr_led_init(void)
{
    int ret = 0;
    
    pgmydev = (struct gpioled_dev *)kmalloc(sizeof(struct gpioled_dev), GFP_KERNEL);
    if(NULL == pgmydev)
    {
        printk("kmalloc failed\n");
        return -1;
    }
    memset(pgmydev, 0, sizeof(struct gpioled_dev));

    mutex_init(&pgmydev->lock);

    /* 
        获取设备树中的属性数据 
            前面操作设备树节点这几步操作和设备树版本的不一样
    */
    
    /* 1、获取设备节点:gpioled */
    if((pgmydev->pnd = of_find_node_by_path("/gpioled")) == NULL)
    {
        printk("of_find_node_by_path failed...\n");
        return -EINVAL;
    }

    /* 2、获取gpio子系统中设备树中的gpio的电气属性,并得到Led所使用的GPIO编号 */
    if((pgmydev->led_gpio = of_get_named_gpio(pgmydev->pnd, "led-gpio", 0)) < 0)
    {
        printk("of_get_named_gpio failed...\n");
        return -EINVAL;
    }

    printk("led-gpio num = %d\n", pgmydev->led_gpio);

    /* 3、设置GPIO1_IO03为输出,并输出高电平,默认熄灭led灯 */
    if((ret = gpio_direction_output(pgmydev->led_gpio, 1)) < 0)
    {
        printk("gpio_direction_output failed...\n");
        return -EINVAL;
    }

    printk("**LED0 initialzation successed***\n");
    
    /* 注册设备流程和设备树版本的LED一样 */

    pgmydev->major = 14;
    pgmydev->minor = 0;
    pgmydev->devno = MKDEV(pgmydev->major, pgmydev->minor);

    ret = register_chrdev_region(pgmydev->devno, 1, DevName);
    if(ret)
    {
        ret = alloc_chrdev_region(&pgmydev->devno, pgmydev->minor, 1, DevName);
        if(ret)
        {
            printk("register failed...\n");
            return -1;
        }

        pgmydev->major = MAJOR(pgmydev->devno);
        pgmydev->minor = MINOR(pgmydev->devno);
    }

    cdev_init(&pgmydev->mdev, &myops);

    pgmydev->mdev.owner = THIS_MODULE;
    cdev_add(&pgmydev->mdev, pgmydev->devno, 1);

    pgmydev->pcls = class_create(THIS_MODULE, DevName);
    if(IS_ERR(pgmydev->pcls))
    {
        printk("class_create failed...\n");
        cdev_del(&pgmydev->mdev);
        unregister_chrdev_region(pgmydev->devno, 1);
        
        return -1;
    }

    pgmydev->pdev = device_create(pgmydev->pcls, NULL, pgmydev->devno, NULL, DevName);
    if(pgmydev->pdev == NULL)
    {
        printk("device_create failed...\n");
        class_destroy(pgmydev->pcls);
        cdev_del(&pgmydev->mdev);
        unregister_chrdev_region(pgmydev->devno, 1);
        
        return -1;
    }

    printk("New character device(%d:%d) added successfully.\n", pgmydev->major, pgmydev->minor);    

    return 0;
}

static void __exit chr_led_exit(void)
{
    printk("Character device(%d:%d) removing.\n", pgmydev->major, pgmydev->minor);
    device_destroy(pgmydev->pcls, pgmydev->devno);
    class_destroy(pgmydev->pcls);

    cdev_del(&pgmydev->mdev); 

    unregister_chrdev_region(pgmydev->devno, 1);
    printk("Removed successfully.\n");
}

static void led_on(struct file *pfile)
{
    struct gpioled_dev *pmydev = pfile->private_data;

    gpio_set_value(pmydev->led_gpio, 0);

}

static void led_off(struct file *pfile)
{
    struct gpioled_dev *pmydev = pfile->private_data;

    gpio_set_value(pmydev->led_gpio, 1);
}

module_init(chr_led_init);
module_exit(chr_led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("imysy_22-2022.10.16");

猜你喜欢

转载自blog.csdn.net/imysy_22_/article/details/127422579