Linux驱动开发—并发与竞争,原子操作,自旋锁,信号量详解

avatar
作者
筋斗云
阅读量:0

1.并发与并行的概念

并发是指在同一时间段内,多个任务交替执行。并发可以发生在单核处理器上,通过任务切换实现

在这里插入图片描述

并行是指在同一时间段内,多个任务同时执行。并行可以发生在多核处理器上,例如下图任务1 和任务3同时进行,这是一个并行的过程

在这里插入图片描述

并发同样可以发生在多核处理器之间,通过真正的并行执行实现。并发的主要目的是提高程序的响应能力和利用系统资源的效率。

2.竞争的概念

竞争是指多个线程或进程争夺相同资源(如内存、文件、网络连接等)时发生的冲突情况。竞争会导致资源争用问题,如死锁、饥饿、竞态条件等。

2.1竞态条件(Race Condition)

竞态条件是指多个线程或进程在没有适当同步的情况下访问和修改共享资源时,操作的执行顺序影响到最终结果,从而导致不确定性和错误的情况。竞态条件通常发生在以下情况下:

  1. 共享资源:多个线程或进程同时访问和修改同一个共享资源,如变量、数据结构、文件等。
  2. 没有同步:对共享资源的访问和修改没有进行适当的同步,导致多个线程或进程在读写过程中相互干扰。
  3. 不确定的执行顺序:由于线程或进程的调度是不可预测的,操作的执行顺序也就无法预测,导致最终结果不确定。

示例
假设有两个线程A和B,它们都试图增加一个共享变量counter的值。如果没有同步机制,可能会发生以下情况:

Thread A: load counter (counter = 0) Thread B: load counter (counter = 0) Thread A: increment counter (counter = 1) Thread B: increment counter (counter = 1) Thread A: store counter (counter = 1) Thread B: store counter (counter = 1) 

最终结果是counter为1,而不是预期的2。

2.2死锁(Deadlock)

死锁是指两个或多个线程或进程在等待彼此持有的资源,导致所有参与者都无法继续执行的情况。死锁通常发生在以下情况下:

  1. 互斥条件:资源不能被共享,必须互斥使用。
  2. 持有并等待条件:线程或进程已经持有一个资源,同时又在等待另一个资源,而不释放它已持有的资源。
  3. 不剥夺条件:资源不能被强制剥夺,必须由持有它的线程或进程自行释放。
  4. 循环等待条件:存在一个资源循环等待链,链中的每个线程或进程都在等待下一个线程或进程持有的资源。

示例
假设有两个线程A和B,以及两个资源R1和R2:

Thread A: lock R1 Thread B: lock R2 Thread A: wait for R2 (held by B) Thread B: wait for R1 (held by A) 

此时,线程A和B都在等待对方释放资源,形成循环等待,导致死锁。

2.3饥饿(Starvation)

饥饿是指一个线程或进程长期无法获得所需资源,从而无法继续执行的情况。饥饿通常发生在以下情况下:

  1. 资源分配不公平:调度器优先分配资源给某些线程或进程,而其他线程或进程一直得不到资源。
  2. 优先级不当:高优先级的线程或进程持续占用资源,低优先级的线程或进程长时间得不到资源。
  3. 资源请求模式:某些线程或进程频繁请求资源,导致其他线程或进程的资源请求一直得不到满足。

示例
假设有多个线程T1、T2、T3,其中T1和T2频繁请求资源R,而T3的请求较少:

Thread T1: request R Thread T2: request R Thread T3: request R (but always after T1 and T2) 

由于T1和T2总是优先获得资源R,T3的请求得不到满足,导致T3长期处于饥饿状态。

3.原子操作

3.1相关概念

原子操作是指在多线程或多进程环境下,不可分割、不能被中断的操作。原子操作要么完全执行,要么完全不执行,执行过程中不会被其他操作干扰。它们是并发编程中的基本构建块,用于实现线程安全的操作。

特点

