原子操作

原子操作就是指不能再进一步分割的操作,一般原子操作用于变量或者位操作。

原子操作 API 函数

Linux内核定义了叫做 atomic_t 的结构体来完成用于 32 位整数的原子操作。在使用中用原子变量来代替整形变量,此结构体定义在 include/linux/types.h 文件中,定义如下:

1
2
3
typedef struct {
int counter;
} atomic_t;

用于 64 位整数的原子操作 (在支持 64 位原子操作的体系结构上)。

1
2
3
typedef struct {
long long counter;
} atomic64_t;

初始化

1
2
3
4
5
6
7
//-------编译时初始化:-----
ATOMIC_INIT(int i);// 静态初始化 atomic_t 变量为 i
atomic_t v ATOMIC_INIT(0);// 示例
atomic64_t v64 = ATOMIC64_INIT(0); // 64位初始化宏
//------运行时初始化:-------
atomic_t v
atomic_set(atomic_t *v, int i);

读/写设置

1
2
3
4
5
6
7
//---------写-------------
void atomic_set(atomic_t *v, int i); // 运行时设置向 v 写入 i 值。
void atomic64_set(atomic64_t *v, long long i); // 运行时设置向 v 写入 i 值。

//---------读-------------
int atomic_read(const atomic_t *v); // 读取 atomic_t 的值
long long atomic64_read(const atomic64_t *v); // 读取 atomic64_t 的值

基本算数运算

  • 加法 (v += i):

    1
    2
    3
    4
    int atomic_add_return(int i, atomic_t *v); // v 加 i, 返回新值
    int atomic64_add_return(long long i, atomic64_t *v); // 64位版本
    void atomic_add(int i, atomic_t *v); // v 加 i, 不返回值 (较老API,性能可能不如带_return的)
    void atomic64_add(long long i, atomic64_t *v);
  • 减法 (v -= i):

    1
    2
    3
    4
    int atomic_sub_return(int i, atomic_t *v); // v 减 i, 返回新值
    int atomic64_sub_return(long long i, atomic64_t *v); // 64位版本
    void atomic_sub(int i, atomic_t *v); // v 减 i, 不返回值 (较老API)
    void atomic64_sub(long long i, atomic64_t *v);
  • 自增 (v++):

    1
    2
    3
    4
    int atomic_inc_return(atomic_t *v); // v 加 1, 返回新值
    long long atomic64_inc_return(atomic64_t *v); // 64位版本
    void atomic_inc(atomic_t *v); // v 加 1, 不返回值
    void atomic64_inc(atomic64_t *v);
  • 自减 (v--):

    1
    2
    3
    4
    int atomic_dec_return(atomic_t *v); // v 减 1, 返回新值
    long long atomic64_dec_return(atomic64_t *v); // 64位版本
    void atomic_dec(atomic_t *v); // v 减 1, 不返回值
    void atomic64_dec(atomic64_t *v);

条件操作

  • 减后测试是否为 0 (常用于引用计数释放):

    1
    2
    int atomic_dec_and_test(atomic_t *v); // v 减 1, 如果结果为0返回1 (true), 否则返回0 (false)
    int atomic64_dec_and_test(atomic64_t *v); // 64位版本
  • 加后测试是否为 0:

    1
    2
    int atomic_inc_and_test(atomic_t *v); // v 加 1, 如果结果为0返回1 (true), 否则返回0 (false) (较少用)
    int atomic64_inc_and_test(atomic64_t *v);
  • 减后测试是否为负:

    1
    2
    int atomic_sub_and_test(int i, atomic_t *v); // v 减 i, 如果结果为负返回1 (true), 否则返回0 (false)
    int atomic64_sub_and_test(long long i, atomic64_t *v);

位操作

内核还提供了一组直接作用于内存地址上特定位的原子操作函数。这些函数不是作用于 atomic_t,而是直接作用于一个指向内存的指针和一个位号。 (作用于 unsigned long *void * + 位偏移)

  • 设置位 (set_bit):

    1
    void set_bit(unsigned int nr, volatile unsigned long *addr); // 将 addr 指向的内存区域的第 nr 位设置为 1
  • 清除位 (clear_bit):

    1
    void clear_bit(unsigned int nr, volatile unsigned long *addr); // 将 addr 指向的内存区域的第 nr 位设置为 0
  • 改变位 (change_bit):

    1
    void change_bit(unsigned int nr, volatile unsigned long *addr); // 翻转 addr 指向的内存区域的第 nr 位 (1变0, 0变1)
  • 测试位 (test_bit):

    1
    int test_bit(unsigned int nr, const volatile unsigned long *addr); // 测试 addr 指向的内存区域的第 nr 位是否为 1 (返回非0表示1, 0表示0)
  • 测试并设置位 (test_and_set_bit):

    1
    int test_and_set_bit(unsigned int nr, volatile unsigned long *addr); // 将第 nr 位设置为 1, 返回该位原来的值
  • 测试并清除位 (test_and_clear_bit):

    1
    int test_and_clear_bit(unsigned int nr, volatile unsigned long *addr); // 将第 nr 位设置为 0, 返回该位原来的值
  • 测试并改变位 (test_and_change_bit):

    1
    int test_and_change_bit(unsigned int nr, volatile unsigned long *addr); // 翻转第 nr 位, 返回该位原来的值

