[ARM Linux 驱动开发] Linux 并发与竞争实验

“同步”、“多线程”、“并发”、“竞争”等等名词已经听的太多了,至今对其本质和原理还只是一知半解。所以这里依然是理解为什么要使用同步机制,以及了解有什么同步手段和使用方法。

这篇文章介绍 linux 驱动开发使用到的同步机制:原子操作、自旋锁、信号量和互斥体。

驱动开发中为什么要使用同步机制?

驱动资源可能需要具有独占性,比如打印机,只能等一个任务处理完毕了再处理下一个。

这里我们以之前写的 LED 驱动为例子,也使其每次只能有一个使用者。

原子操作

原子操作,顾名思义,就是(加、减等)操作具有原子性,不可分割。不同步产生的原因是,对某一变量存储所在内存上的操作不是单条指令实现的。

比如 C 语言里的加法,翻译成汇编指令可能为:先读取某一变量在内存中的值到寄存器,然后在此寄存器上做加法操作,最后把寄存器的值重新写回变量所在的内存。这样 C 语言里的加法就对应不止一条指令,当发生任务切换时,内存上的值可能来不及更新完毕,就导致了不同步的现象产生。

原子操作把在内存上的操作使用一条指令来完成,从而达到同步的效果。linux 驱动中使用 atomic_t 结构体声明原子变量,并有一组 atomic_ 打头的函数用于其上的操作。相关定义需要包含头文件 linux/types.h