不可分割性:原子操作是不可分割的,执行过程中不会被中断,确保操作的完整性。

并发安全:原子操作在多线程环境中是安全的,多个线程可以并发执行而不会引起竞态条件。

应用:

原子操作在多线程编程中有广泛的应用,特别是在以下场景中:

  1. 计数器:原子操作可以用于实现线程安全的计数器,如请求计数、引用计数等。
  2. 标志位:原子操作可以用于设置和清除标志位,以实现状态管理和同步。
  3. 锁的实现:许多锁(如自旋锁)使用原子操作来实现锁的获取和释放。
  4. 无锁数据结构:原子操作是实现无锁(Lock-Free)和无等待(Wait-Free)数据结构的基础,如无锁队列、无锁栈等。

优缺点

优点

  1. 高效:原子操作通常比使用锁更高效,因为它们避免了上下文切换和锁竞争。
  2. 简洁:原子操作提供了简单的接口来实现复杂的同步机制。
  3. 硬件支持:现代处理器直接支持原子操作,确保其高性能。

缺点

  1. 局限性:原子操作适用于简单的同步需求,对于复杂的同步问题可能需要更高级的机制(如锁、条件变量)。
  2. 硬件依赖:不同处理器架构对原子操作的支持可能有所不同,代码的可移植性可能受到影响。

3.2相关API

Linux内核中常用的原子操作包括以下几种:

  1. 原子整数操作
    • atomic_t:定义一个原子变量。
    • atomic_set(atomic_t *v, int i):将原子变量设置为给定值。
    • atomic_read(const atomic_t *v):读取原子变量的值。
    • atomic_add(int i, atomic_t *v):将给定值加到原子变量上。
    • atomic_sub(int i, atomic_t *v):从原子变量中减去给定值。
    • atomic_inc(atomic_t *v):将原子变量递增1。
    • atomic_dec(atomic_t *v):将原子变量递减1。
    • atomic_cmpxchg(atomic_t *v, int old, int new):如果原子变量的值等于old,则将其值设置为new
  2. 位操作
    • set_bit(int nr, volatile unsigned long *addr):设置特定位。
    • clear_bit(int nr, volatile unsigned long *addr):清除特定位。
    • test_and_set_bit(int nr, volatile unsigned long *addr):测试并设置特定位。
    • test_and_clear_bit(int nr, volatile unsigned long *addr):测试并清除特定位。

3.3示例代码

