视频:第10.2讲 Linux并发与竞争试验-自旋锁、信号量与互斥体_哔哩哔哩_bilibili
1. 自旋锁
文档:《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81.pdf》47.3节
函数
cpp
// include/linux/spinlock_types.h
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
API函数的定义在include/linux/spinlock.h

线程与线程之间:
表中的自旋锁API函数适用于SMP或支持抢占的单CPU下线程之间的并发访问,也就是用于线程与线程之间。被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的 API 函数,否则可能导致死锁:
自旋锁会自动禁止抢占,也就是当线程A得到锁以后会暂时禁止内核抢占。如果线程A在持有锁期间进入休眠,那么线程A会自动放弃CPU,此时线程B开始运行,线程B要获取锁,但是此时锁被A线程持有,而且内核还禁止抢占,此时便死锁了。
线程与中断之间:

如上图,线程A先运行并获得lock。当线程A运行functionA时候,中断发生并抢走了CPU使用权。右边的中断服务函数也要获取lock,但lock被A占有,中断一直等待。但是在中断服务函数执行完之前,线程A无法执行,此时便死锁了。
最好的解决方法就是获取锁之前关闭本地中断(本CPU中断),Linux内核提供了相应的API函数:

使用spin_lock_irq/spin_unlock_irq时用户需要能够确定加锁之前的中断状态,但实际上很难做到,因此不推荐使用这两个函数。
建议使用spin_lock_irqsave/ spin_unlock_irqrestore,这组函数会保存中断状态 / 恢复中断状态。一般在线程中使用spin_lock_irqsave/spin_unlock_irqrestore,在中断中使用spin_lock/spin_unlock,示例代码如下所示:
cpp
DEFINE_SPINLOCK(lock); // 定义并初始化一个自旋锁
/* 线程A */
void functionA() {
unsigned long flags; // 用于保存中断状态
// 获取锁并关闭本地中断,保存中断状态到flags
spin_lock_irqsave(&lock, flags);
/* 临界区 */
// 在此执行共享资源操作
// 释放锁并恢复中断状态
spin_unlock_irqrestore(&lock, flags);
}
/* 中断服务函数 */
void irq() {
// 获取锁
spin_lock(&lock);
/* 临界区 */
// 在此执行共享资源操作
// 释放锁
spin_unlock(&lock);
}
自旋锁注意事项
《开发指南》47.3.4节
①、等待自旋锁时处于"自旋",因此锁的持有时间不能太长,否则会降低系统性能。如果临界区比较大、运行时间比较长,要选择其他的并发处理方式,如信号量和互斥体。
②、自旋锁保护的临界区内不能调用任何可能导致线程休眠的API函数,否则可能导致死锁。
③、不能递归申请自旋锁,否则死锁。
④、在编写驱动程序的时候我们必须考虑到驱动的可移植性,因此不管用的是单核还是多核SOC,都将其当做多核SOC来编写驱动程序。
2. 信号量
详见《开发指南》47.4节
相较于自旋锁,信号量的特点:
①、信号量会以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场合。
②、信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
③、如果共享资源的持有时间比较短,不适合使用信号量,因为频繁休眠、切换线程的开销要远大于信号量带来的优势。
函数
cpp
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};

|-------------------------------------------------|--------------------------|
| DEFINE_SEAMPHORE(name) | 定义一个二值信号量,信号量的值为1 |
| void sema_init(struct semaphore *sem, int val) | 设置信号量sem的值为val |
| void down(struct semaphore *sem) | 获取信号量,信号量-1,导致休眠,不可被信号打断 |
| void up(struct semaphore *sem) | 释放信号量,信号量+1,导致休眠,不可被信号打断 |
| int down_trylock(struct semaphore *sem); | 获取信号量,信号量-1,导致休眠,可被信号打断 |
| int down_interruptible(struct semaphore *sem) | 释放信号量,信号量+1,导致休眠,可被信号打断 |
3. 互斥体
详见《开发指南》47.5节
互斥访问:一次只能有一个线程可以访问共享资源。
虽然用二值信号量就可以进行互斥访问,但是Linux提供了更专业的机制------互斥体mutex,其特点如下:
①、mutex可以导致休眠,因此不能在中断中使用mutex。中断中只能使用自旋锁。
②、和信号量一样,mutex保护的临界区可以调用引起阻塞的API函数。
③、因为一次只有一个线程可以持有mutex,因此,必须由mutex的持有者释放mutex。并且mutex不能递归上锁和解锁。
mutex互斥体定义如下:
cpp
struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
};
函数