代码清单 1 原子操作示例
  1. #include <linux/types.h>
  2. #include <linux/module.h>
  3. #include <linux/cdev.h>
  4. #include <linux/device.h>
  5. #include <linux/fs.h>
  6. #include <asm/io.h>
  7. #include <asm/uaccess.h>
  8. #include <linux/of.h>
  9. #include <linux/slab.h>
  10. #include <linux/of_address.h>
  11. #include <linux/of_gpio.h>
  12.  
  13. #define LED_CNT 1
  14. #define LED_NAME "gpioled"
  15.  
  16. struct LedDev
  17. {
  18.     /* 设备号相关 */
  19.     dev_t devId;
  20.     int major;
  21.     int minor;
  22.     /* cdev */
  23.     struct cdev cdev;
  24.     /* mdev */
  25.     struct class* class;
  26.     struct device* device;
  27.     /* GPIO 编号 */
  28.     int gpioLed;
  29.     /* 原子变量 */
  30.     atomic_t lock;
  31. };
  32.  
  33. struct LedDev ledDev;
  34.  
  35. static int led_open(struct inode* inode, struct file* filp)
  36. {
  37.     if (atomic_read(&ledDev.lock) != 0)
  38.     {
  39.         return -EBUSY;
  40.     }
  41.  
  42.     atomic_inc(&ledDev.lock);
  43.     printk("led_open\n");
  44.     return 0;
  45. }
  46.  
  47. static int led_release(struct inode* inode, struct file* filp)
  48. {
  49.     printk("led_release\n");
  50.     atomic_dec(&ledDev.lock);
  51.     return 0;
  52. }
  53.  
  54. static ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
  55. {
  56.     int res;
  57.     char status;
  58.  
  59.     res = copy_from_user(&status, buf, sizeof(status));
  60.     if (res < 0)
  61.     {
  62.         printk("copy_from_user error\n");
  63.         return res;
  64.     }
  65.  
  66.     gpio_set_value(ledDev.gpioLed, status != 1);
  67.     return 0;
  68. }
  69.  
  70. const struct file_operations led_fops =
  71. {
  72.     .owner = THIS_MODULE,
  73.     .open = led_open,
  74.     .release = led_release,
  75.     .write = led_write,
  76. };
  77.  
  78. static int led_init(void)
  79. {
  80.     int res = 0;
  81.     struct device_node* nd;
  82.  
  83.     /* 初始化原子变量 */
  84.     atomic_set(&ledDev.lock, 0);
  85.  
  86.     nd = of_find_node_by_path("/hanhanled");
  87.     if (nd == NULL)
  88.     {
  89.         printk("of_find_node_by_path failed\n");
  90.         return -EINVAL;
  91.     }
  92.  
  93.     ledDev.gpioLed = of_get_named_gpio(nd, "led-gpio"0);
  94.     if (ledDev.gpioLed < 0)
  95.     {
  96.         printk("of_get_named_gpio failed\n");
  97.         return -EINVAL;      
  98.     }
  99.     printk("led gpio number = %d\n", ledDev.gpioLed);
  100.  
  101.     res = gpio_request(ledDev.gpioLed, "gpio-led");
  102.     if (res != 0)
  103.     {
  104.         printk("gpio_request failed\n");
  105.         return -EINVAL;          
  106.     }
  107.  
  108.     res = gpio_direction_output(ledDev.gpioLed, 1);
  109.     if (res < 0)
  110.     {
  111.         printk("gpio_direction_output failed\n");
  112.         goto fail_label;
  113.     }
  114.  
  115.     /////////////////////////////////////////////////////////////
  116.     /* 注册设备号 */
  117.     if (ledDev.major)
  118.     {
  119.         ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
  120.         register_chrdev_region(ledDev.devId, LED_CNT, LED_NAME);
  121.     }
  122.     else
  123.     {
  124.         alloc_chrdev_region(&ledDev.devId, 0, LED_CNT, LED_NAME);
  125.         ledDev.major = MAJOR(ledDev.devId);
  126.         ledDev.minor = MINOR(ledDev.devId);
  127.     }
  128.     printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
  129.  
  130.     /* 设置 cdev */
  131.     ledDev.cdev.owner = THIS_MODULE;
  132.     cdev_init(&ledDev.cdev, &led_fops);
  133.     cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
  134.  
  135.     /* 设置 mdev */
  136.     ledDev.class = class_create(THIS_MODULE, LED_NAME);
  137.     if (IS_ERR(ledDev.class))
  138.     {
  139.         return PTR_ERR(ledDev.class);
  140.     }
  141.  
  142.     ledDev.device = device_create(ledDev.class, NULL, ledDev.devId, NULL, LED_NAME);
  143.     if (IS_ERR(ledDev.device))
  144.     {
  145.         return PTR_ERR(ledDev.device);
  146.     }
  147.  
  148.     return 0;
  149.  
  150. fail_label:
  151.     gpio_free(ledDev.gpioLed);
  152.     return res;
  153. }
  154.  
  155. static void led_exit(void)
  156. {
  157.     device_destroy(ledDev.class, ledDev.devId);
  158.     class_destroy(ledDev.class);
  159.  
  160.     cdev_del(&ledDev.cdev);
  161.     unregister_chrdev_region(ledDev.devId, LED_CNT);
  162.  
  163.     /* 释放 GPIO */
  164.     gpio_free(ledDev.gpioLed);
  165. }
  166.  
  167. module_init(led_init);
  168. module_exit(led_exit);
  169. MODULE_LICENSE("GPL");
  170. MODULE_AUTHOR("hanhan");

如代码清单 1 所示,代码 30 行定义了原子变量。第 84 行在驱动加载阶段将原子变量设置初始值为 0。有任务使用此 led 设备时,将原子变量加一,使用完毕退出时将原子变量减一。

看到设备打开函数(led_open,第 35 到 45 行),首先读取原子变量的值,如果不为零(代表当前设备正在被使用),则返回忙;申请检查成功后,再将原子变量加一,以标志设备正在使用。

再看到设备关闭函数(led_release,第 47 到 52 行),其代表不再使用设备,因此将原子变量减一。

注意,这边如果设备正在被使用,不是阻塞等待,而是直接返回错误。

测试驱动

接下来我们需要写一个应用层应用用来测试上述驱动。代码如清单 2 所示,其中我们使用 sleep() 增长设备使用时间,更方便验证。

