
🎬 渡水无言 :个人主页渡水无言
❄专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》
❄专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》
⭐️流水不争先,争的是滔滔不绝
📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生
| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生
在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连
目录
[3.1 原子操作 LED 驱动代码(atomic.c)](#3.1 原子操作 LED 驱动代码(atomic.c))
[3.3 分段解析驱动代码](#3.3 分段解析驱动代码)
[3.3.1 设备结构体设计(对应代码第 24--34 行)](#3.3.1 设备结构体设计(对应代码第 24–34 行))
[3.3.2 原子变量初始化(对应代码第 134 行)](#3.3.2 原子变量初始化(对应代码第 134 行))
[3.3.3 设备打开函数(对应代码第 44--54 行)](#3.3.3 设备打开函数(对应代码第 44–54 行))
[3.3.4 设备写入函数(对应代码第 77--99 行)](#3.3.4 设备写入函数(对应代码第 77–99 行))
[3.3.5 设备释放函数(对应代码第 106--113 行)](#3.3.5 设备释放函数(对应代码第 106–113 行))
[3.3.6设备树解析与 GPIO 配置(对应代码第 137--157 行)](#3.3.6设备树解析与 GPIO 配置(对应代码第 137–157 行))
[3.3.7字符设备注册与资源释放(对应代码第 159--206 行)](#3.3.7字符设备注册与资源释放(对应代码第 159–206 行))
[4.1、编译驱动程序和测试 APP](#4.1、编译驱动程序和测试 APP)
前言
上一期博客我们讲了 Linux 下处理并发与竞争的四大核心机制:原子操作、自旋锁、信号量、互斥体 ,在本期博客中我们使用原子操作来实现对 LED 这个设备的互斥访问,也就是一次只允许一个应用程序可以使用 LED 灯。
一、实验基础说明
原子操作是处理并发的最基础方式,适合保护简单的整形变量,本次用原子变量实现 LED 设备的互斥访问,核心是通过原子变量标记设备的 "忙 / 闲" 状态。
实验在此次博客基础上实现:ARM Linux 驱动开发篇---基于 pinctrl+GPIO 子系统的蜂鸣器驱动开发(设备树版)--- Ubuntu20.04-CSDN博客
设备树无需修改,直接使用之前配置好的
gpioled节点(GPIO1_IO03)即可,重点聚焦驱动代码的并发保护改造。
二、硬件原理分析(看过之前博客的可以忽略)

从图中可以看出,LED0 接到了 GPIO_3 上,GPIO_3 就是 GPIO1_IO03,当 GPIO1_IO03 输出低电平 (0) 的时候发光二极管 LED0 就会导通点亮,当 GPIO1_IO03 输出高电平 (1) 的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 GPIO1_IO03 的输出电平,输出 0 就亮,输出 1 就灭。
三、实验程序编写
用原子变量标记设备忙 / 空闲状态,利用原子操作不可被中断的特性,保证状态判断的唯一性:
原子变量初始值设为1,代表设备空闲,可被应用访问;
应用打开设备时,原子变量原子性减 1,结果为 0 则占用设备,为负数则表示设备已被占用,返回忙错误;
应用关闭设备时,原子变量原子性加 1,恢复为 1,代表设备释放,供其他应用访问。
3.1 原子操作 LED 驱动代码(atomic.c)
基于原 GPIO 子系统 LED 驱动仅新增原子变量相关代码,其余逻辑完全复用:
1 #include <linux/types.h> // 基本类型定义,包含atomic_t
2 #include <linux/kernel.h> // 内核核心函数(如printk)
3 #include <linux/delay.h> // 延时函数
4 #include <linux/ide.h> // IDE相关宏
5 #include <linux/init.h> // 模块初始化/退出宏
6 #include <linux/module.h> // 模块核心头文件
7 #include <linux/errno.h> // 错误码定义(EBUSY等)
8 #include <linux/gpio.h> // GPIO子系统核心头文件
9 #include <linux/cdev.h> // 字符设备驱动核心头文件
10 #include <linux/device.h> // 设备类/设备节点创建
11 #include <linux/of.h> // 设备树核心头文件
12 #include <linux/of_address.h> // 设备树地址解析
13 #include <linux/of_gpio.h> // 设备树GPIO解析函数
14 #include <asm/mach/map.h> // 内存映射
15 #include <asm/uaccess.h> // 用户/内核空间数据拷贝
16 #include <asm/io.h> // IO操作函数
17
18 #define GPIOLED_CNT 1 /* 设备号个数:1个 */
19 #define GPIOLED_NAME "gpioled" /* 设备名:需和测试APP匹配 */
20 #define LEDOFF 0 /* 关灯:宏定义统一管理状态 */
21 #define LEDON 1 /* 开灯:便于后续修改 */
22
23 /* gpioled设备结构体:封装驱动所有资源(Linux驱动核心设计) */
24 struct gpioled_dev{
25 dev_t devid; /* 设备号(主+次) */
26 struct cdev cdev; /* cdev结构体:字符设备核心 */
27 struct class *class; /* 类:用于自动创建设备节点 */
28 struct device *device; /* 设备:对应/dev下的节点 */
29 int major; /* 主设备号 */
30 int minor; /* 次设备号 */
31 struct device_node *nd; /* 设备节点:指向设备树中gpioled节点 */
32 int led_gpio; /* LED使用的GPIO编号(从设备树解析) */
33 atomic_t lock; /* 原子变量:标记设备占用状态 */
34 };
35
36 struct gpioled_dev gpioled; /* 定义全局设备结构体:便于各函数访问 */
37
38 /*
39 * @description : 打开设备
40 * @param -- inode : 传递给驱动的inode
41 * @param -- filp : 设备文件,保存私有数据
42 * @return : 0 成功;其他 失败
43 */
44 static int led_open(struct inode *inode, struct file *filp)
45 {
46 /* 通过判断原子变量的值来检查LED有没有被别的应用使用 */
47 if (!atomic_dec_and_test(&gpioled.lock)) {
48 atomic_inc(&gpioled.lock);/* 小于0的话就加1,使其原子变量等于0 */
49 return -EBUSY; /* LED被使用,返回忙 */
50 }
51
52 filp->private_data = &gpioled; /* 设置私有数据,传递设备上下文 */
53 return 0;
54 }
55
56 /*
57 * @description : 从设备读取数据(本实验无需读,返回0)
58 * @param -- filp : 设备文件
59 * @param - buf : 用户空间数据缓冲区
60 * @param - cnt : 要读取的数据长度
61 * @param -- offt : 文件偏移
62 * @return : 0
63 */
64 static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
65 {
66 return 0;
67 }
68
69 /*
70 * @description : 向设备写数据(核心:控制LED亮灭)
71 * @param - filp : 设备文件
72 * @param - buf : 用户空间写入的数据
73 * @param - cnt : 数据长度
74 * @param -- offt : 文件偏移
75 * @return : 0 成功;负值 失败
76 */
77 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
78 {
79 int retvalue;
80 unsigned char databuf[1]; /* 接收用户空间数据(1字节:0/1) */
81 unsigned char ledstat; /* LED状态:0=关,1=开 */
82 struct gpioled_dev *dev = filp->private_data; /* 从私有数据恢复设备结构体 */
83
84 /* 从用户空间拷贝数据到内核空间(必须用copy_from_user,禁止直接访问用户指针) */
85 retvalue = copy_from_user(databuf, buf, cnt);
86 if(retvalue < 0) {
87 printk("kernel write failed!\r\n");
88 return -EFAULT;
89 }
90
91 ledstat = databuf[0]; /* 获取用户指令:1=开灯,0=关灯 */
92
93 if(ledstat == LEDON) {
94 gpio_set_value(dev->led_gpio, 0); /* 低电平点亮LED(GPIO子系统API) */
95 } else if(ledstat == LEDOFF) {
96 gpio_set_value(dev->led_gpio, 1); /* 高电平熄灭LED(GPIO子系统API) */
97 }
98 return 0;
99 }
100
101 /*
102 * @description : 关闭/释放设备
103 * @param -- filp : 设备文件
104 * @return : 0 成功;其他 失败
105 */
106 static int led_release(struct inode *inode, struct file *filp)
107 {
108 struct gpioled_dev *dev = filp->private_data; /* 从私有数据恢复设备结构体 */
109
110 /* 关闭驱动文件的时候释放原子变量,恢复设备空闲状态 */
111 atomic_inc(&dev->lock);
112 return 0;
113 }
114
115 /* 设备操作函数集:绑定驱动的open/read/write/release */
116 static struct file_operations gpioled_fops = {
117 .owner = THIS_MODULE, /* 拥有者:本模块(必设,防止模块卸载时崩溃) */
118 .open = led_open, /* 打开设备 */
119 .read = led_read, /* 读取设备 */
120 .write = led_write, /* 写入设备 */
121 .release = led_release, /* 释放设备 */
122 };
123
124 /*
125 * @description : 驱动入口函数(加载驱动时执行)
126 * @param : 无
127 * @return : 0 成功;负值 失败
128 */
129 static int __init led_init(void)
130 {
131 int ret = 0;
132
133 /* 初始化原子变量:初始值设为1,表示设备空闲 */
134 atomic_set(&gpioled.lock, 1);
135
136 /* 1、获取设备树节点:gpioled(绝对路径匹配) */
137 gpioled.nd = of_find_node_by_path("/gpioled");
138 if(gpioled.nd == NULL) {
139 printk("gpioled node cant not found!\r\n");
140 return -EINVAL;
141 } else {
142 printk("gpioled node has been found!\r\n");
143 }
144
145 /* 2、解析设备树的led-gpio属性,获取GPIO编号(核心) */
146 gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
147 if(gpioled.led_gpio < 0) {
148 printk("can't get led-gpio\r\n");
149 return -EINVAL;
150 }
151 printk("led-gpio num = %d\r\n", gpioled.led_gpio);
152
153 /* 3、配置GPIO为输出模式,默认高电平(熄灭LED) */
154 ret = gpio_direction_output(gpioled.led_gpio, 1);
155 if(ret < 0) {
156 printk("can't set gpio!\r\n");
157 }
158
159 /* 4、注册字符设备驱动(标准流程) */
160 /* 4.1 创建设备号(手动指定/自动分配) */
161 if (gpioled.major) { /* 手动指定主设备号 */
162 gpioled.devid = MKDEV(gpioled.major, 0);
163 register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
164 } else { /* 自动分配设备号(推荐) */
165 alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
166 gpioled.major = MAJOR(gpioled.devid); /* 提取分配的主设备号 */
167 gpioled.minor = MINOR(gpioled.devid); /* 提取分配的次设备号 */
168 }
169 printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);
170
171 /* 4.2 初始化cdev(绑定操作函数集) */
172 gpioled.cdev.owner = THIS_MODULE;
173 cdev_init(&gpioled.cdev, &gpioled_fops);
174
175 /* 4.3 添加cdev到内核(注册字符设备) */
176 cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
177
178 /* 4.4 创建类(自动生成/dev节点的前提) */
179 gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
180 if (IS_ERR(gpioled.class)) { /* 检查类创建是否失败 */
181 return PTR_ERR(gpioled.class);
182 }
183
184 /* 4.5 创建设备(生成/dev/gpioled节点) */
185 gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
186 if (IS_ERR(gpioled.device)) { /* 检查设备创建是否失败 */
187 return PTR_ERR(gpioled.device);
188 }
189 return 0;
190 }
191
192 /*
193 * @description : 驱动出口函数(卸载驱动时执行)
194 * @param : 无
195 * @return : 无
196 */
197 static void __exit led_exit(void)
198 {
199 /* 注销字符设备(与init反向操作) */
200 cdev_del(&gpioled.cdev); /* 删除cdev */
201 unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */
202
203 /* 销毁设备和类 */
204 device_destroy(gpioled.class, gpioled.devid);
205 class_destroy(gpioled.class);
206 }
207
208 /* 模块注册:指定入口/出口函数(内核识别驱动的关键) */
209 module_init(led_init);
210 module_exit(led_exit);
211
212 /* 开源协议声明(必须,否则内核标记为"tainted") */
213 MODULE_LICENSE("GPL");
214 /* 作者信息(可选,标注驱动作者) */
215 MODULE_AUTHOR("duan");
3.2、驱动代码核心修改点对照表
本次驱动仅在原 GPIO 子系统 LED 驱动基础上新增 / 修改原子操作相关代码,核心修改点如下表所示:
| 行号范围 | 核心功能 | 关键说明 |
|---|---|---|
| 33 | 原子变量定义 | 在设备结构体中新增atomic_t lock,封装到设备资源中,统一管理设备占用状态 |
| 134 | 原子变量初始化 | 驱动入口函数中通过atomic_set将原子变量设为 1,代表设备初始空闲 |
| 47-50 | 设备占用判断 | 调用atomic_dec_and_test原子减 1 并判断,非 0 则恢复变量并返回-EBUSY(设备忙) |
| 111 | 原子变量释放 | 设备释放时调用atomic_inc原子加 1,恢复为 1,代表设备释放为空闲状态 |
3.3 分段解析驱动代码
3.3.1 设备结构体设计(对应代码第 24--34 行)
在原有封装了设备号、cdev、设备树节点、GPIO 编号等资源的基础上,新增第 33 行原子变量lock,用于标记 LED 设备的占用状态。
struct gpioled_dev{
dev_t devid; /* 设备号(主+次) */
struct cdev cdev; /* cdev结构体:字符设备核心 */
struct class *class; /* 类:用于自动创建设备节点 */
struct device *device; /* 设备:对应/dev下的节点 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点:指向设备树中gpioled节点 */
int led_gpio; /* LED使用的GPIO编号(从设备树解析) */
atomic_t lock; /* 原子变量:标记设备占用状态 */
};
3.3.2 原子变量初始化(对应代码第 134 行)
驱动加载时,在led_init入口函数中通过atomic_set(&gpioled.lock, 1)初始化原子变量,初始值设为 1 ,表示设备上电后处于空闲状态,可被应用程序访问。
/* 初始化原子变量:初始值设为1,表示设备空闲 */
atomic_set(&gpioled.lock, 1);
原子变量仅在驱动加载时初始化一次 ,后续由
open/release函数对其进行原子操作。
3.3.3 设备打开函数(对应代码第 44--54 行)
核心作用:实现对设备独占访问的核心,通过原子变量判断设备是否被占用。
44 static int led_open(struct inode *inode, struct file *filp)
45 {
46 /* 通过判断原子变量的值来检查LED有没有被别的应用使用 */
47 if (!atomic_dec_and_test(&gpioled.lock)) {
48 atomic_inc(&gpioled.lock);/* 小于0的话就加1,使其原子变量等于0 */
49 return -EBUSY; /* LED被使用,返回忙 */
50 }
51
52 filp->private_data = &gpioled; /* 设置私有数据,传递设备上下文 */
53 return 0;
54 }
- 第 47 行 :调用
atomic_dec_and_test(&gpioled.lock),实现原子性减 1 并判断减 1 后的值是否为 0,该操作不可被中断,保证应用并发打开时状态不会错乱。- 若返回
false(减 1 后值为负):说明已有应用占用设备,第 48 行 调用atomic_inc将原子变量恢复为 0,第 49 行 返回-EBUSY(设备忙)。- 若返回
true(减 1 后值为 0):说明设备空闲,成功占用设备,第 52 行 将设备结构体绑定到文件私有数据filp->private_data,传递上下文。
3.3.4 设备写入函数(对应代码第 77--99 行)
该函数与原 GPIO 子系统 LED 驱动完全一致,无任何修改。
77 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
78 {
79 int retvalue;
80 unsigned char databuf[1]; /* 接收用户空间数据(1字节:0/1) */
81 unsigned char ledstat; /* LED状态:0=关,1=开 */
82 struct gpioled_dev *dev = filp->private_data; /* 从私有数据恢复设备结构体 */
83
84 /* 从用户空间拷贝数据到内核空间(必须用copy_from_user,禁止直接访问用户指针) */
85 retvalue = copy_from_user(databuf, buf, cnt);
86 if(retvalue < 0) {
87 printk("kernel write failed!\r\n");
88 return -EFAULT;
89 }
90
91 ledstat = databuf[0]; /* 获取用户指令:1=开灯,0=关灯 */
92
93 if(ledstat == LEDON) {
94 gpio_set_value(dev->led_gpio, 0); /* 低电平点亮LED(GPIO子系统API) */
95 } else if(ledstat == LEDOFF) {
96 gpio_set_value(dev->led_gpio, 1); /* 高电平熄灭LED(GPIO子系统API) */
97 }
98 return 0;
99 }
第 82 行:从文件私有数据中恢复设备结构体,获取 GPIO 编号。
第 85 行:通过copy_from_user完成用户 / 内核空间数据拷贝,禁止直接访问用户空间指针。
第 94/96 行:通过 GPIO 子系统 APIgpio_set_value控制 LED 亮灭,低电平点亮、高电平熄灭,无需直接操作寄存器。
原子操作已在open函数中实现独占访问,因此write函数必然只有一个应用在操作,无需额外保护。
3.3.5 设备释放函数(对应代码第 106--113 行)
应用程序调用close关闭设备时,led_release函数执行,核心是释放原子变量。
106 static int led_release(struct inode *inode, struct file *filp)
107 {
108 struct gpioled_dev *dev = filp->private_data; /* 从私有数据恢复设备结构体 */
109
110 /* 关闭驱动文件的时候释放原子变量,恢复设备空闲状态 */
111 atomic_inc(&dev->lock);
112 return 0;
113 }
第 108 行:从文件私有数据中恢复设备结构体。
第 111 行:调用atomic_inc(&dev->lock)对原子变量原子性加 1,将其从 0 恢复为初始值 1,代表设备释放为空闲状态,供其他应用访问。
实现了 "打开设备占用原子变量 - 关闭设备释放原子变量" 的闭环控制。
3.3.6设备树解析与 GPIO 配置(对应代码第 137--157 行)
与原 GPIO 子系统 LED 驱动完全一致,无修改。
137 gpioled.nd = of_find_node_by_path("/gpioled");
138 if(gpioled.nd == NULL) {
139 printk("gpioled node cant not found!\r\n");
140 return -EINVAL;
141 } else {
142 printk("gpioled node has been found!\r\n");
143 }
144
145 /* 2、解析设备树的led-gpio属性,获取GPIO编号(核心) */
146 gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
147 if(gpioled.led_gpio < 0) {
148 printk("can't get led-gpio\r\n");
149 return -EINVAL;
150 }
151 printk("led-gpio num = %d\r\n", gpioled.led_gpio);
152
153 /* 3、配置GPIO为输出模式,默认高电平(熄灭LED) */
154 ret = gpio_direction_output(gpioled.led_gpio, 1);
155 if(ret < 0) {
156 printk("can't set gpio!\r\n");
157 }
第 137 行:通过of_find_node_by_path根据绝对路径获取设备树gpioled节点。
第 146 行:通过of_get_named_gpio解析设备树led-gpio属性,获取内核可识别的 GPIO 编号。
第 154 行:通过gpio_direction_output将 GPIO 配置为输出模式,默认高电平,LED 初始熄灭。
3.3.7字符设备注册与资源释放(对应代码第 159--206 行)
完全遵循 Linux 标准字符设备驱动注册流程,与原驱动一致。
159 /* 4、注册字符设备驱动(标准流程) */
160 /* 4.1 创建设备号(手动指定/自动分配) */
161 if (gpioled.major) { /* 手动指定主设备号 */
162 gpioled.devid = MKDEV(gpioled.major, 0);
163 register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
164 } else { /* 自动分配设备号(推荐) */
165 alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
166 gpioled.major = MAJOR(gpioled.devid); /* 提取分配的主设备号 */
167 gpioled.minor = MINOR(gpioled.devid); /* 提取分配的次设备号 */
168 }
169 printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);
170
171 /* 4.2 初始化cdev(绑定操作函数集) */
172 gpioled.cdev.owner = THIS_MODULE;
173 cdev_init(&gpioled.cdev, &gpioled_fops);
174
175 /* 4.3 添加cdev到内核(注册字符设备) */
176 cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
177
178 /* 4.4 创建类(自动生成/dev节点的前提) */
179 gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
180 if (IS_ERR(gpioled.class)) { /* 检查类创建是否失败 */
181 return PTR_ERR(gpioled.class);
182 }
183
184 /* 4.5 创建设备(生成/dev/gpioled节点) */
185 gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
186 if (IS_ERR(gpioled.device)) { /* 检查设备创建是否失败 */
187 return PTR_ERR(gpioled.device);
188 }
...
197 static void __exit led_exit(void)
198 {
199 /* 注销字符设备(与init反向操作) */
200 cdev_del(&gpioled.cdev); /* 删除cdev */
201 unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */
202
203 /* 销毁设备和类 */
204 device_destroy(gpioled.class, gpioled.devid);
205 class_destroy(gpioled.class);
206 }
第 161--168 行:创建设备号,支持手动指定和自动分配。
第 172--173 行:初始化 cdev,将设备操作函数集gpioled_fops绑定到 cdev 结构体。
第 176 行:添加 cdev 到内核,完成字符设备的核心注册。
第 179--188 行:创建类和设备,自动生成/dev/gpioled设备节点,实现用户空间对设备的访问。
第 200--205 行:驱动卸载时按与注册反向的顺序释放资源(删除 cdev→注销设备号→销毁设备→销毁类),避免内核内存泄漏。
3.4、驱动函数总体执行流程

3.5、测试APP编写
这份代码是用于验证 Linux 驱动中原子操作实现 LED 设备互斥访问的测试程序,核心作用是:
- 向 LED 驱动发送亮 / 灭控制指令;
- 模拟对 LED 设备的长时间占用(25 秒),验证在占用期间其他应用无法打开设备;
- 占用结束后释放设备,验证其他应用可正常访问。
代码如下:
1 #include "stdio.h" // 标准输入输出(printf)
2 #include "unistd.h" // 系统调用(sleep、close)
3 #include "sys/types.h" // 基本系统类型(pid_t等)
4 #include "sys/stat.h" // 文件状态相关
5 #include "fcntl.h" // 文件操作(open、O_RDWR)
6 #include "stdlib.h" // 通用工具(atoi)
7 #include "string.h" // 字符串操作(本代码未用到,可保留)
8
9 /***************************************************************
10 描述 : 原子变量测试APP,测试原子变量能不能实现一次
11 只允许一个应用程序使用LED。
12 其他 : 无
13 使用方法 :./atomicApp /dev/gpioled 0 关闭LED灯
14 ./atomicApp /dev/gpioled 1 打开LED灯
15 ***************************************************************/
16
17 #define LEDOFF 0 // 定义关灯指令常量
18 #define LEDON 1 // 定义开灯指令常量
19
20 /*
21 * @description : main主程序
22 * @param - argc : argv数组元素个数
23 * @param - argv : 具体参数
24 * @return : 0 成功;其他 失败
25 */
26 int main(int argc, char *argv[])
27 {
28 int fd, retvalue; // fd:文件描述符;retvalue:函数返回值
29 char *filename; // 存储设备节点路径(如/dev/gpioled)
30 unsigned char cnt = 0; // 计数变量,记录循环次数
31 unsigned char databuf[1]; // 存储向驱动写入的控制指令(1字节)
32
33 if(argc != 3){ // 检查参数个数是否为3个
34 printf("Error Usage!\r\n");
35 return -1;
36 }
37
38 filename = argv[1]; // 第二个参数:设备节点路径(如/dev/gpioled)
39
40 /*led */
41 fd = open(filename, O_RDWR); // 以读写模式打开设备文件
42 if(fd < 0){ // 打开失败(如设备被占用)
43 printf("file %s open failed!\r\n", argv[1]);
44 return -1;
45 }
46
47 databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */
48 // 第三个参数转为整数(0/1),存入databuf
49
50 /* 向/dev/gpioled文件写入数据 */
51 retvalue = write(fd, databuf, sizeof(databuf));
52 if(retvalue < 0){ // 写入失败(如驱动返回错误)
53 printf("LED Control Failed!\r\n");
54 close(fd); // 失败时关闭文件,避免资源泄漏
55 return -1;
56 }
57
58 /* 模拟占用25S LED */
59 while(1) {
60 sleep(5); // 休眠5秒
61 cnt++; // 计数+1
62 printf("App running times:%d\r\n", cnt); // 输出运行次数
63 if(cnt >= 5) break; // 计数到5(累计25秒),退出循环
64 }
65
66 printf("App running finished!"); // 提示占用结束
67 retvalue = close(fd); /* 关闭文件 */
68 if(retvalue < 0){ // 关闭文件失败
69 printf("file %s close failed!\r\n", argv[1]);
70 return -1;
71 }
72 return 0; // 程序正常退出
73 }
四、运行测试
4.1、编译驱动程序和测试 APP
编写 Makefile 文件,本次实验的 Makefile 文件和之前的led实验基本一样,只是将 obj-m 变量的值改为atomic.o,Makefile 内容如下所示:
KERNELDIR := /home/duan/linux/linux-imx-rel_imx_4.1.15_2.1.1_ga_alientek_v2.2
CURRENT_PATH := $(shell pwd)
obj-m :=atomic.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
输入如下命令编译出驱动模块文件:
make -j32
编译成功以后就会生成一个名为"atomic.ko"的驱动模块文件。
编译测试 APP
输入如下命令编译测试atomicApp.c 这个测试程序:
arm-linux-gnueabihf-gcc atomicApp.c -o atomicApp
4.2、运行测试
将上一小节编译出来的atomic.ko和atomicApp 这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中。
sudo cp atomic.ko /home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -f
sudo cp atomicApp /home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -f
进入到目录 lib/modules/4.1.15 中,输入如下命令加载 atomic.ko 驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe atomic.ko//加载驱动
驱动加载成功以后会在终端中输出一些信息,如下图所示:

从上图可以看出,gpioled 这个节点找到了。
驱动加载成功以后就可以使用 atomicApp 软件来测试驱动是否工作正常,输入如下命令以下后台运行模式打开 LED 灯," & "表示在后台运行 atomicApp 这个软件:
./atomicApp /dev/gpioled 1& //打开led
输入上述命令以后观察开发板上的红色 LED 灯是否点亮,然后每隔 5 秒都会输出一行" App
running times ",如下图 所示:

从图 48.1.2.1 可以看出, atomicApp 运行正常,输出了" App running times:1 "和" App running
times:2 ",这就是模拟 25S 占用,说明 atomicApp 这个软件正在使用 LED 灯。此时再输入如下
命令关闭 LED 灯:
./atomicApp /dev/gpioled 0 //关闭 LED 灯
输入上述命令以后会发现出现如下图所示信息:

从上图 可以看出,打开 /dev/gpioled 失败!原因是在 atomicAPP 软件正在占用/dev/gpioled ,如果再次运行 atomicApp 软件去操作 /dev/gpioled 肯定会失败。
必须等待图 中的 atomicApp 运行结束,也就是 25S 结束以后其他软件才能去操作 /dev/gpioled 。
这个就是采用原子变量实现一次只能有一个应用程序访问 LED 灯。
如果要卸载驱动的话输入如下命令即可:
rmmod beep.ko
总结
本期博客使用原子操作来实现对 LED 这个设备的互斥访问,也就是一次只允许一个应用程序使用 LED 灯。