4. 自旋锁实验
第10.4讲 Linux并发与竞争试验-自旋锁、信号量以及互斥体操作实验_哔哩哔哩_bilibili
4.1 文件结构
cpp
9_SPINLOCK (工作区)
├── .vscode
│ ├── c_cpp_properties.json
│ └── settings.json
├── 9_spinlock.code-workspace
├── Makefile
├── spinlockAPP.c
└── spinlock.c
4.2 Makefile
cpp
CFLAGS_MODULE += -w
KERNELDIR := /home/for/linux/imx6ull/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek # 内核路径
# KERNELDIR改成自己的 linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek文件路径(这个文件从正点原子"01、例程源码"中直接搜,cp到虚拟机里面)
CURRENT_PATH := $(shell pwd) # 当前路径
obj-m := spinlock.o # 编译文件
build: kernel_modules # 编译模块
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
4.3 spinlock.c
主要有以下更新:
增加了 头文件引用 #include <linux/spinlock_types.h>
增加了 设备结构体中 自旋锁和设备状态 两个变量
增加了 驱动入口函数中 对自旋锁的初始化
增加了 led_open中 加锁解锁和临界区操作
增加了 led_release中 加锁解锁和临界区操作
备注:
实际使用过程中可能会有中断的问题,因此使用spin_lock_irqsave和spin_unlock_irqrestore更好。
cpp
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/spinlock_types.h>
#define GPIOLED_NAME "gpioled"
#define GPIOLED_CNT 1
#define LEDON 1
#define LEDOFF 0
/* 设备结构体 */
struct gpioled_dev{
dev_t devid;
int major;
int minor;
struct cdev cdev;
struct device *device;
struct class *class;
struct device_node *nd;
int led_gpio;
int dev_status; /* 0表示设备可以使用,1不可使用*/
spinlock_t lock;
};
struct gpioled_dev gpioled;
/* 操作集 */
static int led_release(struct inode *inode, struct file *filp){
unsigned long irqflag;
struct gpioled_dev *dev = filp->private_data;
spin_lock_irqsave(&dev->lock, irqflag); // 加锁=================
if(gpioled.dev_status){
gpioled.dev_status = 0; // 设备标记为被空闲
}
spin_unlock_irqrestore(&dev->lock, irqflag); // 解锁===============
return 0;
}
static int led_open(struct inode *inode, struct file *filp){
unsigned long irqflag;
filp->private_data = &gpioled;
spin_lock_irqsave(&gpioled.lock, irqflag); // 加锁=================
if(gpioled.dev_status){ /* 设备不可使用 */
spin_unlock(&gpioled.lock); //解锁
printk("BUSY!!!\r\n");
return -EBUSY;
}
gpioled.dev_status = 1; // 设备标记为被占用
spin_unlock_irqrestore(&gpioled.lock, irqflag); //解锁====================
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos){
int ret;
unsigned char databuf[1];
struct gpioled_dev *dev = filp->private_data;
ret = copy_from_user(databuf, buf, count);
if(ret < 0){
return -EINVAL;
}
if(databuf[0] == LEDON){ //开灯
gpio_set_value(dev->led_gpio, 0); // 低电平开灯
} else if(databuf[0] == LEDOFF){
gpio_set_value(dev->led_gpio, 1); // 高电平关灯
}
return 0;
}
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
.release = led_release,
};
/* 驱动入口 */
static int __init led_init(void){
int ret = 0;
/* 初始化自旋锁 */
spin_lock_init(&gpioled.lock);
gpioled.dev_status = 0;
/* 1.注册字符设备驱动 */
gpioled.devid = 0;
if(gpioled.devid){
gpioled.devid = MKDEV(gpioled.devid, 0);
register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
} else {
alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
/* 2.初始化cdev */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &led_fops);
/* 3.添加cdev */
cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT); // 错误处理先略过了
/* 4.创建类 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if(IS_ERR(gpioled.class)){
return PTR_ERR(gpioled.class);
}
/* 5.创建设备 */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)){
return PTR_ERR(gpioled.device);
}
/* 1.获取设备节点 */
gpioled.nd = of_find_node_by_path("/gpioled"); // 找到刚才在imx6ull-alientek-emmc.dts根节点下加入的gpioled节点
if(gpioled.nd == NULL){
ret = -EINVAL;
goto fail_findnode;
}
/* 2.获取LED对应的GPIO */ // 也就是节点中led-gpios那一行内容
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
if(gpioled.led_gpio < 0){
printk("can't find led_gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
printk("led_gpio num = %d\r\n",gpioled.led_gpio);
/* 3.申请IO */
ret = gpio_request(gpioled.led_gpio, "led-gpios");
if(ret){
printk("Failed to request the led gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
/* 4.使用IO,设置为输出 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret){
goto fail_setoutput; // 如果代码走到这一步,一定已经成功进行了IO申请,因此这里错误处理时需要释放IO
}
/* 5.输出高电平,关灯 */
gpio_set_value(gpioled.led_gpio, 1);
return 0;
fail_setoutput:
gpio_free(gpioled.led_gpio);
fail_findnode:
return ret;
}
/* 驱动出口 */
static void __exit led_exit(void){
gpio_set_value(gpioled.led_gpio, 1); // 高电平 关灯
/* 注销字符设备驱动 */
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
device_destroy(gpioled.class, gpioled.devid);
class_destroy(gpioled.class);
gpio_free(gpioled.led_gpio);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
4.4 spinlockAPP.c
主要有以下更新:
完全没有更新:( 直接贴过来
cpp
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功; else失败
* 调用 ./atomicAPP <filename> <0:1> 0关灯,1开灯
* ./atomicAPP /dev/gpioled 0 关灯
* ./atomicAPP /dev/gpioled 1 开灯
*/
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[]){
if(argc != 3){ // 判断用法是否错误
printf("Error Usage!\r\n");
return -1;
}
char *filename;
int fd = 0;
unsigned char databuf[1];
int retvalue = 0;
int cnt = 0;
filename = argv[1];
fd = open(filename, O_RDWR); // 读写模式打开驱动文件filename
if(fd <0){
printf("file %s open failed!\r\n");
return -1;
}
databuf[0] = atoi(argv[2]); // char 2 int
retvalue = write(fd, databuf, sizeof(databuf)); // 根据驱动里的操作集.write = led_write,执行led_write()函数
if(retvalue <0){
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}
/* 模拟应用占用 */
while(1){
sleep(3);
cnt++;
printf("APP Runing times: %d\r\n",cnt);
if(cnt >= 5)break;
}
printf("App finished!\r\n");
close(fd);
return 0;
}
4.5 测试
cpp
# VSCODE终端
make
arm-linux-gnueabihf-gcc spinlockAPP.c -o spinlockAPP
sudo cp spinlock.ko spinlockAPP /..../nfs/rootfs/lib/modules/4.1.15/
# 串口

和原子操作实验8一样,如果一个应用程序没有执行完,再执行下一个应用程序会报错。
5. 信号量实验
懒得重新创新的文件夹,直接在上面的代码改了。
spinlock.c主要有以下更新:
增加了 头文件引用 #include <linux/semaphore.h>
增加了 设备结构体中 semaphore类型信号量
增加了 驱动入口函数中 对信号量的初始化
增加了 led_open中 信号量加1
增加了 led_release中 信号量减1。信号量的特点是能让应用休眠,因此led_release中不需要其他操作,信号量不为0时会自动唤醒休眠的应用。
cpp
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#define GPIOLED_NAME "gpioled"
#define GPIOLED_CNT 1
#define LEDON 1
#define LEDOFF 0
/* 设备结构体 */
struct gpioled_dev{
dev_t devid;
int major;
int minor;
struct cdev cdev;
struct device *device;
struct class *class;
struct device_node *nd;
int led_gpio;
struct semaphore sem;
};
struct gpioled_dev gpioled;
/* 操作集 */
static int led_release(struct inode *inode, struct file *filp){
struct gpioled_dev *dev = filp->private_data;
up(&gpioled.sem); //信号量加1
return 0;
}
static int led_open(struct inode *inode, struct file *filp){
filp->private_data = &gpioled;
down(&gpioled.sem); // 信号量减1。信号量的特点是能让应用休眠,因此此处不需要其他操作,信号量不为0时会自动唤醒休眠的应用。
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos){
int ret;
unsigned char databuf[1];
struct gpioled_dev *dev = filp->private_data;
ret = copy_from_user(databuf, buf, count);
if(ret < 0){
return -EINVAL;
}
if(databuf[0] == LEDON){ //开灯
gpio_set_value(dev->led_gpio, 0); // 低电平开灯
} else if(databuf[0] == LEDOFF){
gpio_set_value(dev->led_gpio, 1); // 高电平关灯
}
return 0;
}
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
.release = led_release,
};
/* 驱动入口 */
static int __init led_init(void){
int ret = 0;
/* 初始化信号量 */
sema_init(&gpioled.sem, 1); // 二值信号量
/* 1.注册字符设备驱动 */
gpioled.devid = 0;
if(gpioled.devid){
gpioled.devid = MKDEV(gpioled.devid, 0);
register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
} else {
alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
/* 2.初始化cdev */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &led_fops);
/* 3.添加cdev */
cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT); // 错误处理先略过了
/* 4.创建类 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if(IS_ERR(gpioled.class)){
return PTR_ERR(gpioled.class);
}
/* 5.创建设备 */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)){
return PTR_ERR(gpioled.device);
}
/* 1.获取设备节点 */
gpioled.nd = of_find_node_by_path("/gpioled"); // 找到刚才在imx6ull-alientek-emmc.dts根节点下加入的gpioled节点
if(gpioled.nd == NULL){
ret = -EINVAL;
goto fail_findnode;
}
/* 2.获取LED对应的GPIO */ // 也就是节点中led-gpios那一行内容
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
if(gpioled.led_gpio < 0){
printk("can't find led_gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
printk("led_gpio num = %d\r\n",gpioled.led_gpio);
/* 3.申请IO */
ret = gpio_request(gpioled.led_gpio, "led-gpios");
if(ret){
printk("Failed to request the led gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
/* 4.使用IO,设置为输出 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret){
goto fail_setoutput; // 如果代码走到这一步,一定已经成功进行了IO申请,因此这里错误处理时需要释放IO
}
/* 5.输出高电平,关灯 */
gpio_set_value(gpioled.led_gpio, 1);
return 0;
fail_setoutput:
gpio_free(gpioled.led_gpio);
fail_findnode:
return ret;
}
/* 驱动出口 */
static void __exit led_exit(void){
gpio_set_value(gpioled.led_gpio, 1); // 高电平 关灯
/* 注销字符设备驱动 */
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
device_destroy(gpioled.class, gpioled.devid);
class_destroy(gpioled.class);
gpio_free(gpioled.led_gpio);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
spinlockAPP.c不需要改,和自旋锁的一致。不同的是,一个程序在执行时再开始一个应用,并不会像前两次一样出现报错,而是自动休眠,待第一个应用执行结束后自动被唤醒并执行:

6. 互斥体实验
懒得重新创新的文件夹,直接在上面的代码改了。
spinlock.c主要有以下更新:
增加了 头文件引用 #include <linux/mutex.h>
增加了 设备结构体中 互斥体
增加了 驱动入口函数中 对互斥体的初始化
增加了 led_open中 互斥体上锁
增加了 led_release中 互斥体解锁。互斥体同样可以使自动休眠。
cpp
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/stat.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/mutex.h>
#define GPIOLED_NAME "gpioled"
#define GPIOLED_CNT 1
#define LEDON 1
#define LEDOFF 0
/* 设备结构体 */
struct gpioled_dev{
dev_t devid;
int major;
int minor;
struct cdev cdev;
struct device *device;
struct class *class;
struct device_node *nd;
int led_gpio;
struct mutex lock;
};
struct gpioled_dev gpioled;
/* 操作集 */
static int led_release(struct inode *inode, struct file *filp){
struct gpioled_dev *dev = filp->private_data;
mutex_unlock(&dev->lock); // 互斥体 解锁
return 0;
}
static int led_open(struct inode *inode, struct file *filp){
filp->private_data = &gpioled;
mutex_lock(&gpioled.lock); // 互斥体 上锁
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos){
int ret;
unsigned char databuf[1];
struct gpioled_dev *dev = filp->private_data;
ret = copy_from_user(databuf, buf, count);
if(ret < 0){
return -EINVAL;
}
if(databuf[0] == LEDON){ //开灯
gpio_set_value(dev->led_gpio, 0); // 低电平开灯
} else if(databuf[0] == LEDOFF){
gpio_set_value(dev->led_gpio, 1); // 高电平关灯
}
return 0;
}
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
.release = led_release,
};
/* 驱动入口 */
static int __init led_init(void){
int ret = 0;
/* 初始化互斥体 */
mutex_init(&gpioled.lock);
/* 1.注册字符设备驱动 */
gpioled.devid = 0;
if(gpioled.devid){
gpioled.devid = MKDEV(gpioled.devid, 0);
register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
} else {
alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
/* 2.初始化cdev */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &led_fops);
/* 3.添加cdev */
cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT); // 错误处理先略过了
/* 4.创建类 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if(IS_ERR(gpioled.class)){
return PTR_ERR(gpioled.class);
}
/* 5.创建设备 */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)){
return PTR_ERR(gpioled.device);
}
/* 1.获取设备节点 */
gpioled.nd = of_find_node_by_path("/gpioled"); // 找到刚才在imx6ull-alientek-emmc.dts根节点下加入的gpioled节点
if(gpioled.nd == NULL){
ret = -EINVAL;
goto fail_findnode;
}
/* 2.获取LED对应的GPIO */ // 也就是节点中led-gpios那一行内容
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
if(gpioled.led_gpio < 0){
printk("can't find led_gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
printk("led_gpio num = %d\r\n",gpioled.led_gpio);
/* 3.申请IO */
ret = gpio_request(gpioled.led_gpio, "led-gpios");
if(ret){
printk("Failed to request the led gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
/* 4.使用IO,设置为输出 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret){
goto fail_setoutput; // 如果代码走到这一步,一定已经成功进行了IO申请,因此这里错误处理时需要释放IO
}
/* 5.输出高电平,关灯 */
gpio_set_value(gpioled.led_gpio, 1);
return 0;
fail_setoutput:
gpio_free(gpioled.led_gpio);
fail_findnode:
return ret;
}
/* 驱动出口 */
static void __exit led_exit(void){
gpio_set_value(gpioled.led_gpio, 1); // 高电平 关灯
/* 注销字符设备驱动 */
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
device_destroy(gpioled.class, gpioled.devid);
class_destroy(gpioled.class);
gpio_free(gpioled.led_gpio);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
实验同5信号量实验一致。