代码清单 2 驱动测试程序
  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <fcntl.h>
  4. #include <unistd.h>
  5. #include <stdio.h>
  6.  
  7. int main()
  8. {
  9.     int i;
  10.     int fd = open("/dev/gpioled"O_RDWR);
  11.     if (fd < 0)
  12.     {
  13.         printf("open failed\n");
  14.         return -1;
  15.     }
  16.  
  17.     char buf = 1;
  18.     int res = write(fd, &buf, 1);
  19.     if (res < 0)
  20.     {
  21.         printf("write failed\n");
  22.         goto end_proc;
  23.     }
  24.  
  25.     for (i = 0; i < 5; i++)
  26.     {
  27.         sleep(5);
  28.         printf("running times : %d\n", i);
  29.     }
  30.  
  31. end_proc:
  32.     close(fd);
  33. }

如图 1 所示,我们对所写驱动进行了验证:第一次执行测试程序,并使其在后台运行(命令行后加 &),此时没有任务使用 led 设备,所有肯定能成功打开设备。第一个应用会占用设备 25 秒,在此期间我们再次运行测试程序,如预期,设备会打开失败。等一个应用执行完毕,我们再次运行测试程序,如预期,此时可以正常打开设备。

图 1 验证同步性

自旋锁

当我们需要保护的内容仅是整型的话,原子变量是可以满足,但是当保护的内容是诸如结构体这些包含众多内容、且不仅仅是加减等基本操作时,原子变量就不管用了。

自旋锁类似 while 的循环操作,如果获取不到锁,就会一直循环,直到获取锁。因此这种锁叫“自旋”锁,以循环“自旋”检查的方式来保护临界区中的代码。

因为自旋锁是循环实现的,所以很占 CPU 资源,因此临界区中的代码要尽可能的短。同时自旋锁最好不要用于可抢占的代码环境:如果当某一任务获取到锁后,此时发生了中断,中断中又使用到了此锁。因为中断不结束是不会继续调度之前的任务的,就意味着之前任务获取的锁不会被释放,就产生了死锁。

我们可以想象出用原子变量实现自旋锁的方式,和代码清单 1 的思路一致,不过当原子变量的值不满足时,不是退出,而是循环再次读取,直到原子变量的值满足条件。