内存序 (Memory Ordering)

现代体系结构(如 ARM, POWER)通常具有宽松的内存模型。这意味着编译器和 CPU 为了提高性能,可能会对内存访问指令进行重排序。单纯的原子操作只保证了操作本身的原子性,不保证操作前后其他内存访问的相对顺序。

  • 屏障 (Barriers): 为了保证内存访问的顺序符合程序逻辑(例如,确保修改一个共享数据结构的指针 之后 再更新表示该结构有效的标志位),需要使用内存屏障。

  • 隐式屏障: 大多数上面列出的 _return 后缀的原子操作函数(如 atomic_add_return, atomic_inc_return, atomic_cmpxchg)以及 test_and_xxx_bit 函数,在成功执行后都包含一个完整的内存屏障 (smp_mb())。这意味着:

    • 在屏障之前的所有内存访问(读/写)操作,都会在屏障之后的所有内存访问操作之前完成。
    • 其他 CPU 看到的屏障之前的写操作,一定发生在看到屏障之后的写操作(或由屏障保护的状态变化)之前
  • 无屏障操作: 一些不返回值的操作(如 atomic_add, atomic_inc)和 set_bit, clear_bit 等位操作,默认不包含内存屏障。如果这些操作需要在多核间提供顺序保证,必须显式添加内存屏障 (smp_mb(), smp_wmb(), smp_rmb())。例如,在设置一个“数据就绪”标志位之前,需要确保数据本身已经写入完成:

    1
    2
    3
    data = ...;          // 准备数据
    smp_wmb(); // 写内存屏障:确保上面的写操作在下面的写操作之前完成
    set_bit(DATA_READY, &flags); // 设置标志位
  • 显式屏障变体: 较新的内核版本提供了带有显式内存序参数的原子操作 API(如 atomic_add_return_relaxed, atomic_cmpxchg_acquire),允许开发者在性能关键路径上精细控制屏障强度(relaxed, acquire, release, full)。这属于更高级的用法。

实验:

要实现设备互斥访问,需明确原子变量的初始化和操作逻辑:

  1. 初始化原子变量为 1(表示设备初始可用):

    1
    2
    atomic_t lock;
    atomic_set(&leddev->lock, 1); // 初始值设为1,代表“可用”
  2. open函数逻辑(抢占使用权):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    static int ledopen(struct inode *node, struct file *file)
    {
    // 尝试获取锁:减1后若为0,说明成功抢占;若<0,说明已被占用
    if (atomic_dec_return(&leddev->lock) < 0) {
    atomic_inc(&leddev->lock); // 恢复锁状态(加1)
    return -EBUSY; // 设备忙
    }
    file->private_data = leddev;
    return 0;
    }
    • lock初始为 1,第一个进程调用后lock=0(成功占用)。
    • 第二个进程调用时lock先减为 - 1(<0),触发-EBUSY,并恢复lock=0(保持被占用状态)。
  3. release函数逻辑(释放使用权):

    1
    2
    3
    4
    5
    6
    static int ledrelease(struct inode *node, struct file *file)
    {
    struct leddev_struct *leddev = (struct leddev_struct *)file->private_data;
    atomic_inc(&leddev->lock); // 释放锁(加1,恢复为1,允许其他进程使用)
    return 0;
    }

代码:

驱动代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>

#define LEDDRV_CNT 1
#define LEDDRV_NAME "gpioled"
#define LED_OPEN 1
#define LED_CLOSE 0

struct leddev_struct{
dev_t devid;
struct cdev cdev;
struct class *class;
struct device *device;
int major;
struct gpio_desc *leddes;
atomic_t lock;
};
struct leddev_struct *leddev;

static int ledopen(struct inode *node, struct file *file)
{
// 尝试获取锁:减1后若为0,说明成功抢占;若<0,说明已被占用
if(atomic_dec_return(&leddev->lock)<0){//
atomic_inc(&leddev->lock);/* 小于0的话就加1,使其原子变量等于0 */
return -EBUSY;
}

file->private_data = leddev;
//printk("led device open\n");
return 0;
}

static int ledrelease (struct inode *node, struct file *file)
{
//printk("led device closed\n");
struct leddev_struct *leddev=(struct leddev_struct *)file->private_data;
atomic_inc(&leddev->lock);
return 0;
}
static ssize_t ledwrite (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
struct leddev_struct *leddev=(struct leddev_struct *)file->private_data;
int ret=0;
char receive;
if(size!=1)
return -EINVAL;
ret=copy_from_user(&receive,buf,size);
if(ret<0){
return -EFAULT;
}

if(receive==LED_CLOSE){
//printk("led close\n");
gpiod_set_value(leddev->leddes,0);
}
else if(receive==LED_OPEN){
//printk("led open\n");
gpiod_set_value(leddev->leddes,1);
}
return size;
}

