[ARM Linux 驱动开发] Linux 并发与竞争实验
“同步”、“多线程”、“并发”、“竞争”等等名词已经听的太多了,至今对其本质和原理还只是一知半解。所以这里依然是理解为什么要使用同步机制,以及了解有什么同步手段和使用方法。
这篇文章介绍 linux 驱动开发使用到的同步机制:原子操作、自旋锁、信号量和互斥体。
驱动开发中为什么要使用同步机制?
驱动资源可能需要具有独占性,比如打印机,只能等一个任务处理完毕了再处理下一个。
这里我们以之前写的 LED 驱动为例子,也使其每次只能有一个使用者。
原子操作
原子操作,顾名思义,就是(加、减等)操作具有原子性,不可分割。不同步产生的原因是,对某一变量存储所在内存上的操作不是单条指令实现的。
比如 C 语言里的加法,翻译成汇编指令可能为:先读取某一变量在内存中的值到寄存器,然后在此寄存器上做加法操作,最后把寄存器的值重新写回变量所在的内存。这样 C 语言里的加法就对应不止一条指令,当发生任务切换时,内存上的值可能来不及更新完毕,就导致了不同步的现象产生。
原子操作把在内存上的操作使用一条指令来完成,从而达到同步的效果。linux 驱动中使用 atomic_t 结构体声明原子变量,并有一组 atomic_ 打头的函数用于其上的操作。相关定义需要包含头文件 linux/types.h。
- #include <linux/types.h>
- #include <linux/module.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- #include <linux/fs.h>
- #include <asm/io.h>
- #include <asm/uaccess.h>
- #include <linux/of.h>
- #include <linux/slab.h>
- #include <linux/of_address.h>
- #include <linux/of_gpio.h>
- #define LED_CNT 1
- #define LED_NAME "gpioled"
- struct LedDev
- {
- /* 设备号相关 */
- dev_t devId;
- int major;
- int minor;
- /* cdev */
- struct cdev cdev;
- /* mdev */
- struct class* class;
- struct device* device;
- /* GPIO 编号 */
- int gpioLed;
- /* 原子变量 */
- atomic_t lock;
- };
- struct LedDev ledDev;
- static int led_open(struct inode* inode, struct file* filp)
- {
- if (atomic_read(&ledDev.lock) != 0)
- {
- return -EBUSY;
- }
- atomic_inc(&ledDev.lock);
- printk("led_open\n");
- return 0;
- }
- static int led_release(struct inode* inode, struct file* filp)
- {
- printk("led_release\n");
- atomic_dec(&ledDev.lock);
- return 0;
- }
- static ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
- {
- int res;
- char status;
- res = copy_from_user(&status, buf, sizeof(status));
- if (res < 0)
- {
- printk("copy_from_user error\n");
- return res;
- }
- gpio_set_value(ledDev.gpioLed, status != 1);
- return 0;
- }
- const struct file_operations led_fops =
- {
- .owner = THIS_MODULE,
- .open = led_open,
- .release = led_release,
- .write = led_write,
- };
- static int led_init(void)
- {
- int res = 0;
- struct device_node* nd;
- /* 初始化原子变量 */
- atomic_set(&ledDev.lock, 0);
- nd = of_find_node_by_path("/hanhanled");
- if (nd == NULL)
- {
- printk("of_find_node_by_path failed\n");
- return -EINVAL;
- }
- ledDev.gpioLed = of_get_named_gpio(nd, "led-gpio", 0);
- if (ledDev.gpioLed < 0)
- {
- printk("of_get_named_gpio failed\n");
- return -EINVAL;
- }
- printk("led gpio number = %d\n", ledDev.gpioLed);
- res = gpio_request(ledDev.gpioLed, "gpio-led");
- if (res != 0)
- {
- printk("gpio_request failed\n");
- return -EINVAL;
- }
- res = gpio_direction_output(ledDev.gpioLed, 1);
- if (res < 0)
- {
- printk("gpio_direction_output failed\n");
- goto fail_label;
- }
- /////////////////////////////////////////////////////////////
- /* 注册设备号 */
- if (ledDev.major)
- {
- ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
- register_chrdev_region(ledDev.devId, LED_CNT, LED_NAME);
- }
- else
- {
- alloc_chrdev_region(&ledDev.devId, 0, LED_CNT, LED_NAME);
- ledDev.major = MAJOR(ledDev.devId);
- ledDev.minor = MINOR(ledDev.devId);
- }
- printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
- /* 设置 cdev */
- ledDev.cdev.owner = THIS_MODULE;
- cdev_init(&ledDev.cdev, &led_fops);
- cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
- /* 设置 mdev */
- ledDev.class = class_create(THIS_MODULE, LED_NAME);
- if (IS_ERR(ledDev.class))
- {
- return PTR_ERR(ledDev.class);
- }
- ledDev.device = device_create(ledDev.class, NULL, ledDev.devId, NULL, LED_NAME);
- if (IS_ERR(ledDev.device))
- {
- return PTR_ERR(ledDev.device);
- }
- return 0;
- fail_label:
- gpio_free(ledDev.gpioLed);
- return res;
- }
- static void led_exit(void)
- {
- device_destroy(ledDev.class, ledDev.devId);
- class_destroy(ledDev.class);
- cdev_del(&ledDev.cdev);
- unregister_chrdev_region(ledDev.devId, LED_CNT);
- /* 释放 GPIO */
- gpio_free(ledDev.gpioLed);
- }
- module_init(led_init);
- module_exit(led_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("hanhan");
如代码清单 1 所示,代码 30 行定义了原子变量。第 84 行在驱动加载阶段将原子变量设置初始值为 0。有任务使用此 led 设备时,将原子变量加一,使用完毕退出时将原子变量减一。
看到设备打开函数(led_open,第 35 到 45 行),首先读取原子变量的值,如果不为零(代表当前设备正在被使用),则返回忙;申请检查成功后,再将原子变量加一,以标志设备正在使用。
再看到设备关闭函数(led_release,第 47 到 52 行),其代表不再使用设备,因此将原子变量减一。
注意,这边如果设备正在被使用,不是阻塞等待,而是直接返回错误。
测试驱动
接下来我们需要写一个应用层应用用来测试上述驱动。代码如清单 2 所示,其中我们使用 sleep() 增长设备使用时间,更方便验证。
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <stdio.h>
- int main()
- {
- int i;
- int fd = open("/dev/gpioled", O_RDWR);
- if (fd < 0)
- {
- printf("open failed\n");
- return -1;
- }
- char buf = 1;
- int res = write(fd, &buf, 1);
- if (res < 0)
- {
- printf("write failed\n");
- goto end_proc;
- }
- for (i = 0; i < 5; i++)
- {
- sleep(5);
- printf("running times : %d\n", i);
- }
- end_proc:
- close(fd);
- }
如图 1 所示,我们对所写驱动进行了验证:第一次执行测试程序,并使其在后台运行(命令行后加 &),此时没有任务使用 led 设备,所有肯定能成功打开设备。第一个应用会占用设备 25 秒,在此期间我们再次运行测试程序,如预期,设备会打开失败。等一个应用执行完毕,我们再次运行测试程序,如预期,此时可以正常打开设备。

自旋锁
当我们需要保护的内容仅是整型的话,原子变量是可以满足,但是当保护的内容是诸如结构体这些包含众多内容、且不仅仅是加减等基本操作时,原子变量就不管用了。
自旋锁类似 while 的循环操作,如果获取不到锁,就会一直循环,直到获取锁。因此这种锁叫“自旋”锁,以循环“自旋”检查的方式来保护临界区中的代码。
因为自旋锁是循环实现的,所以很占 CPU 资源,因此临界区中的代码要尽可能的短。同时自旋锁最好不要用于可抢占的代码环境:如果当某一任务获取到锁后,此时发生了中断,中断中又使用到了此锁。因为中断不结束是不会继续调度之前的任务的,就意味着之前任务获取的锁不会被释放,就产生了死锁。
我们可以想象出用原子变量实现自旋锁的方式,和代码清单 1 的思路一致,不过当原子变量的值不满足时,不是退出,而是循环再次读取,直到原子变量的值满足条件。
- #include <linux/types.h>
- #include <linux/module.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- #include <linux/fs.h>
- #include <asm/io.h>
- #include <asm/uaccess.h>
- #include <linux/of.h>
- #include <linux/slab.h>
- #include <linux/of_address.h>
- #include <linux/of_gpio.h>
- #define LED_CNT 1
- #define LED_NAME "gpioled"
- struct LedDev
- {
- /* 设备号相关 */
- dev_t devId;
- int major;
- int minor;
- /* cdev */
- struct cdev cdev;
- /* mdev */
- struct class* class;
- struct device* device;
- /* GPIO 编号 */
- int gpioLed;
- /* 自旋锁 */
- spinlock_t lock;
- int isDevBusy;
- };
- struct LedDev ledDev;
- static int led_open(struct inode* inode, struct file* filp)
- {
- unsigned long flags;
- spin_lock_irqsave(&ledDev.lock, flags);
- if (ledDev.isDevBusy)
- {
- spin_unlock_irqrestore(&ledDev.lock, flags);
- return -EBUSY;
- }
- ledDev.isDevBusy = 1;
- spin_unlock_irqrestore(&ledDev.lock, flags);
- printk("led_open\n");
- return 0;
- }
- static int led_release(struct inode* inode, struct file* filp)
- {
- unsigned long flags;
- printk("led_release\n");
- spin_lock_irqsave(&ledDev.lock, flags);
- ledDev.isDevBusy = 0;
- spin_unlock_irqrestore(&ledDev.lock, flags);
- return 0;
- }
- static ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
- {
- int res;
- char status;
- res = copy_from_user(&status, buf, sizeof(status));
- if (res < 0)
- {
- printk("copy_from_user error\n");
- return res;
- }
- gpio_set_value(ledDev.gpioLed, status != 1);
- return 0;
- }
- const struct file_operations led_fops =
- {
- .owner = THIS_MODULE,
- .open = led_open,
- .release = led_release,
- .write = led_write,
- };
- static int led_init(void)
- {
- int res = 0;
- struct device_node* nd;
- /* 初始化自旋锁 */
- spin_lock_init(&ledDev.lock);
- ledDev.isDevBusy = 0;
- nd = of_find_node_by_path("/hanhanled");
- if (nd == NULL)
- {
- printk("of_find_node_by_path failed\n");
- return -EINVAL;
- }
- ledDev.gpioLed = of_get_named_gpio(nd, "led-gpio", 0);
- if (ledDev.gpioLed < 0)
- {
- printk("of_get_named_gpio failed\n");
- return -EINVAL;
- }
- printk("led gpio number = %d\n", ledDev.gpioLed);
- res = gpio_request(ledDev.gpioLed, "gpio-led");
- if (res != 0)
- {
- printk("gpio_request failed\n");
- return -EINVAL;
- }
- res = gpio_direction_output(ledDev.gpioLed, 1);
- if (res < 0)
- {
- printk("gpio_direction_output failed\n");
- goto fail_label;
- }
- /////////////////////////////////////////////////////////////
- /* 注册设备号 */
- if (ledDev.major)
- {
- ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
- register_chrdev_region(ledDev.devId, LED_CNT, LED_NAME);
- }
- else
- {
- alloc_chrdev_region(&ledDev.devId, 0, LED_CNT, LED_NAME);
- ledDev.major = MAJOR(ledDev.devId);
- ledDev.minor = MINOR(ledDev.devId);
- }
- printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
- /* 设置 cdev */
- ledDev.cdev.owner = THIS_MODULE;
- cdev_init(&ledDev.cdev, &led_fops);
- cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
- /* 设置 mdev */
- ledDev.class = class_create(THIS_MODULE, LED_NAME);
- if (IS_ERR(ledDev.class))
- {
- return PTR_ERR(ledDev.class);
- }
- ledDev.device = device_create(ledDev.class, NULL, ledDev.devId, NULL, LED_NAME);
- if (IS_ERR(ledDev.device))
- {
- return PTR_ERR(ledDev.device);
- }
- return 0;
- fail_label:
- gpio_free(ledDev.gpioLed);
- return res;
- }
- static void led_exit(void)
- {
- device_destroy(ledDev.class, ledDev.devId);
- class_destroy(ledDev.class);
- cdev_del(&ledDev.cdev);
- unregister_chrdev_region(ledDev.devId, LED_CNT);
- /* 释放 GPIO */
- gpio_free(ledDev.gpioLed);
- }
- module_init(led_init);
- module_exit(led_exit);
- MODULE_LICENSE("GPL");
- 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_irqsave 和 spin_unlock_irqrestore 之间,我们将需要保护的变量设置为设备不在使用。
信号量
自旋锁的一个问题是,一旦临界区代码处理时间过长,就会占用大量 CPU 资源。而信号量可以解决这个问题,当条件不满足时,相关任务可以进入休眠状态,此时会去调度其他任务,待条件满足再继续执行先前的任务。由于会进入休眠状态,所以信号量不能用于中断代码中(因为中断需要即使处理和响应)。
- #include <linux/semaphore.h>
- #include <linux/types.h>
- #include <linux/module.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- #include <linux/fs.h>
- #include <asm/io.h>
- #include <asm/uaccess.h>
- #include <linux/of.h>
- #include <linux/slab.h>
- #include <linux/of_address.h>
- #include <linux/of_gpio.h>
- #define LED_CNT 1
- #define LED_NAME "gpioled"
- struct LedDev
- {
- /* 设备号相关 */
- dev_t devId;
- int major;
- int minor;
- /* cdev */
- struct cdev cdev;
- /* mdev */
- struct class* class;
- struct device* device;
- /* GPIO 编号 */
- int gpioLed;
- /* 信号量 */
- struct semaphore sem;
- };
- struct LedDev ledDev;
- static int led_open(struct inode* inode, struct file* filp)
- {
- if (down_interruptible(&ledDev.sem))
- {
- return -ERESTARTSYS;
- }
- #if 0
- down(&ledDev.sem);
- #endif
- printk("led_open\n");
- return 0;
- }
- static int led_release(struct inode* inode, struct file* filp)
- {
- printk("led_release\n");
- up(&ledDev.sem);
- return 0;
- }
- static ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
- {
- int res;
- char status;
- res = copy_from_user(&status, buf, sizeof(status));
- if (res < 0)
- {
- printk("copy_from_user error\n");
- return res;
- }
- gpio_set_value(ledDev.gpioLed, status != 1);
- return 0;
- }
- const struct file_operations led_fops =
- {
- .owner = THIS_MODULE,
- .open = led_open,
- .release = led_release,
- .write = led_write,
- };
- static int led_init(void)
- {
- int res = 0;
- struct device_node* nd;
- /* 初始化信号量 */
- sema_init(&ledDev.sem, 1);
- nd = of_find_node_by_path("/hanhanled");
- if (nd == NULL)
- {
- printk("of_find_node_by_path failed\n");
- return -EINVAL;
- }
- ledDev.gpioLed = of_get_named_gpio(nd, "led-gpio", 0);
- if (ledDev.gpioLed < 0)
- {
- printk("of_get_named_gpio failed\n");
- return -EINVAL;
- }
- printk("led gpio number = %d\n", ledDev.gpioLed);
- res = gpio_request(ledDev.gpioLed, "gpio-led");
- if (res != 0)
- {
- printk("gpio_request failed\n");
- return -EINVAL;
- }
- res = gpio_direction_output(ledDev.gpioLed, 1);
- if (res < 0)
- {
- printk("gpio_direction_output failed\n");
- goto fail_label;
- }
- /////////////////////////////////////////////////////////////
- /* 注册设备号 */
- if (ledDev.major)
- {
- ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
- register_chrdev_region(ledDev.devId, LED_CNT, LED_NAME);
- }
- else
- {
- alloc_chrdev_region(&ledDev.devId, 0, LED_CNT, LED_NAME);
- ledDev.major = MAJOR(ledDev.devId);
- ledDev.minor = MINOR(ledDev.devId);
- }
- printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
- /* 设置 cdev */
- ledDev.cdev.owner = THIS_MODULE;
- cdev_init(&ledDev.cdev, &led_fops);
- cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
- /* 设置 mdev */
- ledDev.class = class_create(THIS_MODULE, LED_NAME);
- if (IS_ERR(ledDev.class))
- {
- return PTR_ERR(ledDev.class);
- }
- ledDev.device = device_create(ledDev.class, NULL, ledDev.devId, NULL, LED_NAME);
- if (IS_ERR(ledDev.device))
- {
- return PTR_ERR(ledDev.device);
- }
- return 0;
- fail_label:
- gpio_free(ledDev.gpioLed);
- return res;
- }
- static void led_exit(void)
- {
- device_destroy(ledDev.class, ledDev.devId);
- class_destroy(ledDev.class);
- cdev_del(&ledDev.cdev);
- unregister_chrdev_region(ledDev.devId, LED_CNT);
- /* 释放 GPIO */
- gpio_free(ledDev.gpioLed);
- }
- module_init(led_init);
- module_exit(led_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("hanhan");
代码清单 4 是信号量的示例。如第 31 行所示,使用 semaphore 声明一个信号量。在驱动加载时(第 88 行),将信号量进行初始化,此时设置初始值为 1,代表一次只能有一个任务进入临界区。
打开设备时(led_open 函数,第 36 至 49 行),调用 down_interruptible 函数,其中的 down 可以看成是“减法”,只要信号量够减(此处初始值设置为 1)就能继续执行后续代码,否则就会休眠,直到满足“够减”的条件。
注意这边有 down_interruptible 和 down 两个函数,两者的区别是:down_interruptible 是当休眠的任务即使不满足条件,其他信号也能唤醒它,不满足条件时的唤醒会返回一个错误值;而 down 只能是满足条件时唤醒。一般会选用 down_interruptible 函数,因为大多数情况都会有需要响应退出信号的需求。
关闭设备时(led_release 函数,第 51 至 55 行),调用 up 函数,可以理解为“加法”。
注意,此时是如果设备正在被使用,是阻塞等待,直到前一个设备退出。
测试驱动
如图 2 所示,同样是先将一个测试程序置于后台运行,在此程序结束之前,执行另一个测试程序。此时和原子变量、自旋锁不同,open 不会立即返回,而是进入阻塞状态,直到第一个测试程序运行结束。

互斥量
在之前的信号量里,我们实现了互斥性(初始值为 1,即一次性只能有一个任务进入临界区),互斥量和其很相似。这边可以看到信号量和互斥量的一个不同点,信号量是可以多个任务同时进入临界区的。互斥量获取不到锁时,也会进入睡眠状态。
- #include <linux/semaphore.h>
- #include <linux/types.h>
- #include <linux/module.h>
- #include <linux/cdev.h>
- #include <linux/device.h>
- #include <linux/fs.h>
- #include <asm/io.h>
- #include <asm/uaccess.h>
- #include <linux/of.h>
- #include <linux/slab.h>
- #include <linux/of_address.h>
- #include <linux/of_gpio.h>
- #define LED_CNT 1
- #define LED_NAME "gpioled"
- struct LedDev
- {
- /* 设备号相关 */
- dev_t devId;
- int major;
- int minor;
- /* cdev */
- struct cdev cdev;
- /* mdev */
- struct class* class;
- struct device* device;
- /* GPIO 编号 */
- int gpioLed;
- /* 互斥体 */
- struct mutex mut;
- };
- struct LedDev ledDev;
- static int led_open(struct inode* inode, struct file* filp)
- {
- if (mutex_lock_interruptible(&ledDev.mut))
- {
- return -ERESTARTSYS;
- }
- #if 0
- mutex_lock(&ledDev.mut);
- #endif
- printk("led_open\n");
- return 0;
- }
- static int led_release(struct inode* inode, struct file* filp)
- {
- printk("led_release\n");
- mutex_unlock(&ledDev.mut);
- return 0;
- }
- static ssize_t led_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
- {
- int res;
- char status;
- res = copy_from_user(&status, buf, sizeof(status));
- if (res < 0)
- {
- printk("copy_from_user error\n");
- return res;
- }
- gpio_set_value(ledDev.gpioLed, status != 1);
- return 0;
- }
- const struct file_operations led_fops =
- {
- .owner = THIS_MODULE,
- .open = led_open,
- .release = led_release,
- .write = led_write,
- };
- static int led_init(void)
- {
- int res = 0;
- struct device_node* nd;
- /* 初始化互斥体 */
- mutex_init(&ledDev.mut);
- nd = of_find_node_by_path("/hanhanled");
- if (nd == NULL)
- {
- printk("of_find_node_by_path failed\n");
- return -EINVAL;
- }
- ledDev.gpioLed = of_get_named_gpio(nd, "led-gpio", 0);
- if (ledDev.gpioLed < 0)
- {
- printk("of_get_named_gpio failed\n");
- return -EINVAL;
- }
- printk("led gpio number = %d\n", ledDev.gpioLed);
- res = gpio_request(ledDev.gpioLed, "gpio-led");
- if (res != 0)
- {
- printk("gpio_request failed\n");
- return -EINVAL;
- }
- res = gpio_direction_output(ledDev.gpioLed, 1);
- if (res < 0)
- {
- printk("gpio_direction_output failed\n");
- goto fail_label;
- }
- /////////////////////////////////////////////////////////////
- /* 注册设备号 */
- if (ledDev.major)
- {
- ledDev.devId = MKDEV(ledDev.major, ledDev.minor);
- register_chrdev_region(ledDev.devId, LED_CNT, LED_NAME);
- }
- else
- {
- alloc_chrdev_region(&ledDev.devId, 0, LED_CNT, LED_NAME);
- ledDev.major = MAJOR(ledDev.devId);
- ledDev.minor = MINOR(ledDev.devId);
- }
- printk("major = %d, minor = %d\n", ledDev.major, ledDev.minor);
- /* 设置 cdev */
- ledDev.cdev.owner = THIS_MODULE;
- cdev_init(&ledDev.cdev, &led_fops);
- cdev_add(&ledDev.cdev, ledDev.devId, LED_CNT);
- /* 设置 mdev */
- ledDev.class = class_create(THIS_MODULE, LED_NAME);
- if (IS_ERR(ledDev.class))
- {
- return PTR_ERR(ledDev.class);
- }
- ledDev.device = device_create(ledDev.class, NULL, ledDev.devId, NULL, LED_NAME);
- if (IS_ERR(ledDev.device))
- {
- return PTR_ERR(ledDev.device);
- }
- return 0;
- fail_label:
- gpio_free(ledDev.gpioLed);
- return res;
- }
- static void led_exit(void)
- {
- device_destroy(ledDev.class, ledDev.devId);
- class_destroy(ledDev.class);
- cdev_del(&ledDev.cdev);
- unregister_chrdev_region(ledDev.devId, LED_CNT);
- /* 释放 GPIO */
- gpio_free(ledDev.gpioLed);
- }
- module_init(led_init);
- module_exit(led_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("hanhan");
代码清单 5 是互斥量的示例,和之前的代码示例也如出一辙。在驱动加载时使用 mutex_init 函数初始化互斥量(第 88 行)。
当设备打开时(led_open 函数,第 36 至 49 行),使用 mutex_lock_interruptible 或 mutex_lock 加锁。两者的区别与 down_interruptible 和 down 之间的区别一样:mutex_lock_interruptible 在睡眠时还可由其他信号唤醒;mutex_lock 只能条件满足时唤醒。
当设备关闭时(led_release 函数,第 51 至 56 行),使用 mutex_unlock 解锁。