代码清单 3 自旋锁示例
  1. #include <linux/types.h>
  2. #include <linux/module.h>
  3. #include <linux/cdev.h>
  4. #include <linux/device.h>
  5. #include <linux/fs.h>
  6. #include <asm/io.h>
  7. #include <asm/uaccess.h>
  8. #include <linux/of.h>
  9. #include <linux/slab.h>
  10. #include <linux/of_address.h>
  11. #include <linux/of_gpio.h>
  12.  
  13. #define LED_CNT 1
  14. #define LED_NAME "gpioled"
  15.  
  16. struct LedDev
  17. {
  18.     /* 设备号相关 */
  19.     dev_t devId;
  20.     int major;
  21.     int minor;
  22.     /* cdev */
  23.     struct cdev cdev;
  24.     /* mdev */
  25.     struct class* class;
  26.     struct device* device;
  27.     /* GPIO 编号 */
  28.     int gpioLed;
  29.     /* 自旋锁 */
  30.     spinlock_t lock;
  31.     int isDevBusy;
  32. };
  33.  
  34. struct LedDev ledDev;
  35.  
  36. static int led_open(struct inode* inode, struct file* filp)
  37. {
  38.     unsigned long flags;
  39.     spin_lock_irqsave(&ledDev.lock, flags);
  40.     if (ledDev.isDevBusy)
  41.     {
  42.         spin_unlock_irqrestore(&ledDev.lock, flags);
  43.         return -EBUSY;
  44.     }
  45.     ledDev.isDevBusy = 1;
  46.     spin_unlock_irqrestore(&ledDev.lock, flags);
  47.  
  48.     printk("led_open\n");
  49.     return 0;
  50. }
  51.  
  52. static int led_release(struct inode* inode, struct file* filp)
  53. {
  54.     unsigned long flags;
  55.     printk("led_release\n");
  56.     spin_lock_irqsave(&ledDev.lock, flags);
  57.     ledDev.isDevBusy = 0;
  58.     spin_unlock_irqrestore(&ledDev.lock, flags);
  59.     return 0;
  60. }
  61.  
  62. static ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
  63. {
  64.     int res;
  65.     char status;
  66.  
  67.     res = copy_from_user(&status, buf, sizeof(status));
  68.     if (res < 0)
  69.     {
  70.         printk("copy_from_user error\n");
  71.         return res;
  72.     }
  73.  
  74.     gpio_set_value(ledDev.gpioLed, status != 1);
  75.     return 0;
  76. }
  77.  
  78. const struct file_operations led_fops =
  79. {
  80.     .owner = THIS_MODULE,
  81.     .open = led_open,
  82.     .release = led_release,
  83.     .write = led_write,
  84. };
  85.  
  86. static int led_init(void)
  87. {
  88.     int res = 0;
  89.     struct device_node* nd;
  90.  
  91.     /* 初始化自旋锁 */
  92.     spin_lock_init(&ledDev.lock);
  93.     ledDev.isDevBusy = 0;
  94.  
  95.     nd = of_find_node_by_path("/hanhanled");
  96.     if (nd == NULL)
  97.     {
  98.         printk("of_find_node_by_path failed\n");
  99.         return -EINVAL;
  100.     }
  101.  
  102.     ledDev.gpioLed = of_get_named_gpio(nd, "led-gpio", 0);
  103.     if (ledDev.gpioLed < 0)
  104.     {
  105.         printk("of_get_named_gpio failed\n");
  106.         return -EINVAL;      
  107.     }
  108.     printk("led gpio number = %d\n", ledDev.gpioLed);
  109.  
  110.     res = gpio_request(ledDev.gpioLed, "gpio-led");
  111.     if (res != 0)
  112.     {
  113.         printk("gpio_request failed\n");
  114.         return -EINVAL;          
  115.     }
  116.  
  117.     res = gpio_direction_output(ledDev.gpioLed, 1);
  118.     if (res < 0)
  119.     {
  120.         printk("gpio_direction_output failed\n");
  121.         goto fail_label;
  122.     }
  123.  
  124.     /////////////////////////////////////////////////////////////
  125.     /* 注册设备号 */
  126.     if (ledDev.major)
  127.     {
  128.         ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
  129.         register_chrdev_region(ledDev.devId, LED_CNT, LED_NAME);
  130.     }
  131.     else
  132.     {
  133.         alloc_chrdev_region(&ledDev.devId, 0, LED_CNT, LED_NAME);
  134.         ledDev.major = MAJOR(ledDev.devId);
  135.         ledDev.minor = MINOR(ledDev.devId);
  136.     }
  137.     printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
  138.  
  139.     /* 设置 cdev */
  140.     ledDev.cdev.owner = THIS_MODULE;
  141.     cdev_init(&ledDev.cdev, &led_fops);
  142.     cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
  143.  
  144.     /* 设置 mdev */
  145.     ledDev.class = class_create(THIS_MODULE, LED_NAME);
  146.     if (IS_ERR(ledDev.class))
  147.     {
  148.         return PTR_ERR(ledDev.class);
  149.     }
  150.  
  151.     ledDev.device = device_create(ledDev.class, NULL, ledDev.devId, NULL, LED_NAME);
  152.     if (IS_ERR(ledDev.device))
  153.     {
  154.         return PTR_ERR(ledDev.device);
  155.     }
  156.  
  157.     return 0;
  158.  
  159. fail_label:
  160.     gpio_free(ledDev.gpioLed);
  161.     return res;
  162. }
  163.  
  164. static void led_exit(void)
  165. {
  166.     device_destroy(ledDev.class, ledDev.devId);
  167.     class_destroy(ledDev.class);
  168.  
  169.     cdev_del(&ledDev.cdev);
  170.     unregister_chrdev_region(ledDev.devId, LED_CNT);
  171.  
  172.     /* 释放 GPIO */
  173.     gpio_free(ledDev.gpioLed);
  174. }
  175.  
  176. module_init(led_init);
  177. module_exit(led_exit);
  178. MODULE_LICENSE("GPL");
  179. MODULE_AUTHOR("hanhan");