#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/device.h>  #define DEVICE_NAME "my_char_device"   #define BUFFER_SIZE 64  static atomic_t counter = ATOMIC_INIT(0);  static int major_number; static struct class *my_class = NULL; static struct device *my_device = NULL; static struct cdev mydev; // 声明 cdev 结构体  static int device_open(struct inode *inode, struct file *file) {     printk(KERN_INFO "Device opened\n");     return 0; }  static int device_release(struct inode *inode, struct file *file) {     printk(KERN_INFO "Device closed\n");     return 0; }  static ssize_t device_read(struct file *filp, char *buffer, size_t len, loff_t *offset) {     char msg[BUFFER_SIZE];     int msg_len;          msg_len = snprintf(msg, BUFFER_SIZE, "%d\n", atomic_read(&counter));     if (*offset >= msg_len) {         return 0;     }      if (len > msg_len - *offset) {         len = msg_len - *offset;     }      if (copy_to_user(buffer, msg + *offset, len)) {         return -EFAULT;     }      *offset += len;     return len; }  static ssize_t device_write(struct file *filp, const char *buffer, size_t len, loff_t *offset) {     char msg[BUFFER_SIZE];          if (len > BUFFER_SIZE - 1) {         len = BUFFER_SIZE - 1;     }      if (copy_from_user(msg, buffer, len)) {         return -EFAULT;     }      msg[len] = '\0';      if (strcmp(msg, "inc\n") == 0) {         atomic_inc(&counter);     } else if (strcmp(msg, "dec\n") == 0) {         atomic_dec(&counter);     } else if (sscanf(msg, "add %d\n", &len) == 1) {         atomic_add(len, &counter);     } else if (sscanf(msg, "sub %d\n", &len) == 1) {         atomic_sub(len, &counter);     } else {         return -EINVAL;     }      return len; }  static struct file_operations fops = {     .open = device_open,     .release = device_release,     .read = device_read,     .write = device_write, };   static int __init test_init(void) {     int retval;     dev_t dev;      printk(KERN_INFO "module init success\n");      // 1. 动态分配主次设备号     retval = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);     if (retval < 0)     {         printk(KERN_ERR "Failed to allocate major number\n");         goto fail_alloc_chrdev_region;     }      major_number = MAJOR(dev);     printk(KERN_INFO "major number is: %d, minor number is: %d\n", major_number, MINOR(dev));      // 2. 初始化 cdev 结构体并添加到内核     cdev_init(&mydev, &fops);     retval = cdev_add(&mydev, dev, 1);     if (retval < 0)     {         printk(KERN_ERR "Failed to add cdev\n");         goto fail_cdev_add;     }      // 3. 创建设备类     my_class = class_create(THIS_MODULE, "my_class");     if (IS_ERR(my_class))     {         printk(KERN_ERR "Failed to create class\n");         retval = PTR_ERR(my_class);         goto fail_class_create;     }      // 4.  申请设备,内核空间就会通知用户空间的udev 进行创建设备,驱动程序本身自己是创建不了文件的!     my_device = device_create(my_class, NULL, dev, NULL, DEVICE_NAME);     if (IS_ERR(my_device))     {         printk(KERN_ERR "Failed to create device\n");         retval = PTR_ERR(my_device);         goto fail_device_create;     }      printk(KERN_INFO "my_char_device: module loaded\n");     return 0;  fail_device_create:     class_destroy(my_class); fail_class_create:     cdev_del(&mydev); fail_cdev_add:     unregister_chrdev_region(dev, 1); fail_alloc_chrdev_region:     return retval; }  static void __exit test_exit(void) {     dev_t dev = MKDEV(major_number, 0);     if (my_device)         device_destroy(my_class, dev);     if (my_class)         class_destroy(my_class);     cdev_del(&mydev);     unregister_chrdev_region(dev, 1);     printk(KERN_INFO "my_char_device: module unloaded\n"); }  module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("Marxist"); MODULE_LICENSE("GPL");  

3.4测试

可以使用echo 和 cat 来简单演示 原子操作加减

root@imx8qmmek:~/module_test# echo "inc" > /dev/my_char_device  root@imx8qmmek:~/module_test# echo "add 5" > /dev/my_char_device  -sh: echo: write error: Invalid argument root@imx8qmmek:~/module_test# cat /dev/my_char_device  6 

ps:不知道为什么 sscanf 会报错,但是仍然是成功执行了

4.自旋锁

自旋锁(spinlock)是一种用于多处理器系统中的同步机制,它可以保护共享资源免受多个进程或线程的并发访问。自旋锁在等待锁的时候不会引起调度器的上下文切换,而是不断地循环检查锁的状态,直到锁可用为止。

4.1自旋锁的基本概念

  1. 自旋锁的定义和用途: 自旋锁通过忙等待(busy-waiting)的方式来实现锁机制,主要用于多处理器环境中短时间的临界区保护。由于自旋锁不会引起进程切换,因此在锁竞争不激烈且临界区很短的情况下,它比互斥锁更高效。
  2. 忙等待(Busy-waiting): 当一个线程试图获取一个自旋锁而锁已经被其他线程持有时,该线程会在一个循环中不断检查锁的状态,而不是被阻塞和切换到其他线程。这种等待方式称为忙等待。
  3. 适用场景: 自旋锁适用于以下场景:
    • 临界区非常短,持有锁的时间很短。
    • 在中断上下文中使用,因为在中断上下文中不能使用可能引起睡眠的锁。
    • 在多处理器系统中,由于自旋锁避免了上下文切换的开销,短时间的忙等待可能更高效。
  4. 限制
    • 自旋锁不能在单处理器系统中使用,因为忙等待会导致死锁。
    • 不适合长时间持有锁的情况,因为忙等待会浪费CPU资源。

