ARM Linux 驱动开发篇--- Linux 并发与竞争实验(原子操作)--- Ubuntu20.04

🎬 渡水无言个人主页渡水无言

专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》

专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏

⭐️流水不争先,争的是滔滔不绝

📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生

| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生

在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录

前言

一、实验基础说明

二、硬件原理分析(看过之前博客的可以忽略)

三、实验程序编写

[3.1 原子操作 LED 驱动代码(atomic.c)](#3.1 原子操作 LED 驱动代码(atomic.c))

3.2、驱动代码核心修改点对照表

[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 行))

3.4、测试APP编写

四、运行测试

[4.1、编译驱动程序和测试 APP](#4.1、编译驱动程序和测试 APP)

4.2、运行测试

总结


前言

上一期博客我们讲了 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 设备互斥访问的测试程序,核心作用是:

  1. 向 LED 驱动发送亮 / 灭控制指令;
  2. 模拟对 LED 设备的长时间占用(25 秒),验证在占用期间其他应用无法打开设备;
  3. 占用结束后释放设备,验证其他应用可正常访问。

代码如下:

复制代码
  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 灯。

相关推荐
RisunJan5 小时前
Linux命令-mysqlimport(为MySQL服务器用命令行方式导入数据)
linux·服务器·mysql
吕司5 小时前
Linux线程的概念
linux·运维·服务器
123过去7 小时前
hashid使用教程
linux·网络·测试工具·安全
C+++Python7 小时前
Linux/C++多进程
linux·运维·c++
Stack Overflow?Tan908 小时前
linux ubuntu22.04安装ROS2humble完整版的流程
linux·docker·ros2
zly35008 小时前
centos7 sshd无法启动
linux·运维·服务器
New农民工8 小时前
因为优化等级出现的 莫名其妙的bug
嵌入式
IMPYLH9 小时前
Linux 的 hostid 命令
linux·运维·服务器·bash
编程大师哥9 小时前
Linux 命名管道(FIFO)通信 超清晰讲解
linux·运维·服务器