如代码清单 3 所示,使用 spinlock_t 来声明自旋锁(第 30 行);isDevBusy 变量(第 31 行)是我们需要保护的变量,其指示设备是否正在被使用。驱动加载时,我们初始化锁和设置所要保护变量的初始值(第 92 至 93 行)。

打开设备时(led_open 函数,第 36 至 50 行),使用 spin_lock_irqsave 函数来获取锁,其保存了当前 irq 环境的上下文,同时禁止 irq 中断(前面提及自旋锁最好不要用于中断环境)。获取到锁之后,我们就可以放心对需要保护的内容进行操作了:如果当前变量指示设备正在使用,则返回忙(不要忘记了解锁);否则将变量设置为正在使用。最后使用 spin_unlock_irqrestore 函数来解锁,并恢复 irq 上下文。

类似的,在关闭设备时(led_release 函数,第 52 至 60 行),同在在 spin_lock_irqsavespin_unlock_irqrestore 之间,我们将需要保护的变量设置为设备不在使用。

信号量

自旋锁的一个问题是,一旦临界区代码处理时间过长,就会占用大量 CPU 资源。而信号量可以解决这个问题,当条件不满足时,相关任务可以进入休眠状态,此时会去调度其他任务,待条件满足再继续执行先前的任务。由于会进入休眠状态,所以信号量不能用于中断代码中(因为中断需要即使处理和响应)。