4.2Linux内核中的自旋锁

在Linux内核中,自旋锁的API提供了一组函数来初始化、获取和释放自旋锁。以下是常用的自旋锁API:

  1. 定义和初始化自旋锁
    • spinlock_t my_lock;:定义一个自旋锁。
    • spin_lock_init(&my_lock);:初始化自旋锁。
  2. 获取和释放自旋锁
    • spin_lock(&my_lock);:获取自旋锁。如果锁已经被其他处理器获取,该处理器将进入忙等待。
    • spin_unlock(&my_lock);:释放自旋锁。
  3. 获取和释放中断上下文中的自旋锁
    • spin_lock_irqsave(&my_lock, flags);:获取自旋锁并保存中断状态,禁用本地中断。
    • spin_unlock_irqrestore(&my_lock, flags);:释放自旋锁并恢复中断状态。

4.3示例代码

#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/device.h>  #define DEVICE_NAME "my_char_device"  #define BUFFER_SIZE 64  static atomic_t counter = ATOMIC_INIT(0); static spinlock_t my_lock; static int major_number; static struct class *my_class = NULL; static struct device *my_device = NULL; static struct cdev mydev; // 声明 cdev 结构体 static int shared_resource = 0;  static int device_open(struct inode *inode, struct file *file) {     printk(KERN_INFO "Device opened\n");     spin_lock_init(&my_lock); // 打开设备的时候 初始化自旋锁     return 0; }  static int device_release(struct inode *inode, struct file *file) {     printk(KERN_INFO "Device closed\n");     return 0; }  static ssize_t device_read(struct file *filp, char *buffer, size_t len, loff_t *offset) {      // 保护共享资源     spin_lock(&my_lock);     shared_resource++;     printk(KERN_INFO "Shared resource value: %d\n", shared_resource);     spin_unlock(&my_lock);    // 模拟中断上下文中使用自旋锁     unsigned long flags;     spin_lock_irqsave(&my_lock, flags);     shared_resource++;     printk(KERN_INFO "Shared resource value (interrupt context): %d\n", shared_resource);     spin_unlock_irqrestore(&my_lock, flags);     return 0; }  static ssize_t device_write(struct file *filp, const char *buffer, size_t len, loff_t *offset) {     char msg[BUFFER_SIZE];      if (len > BUFFER_SIZE - 1)     {         len = BUFFER_SIZE - 1;     }      if (copy_from_user(msg, buffer, len))     {         return -EFAULT;     }      msg[len] = '\0';      if (strcmp(msg, "inc\n") == 0)     {         atomic_inc(&counter);     }     else if (strcmp(msg, "dec\n") == 0)     {         atomic_dec(&counter);     }     else if (sscanf(msg, "add %d\n", &len) == 1)     {         atomic_add(len, &counter);     }     else if (sscanf(msg, "sub %d\n", &len) == 1)     {         atomic_sub(len, &counter);     }     else     {         return -EINVAL;     }      return len; }  static struct file_operations fops = {     .open = device_open,     .release = device_release,     .read = device_read,     .write = device_write, };  static int __init test_init(void) {     int retval;     dev_t dev;      printk(KERN_INFO "module init success\n");      // 1. 动态分配主次设备号     retval = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);     if (retval < 0)     {         printk(KERN_ERR "Failed to allocate major number\n");         goto fail_alloc_chrdev_region;     }      major_number = MAJOR(dev);     printk(KERN_INFO "major number is: %d, minor number is: %d\n", major_number, MINOR(dev));      // 2. 初始化 cdev 结构体并添加到内核     cdev_init(&mydev, &fops);     retval = cdev_add(&mydev, dev, 1);     if (retval < 0)     {         printk(KERN_ERR "Failed to add cdev\n");         goto fail_cdev_add;     }      // 3. 创建设备类     my_class = class_create(THIS_MODULE, "my_class");     if (IS_ERR(my_class))     {         printk(KERN_ERR "Failed to create class\n");         retval = PTR_ERR(my_class);         goto fail_class_create;     }      // 4.  申请设备,内核空间就会通知用户空间的udev 进行创建设备,驱动程序本身自己是创建不了文件的!     my_device = device_create(my_class, NULL, dev, NULL, DEVICE_NAME);     if (IS_ERR(my_device))     {         printk(KERN_ERR "Failed to create device\n");         retval = PTR_ERR(my_device);         goto fail_device_create;     }      printk(KERN_INFO "my_char_device: module loaded\n");     return 0;  fail_device_create:     class_destroy(my_class); fail_class_create:     cdev_del(&mydev); fail_cdev_add:     unregister_chrdev_region(dev, 1); fail_alloc_chrdev_region:     return retval; }  static void __exit test_exit(void) {     dev_t dev = MKDEV(major_number, 0);     if (my_device)         device_destroy(my_class, dev);     if (my_class)         class_destroy(my_class);     cdev_del(&mydev);     unregister_chrdev_region(dev, 1);     printk(KERN_INFO "my_char_device: module unloaded\n"); }  module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("Marxist"); MODULE_LICENSE("GPL");  

