02、原子操作(执行时间比较短)
一、原子操作
原子操作是指在执行过程中不会被中断的操作,它被视为一个整体,要么完全执行,要么完全不执行。这种特性使得原子操作在多线程编程中具有重要的意义,因为它可以避免由于竞争条件(race condition)导致的数据不一致问题。例如,当我们对一个整型变量进行赋值时,看似简单的操作实际上可能会被分解为多个机器指令。如果在这些指令之间发生了线程切换,就可能导致数据竞争。
让我们以一个简单的 C 语言代码为例:
int v; // 定义一个 int 类型的变量 v
v = 1; // 将 int 类型的变量 v 赋值为 1
2
从表面上看,这段代码只是将变量 v
赋值为 1,似乎是一个单一的操作。然而,C 语言程序在编译后会被翻译成汇编指令,而这些汇编指令可能会包含多个步骤。例如,在某些架构上,上述赋值操作可能需要以下步骤:
- 将立即数
1
加载到寄存器中。 - 将寄存器中的值写入内存地址(即变量
v
的地址)。
如果在这些步骤之间发生了线程切换或其他中断,另一个线程可能会读取或修改变量 v
的值,从而导致不可预测的行为。因此,普通的赋值操作并不能保证线程安全。\
为了确保操作的原子性,Linux 内核提供了一系列原子操作接口,这些接口利用了底层硬件的特性(如原子指令和内存屏障)来实现不可分割的操作。例如,Linux 内核中的 atomic_t
类型及其相关函数可以用于对整型变量进行原子操作。以下是一个使用原子操作的示例:
#include <linux/atomic.h>
atomic_t v; // 定义一个原子类型的变量 v
atomic_set(&v, 1); // 使用原子操作将变量 v 设置为 1
2
3
4
5
在这个例子中,atomic_set
是一个内核提供的原子操作函数,它能够确保设置操作是不可分割的。即使在多核处理器上,多个线程同时调用 atomic_set
也不会导致数据竞争。
二、原子相关API
成功定义原子变量之后,必然要对原子变量进行读取、加减等动作,原子操作的部分常用 API 函数如下所示,定义在“内核源码/include/linux/atomic.h”文件中,所以在接下来的实验中需要加入该头文件的引用。
函数 | 描述 |
---|---|
ATOMIC_INIT(int i) | 定义原子变量的时候对其初始化, 赋值为 i |
int atomic_read(atomic_t *v) | 读取 v 的值, 并且返回。 |
void atomic_set(atomic_t *v, int i) | 向原子变量 v 写入 i 值。 |
void atomic_add(int i, atomic_t *v) | 原子变量 v 加上 i 值。 |
void atomic_sub(int i, atomic_t *v) | 原子变量 v 减去 i 值。 |
void atomic_inc(atomic_t *v) | 原子变量 v 加 1 |
void atomic_dec(atomic_t *v) | 原子变量 v 减 1 |
int atomic_dec_return(atomic_t *v) | 原子变量 v 减 1, 并返回 v 的值。 |
int atomic_inc_return(atomic_t *v) | 原子变量 v 加 1, 并返回 v 的值。 |
int atomic_sub_and_test(int i, atomic_t *v) | 原子变量 v 减 i, 如果结果为 0 就返回真, 否则返回假 |
int atomic_dec_and_test(atomic_t *v) | 原子变量 v 减 1, 如果结果为 0 就返回真, 否则返回假 |
int atomic_inc_and_test(atomic_t *v) | 原子变量 v 加 1, 如果结果为 0 就返回真, 否则返回假 |
int atomic_add_negative(int i, atomic_t *v) | 原子变量 v 加 i, 如果结果为负就返回真, 否则返回假 |
下面对原子位操作进行讲解,和原子整形变量不同,原子位操作没有 atomic_t 的数据结构,原子位操作是直接对内存进行操作,原子位操作相关 API 函数如下:
函数 | 描述 |
---|---|
void set_bit(int nr, void *p) | 将 p 地址的第 nr 位置 1。 |
void clear_bit(int nr,void *p) | 将 p 地址的第 nr 位清零。 |
void change_bit(int nr, void *p) | 将 p 地址的第 nr 位进行翻转。 |
int test_bit(int nr, void *p) | 获取 p 地址的第 nr 位的值。 |
int test_and_set_bit(int nr, void *p) | 将 p 地址的第 nr 位置 1, 并且返回 nr 位原来的值。 |
int test_and_clear_bit(int nr, void *p) | 将 p 地址的第 nr 位清零, 并且返回 nr 位原来的值。 |
int test_and_change_bit(int nr, void *p) | 将 p 地址的第 nr 位翻转, 并且返回 nr 位原来的值。 |
三、结构体
在 Linux 内核中,原子操作(Atomic Operations)是多线程编程和并发控制中的核心机制之一。为了确保在多处理器环境中对共享变量的操作是不可分割的(即不会被其他线程或处理器中断),内核提供了专门的数据结构和接口来实现原子性。其中,atomic_t
和 atomic64_t
是两个关键的结构体,分别用于处理 32 位系统和 64 位系统的整型数据原子操作。
在 Linux 内核源码中,atomic_t
和 atomic64_t
的定义位于头文件 include/linux/types.h
中。以下是它们的具体定义:
typedef struct {
int counter;
} atomic_t;
#ifdef CONFIG_64BIT
typedef struct {
s64 counter;
} atomic64_t;
#endif
2
3
4
5
6
7
8
9
从定义可以看出,这两个结构体的核心是一个名为 counter
的成员变量。对于 atomic_t
,其 counter
类型为 int
,通常对应于 32 位整数;而对于 atomic64_t
,其 counter
类型为 long
,在 64 位系统中通常对应于 64 位整数。这种设计使得它们能够适应不同架构的需求,同时保持代码的可移植性和简洁性。
需要注意的是,atomic64_t
的定义依赖于宏 CONFIG_64BIT
,这表明它仅在 64 位系统配置下有效。这种条件编译的设计体现了内核对硬件架构的高度适配能力。在 32 位系统中,由于硬件本身可能不支持原生的 64 位原子操作,因此需要通过软件模拟或其他机制来实现类似的功能。
四、内核使用场景
整形接口:
atomic64_read
按位接口:
set_bit
五、实验
驱动代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/atomic.h>
#include <linux/errno.h>
static atomic64_t v = ATOMIC_INIT(1);//初始化原子类型变量v,并设置为1
static int open_test(struct inode *inode,struct file *file)
{
if(atomic64_read(&v) != 1){//读取原子类型变量v的值并判断是否等于1
return -EBUSY;
}
atomic64_set(&v,0);//将原子类型变量v的值设置为0
//printk("\nthis is open_test \n");
return 0;
}
static ssize_t read_test(struct file *file,char __user *ubuf,size_t len,loff_t *off)
{
int ret;
char kbuf[10] = "linux";//定义char类型字符串变量kbuf
printk("\nthis is read_test \n");
ret = copy_to_user(ubuf,kbuf,strlen(kbuf));//使用copy_to_user接收用户空间传递的数据
if (ret != 0){
printk("copy_to_user is error \n");
}
printk("copy_to_user is ok \n");
return 0;
}
static char kbuf[10] = {0};//定义char类型字符串全局变量kbuf
static ssize_t write_test(struct file *file,const char __user *ubuf,size_t len,loff_t *off)
{
int ret;
ret = copy_from_user(kbuf,ubuf,len);//使用copy_from_user接收用户空间传递的数据
if (ret != 0){
printk("copy_from_user is error\n");
}
if(strcmp(kbuf,"linux") == 0 ){//如果传递的kbuf是linux就睡眠四秒钟
ssleep(4);
}
else if(strcmp(kbuf,"kernel") == 0){//如果传递的kbuf是kernel就睡眠两秒钟
ssleep(2);
}
printk("copy_from_user buf is %s \n",kbuf);
return 0;
}
static int release_test(struct inode *inode,struct file *file)
{
//printk("\nthis is release_test \n");
atomic64_set(&v,1);//将原子类型变量v的值赋1
return 0;
}
struct chrdev_test {
dev_t dev_num;//定义dev_t类型变量dev_num来表示设备号
int major,minor;//定义int类型的主设备号major和次设备号minor
struct cdev cdev_test;//定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备
struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类
};
struct chrdev_test dev1;//创建chrdev_test类型的
struct file_operations fops_test = {
.owner = THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = open_test,//将open字段指向open_test(...)函数
.read = read_test,//将read字段指向read_test(...)函数
.write = write_test,//将write字段指向write_test(...)函数
.release = release_test,//将release字段指向release_test(...)函数
};
static int __init atomic_init(void)
{
if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0 ){//自动获取设备号,设备名chrdev_name
printk("alloc_chrdev_region is error \n");
}
printk("alloc_chrdev_region is ok \n");
dev1.major = MAJOR(dev1.dev_num);//使用MAJOR()函数获取主设备号
dev1.minor = MINOR(dev1.dev_num);//使用MINOR()函数获取次设备号
printk("major is %d,minor is %d\n",dev1.major,dev1.minor);
cdev_init(&dev1.cdev_test,&fops_test);//使用cdev_init()函数初始化cdev_test结构体,并链接到fops_test结构体
dev1.cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用cdev_add()函数进行字符设备的添加
dev1.class_test = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_test
device_create(dev1.class_test,0,dev1.dev_num,0,"device_test");//使用device_create进行设备的创建,设备名称为device_test
return 0;
}
static void __exit atomic_exit(void)
{
device_destroy(dev1.class_test,dev1.dev_num);//删除创建的设备
class_destroy(dev1.class_test);//删除创建的类
cdev_del(&dev1.cdev_test);//删除添加的字符设备cdev_test
unregister_chrdev_region(dev1.dev_num,1);//释放字符设备所申请的设备号
printk("module exit \n");
}
module_init(atomic_init);
module_exit(atomic_exit)
MODULE_LICENSE("GPL v2");
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
测试应用代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;//定义int类型的文件描述符
char str1[10] = {0};//定义读取缓冲区str1
fd = open(argv[1],O_RDWR);//调用open函数,打开输入的第一个参数文件,权限为可读可写
if(fd < 0 ){
printf("file open failed \n");
return -1;
}
/*如果第二个参数为Linux,条件成立,调用write函数,写入Linux*/
if (strcmp(argv[2],"Linux") == 0 ){
write(fd,"Linux",10);
}
/*如果第二个参数为kernel,条件成立,调用write函数,写入kernel*/
else if (strcmp(argv[2],"kernel") == 0 ){
write(fd,"kernel",10);
}
close(fd);
return 0;
}
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