代码清单 4 信号量示例
  1. #include <linux/semaphore.h>
  2. #include <linux/types.h>
  3. #include <linux/module.h>
  4. #include <linux/cdev.h>
  5. #include <linux/device.h>
  6. #include <linux/fs.h>
  7. #include <asm/io.h>
  8. #include <asm/uaccess.h>
  9. #include <linux/of.h>
  10. #include <linux/slab.h>
  11. #include <linux/of_address.h>
  12. #include <linux/of_gpio.h>
  13.  
  14. #define LED_CNT 1
  15. #define LED_NAME "gpioled"
  16.  
  17. struct LedDev
  18. {
  19.     /* 设备号相关 */
  20.     dev_t devId;
  21.     int major;
  22.     int minor;
  23.     /* cdev */
  24.     struct cdev cdev;
  25.     /* mdev */
  26.     struct class* class;
  27.     struct device* device;
  28.     /* GPIO 编号 */
  29.     int gpioLed;
  30.     /* 信号量 */
  31.     struct semaphore sem;
  32. };
  33.  
  34. struct LedDev ledDev;
  35.  
  36. static int led_open(struct inode* inode, struct file* filp)
  37. {
  38.     if (down_interruptible(&ledDev.sem))
  39.     {
  40.         return -ERESTARTSYS;
  41.     }
  42.  
  43. #if 0
  44.     down(&ledDev.sem);
  45. #endif
  46.  
  47.     printk("led_open\n");
  48.     return 0;
  49. }
  50.  
  51. static int led_release(struct inode* inode, struct file* filp)
  52. {
  53.     printk("led_release\n");
  54.     up(&ledDev.sem);
  55.     return 0;
  56. }
  57.  
  58. static ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
  59. {
  60.     int res;
  61.     char status;
  62.  
  63.     res = copy_from_user(&status, buf, sizeof(status));
  64.     if (res < 0)
  65.     {
  66.         printk("copy_from_user error\n");
  67.         return res;
  68.     }
  69.  
  70.     gpio_set_value(ledDev.gpioLed, status != 1);
  71.     return 0;
  72. }
  73.  
  74. const struct file_operations led_fops =
  75. {
  76.     .owner = THIS_MODULE,
  77.     .open = led_open,
  78.     .release = led_release,
  79.     .write = led_write,
  80. };
  81.  
  82. static int led_init(void)
  83. {
  84.     int res = 0;
  85.     struct device_node* nd;
  86.  
  87.     /* 初始化信号量 */
  88.     sema_init(&ledDev.sem, 1);
  89.  
  90.     nd = of_find_node_by_path("/hanhanled");
  91.     if (nd == NULL)
  92.     {
  93.         printk("of_find_node_by_path failed\n");
  94.         return -EINVAL;
  95.     }
  96.  
  97.     ledDev.gpioLed = of_get_named_gpio(nd, "led-gpio", 0);
  98.     if (ledDev.gpioLed < 0)
  99.     {
  100.         printk("of_get_named_gpio failed\n");
  101.         return -EINVAL;      
  102.     }
  103.     printk("led gpio number = %d\n", ledDev.gpioLed);
  104.  
  105.     res = gpio_request(ledDev.gpioLed, "gpio-led");
  106.     if (res != 0)
  107.     {
  108.         printk("gpio_request failed\n");
  109.         return -EINVAL;          
  110.     }
  111.  
  112.     res = gpio_direction_output(ledDev.gpioLed, 1);
  113.     if (res < 0)
  114.     {
  115.         printk("gpio_direction_output failed\n");
  116.         goto fail_label;
  117.     }
  118.  
  119.     /////////////////////////////////////////////////////////////
  120.     /* 注册设备号 */
  121.     if (ledDev.major)
  122.     {
  123.         ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
  124.         register_chrdev_region(ledDev.devId, LED_CNT, LED_NAME);
  125.     }
  126.     else
  127.     {
  128.         alloc_chrdev_region(&ledDev.devId, 0, LED_CNT, LED_NAME);
  129.         ledDev.major = MAJOR(ledDev.devId);
  130.         ledDev.minor = MINOR(ledDev.devId);
  131.     }
  132.     printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
  133.  
  134.     /* 设置 cdev */
  135.     ledDev.cdev.owner = THIS_MODULE;
  136.     cdev_init(&ledDev.cdev, &led_fops);
  137.     cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
  138.  
  139.     /* 设置 mdev */
  140.     ledDev.class = class_create(THIS_MODULE, LED_NAME);
  141.     if (IS_ERR(ledDev.class))
  142.     {
  143.         return PTR_ERR(ledDev.class);
  144.     }
  145.  
  146.     ledDev.device = device_create(ledDev.class, NULL, ledDev.devId, NULL, LED_NAME);
  147.     if (IS_ERR(ledDev.device))
  148.     {
  149.         return PTR_ERR(ledDev.device);
  150.     }
  151.  
  152.     return 0;
  153.  
  154. fail_label:
  155.     gpio_free(ledDev.gpioLed);
  156.     return res;
  157. }
  158.  
  159. static void led_exit(void)
  160. {
  161.     device_destroy(ledDev.class, ledDev.devId);
  162.     class_destroy(ledDev.class);
  163.  
  164.     cdev_del(&ledDev.cdev);
  165.     unregister_chrdev_region(ledDev.devId, LED_CNT);
  166.  
  167.     /* 释放 GPIO */
  168.     gpio_free(ledDev.gpioLed);
  169. }
  170.  
  171. module_init(led_init);
  172. module_exit(led_exit);
  173. MODULE_LICENSE("GPL");
  174. MODULE_AUTHOR("hanhan");
  175.  

