视频:第10.1讲 Linux并发与竞争试验-并发与竞争基础概念与原子操作_哔哩哔哩_bilibili
手册:《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.81.pdf》四十七章
(概念网上很多,不写了。直接看代码吧)
1.函数
1.1原子整形操作API函数
(详见《开发指南》47.2.2节)
Linux内核定义了atomic_t结构体来完成整形数据的原子操作,在使用中用原子变量来代替整形变量:
cpp
// include/linux/types.h
typedef struct {
int counter;
} atomic_t;
内核提供的API函数《开发指南》中已经整理好了:

示例:
cpp
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函数中对锁的处理
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/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); // 信号量加1
return 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代码基本一致,只在最后增加了一个定时循环模拟程序占用。
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;
}
2.4 测试
bash
# 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!"以后,再执行第二个程序,会发现能正常执行了:
