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

news/2024/5/8 20:33:30/文章来源:https://blog.csdn.net/imysy_22_/article/details/127422579

并发控制机制

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

操作系统的并发性(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");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_404379.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

yolov5和yolov7之实例分割训练

还没来得及实验&#xff0c;我在这里就给出几个参考的链接 1、How to train your segmentation data with seg in U7? What is the data set format?Thanks&#xff01;&#xff01; Issue #732 WongKinYiu/yolov7 GitHub 2、JSON2YOLO/general_json2yolo.py at master …

南大通用GBase8s 常用SQL语句(287)

UPDATE 语句 使用 UPDATE 语句来更改表或视图中一个或多个现有的行的一个或多个列中的值。 语法 Target WHERE 选项 元素 描述 限制 语法 alias 您在此为本地表或远程表声明的临时的名称 如果 SET 是 alias 的标识符&#xff0c;则 AS 关键字必须在 alias 之前 …

计算机网络(六) | 应用层:HTTP协议详解

目录HTTP协议HTTP协议简介理解应用层协议HTTP协议的工作过程HTTP协议格式Fiddler的简介Fiddler的使用HTTP请求格式概述HTTP响应格式概述HTTP请求格式详解URL方法请求报头请求正文(body)HTTP响应格式详解状态码响应报头响应正文(body)构造HTTP请求form表单构造HTTP请求通过ajax构…

Unity9 路径权限、场景的加载、异步加载、场景跳转

Application类 using System.Collections; using System.Collections.Generic; using UnityEngine;public class ApplicationTest : MonoBehaviour {// Start is called before the first frame updatevoid Start(){//游戏数据文件夹路径 只读、加密压缩Debug.Log(Application…

华为云数据库GaussDB(for MySQL)携高新技术强势而来

华为云数据库GaussDB(for MySQL)携高新技术强势而来 企业数据库堪比人的心脏一样重要&#xff0c;不仅要承担企业数据存储、维护、管理、分析等任务&#xff0c;还要保证企业数据的完整性和准确性&#xff0c;并且需要承载着对其他业务系统或客户信息进行整合处理的功能&#x…

网课搜题公众号在线查题系统

网课搜题公众号在线查题系统 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xf…

【学习笔记】JSP内置对象

JSP内置对象pageContextRequestResponseconfig [ServletConfig]outApplication [ServletContext]pageexceptionsession 其中,pageContext、Request、Application、session 用于存储数据 它们都使用setAttribute() 来存数据 <% pageContext.setAttribute("name1&quo…

Dubbo的简单使用

一、架构演进 发展演变1.1 单一应用架构 当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。适用于小型网站,小型管理系统,将所有功能都部署到一个功能里,简单易用。 缺点性能扩展…

操作系统:文件IO

文章目录文件描述符文件描述符池基本文件操作openopen返回文件描述符的规则open的文件描述符与fopen的文件指针closewritereadlseekfcntl共享文件操作同一进程&#xff0c;多次open同一文件多个进程&#xff0c;多次open同一文件dupdup2利用dup、dup2实现重定位高级文件IO非阻塞…

【网络安全-键盘监视】学会以后去捉弄舍友,看他有了什么不可告人的秘密

&#x1f343;博主昵称&#xff1a;一拳必胜客 博主主页面链接&#xff1a;博主主页传送门 博主专栏页面连接&#xff1a;专栏传送门–计算机考研 &#x1f351;创作初心&#xff1a;本博客的初心是每天分享记录自己学习的脚步&#xff0c;和各位技术友探讨交流&#xff0c;同时…

通过jmap、jstack分析问题,以及分析方法

一、问题 多批次导入任务&#xff0c;会出现异步线程停止工作的情况&#xff0c;后续无论导入多少任务&#xff0c;异步线程都不会执行&#xff0c;只有重启能解决。 二、工具使用 进入jdk的bin目录&#xff1a; cd /beeb/ap/uip/jdk1.8.0_231/bin 2.1 拉取jstack日志&…

setContentView源码解析

一、引言 本文将解析activity加载布局文件的setContentView源码&#xff0c;由于会涉及到部分activity的启动流程&#xff0c;这里附上一张activity启动流程图&#xff1a; 关于 setContentView 源码分两种情况&#xff0c;因为我们的 activity 有两个&#xff1a; 一种是继…

halcon脚本-边缘及骨架的识别【附源码】

文章目录前言一、原图1.边缘图2.骨架图二、实现思路1.边缘图2.骨架图三、halcon脚本实现1.边缘图2.骨架图四、实现效果1.边缘图2.骨架图前言 本文实现基于图像进行边缘或者骨架的识别&#xff0c;可实现让机器人画画 一、原图 1.边缘图 2.骨架图 二、实现思路 1.边缘图 提取…

【阅读】一周翻过《构建之法》,笔记整理

文章目录&#x1f6a9; 前言&#x1f966; 阅读笔记&#x1f33e; 结语&#x1f6a9; 前言 我的阅读方式 我拿到这本书挺久了&#xff0c;之前已经零散地看过一部分&#xff0c;最近一周集中地花了一些时间&#xff0c;将整本书看过了一遍。看得比较粗略&#xff0c;正如“好读…

怎么管理员工混工时的现象?

如果员工存在混工时&#xff0c;这表明公司的管理存在漏洞。让懒散的员工认为自己可以混日子&#xff0c;让没有绩效考核的员工默认可以混日子&#xff0c;同时让对公司感到失望的员工可以完全躺平等待被公司解雇。 工时管理一直是企业需求与痛点。特别是在疫情时期&#xff0c…

华为云数据库GaussDB 为企业核心数据运行提供优质保障

华为云数据库GaussDB 为企业核心数据运行提供优质保障 在网络信息科学技术不断发展的今天&#xff0c;海量存储信息数据中大量的存储资源被浪费&#xff1f;你有没有遇见信息数据备份恢复很慢的情况&#xff1f;让人特别的烦躁&#xff0c;特别是企业遇见了这些情况&#xff0c…

JDBC工具类和JDBC登录案例

JDBC工具类: 抽取JDBC工具类:JDBCUtils 目的:简化书写 分析∶ 1.注册驱动也抽取 2.抽取一个方法获取连接对象 3.抽取一个方法释放资源 代码实现:/*** JDBC工具类*/ public class JDBCUtils {private static String url;private static String user;private static Strin…

Cadence Allegro PCB设计88问解析(十一) 之 Allegro中文件自动保存时间设置

一个学习信号完整性的layout工程师 大家在设计图纸或者编辑文档时&#xff0c;最常点击的应该就是保存图标了。谁也不想因为软件闪退、电脑断电等情况&#xff0c;我们的劳动成果就白白的消失了。在我们用Allegro进行PCB设计&#xff0c;就会有一个自动保存的功能&#xff0c;每…

数字化转型大潮下,企业如何做好绩效管理?

久其EPM白皮书的正式发布&#xff0c;为企业财务的数字化转型实践提供了极具价值的参考。企业绩效管理&#xff08;EPM&#xff09;成为行业热点对于一家企业来说&#xff0c;做好绩效管理究竟有多重要&#xff1f;对于这个问题&#xff0c;全球商界传奇人物、GE前CEO杰克韦尔奇…

事件对象的介绍

● 什么是事件对象&#xff1f;就是当你触发一个事件以后&#xff0c;对该事件的一些描述信息 ● 例如&#xff1a; ○ 你触发一个点击事件时&#xff0c;你点了哪个位置&#xff0c;坐标多少&#xff0c;都在事件对象的属性中体现 ○ 你触发一个键盘事件时&#xff0c;你按下…