代码清单 4 是信号量的示例。如第 31 行所示,使用 semaphore 声明一个信号量。在驱动加载时(第 88 行),将信号量进行初始化,此时设置初始值为 1,代表一次只能有一个任务进入临界区。

打开设备时(led_open 函数,第 36 至 49 行),调用 down_interruptible 函数,其中的 down 可以看成是“减法”,只要信号量够减(此处初始值设置为 1)就能继续执行后续代码,否则就会休眠,直到满足“够减”的条件。

注意这边有 down_interruptibledown 两个函数,两者的区别是:down_interruptible 是当休眠的任务即使不满足条件,其他信号也能唤醒它,不满足条件时的唤醒会返回一个错误值;而 down 只能是满足条件时唤醒。一般会选用 down_interruptible 函数,因为大多数情况都会有需要响应退出信号的需求。

关闭设备时(led_release 函数,第 51 至 55 行),调用 up 函数,可以理解为“加法”。

注意,此时是如果设备正在被使用,是阻塞等待,直到前一个设备退出。

测试驱动

如图 2 所示,同样是先将一个测试程序置于后台运行,在此程序结束之前,执行另一个测试程序。此时和原子变量、自旋锁不同,open 不会立即返回,而是进入阻塞状态,直到第一个测试程序运行结束。

图 2 验证同步性

互斥量

在之前的信号量里,我们实现了互斥性(初始值为 1,即一次性只能有一个任务进入临界区),互斥量和其很相似。这边可以看到信号量和互斥量的一个不同点,信号量是可以多个任务同时进入临界区的。互斥量获取不到锁时,也会进入睡眠状态。

