当前位置: 首页 > news >正文

Linux驱动开发笔记(七)——并发与竞争(上)——原子操作

视频:第10.1讲 Linux并发与竞争试验-并发与竞争基础概念与原子操作_哔哩哔哩_bilibili

手册:《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81.pdf》四十七章


(概念网上很多,不写了。直接看代码吧)

1.函数

1.1原子整形操作API函数

(详见《开发指南》47.2.2节)

        Linux内核定义了atomic_t结构体来完成整形数据的原子操作,在使用中用原子变量来代替整形变量:

// include/linux/types.h
typedef struct {int counter;
} atomic_t;

        内核提供的API函数《开发指南》中已经整理好了:

示例:

atomic_t v = ATOMIC_INIT(0);     /* 定义并初始化原子变量v=0  */ 
atomic_set(&v, 10);     /* 设置v=10 */ 
atomic_read(&v);        /* 读取v的值,v=10 */ 
atomic_inc(&v);         /* v的值加1,v=11 */
函数定义(在arch/arm/include/asm/atomic.h中定义)
ATOMIC_INIT(int i)

#define ATOMIC_INIT(i)    { (i) } 

虽说ATOMIC_INIT()也是只有一个值,但因为atomic_t是一个结构体而不是int,因此还是只能用ATOMIC_INIT初始化

int atomic_read(atomic_t *v)

#define atomic_read(v)    ACCESS_ONCE((v)->counter)

其中#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))

void atomic_set(atomic_t *v, int i)#define atomic_set(v,i)    (((v)->counter) = (i))
void atomic_add(int i, atomic_t *v)static inline void atomic_add(int i, atomic_t *v){
    atomic_add_return(i, v);
}  这个函数定义在include/asm-generic/atomic.h
void atomic_sub(int i, atomic_t *v)static inline void atomic_sub(int i, atomic_t *v){
    atomic_sub_return(i, v);
}  这个函数定义在include/asm-generic/atomic.h
void atomic_inc(atomic_t *v)#define atomic_inc(v)   atomic_add(1, v)
void atomic_dec(atomic_t *v)#define atomic_dec(v)  atomic_sub(1, v)
int atomic_dec_return(atomic_t *v)#define atomic_inc_return(v)    (atomic_add_return(1, v))
int atomic_inc_return(atomic_t *v)#define atomic_inc_return(v)    (atomic_add_return(1, v))

int atomic_sub_and_test(int i, atomic_t *v)

#define atomic_sub_and_test(i, v) (atomic_sub_return(i, v) == 0)
int atomic_dec_and_test(atomic_t *v)#define atomic_dec_and_test(v)    (atomic_sub_return(1, v) == 0)
int atomic_inc_and_test(atomic_t *v)#define atomic_inc_and_test(v)    (atomic_add_return(1, v) == 0)
int atomic_add_negative(int i, atomic_t *v)#define atomic_add_negative(i,v) (atomic_add_return(i, v) < 0)

1.2 原子位操作API函数

        Linux内核也提供了一系列的原子位操作API函数,直接对内存进行操作:

2. 代码

第10.3讲 Linux并发与竞争试验-原子操作实验_哔哩哔哩_bilibili

        使用原子操作实现对LED设备的互斥访问,一次只能有一个应用程序使用LED。

2.1 文件夹结构

        还是一样,代码直接从之前的实验贴过来:

8_ATOMIC (工作区)
├── .vscode
│   ├── c_cpp_properties.json
│   └── settings.json
├── 8_atomic.code-workspace
├── Makefile
├── atomicAPP.c
└── atomic.c

        把Makefile的obj-m这一行改为obj-m := atomic.o

2.2 atomic.c

直接在上次实验6 led的基础上改就可以,主要有以下更新:

        增加了 设备结构体gpioled_dev中 atomic_t类型的锁lock

        增加了 驱动入口函数中对锁的初始化atomic_set()或ATOMIC_INIT()

        增加了 led_open函数、led_release函数中对锁的处理