static struct file_operations led_fops={
.owner=THIS_MODULE,
.open=ledopen,
.write=ledwrite,
.release=ledrelease,
};

int leddrv_probe(struct platform_device *pdev){
int ret=0;
struct device *dev=&pdev->dev;
printk("matched success!\n");
leddev=devm_kzalloc(dev,sizeof(struct leddev_struct),GFP_KERNEL);
if(!leddev){
printk("kzalloc failed!\n");
return -ENOMEM;
}
if(leddev->major)
{
leddev->devid=MKDEV(leddev->major,0);
ret=register_chrdev_region(leddev->devid,LEDDRV_CNT,LEDDRV_NAME);
}
else{
ret=alloc_chrdev_region(&leddev->devid,0,LEDDRV_CNT,LEDDRV_NAME);
leddev->major=MAJOR(leddev->devid);
}
if(ret<0){
printk("devid register failed!\n");
goto chrdev_fail;
}
printk("major is %d\n",leddev->major);

cdev_init(&leddev->cdev,&led_fops);
ret=cdev_add(&leddev->cdev,leddev->devid,LEDDRV_CNT);
if(ret<0)
{
printk("cdev create error!\n");
goto cdev_fail;
}

leddev->class=class_create(THIS_MODULE,LEDDRV_NAME);
if(IS_ERR(leddev->class))
{
printk("class create error!\n");
ret=PTR_ERR(leddev->class);
goto class_fail;
}

leddev->device=device_create(leddev->class,NULL,leddev->devid,NULL,LEDDRV_NAME);
if(IS_ERR(leddev->device))
{
printk("device create error!\n");
ret=PTR_ERR(leddev->device);
goto device_fail;
}

leddev->leddes=devm_gpiod_get(dev,"led",GPIOD_OUT_LOW);
if(IS_ERR(leddev->leddes))
{
printk("leddes get error!\n");
ret=PTR_ERR(leddev->leddes);
goto leddes_fail;
}
platform_set_drvdata(pdev, leddev);

atomic_set(&leddev->lock,1);

return 0;
leddes_fail:
device_destroy(leddev->class,leddev->devid);
device_fail:
class_destroy(leddev->class);
class_fail:
cdev_del(&leddev->cdev);
cdev_fail:
unregister_chrdev_region(leddev->devid,LEDDRV_CNT);
chrdev_fail:

return ret;
}
int leddev_resume(struct platform_device *pdev){
struct leddev_struct *leddev = platform_get_drvdata(pdev);

device_destroy(leddev->class,leddev->devid);
class_destroy(leddev->class);
cdev_del(&leddev->cdev);
unregister_chrdev_region(leddev->devid,LEDDRV_CNT);

return 0;
}


const struct of_device_id led_device_id[]={
{.compatible="gpio-led"},
{},
};
struct platform_driver led_dev_driver={
.probe=leddrv_probe,
.driver={
.of_match_table=led_device_id,
.name="gpioled",
},
.remove=leddev_resume,
};

static int __init leddrv_init(void){
int ret=0;
platform_driver_register(&led_dev_driver);
return ret;
}

static void __exit leddrv_exit(void){
platform_driver_unregister(&led_dev_driver);
}


module_init(leddrv_init);
module_exit(leddrv_exit);

MODULE_AUTHOR("KEVIN");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("GPIO LED Platform Driver");

应用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char **argv) {
int fd;
char input_mode;

if (argc < 3) {
printf("Usage: ./%s /dev/<device> module(0/1)\n", argv[0]);
exit(1);
}

// 打开设备文件(确保有写权限)
fd = open(argv[1], O_WRONLY);
if (fd < 0) {
perror("open failed"); // 显示系统错误
exit(1);
}

// 解析并验证模块参数
input_mode = atoi(argv[2]);
if (input_mode != 0 && input_mode != 1) {
printf("Error: module must be 0 or 1\n");
exit(1);
}

// 写入数据并验证写入成功
if (write(fd, &input_mode, sizeof(input_mode)) != sizeof(input_mode)) {
perror("write failed"); // 显示系统错误
printf("Failed to write mode=%d\n", input_mode);
exit(1);
}
while(1);//让代码一直运行,来验证原子操作的效果

close(fd); // 显式关闭文件描述符(虽然 exit 会自动关闭,但显式更好)
exit(0);
}

实验效果:

我们首先在开发板上启动驱动模块,随后使用命令打开led灯

image-20250728180023396

我们再通过mobaxterm使用命令关闭led灯:

image-20250728175724978

可以发现显示open failed: Device or resource busy,通过结果可以知道该设备已经被占用了。