代码清单 5 互斥量示例
  1. #include <linux/semaphore.h>
  2. #include <linux/types.h>
  3. #include <linux/module.h>
  4. #include <linux/cdev.h>
  5. #include <linux/device.h>
  6. #include <linux/fs.h>
  7. #include <asm/io.h>
  8. #include <asm/uaccess.h>
  9. #include <linux/of.h>
  10. #include <linux/slab.h>
  11. #include <linux/of_address.h>
  12. #include <linux/of_gpio.h>
  13.  
  14. #define LED_CNT 1
  15. #define LED_NAME "gpioled"
  16.  
  17. struct LedDev
  18. {
  19.     /* 设备号相关 */
  20.     dev_t devId;
  21.     int major;
  22.     int minor;
  23.     /* cdev */
  24.     struct cdev cdev;
  25.     /* mdev */
  26.     struct class* class;
  27.     struct device* device;
  28.     /* GPIO 编号 */
  29.     int gpioLed;
  30.     /* 互斥体 */
  31.     struct mutex mut;
  32. };
  33.  
  34. struct LedDev ledDev;
  35.  
  36. static int led_open(struct inode* inode, struct file* filp)
  37. {
  38.     if (mutex_lock_interruptible(&ledDev.mut))
  39.     {
  40.         return -ERESTARTSYS;
  41.     }
  42.  
  43. #if 0
  44.     mutex_lock(&ledDev.mut);
  45. #endif
  46.  
  47.     printk("led_open\n");
  48.     return 0;
  49. }
  50.  
  51. static int led_release(struct inode* inode, struct file* filp)
  52. {
  53.     printk("led_release\n");
  54.     mutex_unlock(&ledDev.mut);
  55.     return 0;
  56. }
  57.  
  58. static ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
  59. {
  60.     int res;
  61.     char status;
  62.  
  63.     res = copy_from_user(&status, buf, sizeof(status));
  64.     if (res < 0)
  65.     {
  66.         printk("copy_from_user error\n");
  67.         return res;
  68.     }
  69.  
  70.     gpio_set_value(ledDev.gpioLed, status != 1);
  71.     return 0;
  72. }
  73.  
  74. const struct file_operations led_fops =
  75. {
  76.     .owner = THIS_MODULE,
  77.     .open = led_open,
  78.     .release = led_release,
  79.     .write = led_write,
  80. };
  81.  
  82. static int led_init(void)
  83. {
  84.     int res = 0;
  85.     struct device_node* nd;
  86.  
  87.     /* 初始化互斥体 */
  88.     mutex_init(&ledDev.mut);
  89.  
  90.     nd = of_find_node_by_path("/hanhanled");
  91.     if (nd == NULL)
  92.     {
  93.         printk("of_find_node_by_path failed\n");
  94.         return -EINVAL;
  95.     }
  96.  
  97.     ledDev.gpioLed = of_get_named_gpio(nd, "led-gpio", 0);
  98.     if (ledDev.gpioLed < 0)
  99.     {
  100.         printk("of_get_named_gpio failed\n");
  101.         return -EINVAL;      
  102.     }
  103.     printk("led gpio number = %d\n", ledDev.gpioLed);
  104.  
  105.     res = gpio_request(ledDev.gpioLed, "gpio-led");
  106.     if (res != 0)
  107.     {
  108.         printk("gpio_request failed\n");
  109.         return -EINVAL;          
  110.     }
  111.  
  112.     res = gpio_direction_output(ledDev.gpioLed, 1);
  113.     if (res < 0)
  114.     {
  115.         printk("gpio_direction_output failed\n");
  116.         goto fail_label;
  117.     }
  118.  
  119.     /////////////////////////////////////////////////////////////
  120.     /* 注册设备号 */
  121.     if (ledDev.major)
  122.     {
  123.         ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
  124.         register_chrdev_region(ledDev.devId, LED_CNT, LED_NAME);
  125.     }
  126.     else
  127.     {
  128.         alloc_chrdev_region(&ledDev.devId, 0, LED_CNT, LED_NAME);
  129.         ledDev.major = MAJOR(ledDev.devId);
  130.         ledDev.minor = MINOR(ledDev.devId);
  131.     }
  132.     printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
  133.  
  134.     /* 设置 cdev */
  135.     ledDev.cdev.owner = THIS_MODULE;
  136.     cdev_init(&ledDev.cdev, &led_fops);
  137.     cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
  138.  
  139.     /* 设置 mdev */
  140.     ledDev.class = class_create(THIS_MODULE, LED_NAME);
  141.     if (IS_ERR(ledDev.class))
  142.     {
  143.         return PTR_ERR(ledDev.class);
  144.     }
  145.  
  146.     ledDev.device = device_create(ledDev.class, NULL, ledDev.devId, NULL, LED_NAME);
  147.     if (IS_ERR(ledDev.device))
  148.     {
  149.         return PTR_ERR(ledDev.device);
  150.     }
  151.  
  152.     return 0;
  153.  
  154. fail_label:
  155.     gpio_free(ledDev.gpioLed);
  156.     return res;
  157. }
  158.  
  159. static void led_exit(void)
  160. {
  161.     device_destroy(ledDev.class, ledDev.devId);
  162.     class_destroy(ledDev.class);
  163.  
  164.     cdev_del(&ledDev.cdev);
  165.     unregister_chrdev_region(ledDev.devId, LED_CNT);
  166.  
  167.     /* 释放 GPIO */
  168.     gpio_free(ledDev.gpioLed);
  169. }
  170.  
  171. module_init(led_init);
  172. module_exit(led_exit);
  173. MODULE_LICENSE("GPL");
  174. MODULE_AUTHOR("hanhan");

代码清单 5 是互斥量的示例,和之前的代码示例也如出一辙。在驱动加载时使用 mutex_init 函数初始化互斥量(第 88 行)。

当设备打开时(led_open 函数,第 36 至 49 行),使用 mutex_lock_interruptiblemutex_lock 加锁。两者的区别与 down_interruptibledown 之间的区别一样:mutex_lock_interruptible 在睡眠时还可由其他信号唤醒;mutex_lock 只能条件满足时唤醒。

当设备关闭时(led_release 函数,第 51 至 56 行),使用 mutex_unlock 解锁。