4.4测试

使用cat 读取设备,使用dmesg 查看内核消息

[33585.032083] module init success [33585.035302] major number is: 235, minor number is: 0 [33585.040606] my_char_device: module loaded [33594.159584] Device opened [33594.162327] Shared resource value: 1 [33594.165941] Shared resource value (interrupt context): 2 [33594.171320] Device closed [33607.662150] Device opened [33607.664859] Shared resource value: 3 [33607.668436] Shared resource value (interrupt context): 4 [33607.673827] Device closed  

5.信号量

信号量(Semaphore)是一种用于多线程或多进程同步和互斥的机制。它由荷兰计算机科学家 Edsger Dijkstra 在1960年代提出,用于解决操作系统中的资源共享问题。信号量主要用于控制对共享资源的访问,避免竞争条件和死锁。

5.1基本概念

  1. 信号量的类型
    • 计数信号量(Counting Semaphore):用于控制对多个相同资源的访问。其计数值可以是任何非负整数。
    • 二值信号量(Binary Semaphore):也称为互斥量(Mutex),只允许两种状态(0或1),通常用于保护对单个资源的独占访问。
  2. 基本操作
    • P 操作(Proberen,或称为 down 操作):试图获取信号量。如果信号量的计数值大于0,计数值减1,操作成功;否则,进程将被阻塞,直到信号量的计数值变为正数。
    • V 操作(Verhogen,或称为 up 操作):释放信号量,将计数值加1,并唤醒被阻塞的进程(如果有的话)。
  3. 用途
    • 互斥(Mutual Exclusion):确保一次只有一个进程或线程能够访问共享资源。
    • 同步(Synchronization):协调多个进程或线程的执行顺序,以实现正确的操作顺序。

5.2示例代码