#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/atomic.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;atomic_t lock;  /* 原子操作 */
};struct gpioled_dev gpioled;/* 操作集 */
static int led_release(struct inode *inode, struct file *filp){struct gpioled_dev *dev = filp->private_data;atomic_inc(&dev->lock); // 信号量加1return 0;
}
static int led_open(struct inode *inode, struct file *filp){filp->private_data = &gpioled;if(atomic_read(&gpioled.lock) <= 0){printk("BUSY!!!\r\n");return -EBUSY;} else {atomic_dec(&gpioled.lock); // 信号量减1}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;atomic_set(&gpioled.lock, 1);  // 初始化// 或 gpioled.lock = ATOMIC_INIT(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");

2.3 atomicAPP.c

和上次实验的APP代码基本一致,只在最后增加了一个定时循环模拟程序占用。

#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  1int 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);  // 读写模式打开驱动文件filenameif(fd <0){printf("file %s open failed!\r\n");return -1;}databuf[0] = atoi(argv[2]);  // char 2 intretvalue = 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;
}

2.4 测试

# VSCODE终端
make    # 编译驱动
arm-linux-gnueabihf-gcc atomicAPP.c -o atomicAPP   # 编译应用程序
sudo cp atomic.ko atomicAPP /..../nfs/rootfs/lib/modules/4.1.15/  # cp到开发板# 串口
cd /lib/modules/4.1.15/
depmod
modprobe atomic.ko
./atomicAPP /dev/gpioled 1 &  # 这个&表示后台执行,这样就可以继续输入其他命令。 此时红灯应当亮起
ps  # 查看后台程序,列表应当能找到./atomicAPP /dev/gpioled 1这个程序

        APP定时了15s,趁其结束之前再次输入命令./atomicAPP /dev/gpioled 1,会发现报错了:

        因为第一个程序正在执行,此时lock为0。现在再执行另一个应用程序,在led_open函数时会发现lock==0,便报错退出。等待第一个程序输出“APP finished!”以后,再执行第二个程序,会发现能正常执行了:

http://www.xdnf.cn/news/1366813.html

相关文章:

  • 深度学习-----《PyTorch深度学习核心应用解析:从环境搭建到模型优化的完整实践指南》
  • 链表OJ习题(2)
  • 操作系统中,进程与线程的定义与区别
  • 似然函数对数似然函数负对数似然函数
  • Ant Design for UI 选择下拉框
  • BIO、NIO 和 AIO
  • 2025.8.25回溯算法-集合
  • Typora + PicList + Gitee 图床完整配置教程
  • 【ElasticSearch】json查询语法和可用的客户端
  • ESP32开发WSL_VSCODE环境搭建
  • Mysql系列--8、索引
  • Java延迟任务实现方案详解:从DelayQueue到实际应用
  • 2.3零基础玩转uni-app轮播图:从入门到精通 (咸虾米总结)
  • 【Docker基础】Docker-compose进阶配置:健康检查与服务就绪
  • K8s Pod驱逐机制详解与实战
  • C++ extern 关键字面试深度解析
  • 开源 C++ QT Widget 开发(六)通讯--TCP调试
  • 安全合规:AC(上网行为安全)--下
  • vue 一键打包上传
  • Genymotion 虚拟机如何安装 APK?(ARM 插件安装教程)
  • ICCV 2025|TRACE:无需标注,用3D高斯直接学习物理参数,从视频“预知”未来!
  • 二、添加3D形状
  • More Effective C++ 条款07:不要重载、和,操作符
  • 【系统架构设计师】数据库设计(一):数据库技术的发展、数据模型、数据库管理系统、数据库三级模式
  • 审核问题——首次进入APP展示隐私政策弹窗
  • 大模型(一)什么是 MCP?如何使用 Charry Studio 集成 MCP?
  • 深分页实战
  • 计算机网络:HTTP、抓包、TCP和UDP报文及重要概念
  • GPT5的Test-time compute(测试时计算)是什么?
  • Legion Y7000P IRX9 DriveList