#include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/uaccess.h> #include <linux/device.h> #include <linux/semaphore.h> #define DEVICE_NAME "my_char_device"  #define BUFFER_SIZE 64  static atomic_t counter = ATOMIC_INIT(0);  static int major_number; static struct class *my_class = NULL; static struct device *my_device = NULL; static struct cdev mydev; // 声明 cdev 结构体    static struct semaphore my_semaphore;  //定义信号量 static int shared_resource = 0;  //共享资源   static int device_open(struct inode *inode, struct file *file) {     printk(KERN_INFO "Device opened\n");     // 初始化信号量 设为1  代表互斥,只能有一个进程访问     sema_init(&my_semaphore, 1);     return 0; }  static int device_release(struct inode *inode, struct file *file) {     printk(KERN_INFO "Device closed\n");     return 0; }  static ssize_t device_read(struct file *filp, char *buffer, size_t len, loff_t *offset) {      int ret;     size_t to_copy;      // 获取信号量     if (down_interruptible(&my_semaphore)) {         return -ERESTARTSYS;     }      // 模拟文件的读取行为     if (*offset >= sizeof(shared_resource)) {         // 文件末尾,返回 0         up(&my_semaphore);         return 0;     }      // 确定要复制的字节数     to_copy = min(len, (size_t)sizeof(shared_resource) - *offset);      // 访问共享资源     ret = snprintf(buffer, to_copy, "%d\n", shared_resource);      // 更新偏移量     *offset += ret;      // 释放信号量     up(&my_semaphore);      return ret; }  static ssize_t device_write(struct file *filp, const char *buffer, size_t len, loff_t *offset) {      char buf[64];     int new_value;      // 获取信号量     if (down_interruptible(&my_semaphore)) {         return -ERESTARTSYS;     }      // 从用户空间复制数据     if (copy_from_user(buf, buffer, len)) {         up(&my_semaphore);         return -EFAULT;     }      buf[len] = '\0';     if (sscanf(buf, "%d", &new_value) == 1) {         shared_resource = new_value;     }      // 释放信号量     up(&my_semaphore);      return len; }  static struct file_operations fops = {     .open = device_open,     .release = device_release,     .read = device_read,     .write = device_write, };  static int __init test_init(void) {     int retval;     dev_t dev;      printk(KERN_INFO "module init success\n");      // 1. 动态分配主次设备号     retval = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);     if (retval < 0)     {         printk(KERN_ERR "Failed to allocate major number\n");         goto fail_alloc_chrdev_region;     }      major_number = MAJOR(dev);     printk(KERN_INFO "major number is: %d, minor number is: %d\n", major_number, MINOR(dev));      // 2. 初始化 cdev 结构体并添加到内核     cdev_init(&mydev, &fops);     retval = cdev_add(&mydev, dev, 1);     if (retval < 0)     {         printk(KERN_ERR "Failed to add cdev\n");         goto fail_cdev_add;     }      // 3. 创建设备类     my_class = class_create(THIS_MODULE, "my_class");     if (IS_ERR(my_class))     {         printk(KERN_ERR "Failed to create class\n");         retval = PTR_ERR(my_class);         goto fail_class_create;     }      // 4.  申请设备,内核空间就会通知用户空间的udev 进行创建设备,驱动程序本身自己是创建不了文件的!     my_device = device_create(my_class, NULL, dev, NULL, DEVICE_NAME);     if (IS_ERR(my_device))     {         printk(KERN_ERR "Failed to create device\n");         retval = PTR_ERR(my_device);         goto fail_device_create;     }      printk(KERN_INFO "my_char_device: module loaded\n");             return 0;  fail_device_create:     class_destroy(my_class); fail_class_create:     cdev_del(&mydev); fail_cdev_add:     unregister_chrdev_region(dev, 1); fail_alloc_chrdev_region:     return retval; }  static void __exit test_exit(void) {     dev_t dev = MKDEV(major_number, 0);     if (my_device)         device_destroy(my_class, dev);     if (my_class)         class_destroy(my_class);     cdev_del(&mydev);     unregister_chrdev_region(dev, 1);     printk(KERN_INFO "my_char_device: module unloaded\n"); }  module_init(test_init); module_exit(test_exit); MODULE_AUTHOR("Marxist"); MODULE_LICENSE("GPL"); 

5.3测试

root@imx8qmmek:~/module_test# rmmod kernel_test.ko  root@imx8qmmek:~/module_test# insmod kernel_test.ko  root@imx8qmmek:~/module_test# echo 5 > /dev/my_char_device  root@imx8qmmek:~/module_test# cat /dev/my_char_device  5  

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!