Cover image for Linux Concurrency and Competition

Linux Concurrency and Competition


Timeline

Timeline

2025-11-12

init

2026-05-21

add rwlock, rw_semaphore , rcu and per_cpu

This article introduces the concepts of concurrency and competition in Linux driver development and their common causes (multithreading, interrupts, preemption, and multi-core concurrency), and explains in detail the mechanisms used by the Linux kernel to address these issues, with a focus on the principles of integer atomic operations and related API functions.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Competition
5. Advanced Character Device Progression
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hot Plug
11. pinctrl Subsystem
12. GPIO Subsystem
13. Input Subsystem
14. Single Bus
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network Device
23. ADC
24. IIO
25. USB
26. LCD

Concurrency and Race Condition Concepts

Below, concurrency and parallelism are collectively referred to as concurrency.

In a concurrent execution environment, multiple programs may access the same shared resource simultaneously. When multiple tasks attempt to operate on such resources at the same time, execution anomalies or data errors may occur, and such problems are called race conditions.

Common causes of race conditions include:

  1. Multithreaded access. Since Linux is a multitasking operating system, multiple threads may access the same shared resource simultaneously, which is the fundamental cause of race conditions.
  2. Interrupt accessWhen a process is accessing a shared resource and an interrupt preempts the executing process, competition may also occur between the process that issued the interrupt and the interrupted process.
  3. Preemptive AccessLinux 2.6 and later versions introduced a preemptive kernel, where high-priority tasks can interrupt low-priority tasks. If a task accessing a shared resource is interrupted and another task subsequently accesses the same resource, competition may arise.
  4. **Multi-core Concurrent Access (SMP)**In multi-core processor systems, different CPU cores may concurrently access the same shared resource, leading to inter-core competition.

The Linux kernel provides various mechanisms to address this issue, commonly used methods includeatomic operationsspin locksmutex lockssemaphoresand so on.

Atomic Operations

Atomic operations treat a read-write operation on an integer variable as a whole, ensuring it is indivisible, thereby avoiding competition.

Atomic operations can be further subdivided into “integer atomic operations” and “bit atomic operations”, here we first explain integer atomic operations.

integer atomic operations

In the Linux kernel, useatomic_tandatomic64_tstructures to define atomic variables for 32-bit and 64-bit systems respectively

include/linux/types.h

1
2
3
4
5
6
7
8
9
10
11
typedef struct {
int counter;
} atomic_t;

#define ATOMIC_INIT(i) { (i) }

#ifdef CONFIG_64BIT
typedef struct {
s64 counter;
} atomic64_t;
#endif

32-bit atomic_t

include/linux/atomic.h

function prototypefunction descriptionparametersreturn value
#define ATOMIC_INIT(i)initialize atomic_t variable toii: initial integer valuenone
int atomic_read(const atomic_t *v)Read the value of the atomic variablev: atomic_t pointerReturnsvthe current value of
void atomic_set(atomic_t *v, int i)Set the atomic variable toiv: variable pointer;i: value to writeNone
void atomic_add(int i, atomic_t *v)Atomicallyv += ii: increment value;v: variable pointerNone
void atomic_sub(int i, atomic_t *v)Atomicallyv -= ii: decrement value;v: variable pointerNone
void atomic_inc(atomic_t *v)Atomicallyv++v: variable pointerNone
void atomic_dec(atomic_t *v)Atomicallyv--v: variable pointerNone
int atomic_inc_return(atomic_t *v)Atomicallyv++and return the new value (add first, then return)v: variable pointerthe value after increment
int atomic_dec_return(atomic_t *v)Atomicallyv--and return the new value (subtract first, then return)v: variable pointerthe value after decrement
int atomic_sub_and_test(int i, atomic_t *v)atomv -= i, if the result is0return truei: value to subtract;v: variable pointeris 0 → return 1, not 0 → return 0
int atomic_dec_and_test(atomic_t *v)atomv--, if the result is0return truev: variable pointeris 0 → return 1, not 0 → return 0
int atomic_inc_and_test(atomic_t *v)atomv++, if the result is0return truev: variable pointerIf 0 → return 1, otherwise 0
int atomic_add_negative(int i, atomic_t *v)Atomicv += i, if result**< 0**Return truei: Increment value;v: Variable pointerResult < 0 → return 1, otherwise 0

64-bit atomic64_t

Function prototypeFunction descriptionParametersReturn value
#define ATOMIC64_INIT(i)Initialize atomic64_t toii: Initial 64-bit integerNone
long long atomic64_read(const atomic64_t *v)Read the value of a 64-bit atomic variablev: variable pointerReturns the current 64-bit value
void atomic64_set(atomic64_t *v, long long i)Set atomic variable toiv: variable pointer;i: 64-bit value to writeNone
void atomic64_add(long long i, atomic64_t *v)Atomically executev += ii: increment value;v: variable pointerNone
void atomic64_sub(long long i, atomic64_t *v)Atomically executev -= ii: decrement value;v: variable pointerNone
void atomic64_inc(atomic64_t *v)Atomic executionv++v: Variable pointerNone
void atomic64_dec(atomic64_t *v)Atomic executionv--v: Variable pointerNone
long long atomic64_add_return(long long i, atomic64_t *v)Atomic executionv += iand return new value (add first, then return)ivReturn the value after addition
long long atomic64_sub_return(long long i, atomic64_t *v)Atomic executionv -= iand return new value (subtract first, then return)ivReturn the value after subtraction
long long atomic64_inc_return(atomic64_t *v)Atomicv++and return new valuevReturn the value after increment
long long atomic64_dec_return(atomic64_t *v)atomv--and return the new valuevreturn the decremented value
int atomic64_add_negative(long long i, atomic64_t *v)atomv += i, if the result**< 0**returns trueivresult < 0 → 1, otherwise 0
int atomic64_add_and_test(long long i, atomic64_t *v)atomv += i, if the result**== 0**returns trueivresult == 0 → 1, otherwise 0
int atomic64_sub_and_test(long long i, atomic64_t *v)atomv -= i, if the result**== 0**returns trueivresult == 0 → 1, otherwise 0
int atomic64_inc_and_test(atomic64_t *v)atomv++, if the result**== 0**returns truevis 0 → 1, otherwise 0
int atomic64_dec_and_test(atomic64_t *v)atomv--, if the result**== 0**returns truevis 0 → 1, otherwise 0
long long atomic64_xchg(atomic64_t *v, long long i)atomic swap: setvtoiand return the old valuevireturns the old value before the swap
long long atomic64_cmpxchg(atomic64_t *v, long long old, long long new)atomic compare-and-swap: if *v == old, write newvoldnewreturns the old value before the swap (can be used to check success)

incorrect usage

The following usage is incorrect:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static atomic_t atomic_key = ATOMIC_INIT(1);

int atomic_t_test_open(struct inode *inode, struct file *file)
{
file->private_data = test_drv_data;
if(atomic_read(&atomic_key) == 0){
return -EBUSY;
}
atomic_dec(&atomic_key);

return 0;
}

int atomic_t_test_release(struct inode *inode, struct file *file)
{
atomic_inc(&atomic_key);
return 0;
}

The reason isatomic_read(&atomic_key)andatomic_dec(&atomic_key)These two operations are separate, and two processes can simultaneouslyatomic_read(&atomic_key)thenatomic_dec(&atomic_key)succeed. These two operations can be combined into one

1
2
if (!atomic_dec_and_test(&atomic_key))
return -EBUSY;

Example:

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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/atomic.h>

struct test_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
};

static struct test_drv_data *test_drv_data;

static atomic_t atomic_key = ATOMIC_INIT(1);

int atomic_t_test_open(struct inode *inode, struct file *file)
{
file->private_data = test_drv_data;
if (atomic_dec_and_test(&atomic_key) == 0){
pr_err("this device is opened by another process\n");
return -EBUSY;
}
pr_info("atomic_t_test_open is called\n");
return 0;
}

int atomic_t_test_release(struct inode *inode, struct file *file)
{
atomic_inc(&atomic_key);
return 0;
}

ssize_t atomic_t_test_read (struct file *file, char __user *buf, size_t size, loff_t *offset){
pr_info("atomic_t_test_read is called\n");
return 0;
}
ssize_t atomic_t_test_write (struct file *file, const char __user *buf, size_t size, loff_t *offset){
pr_info("atomic_t_test_write is called\n");
return 0;
}

struct file_operations fops = {
.owner = THIS_MODULE,
.open = atomic_t_test_open,
.release = atomic_t_test_release,
};

static int __init atomic_t_test_init(void)
{
int err;
test_drv_data = (struct test_drv_data *)kzalloc(sizeof(struct test_drv_data), GFP_KERNEL);
if (test_drv_data == NULL) {
err = -ENOMEM;
goto kzalloc_fail;
}
err = alloc_chrdev_region(&test_drv_data->dev_num, 0, 1, "atomic_t test chrdev region");
if (err < 0)
goto alloc_chrdev_region_fail;
cdev_init(&test_drv_data->cdev, &fops);
err = cdev_add(&test_drv_data->cdev, test_drv_data->dev_num, 1);
if (err < 0)
goto cdev_add_fail;
test_drv_data->class = class_create(THIS_MODULE, "atomic_t_test");
if (IS_ERR(test_drv_data->class)) {
err = PTR_ERR(test_drv_data->class);
goto class_create_fail;
}
test_drv_data->dev = device_create(test_drv_data->class, NULL, test_drv_data->dev_num, NULL,
"atomic_t_test%d", 0);
if (IS_ERR(test_drv_data->dev)) {
err = PTR_ERR(test_drv_data->dev);
goto device_create_fail;
}

return 0;
device_create_fail:
class_destroy(test_drv_data->class);
class_create_fail:
cdev_del(&test_drv_data->cdev);
cdev_add_fail:
unregister_chrdev_region(test_drv_data->dev_num, 1);
alloc_chrdev_region_fail:
kfree(test_drv_data);
kzalloc_fail:
return err;
}

static void __exit atomic_t_test_exit(void)
{
device_destroy(test_drv_data->class, test_drv_data->dev_num);
class_destroy(test_drv_data->class);
cdev_del(&test_drv_data->cdev);
unregister_chrdev_region(test_drv_data->dev_num, 1);
kfree(test_drv_data);
}

module_init(atomic_t_test_init);
module_exit(atomic_t_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@163.com>");
MODULE_DESCRIPTION("This is a test sample for atomic_t");

Test code:

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>

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

pid = fork();

if (pid < 0) {
goto fail;
} else if (pid == 0) { // child process
fd = open("/dev/atomic_t_test0", O_RDWR);
if (fd < 0)
goto fail;

printf("child open device success\n");
// do something
sleep(3);
close(fd);

} else { // parent process
fd = open("/dev/atomic_t_test0", O_RDWR);
if(fd < 0){
goto fail;
}
printf("parent open device success\n");
// do something
sleep(3);
close(fd);
}

return 0;
fail:
fprintf(stderr, "Error:%s[errno:%d]\n", strerror(errno), errno);
exit(EXIT_FAILURE);
}

Test:

1
2
3
4
5
6
7
$ insmod atomic_t.ko
[ 13.631548] atomic_t: loading out-of-tree module taints kernel.
$ ./test_atomic_t.o
[ 20.232024] atomic_t_test_open is called
[ 20.233129] this device is opened by another process
parent open device success
Error:Device or resource busy[errno:16]

atomic_cmpxchg

This function is relatively difficult to understand; its purpose is:

  • If the current value of the atomic variable equalsold
  • Change it tonew
  • Return the value before modification (becausevthe value before modification equalsold, so the returned value equalsold)
  • If the current value ≠old
    • the atomic variable will not be modified
    • directly returns the “current value”
1
2
3
#include <linux/atomic.h>

long long atomic64_cmpxchg(atomic64_t *v, long long old, long long new);

Parameters

ParameterDescription
vPointer to theatomic64_tvariable to operate on
oldExpected old value (expected value)
newIf the atomic variable’s value equalsold, replace it withnewReplace it

Return Value

  • Returnsthe original value of the atomic variable before the operation
  • If the original value equalsold, replace the original value with new, return value = old.
  • If the original value does not equalold, replacement fails, return value ≠ old.

Example

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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/atomic.h>

struct device_test {
// Device number
dev_t dev_id;
// Class
struct class *class;
// Device node under the class
struct device *device;
// cdev, character device
struct cdev cdev_test;
};

static struct device_test device1;

static atomic64_t cnt = ATOMIC_INIT(1);

static int major_num;
static int minor_num;

module_param(major_num, int, S_IRUGO);
module_param(minor_num, int, S_IRUGO);

int cdev_test_open(struct inode *inode, struct file *file)
{
if (atomic64_cmpxchg(&cnt, 1, 0) != 1) {
return -EBUSY;
}
file->private_data = &device1;
pr_info("cdev_test_open was called");
return 0;
}

ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size,
loff_t *off)
{
//struct device_test *dev1 = (struct device_test *)file->private_data;

pr_info("cdev_test_read was called");
return 0;
}

ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size,
loff_t *off)
{
//struct device_test *dev1 = (struct device_test *)file->private_data;

return 0;
}
int cdev_test_release(struct inode *inode, struct file *file)
{
atomic64_set(&cnt, 1);
pr_info("cdev_test_release was called");
return 0;
}

static struct file_operations cdev_test_ops = { .owner = THIS_MODULE,
.open = cdev_test_open,
.read = cdev_test_read,
.write = cdev_test_write,
.release = cdev_test_release };

static int __init my_driver_init(void)
{
int ret;

// Request device number
if (major_num) { //Driver parameter passing, static request for device number
device1.dev_id = MKDEV(major_num, minor_num);

pr_info("major from module_param: %d", MAJOR(device1.dev_id));
pr_info("minor from module_param: %d", MINOR(device1.dev_id));

ret = register_chrdev_region(device1.dev_id, 1,
"my_driver device");

if (ret < 0) {
pr_err("register_chrdev_region error\n");
goto get_chrdev_region_err;
}
pr_info("register_chrdev_region ok\n");

} else { //Dynamic request for device number
ret = alloc_chrdev_region(&device1.dev_id, 0, 1,
"my_driver device");
if (ret < 0) {
pr_err("alloc_chrdev_region error\n");
goto get_chrdev_region_err;
}
pr_info("alloc_chrdev_region ok\n");

pr_info("major allocated: %d", MAJOR(device1.dev_id));
pr_info("minor allocated: %d", MINOR(device1.dev_id));
}
// cdev initialization, register character device
cdev_init(&device1.cdev_test, &cdev_test_ops);

//Point the owner field to this module to prevent unloading the module while its operations are in use
device1.cdev_test.owner = THIS_MODULE;
// Add a character device
ret = cdev_add(&device1.cdev_test, device1.dev_id, 1);

if (ret < 0) {
pr_err("cdev_add error\n");
goto cdev_add_err;
}
pr_info("cdev_add ok\n");

// Create a class
device1.class = class_create(THIS_MODULE, "test");
if (IS_ERR(device1.class)) {
ret = PTR_ERR(device1.class);
pr_err("class create error\n");
goto class_create_err;
}
// Create a device node under the class, /dev/my_driver
device1.device = device_create(device1.class, NULL, device1.dev_id,
NULL, "my_driver");
if (IS_ERR(device1.device)) {
ret = PTR_ERR(device1.device);
pr_err("device create error\n");
goto device_create_err;
}
pr_info("my_driver: Module loaded\n");

return 0;

device_create_err:
class_destroy(device1.class);
class_create_err:
cdev_del(&device1.cdev_test);
cdev_add_err:
unregister_chrdev_region(device1.dev_id, 1);
get_chrdev_region_err:
return ret;
}

static void __exit my_driver_exit(void)
{
// Delete the device node
device_destroy(device1.class, device1.dev_id);
// Delete the class of the device node
class_destroy(device1.class);
// Delete the cdev
cdev_del(&device1.cdev_test);
// Release the device number
unregister_chrdev_region(device1.dev_id, 1);

pr_info("unregister_chrdev_region ok\n");
pr_info("my_driver: Module unloaded\n");
}

module_init(my_driver_init);
module_exit(my_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zhao Hang");
MODULE_DESCRIPTION("my_driver Kernel Module");

Bit atomic operation

Function prototypeFunction descriptionParameter descriptionReturn value
void set_bit(int nr, void *p)Set thepbit of the addressnrposition / place / rank / digit / measure word for peopleto 1nr: bit number (0 indicates the least significant bit)p: starting addressnone
void clear_bit(int nr, void *p)setpthenr-th bit of the addressto zerosame as abovenone
void change_bit(int nr, void *p)togglenr-th bitinvert(0→1 or 1→0)same as abovenone
int test_bit(int nr, void *p)Readnrthe value of the bitSame as aboveReturns the value of the bit: 0 or 1
int test_and_set_bit(int nr, void *p)Setnrthe bit to 1 and return the original valueSame as aboveReturns the value of the bit before the operation (0 or 1)
int test_and_clear_bit(int nr, void *p)Clearnrthe bit to 0 and return the original valueSame as aboveReturns the value of the bit before the operation
int test_and_change_bit(int nr, void *p)Togglenrthe bit (1↔0) and return the original valueSame as aboveReturns the value of the bit before the operation

Bit numbering starts fromp points to the starting address of the memorycounting from:

  • nr = 0→ the least significant bit (bit0) of the first byte
  • nr = 7→ the most significant bit of the first byte
  • nr = 8→ bit0 of the second byte
  • and so on

Bit numbering is an absolute bit index, not an offset within a byte.

Spinlock

A spinlock is a locking mechanism proposed to protect shared resources.When a thread attempts to acquire a spinlock and finds that the lock is already held by another thread, it does not enter a sleep state to wait, but instead continuously loops to try to acquire the lock until it succeeds. This process is called ‘spinning’, hence the name ‘spinlock’.

Definition

include/linux/spinlock_types.h

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct spinlock {
union {
struct raw_spinlock rlock;

#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;

offsetof is defined ininclude/linux/stddef.hIts purpose is to calculate the offset from a struct to a specific field.

1
2
3
4
5
#ifdef __compiler_offsetof
#define offsetof(TYPE, MEMBER) __compiler_offsetof(TYPE, MEMBER)
#else
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#endif

The above spinlock field uses offsetof to calculate and set padding fields, ensuring that the two union members’dep_mapaddresses are exactly the same, thus ensuring that regardless of whetherCONFIG_DEBUG_LOCK_ALLOCspinlock_tthe size and alignment ofare consistent

There is another purpose:

spinlock_tisa generic lock type

  • Do not want to exposeraw_spinlockthe internal structure of
  • Butlockdepneedsdep_map

This design allows kernel code to be written like this:

1
lockdep_init_map(&lock->dep_map, ...);

Instead of:

1
lock->rlock.dep_map   // ❌ This is not desired

Spinlock-related API functions are defined in the kernel source codeinclude/linux/spinlock.hfile (the spinlock.h header includes spinlock_types.h, etc., so just include the spinlock.h header file)

API

API / MacroReturn TypeFunction DescriptionBlockingInterrupt StateReturn Value / Remarks
DEFINE_SPINLOCK(name)voidStatically define and initialize a spinlock--None
spin_lock_init(spinlock_t *lock)voidDynamically initialize a spinlock (only initializes, does not allocate memory)--None
spin_lock(spinlock_t *lock)voidAcquire spinlockBlocking (spinning)UnchangedNone
spin_lock_bh(spinlock_t *lock)voidAcquire spinlock and disable softirqBlockingDisable softirqNone
spin_lock_irq(spinlock_t *lock)voidAcquire spinlock and disable local CPU interruptsBlockingDisable local interruptsNone
spin_lock_irqsave(spinlock_t *lock, unsigned long flags)voidAcquire spinlock and save local interrupt stateBlockingDisable interrupts, save flagsNone
spin_trylock(spinlock_t *lock)intTry to acquire spinlockNon-blockingNo changeReturns 1 on success, 0 on failure
spin_trylock_bh(spinlock_t *lock)intTry to acquire spinlock and disable softirqNon-blockingDisable softirqReturns 1 on success, 0 on failure
spin_trylock_irq(spinlock_t *lock)intTry to acquire spinlock and disable interruptsNon-blockingDisable local interruptsReturns 1 on success, 0 on failure
spin_trylock_irqsave(spinlock_t *lock, unsigned long flags)intTry to acquire spinlock and save interrupt stateNon-blockingDisable interrupts, save flagsReturns 1 on success, 0 on failure
spin_unlock(spinlock_t *lock)voidRelease spinlock--None
spin_unlock_bh(spinlock_t *lock)voidRelease spinlock and restore softirq state-Restore softirqNone
spin_unlock_irq(spinlock_t *lock)voidRelease spinlock and restore interrupts-Restore interruptsNone
spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)voidRelease spinlock and restore interrupt state-Restore interrupt state flagsNone
spin_is_locked(spinlock_t *lock)intCheck if lock is heldNon-blocking-Returns 1 if locked, otherwise 0
spin_is_contended(spinlock_t *lock)intCheck if lock is contendedNon-blocking-Returns 1 if there is contention, otherwise 0

Using a spin lock involves the following 3 steps:

  1. First, acquire the spin lock when accessing critical resources
  2. After acquiring the spin lock, enter the critical section; if unable to acquire it, wait in place.
  3. Release the spin lock when exiting the critical section.

Example

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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/sched.h>

struct test_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
};

static struct test_drv_data *test_drv_dat;
static bool status = 1;
static spinlock_t lock;

int spinlock_test_open(struct inode *inode, struct file *file)
{
file->private_data = test_drv_dat;
spin_lock(&lock);
if(status == 0){
spin_unlock(&lock);
return -EBUSY;
}
status = 0;
spin_unlock(&lock);
pr_info("spinlock_test_open is called by [pid: %d]\n",task_pid_nr(current));

return 0;
}
ssize_t spinlock_test_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
pr_info("spinlock_test_read is called\n");
return 0;
}
ssize_t spinlock_test_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
pr_info("spinlock_test_write is called\n");
return 0;
}
int spinlock_test_release(struct inode *inode, struct file *file)
{
spin_lock(&lock);
status = 1;
spin_unlock(&lock);
pr_info("spinlock_test_release is called by [pid: %d]\n", task_pid_nr(current));
return 0;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = spinlock_test_open,
.read = spinlock_test_read,
.write = spinlock_test_write,
.release = spinlock_test_release,
};

static int __init spinlock_test_init(void)
{
int err;
test_drv_dat = kzalloc(sizeof(struct test_drv_data), GFP_KERNEL);
if (test_drv_dat == NULL) {
err = -ENOMEM;
goto kzalloc_fail;
}
err = alloc_chrdev_region(&test_drv_dat->dev_num, 0, 1, "spinlock test chrdev region\n");

if (err < 0)
goto alloc_chrdev_region_fail;
cdev_init(&test_drv_dat->cdev, &fops);
test_drv_dat->cdev.owner = THIS_MODULE;
err = cdev_add(&test_drv_dat->cdev, test_drv_dat->dev_num, 1);
if (err < 0)
goto cdev_add_fail;
test_drv_dat->class = class_create(THIS_MODULE, "chrdev");
if (IS_ERR(test_drv_dat->class)) {
err = PTR_ERR(test_drv_dat->class);
goto class_create_fail;
}
test_drv_dat->dev = device_create(test_drv_dat->class, NULL, test_drv_dat->dev_num, NULL,
"spinlock_test%d", 0);
if (IS_ERR(test_drv_dat->dev)) {
err = PTR_ERR(test_drv_dat->dev);
goto device_create_fail;
}
// Initialize the spin lock
spin_lock_init(&lock);

return 0;

device_create_fail:
class_destroy(test_drv_dat->class);
class_create_fail:
cdev_del(&test_drv_dat->cdev);
cdev_add_fail:
unregister_chrdev_region(test_drv_dat->dev_num, 1);
alloc_chrdev_region_fail:
kfree(test_drv_dat);
kzalloc_fail:
return err;
}

static void __exit spinlock_test_exit(void)
{
device_destroy(test_drv_dat->class, test_drv_dat->dev_num);
class_destroy(test_drv_dat->class);
cdev_del(&test_drv_dat->cdev);
unregister_chrdev_region(test_drv_dat->dev_num, 1);
kfree(test_drv_dat);
}

module_init(spinlock_test_init);
module_exit(spinlock_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@163.com>");
MODULE_DESCRIPTION("This is a test sample for spinlock");

Test:

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
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char **argv)
{
pid_t pid;
int fd = -1;

pid = fork();
if (pid < 0) {
perror("fork error");
fprintf(stderr, "Error: %s[errno: %d]\n", strerror(errno), errno);
exit(EXIT_FAILURE);
} else if (pid == 0) { // child process
printf("[child: <pid: %d>] try to open /dev/spinlock_test0\n",getpid());
fd = open("/dev/spinlock_test0", O_RDWR);
if (fd < 0) {
perror("[child] open error");
fprintf(stderr, "[child] Error: %s[errno: %d]\n", strerror(errno), errno);
exit(EXIT_FAILURE);
}
printf("[child: <pid: %d>] open /dev/spinlock_test0 success\n", getpid());
sleep(2);
close(fd);

} else { // parent process
printf("[parent: <pid: %d>] try to open /dev/spinlock_test0\n", getpid());
fd = open("/dev/spinlock_test0", O_RDWR);
if (fd < 0) {
perror("[parent] open error");
fprintf(stderr, "[parent] Error: %s[errno: %d]\n", strerror(errno), errno);
exit(EXIT_FAILURE);
}
sleep(2);
printf("[parent: <pid: %d>] open /dev/spinlock_test0 success\n", getpid());

close(fd);
}

return 0;
}

Test case:

1
2
3
4
5
6
7
8
9
10
$ insmod spinlock_test.ko
[ 10.948417] spinlock_test: loading out-of-tree module taints kernel.
$ ./test_spinlock.o
[child: <pid: 102>] try to open /dev/spinlock_test0
[parent: <pid: 101>] try to open /dev/spinlock_test0
[ 16.331206] spinlock_test_open is called by [pid: 102]
[child: <pid: 102>] open /dev/spinlock_test0 success
[parent] open error: Device or resource busy
[parent] Error: Device or resource busy[errno: 16]
$ [ 18.335915] spinlock_test_release is called by [pid: 102]

Notes

  1. BecauseA spin lock will “wait in place,” which continues to occupy the CPU and consumes CPU resources, so the lock duration must not be too long. That is, the code in the critical section should not be too extensive.
  2. In the critical section protected by the spin lock,functions that may cause thread sleep cannot be called, otherwise a deadlock may occur
  3. Spin locks are generallyUsed on multi-core SOCs
  4. Only one task can hold a mutex at a time; this is not a rule but a fact.
  5. Multiple unlocks are not allowed.
  6. They must be initialized through the API.
  7. A task holding a mutex cannot exit, because the mutex will remain locked, and potential contenders will wait forever (sleep).
  8. Locked memory regions cannot be freed.
  9. A held mutex must not be reinitialized.
  10. Because they involve rescheduling, mutexes cannot be used in atomic contexts such as tasklets and timers.

Spinlock deadlock

A spinlock deadlock refers to a situation where multiple tasks or processes wait for each other to release resources, causing none to proceed.

For example, suppose there are two processes, A and B. Process A holds a spinlock on resource 1 and also wants to acquire resource 2. Process B holds a spinlock on resource 2 and also wants to acquire resource 1. In this case, both processes are waiting for each other to release resources, causing a deadlock.

Another example: while process A holds a spinlock, an interrupt occurs, and the CPU switches to execute the interrupt handler, which also needs to acquire the same spinlock. Since the lock is already held by A, the interrupt handler can only spin and wait, causing the system to enter a deadlock state.

  • For example,When the interrupt handler also needs to acquire a spinlock, the driver must disable interrupts while holding the spinlock (spin_lock_irqsave, spin_lock_irqrestore) and enable interrupts when releasing the spinlock.

  • Also, hold the spinlock for as short a time as possible; holding it for a long time may exhaust system resources and cause a deadlock.

  • Finally, alsoavoid a function that has acquired a spinlock from calling other functions that also attempt to acquire the same lock, otherwise it will also cause a deadlock.

  • No function that can cause sleep or blocking should be called within the critical section

Read-Write Lock

Definition

Usingspinlockto protect a critical section, multiple reads cannot be concurrent and can only bespin, to improve the overall system performance, the kernel defines a type of lock:

  • Allowing multiple processor processes (or threads or interrupt contexts)to perform read operations concurrentlySMP), which is safe and improves theSMPsystem’s performance.
  • During writing, ensure complete mutual exclusion of the critical section.

Read/Write Spinlockis introduced under the protection ofSMPthe shared data structure under the system, and its introduction is to increase the kernel’s concurrency capability**. As long as the kernel control pathdoes notmodify the data structure, the read/write spin lock allows multiple kernel control paths**to simultaneously read the same data structure.

If a kernel control path wants to write to this structure, it must first acquire the write lock of the read/write lock,the write lock grants exclusive access to this resource. The purpose of this design is that allowing concurrent reads of the data structure can improve system performance.

Example

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
/*
* example_rwlock.c
*/
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/rwlock.h>

static DEFINE_RWLOCK(myrwlock);

static void example_read_lock(void)
{
unsigned long flags;

read_lock_irqsave(&myrwlock, flags);
pr_info("Read Locked\n");

/* Read from something */

read_unlock_irqrestore(&myrwlock, flags);
pr_info("Read Unlocked\n");
}

static void example_write_lock(void)
{
unsigned long flags;

write_lock_irqsave(&myrwlock, flags);
pr_info("Write Locked\n");

/* Write to something */

write_unlock_irqrestore(&myrwlock, flags);
pr_info("Write Unlocked\n");
}

static int __init example_rwlock_init(void)
{
pr_info("example_rwlock started\n");

example_read_lock();
example_write_lock();

return 0;
}

static void __exit example_rwlock_exit(void)
{
pr_info("example_rwlock exit\n");
}

module_init(example_rwlock_init);
module_exit(example_rwlock_exit);

MODULE_DESCRIPTION("Read/Write locks example");
MODULE_LICENSE("GPL");

API

InitializationAPI**:

FunctionDescription
DEFINE_RWLOCK(rwlock_t lock)Define and initialize a read/write lock
void rwlock_init(rwlock_t *lock)Initialize a read/write lock

Read OperationAPI

FunctionDescription
void read_lock(rwlock_t *lock)Acquire read lock
void read_unlock(rwlock_t *lock)Release read lock
void read_lock_irq(rwlock_t *lock)Disable local interrupts and acquire read lock
void read_unlock_irq(rwlock_t *lock)Enable local interrupts and release read lock
void read_lock_irqsave(rwlock_t *lock, unsigned long flags)Save interrupt state, disable local interrupts, and acquire read lock
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags)Restore interrupt state to previous state, enable local interrupts, and release read lock
void read_lock_bh(rwlock_t *lock)Disable bottom halves and acquire read lock
void read_unlock_bh(rwlock_t *lock)Enable bottom halves and release read lock

Write OperationAPI

FunctionDescription
void write_lock(rwlock_t *lock)Acquire write lock
void write_unlock(rwlock_t *lock)Release write lock
void write_lock_irq(rwlock_t *lock)Disable local interrupts and acquire write lock
void write_unlock_irq(rwlock_t *lock)Enable local interrupts and release write lock
void write_lock_irqsave(rwlock_t *lock, unsigned long flags)Save interrupt state, disable local interrupts, and acquire write lock
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags)Restore interrupt state to previous state, enable local interrupts, and release read lock
void write_lock_bh(rwlock_t *lock)Disable bottom halves and acquire read lock
void write_unlock_bh(rwlock_t *lock)Enable bottom halves and release read lock

Semaphore

Spinlocks handle concurrency and competition through ‘busy waiting’, so the protected critical section cannot be too long to avoid wasting CPU resources. However, in some cases we inevitablyneed to protect certain resources for a long time. In such cases, semaphores can be used.

Semaphores cause the caller to sleep, so they are also called sleep locks.

Semaphores haveP operationandV operation. The P operation decrements the semaphore value by one. If the semaphore value is less than or equal to 0, it indicates that all resources are occupied; if another process needs to use it, that process will be added to the waiting queue. If the value is greater than 0, it means there are n available resources.

Definition

include/linux/semaphore

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
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2008 Intel Corporation
* Author: Matthew Wilcox <willy@linux.intel.com>
*
* Please see kernel/locking/semaphore.c for documentation of these functions
*/
#ifndef __LINUX_SEMAPHORE_H
#define __LINUX_SEMAPHORE_H

#include <linux/list.h>
#include <linux/spinlock.h>

/* Please don't access any members of this structure directly */
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};

#define __SEMAPHORE_INITIALIZER(name, n) \
{ \
.lock = __RAW_SPIN_LOCK_UNLOCKED((name).lock), \
.count = n, \
.wait_list = LIST_HEAD_INIT((name).wait_list), \
}

#define DEFINE_SEMAPHORE(name) \
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

static inline void sema_init(struct semaphore *sem, int val)
{
static struct lock_class_key __key;
*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}

extern void down(struct semaphore *sem);
extern int __must_check down_interruptible(struct semaphore *sem);
extern int __must_check down_killable(struct semaphore *sem);
extern int __must_check down_trylock(struct semaphore *sem);
extern int __must_check down_timeout(struct semaphore *sem, long jiffies);
extern void up(struct semaphore *sem);

#endif /* __LINUX_SEMAPHORE_H */

API

API / MacroReturn TypeFunction Description
DEFINE_SEMAPHORE(name)N/ADefine and initialize a semaphore with an initial value of 1 (mutex semaphore).
sema_init(struct semaphore *sem, int val)voidInitialize a semaphoresem, initial value isval
down(struct semaphore *sem)voidAcquire the semaphore. If count > 0, decrement by 1 and return immediately; if count is 0, block until it can be acquired.
down_interruptible(struct semaphore *sem)intAcquire the semaphore. If interrupted by a signal, return non-zero; otherwise block until acquisition succeeds and return 0.
down_killable(struct semaphore *sem)intAcquire the semaphore. If the process receives a fatal signal, return non-zero; otherwise block until acquisition succeeds and return 0.
down_trylock(struct semaphore *sem)intTry to acquire the semaphore immediately. Return non-zero if successful, otherwise return 0 immediately without blocking.
down_timeout(struct semaphore *sem, long jiffies)intTry to acquire the semaphore, blocking for at mostjiffiesclock ticks. Return value >0 indicates success, 0 indicates timeout, negative value indicates error or signal interruption.
up(struct semaphore *sem)voidRelease the semaphore (increment count by 1) and wake up tasks in the waiting queue.

Difference between down and down_interruptible:

Featuredown(struct semaphore *sem)down_interruptible(struct semaphore *sem)
Blocking behaviorIf the semaphorecountis 0, the calling process willblock indefinitelyuntil the semaphore becomes availableIf the semaphorecountis 0, the calling process will block, butcan be interrupted by a signal
Signal interruptionDoes not respond to signals; an interrupt will not causedown()returnResponds to signals; if a signal is received while blocked, thenImmediately return non-zero
Return valuevoid(Always succeeds, unless kernel bug)int, 0 indicates success, non-zero indicates interrupted by signal, acquisition failed
Usage scenarioNo need to respond to interrupts, ensure semaphore acquisitionNeed interruptible blocking, e.g., user process can respond to Ctrl+C or other signals

Example

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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/semaphore.h>
#include <linux/sched.h>

struct test_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
};

static struct test_drv_data *drv_dat;
static struct semaphore sema;

int semaphore_test_open(struct inode *inode, struct file *file)
{
int ret;
file->private_data = drv_dat;
ret = down_interruptible(&sema);
if (ret != 0) {
pr_info("semaphore_test_open is called by [pid: %d] but is interrupted\n",
current->pid);
return -EINTR;
}
pr_info("semaphore_test_open is called by [pid: %d]\n", current->pid);
return 0;
}

int semaphore_test_release(struct inode *inode, struct file *file)
{
up(&sema);
pr_info("semaphore_test_release is called by [pid: %d]\n", current->pid);
return 0;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = semaphore_test_open,
.release = semaphore_test_release,
};

static int __init semaphore_test_init(void)
{
int err;

drv_dat = (struct test_drv_data *)kzalloc(sizeof(struct test_drv_data), GFP_KERNEL);
if (drv_dat == NULL) {
err = -ENOMEM;
goto kzalloc_fail;
}

err = alloc_chrdev_region(&drv_dat->dev_num, 0, 1, "semaphore_test_chrdev_region");
if (err < 0)
goto alloc_chrdev_region_fail;
cdev_init(&drv_dat->cdev, &fops);
drv_dat->cdev.owner = THIS_MODULE;
err = cdev_add(&drv_dat->cdev, drv_dat->dev_num, 1);
if (err < 0)
goto cdev_add_fail;
drv_dat->class = class_create(THIS_MODULE, "chrdev_test");
if (IS_ERR(drv_dat->class)) {
err = PTR_ERR(drv_dat->class);
goto class_create_fail;
}

drv_dat->dev =
device_create(drv_dat->class, NULL, drv_dat->dev_num, NULL, "semaphore_test%d", 0);
if (IS_ERR(drv_dat->dev)) {
err = PTR_ERR(drv_dat->dev);
goto device_create_fail;
}
// Initialize semaphore
sema_init(&sema, 1);

return 0;
device_create_fail:
class_destroy(drv_dat->class);
class_create_fail:
cdev_del(&drv_dat->cdev);
cdev_add_fail:
unregister_chrdev_region(drv_dat->dev_num, 1);
alloc_chrdev_region_fail:
kfree(drv_dat);
kzalloc_fail:
return err;
}

static void __exit semaphore_test_exit(void)
{
device_destroy(drv_dat->class, drv_dat->dev_num);
class_destroy(drv_dat->class);
cdev_del(&drv_dat->cdev);
unregister_chrdev_region(drv_dat->dev_num, 1);
kfree(drv_dat);
}

module_init(semaphore_test_init);
module_exit(semaphore_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@163.com>");
MODULE_DESCRIPTION("This is test sample for semaphore");

Test case:

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
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

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

pid = fork();
if (pid < 0) {
perror("fork error");
exit(EXIT_FAILURE);
} else if (pid == 0) { // child
fd = open("/dev/semaphore_test0", O_RDWR);
if (fd < 0) {
perror("child open /dev/semaphore_test0 error");
exit(EXIT_FAILURE);
}
printf("[child: pid<%d>] open /dev/semaphore_test0 success\n", getpid());
sleep(2);
close(fd);
} else { // parent
fd = open("/dev/semaphore_test0", O_RDWR);
if (fd < 0) {
perror("parent open /dev/semaphore_test0 error");
exit(EXIT_FAILURE);
}
printf("[parent: pid<%d>] open /dev/semaphore_test0 success\n", getpid());
sleep(2);
close(fd);
}

return 0;
}
1
2
3
4
5
6
7
8
9
$ insmod semaphore_test.ko
[ 15.981179] semaphore_test: loading out-of-tree module taints kernel.
$ ./test_semaphore.o
[ 20.535287] semaphore_test_open is called by [pid: 101]
[parent: pid<101>] open /dev/semaphore_test0 success
[ 22.544668] semaphore_test_release is called by [pid: 101]
[ 22.545409] semaphore_test_open is called by [pid: 102]
[child: pid<102>] open /dev/semaphore_test0 success
[ 24.550326] semaphore_test_release is called by [pid: 102]

Note

  1. The semaphore value cannot be less than 0
  2. When accessing shared resources, the semaphore performs a “decrement” operation, and after access, an “increment” operation
  3. When the semaphore value is 0, threads wanting to access the shared resource must wait, when the semaphore is greater than 0, waiting threads can access
  4. becauseSemaphores can cause sleep, so they cannot be used in interrupt contexts.
  5. If the shared resource is held for a relatively long time, semaphores are generally used instead of spinlocks.
  6. When using both semaphores and spinlocks, acquire the semaphore first, then use the spinlock, because semaphores can cause sleep.

read-write semaphore

The difference from read-write locks is that read-write semaphores are sleep locks, while read-write locks are spinlocks. Choice:

Can the critical section potentially sleep?
├─ Yes → Use read-write semaphore (rw_semaphore) or mutex (mutex)
│ └─ Read operations far more than write operations? → Yes → read-write semaphore
│ └─ Read operations not significantly more than write operations? → Mutex is simpler and has better performance

└─ No → Is the critical section very short (a few integer operations)?
├─ Yes → A normal spinlock (spinlock_t) may be better than a read-write spinlock
└─ No → Reads far more than writes? → Yes → read-write spinlock (rwlock_t)
└─ No → normal spinlock

Functions that may cause sleep cannot be used in interrupt contexts:

Because the interrupt context does not belong to any process, it has no process control block (task_struct), it fundamentally lacks the infrastructure required for ‘sleeping.’ When a process wants to enter a sleeping state, it actively marks itself as ‘sleeping,’ removes itself from the run queue, and tells the scheduler: ‘Please wake me up when the lock is available.’ This operation depends on the process context.

API

API Function DefinitionsFunction Description
DECLARE_RWSEM(name)Declare a read-write semaphore named name and initialize it.
void init_rwsem(struct rw_semaphore *sem);Initialize the read-write semaphore sem.
void down_read(struct rw_semaphore *sem);Used by readers to acquire sem; if not acquired, the caller sleeps and waits.
void up_read(struct rw_semaphore *sem);Readers release sem.
int down_read_trylock(struct rw_semaphore *sem);Readers attempt to acquire sem; return 1 if acquired, 0 if not. Can be used in interrupt context.
void down_write(struct rw_semaphore *sem);Used by writers to acquire sem; if not acquired, the caller sleeps and waits.
int down_write_trylock(struct rw_semaphore *sem);Writers attempt to acquire sem; return 1 if acquired, 0 if not. Can be used in interrupt context.
void up_write(struct rw_semaphore *sem);Writers release sem.
void downgrade_write(struct rw_semaphore *sem);Downgrade a writer to a reader.

Example

Initialization:

1
2
3
4
5
6
7
8
#include <linux/rwsem.h>

/* Static declaration and initialization */
DECLARE_RWSEM(my_rwsem);

/* Dynamic initialization (struct member) */
struct rw_semaphore my_rwsem;
init_rwsem(&my_rwsem);

Using read-write lock:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/* Reader: read configuration and copy to user space */
ssize_t my_read(struct file *filp, char __user *buf, size_t size, loff_t *off)
{
int ret;

down_read(&my_rwsem); /* Acquire read lock, can sleep */
ret = copy_to_user(buf, my_data, size); /* Allowed, because holding a semaphore that may sleep */
up_read(&my_rwsem); /* Release read lock */

return ret;
}

/* Writer: update configuration */
ssize_t my_write(struct file *filp, const char __user *buf, size_t size, loff_t *off)
{
down_write(&my_rwsem); /* Acquire write lock, exclusive */
copy_from_user(my_data, buf, size); /* Sleep allowed */
up_write(&my_rwsem); /* Release write lock */

return size;
}

Mutex lock

Only one visitor can access the same resource at the same time, other visitors can access this resource only after it finishes. This is mutual exclusion.

Mutex lock is very similar to the case where the semaphore is 1, but mutexes are more concise and efficient. However, there are more things to be aware of.

Definition

include/linux/mutex.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct mutex {
atomic_long_t owner;
spinlock_t wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
struct list_head wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};

API

Function/MacroReturn TypeDescription
DEFINE_MUTEX(name)struct mutexStatically define and initialize a mutex, initial state is unlocked
mutex_init(struct mutex *lock)voidInitialize a mutex to unlocked state
mutex_destroy(struct mutex *lock)voidDestroy a mutex (only works under DEBUG_MUTEXES)
mutex_lock(struct mutex *lock)voidAcquire the mutex, block if locked until available
mutex_lock_interruptible(struct mutex *lock)intAcquire the mutex, block if locked, interruptible by signals; returns 0 on success, -EINTR if interrupted
mutex_lock_killable(struct mutex *lock)intAcquire the mutex, block if locked, interruptible by fatal signals; return value same as above
mutex_lock_io(struct mutex *lock)voidAcquire the mutex, allowed in I/O context (uncommon)
mutex_lock_nested(struct mutex *lock, unsigned int subclass)voidNested lock acquisition for Lockdep analysis, effectively equivalent tomutex_lock()
mutex_lock_interruptible_nested(struct mutex *lock, unsigned int subclass)intInterruptible nested lock acquisition for Lockdep analysis
mutex_lock_killable_nested(struct mutex *lock, unsigned int subclass)intFatal-signal-interruptible nested lock acquisition for Lockdep analysis
mutex_lock_nest_lock(struct mutex *lock, struct lockdep_map *nest_lock)voidNested lock acquisition, used for Lockdep analysis
mutex_trylock(struct mutex *lock)intTry to acquire mutex, non-blocking, returns 1 on success, 0 on failure
mutex_trylock_recursive(struct mutex *lock)enum mutex_trylock_recursive_enumTry to acquire mutex, allows recursion, returns 0/1/recursion flag
mutex_unlock(struct mutex *lock)voidRelease mutex, must be called by the thread holding the lock
atomic_dec_and_mutex_lock(atomic_t *cnt, struct mutex *lock)intDecrement atomic counter by 1, acquire mutex if it reaches 0; otherwise return 0
mutex_is_locked(struct mutex *lock)boolQuery whether the mutex is held, true if locked, false if not

Similar to the interruptible series of functions for wait queues, it is recommended to usemutex_lock_interruptible(), which allows the driver to be interrupted by all signals, while formutex_lock_killable(), only signals that kill the process can interrupt the driver.

Callmutex_lock()with great caution; it should only be used when it is guaranteed that the mutex will be released under all circumstances. In user context, it is recommended to always usemutex_lock_interruptible()to acquire the mutex, becausemutex_lock()will not return even if a signal (such as Ctrl+C) is received.

Example

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
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/cdev.h>
#include <linux/mutex.h>
#include <linux/sched.h>

struct test_drv_data {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
};

static struct test_drv_data *drv_dat;
static struct mutex drv_mutex;

int mutex_test_open(struct inode *inode, struct file *file)
{
int err;
file->private_data = drv_dat;
err = mutex_lock_interruptible(&drv_mutex);
if (err != 0) {
pr_err("pid: %d is interrupted", current->pid);
return err;
}
pr_info("mutex_test_open is called by pid: %d\n", current->pid);
return 0;
}

int mutex_test_release(struct inode *inode, struct file *file)
{
mutex_unlock(&drv_mutex);
pr_info("mutex_test_release is called by pid: %d\n", current->pid);
return 0;
}

static struct file_operations fops = {
.owner = THIS_MODULE,
.open = mutex_test_open,
.release = mutex_test_release,
};

static int __init mutex_test_init(void)
{
int err;
drv_dat = (struct test_drv_data *)kzalloc(sizeof(struct test_drv_data), GFP_KERNEL);

if (drv_dat == NULL) {
err = -ENOMEM;
goto kzalloc_fail;
}

err = alloc_chrdev_region(&drv_dat->dev_num, 0, 1, "mutex_test_chrdev_region");
if (err < 0)
goto alloc_chrdev_region_fail;

cdev_init(&drv_dat->cdev, &fops);
drv_dat->cdev.owner = THIS_MODULE;
err = cdev_add(&drv_dat->cdev, drv_dat->dev_num, 1);
if (err < 0)
goto cdev_add_fail;

drv_dat->class = class_create(THIS_MODULE, "chrdev_test");
if (IS_ERR(drv_dat->class)) {
err = PTR_ERR(drv_dat->class);
goto class_create_fail;
}

drv_dat->dev =
device_create(drv_dat->class, NULL, drv_dat->dev_num, NULL, "mutex_test%d", 0);
if (IS_ERR(drv_dat->dev)) {
err = PTR_ERR(drv_dat->dev);
goto device_create_fail;
}
// Initialize mutex
mutex_init(&drv_mutex);
return 0;
device_create_fail:
class_destroy(drv_dat->class);
class_create_fail:
cdev_del(&drv_dat->cdev);
cdev_add_fail:
unregister_chrdev_region(drv_dat->dev_num, 1);
alloc_chrdev_region_fail:
kfree(drv_dat);
kzalloc_fail:
return err;
}

static void __exit mutex_test_exit(void)
{
kfree(drv_dat);
}

module_init(mutex_test_init);
module_exit(mutex_test_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@163.com>");
MODULE_DESCRIPTION("This is a test sample for mutex");

Test case:

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
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
pid_t pid;
int fd;
char prefix[16];

pid = fork();
if (pid < 0) {
perror("fork error");
exit(EXIT_FAILURE);
} else if (pid == 0) {
snprintf(prefix, sizeof(prefix), "[pid: %d]", getpid());

fd = open("/dev/mutex_test0", O_RDWR);
if (fd < 0) {
perror(prefix);
exit(EXIT_FAILURE);
}
printf("%s child open success\n", prefix);
sleep(2);
close(fd);

} else {
snprintf(prefix, sizeof(prefix), "[pid: %d]", getpid());

fd = open("/dev/mutex_test0", O_RDWR);
if (fd < 0) {
perror(prefix);
exit(EXIT_FAILURE);
}
printf("%s parent open success\n", prefix);
sleep(2);
close(fd);
}

return 0;
}
1
2
3
4
5
6
7
8
9
$ insmod mutex_test.ko
[ 13.589343] mutex_test: loading out-of-tree module taints kernel.
$ ./test_mutex.o
[ 17.141253] mutex_test_open is called by pid: 101
[pid: 101] parent open success
[ 19.150144] mutex_test_release is called by pid: 101
[ 19.151379] mutex_test_open is called by pid: 102
[pid: 102] child open success
[ 21.156199] mutex_test_release is called by pid: 102

Precautions

  1. Mutex locks cause sleep, somutex locks cannot be used in interrupts
  2. Only one thread can hold a mutex lock at a time, and only the holder can unlock it
  3. Recursive locking and unlocking are not allowed

RCU

RCURead-Copy-Update, read-copy-update) as a high-performance concurrency control mechanism, specifically designed to solve the “read-heavy, write-light” scenario. By separating the synchronization logic of read and write operations, it makesread operations almost overhead-free(no locking, no atomic instructions, no blocking), while write operations ensure safety through “copy-on-update” and “deferred release”.

RCU has become an indispensable basic component in the Linux kernel, widely used in core modules such as process scheduling, memory management, and file systems.

Applicable scenarios of RCUIt mainly has the following characteristics:

  1. Data structureMainly accessed through pointers;
  2. Read operations far outnumber update operations;
  3. Read-side code cannot tolerate the overhead of locks;
  4. Update operations are relatively infrequent;

The core idea of RCU can be summarized as**“Read directly, copy and modify when writing”**

Consider a real-life scenario: There is a bulletin board at home that records the family members’ schedules. When the mother needs to update the schedule, she does not prevent others from viewing the bulletin board. Instead, she first copies the current content onto a new piece of paper, makes modifications on the new paper, and then, at a moment when no one will notice, replaces the old paper on the bulletin board with the new one. After the replacement, she does not immediately destroy the old paper but waits for a period of time to ensure that everyone who might be looking at the old bulletin board has finished reading before discarding the old paper.

In this analogy,"the moment of replacement"corresponds to thePublishingoperation,"waiting to ensure"corresponds toGrace Period, while**"Discard Old Paper"corresponds toReclamation**. These three concepts form the core triangle of RCU

terminology

  • Read-Side Critical Section: The code segment where read operations access RCU-protected data must be marked withrcu_read_lock()andrcu_read_unlock(). Within this interval, read operations can safely access old data (even if write operations have updated the data).

  • Grace Period: The time period from when a write operation begins updating data until all read-side critical sections (for old data) have ended. The end of the grace period means “all read operations that might have accessed old data have completed”, at which point the old data can be safely freed.

  • Write-Side Operation: Consists of three steps:

    1. Copy: Create a copy of the data (or new data).
    2. Update: Atomically switch the pointer from old data to new data.
    3. Deferred Free: Wait for the grace period to end, then free the old data.

How RCU Works

Read-Side

Lock-free access in read-side operations is the core source of RCU’s performance advantage. The process is as follows:

  1. Viarcu_read_lock()enter the read-side critical section (essentially disabling preemption, or tracking read-side state in Preemptible RCU).
  2. Usercu_dereference(p)to obtain a pointer to the shared data (includes memory barriers to ensure atomicity and visibility of the pointer read).
  3. Access the data pointed to by the pointer (read directly without locking).
  4. Throughrcu_read_unlock()exit the read-side critical section.
1
2
3
4
5
rcu_read_lock();
/* Read the protected data */
data = rcu_dereference(global_pointer);
/* Use the data */
rcu_read_unlock();

Here,rcu_read_lock()andrcu_read_unlock()do not perform actual locking and unlocking operations like traditional locks. In non-preemptive kernels, they may be complete no-ops. Their real purpose is totell the compiler not to reorder instructions, and to mark the beginning and end of the critical section in preemptive kernels

rcu_dereference()is a critical macro that ensures read operations proceed in the expected order on processors with weak memory ordering, such as Alpha. It can be seen as a wrapper for memory barriers, ensuring that the pointer read occurs before fetching the actual data

The key characteristic of the read side iszero overhead- no atomic operations, no memory barriers (on most architectures), no lock contention. This makes the RCU read path extremely efficient, maintaining stable performance even under high concurrency read scenarios.

Write-Side

Write-side operations must ensure that ‘old data is freed only after all read operations complete,’ with the following process:

  1. Copy/create new data: For example, allocate new memory for a linked list node and initialize it.
  2. Atomically update the pointer: Usercu_assign_pointer(p, new_ptr)to switch the shared pointer from old data to new data (includes memory barriers to ensure the new pointer is visible to all CPUs).
  3. Wait for grace period: Viasynchronize_rcu()(blocking wait) orcall_rcu()(asynchronous callback) to wait for the grace period to end.
  4. Release old data: After the grace period ends, safely release the old data (e.g.,kfree(old_ptr))。

Example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Writer-side operation: update global_ptr
struct data *new_data = kmalloc(sizeof(*new_data), GFP_KERNEL);
if (!new_data)
return -ENOMEM;
// Initialize new data...
new_data->value = new_value;

// Publish operation, atomically update the pointer, visible to readers
rcu_assign_pointer(global_ptr, new_data);

// Wait for grace period to end (all read operations on old data have completed)
synchronize_rcu();

// Release old data
kfree(old_data);

Grace Period

How to ensure safe release?

The grace period is the core mechanism of RCU, and its role isto ensure that all read-side critical sections that started before the pointer update have ended. How does RCU detect the end of a grace period?

  • Core idea: Each CPU maintains an “RCU state” (e.g.,rcu_seq), recording whether there is an active read-side critical section. When a write operation requests a grace period, RCU waits for all CPUs to undergo a “context switch” (e.g., scheduling, interrupt return, etc.) — this means the read-side critical section on that CPU has ended (because read-side critical sections disable preemption and cannot be interrupted by scheduling; a context switch indicates the read-side has ended).
  • Implementation details: The Linux kernel usesrcu_nodea tree structure to track the RCU state of each CPU,synchronize_rcu()and blocks until all CPUs have completed a “grace period acknowledgment” (rcu_qs())。

The grace period may last up to hundreds of milliseconds (depending on system load); the write side should not assume it ends quickly, avoiding logic that depends on the grace period duration.

The Linux kernel usescontext switchesuser-mode executionandidle loopserve as markers for exiting read-side critical sections. After a CPU passes through one of these states, the kernel considers all RCU read-side critical sections on that CPU to have completed

The implementation of RCU heavily relies onmemory barriersto ensure sequential consistency in multi-core environments, preventing compilers and CPUs from reordering instructions for optimization

  • On the read side,rcu_dereference()includes a read memory barrier, ensuring that operations after fetching the pointer are not reordered before the pointer fetch.

  • On the update side,rcu_assign_pointer()includes a write memory barrier, ensuring that the pointer update itself is not visible to other CPUs until all store operations before the pointer update are visible to them

This ordering guarantee is crucial for RCU correctness. Consider the data publication scenario: the updater must first initialize new data, then publish the pointer. Without memory barriers, the CPU or compiler might reorder these two steps, causing readers to see incompletely initialized data

Grace periods extend to contain pre-existing RCU read-side critical sections.
Grace periods extend to contain pre-existing RCU read-side critical sections.

RCU synchronization primitives

Read-Side

1
2
3
void rcu_read_lock(void);

void rcu_read_unlock(void);

The above two APIs are used to mark the beginning and end of read-side critical sections, ensuring that read operations within this interval can safely access old data.

  • In Classic RCU (non-preemptible RCU),rcu_read_lock()Disable preemption (preempt_disable()),rcu_read_unlock()Enable preemption (preempt_enable())。
  • In Preemptible RCU, byrcu_read_lock_bh()disabling softirqs, or usingrcu_read_lock()with kernel preemption count.

can be used torcu_dereferencedereference

1
typeof(p) rcu_dereference(p);

safely access RCU-protected pointers on the read side, ensuring:

  • Atomicity of pointer reads (preventing partial pointer loads due to compiler optimizations).
  • Memory barriers (ensuring new data is visible to the read side, avoiding CPU cache inconsistencies).

Example:

1
2
3
4
5
6
// Read-side operation: accessing RCU-protected pointers
rcu_read_lock();
struct data *d = rcu_dereference(global_ptr); // Safely obtain pointer
if (d)
printk("value: %d\n", d->value); // Access data
rcu_read_unlock();

Write-Side

rcu_assign_pointer

1
void rcu_assign_pointer(p, typeof(p) v);

Write-side atomic update of RCU-protected pointers, ensuring:

  • Atomicity of pointer updates.
  • Memory barrier (ensures new data is fully initialized before being visible to readers).

synchronize_rcu()

  • Function: Blocks until the grace period ends; after return, old data can be safely freed.

  • Note: Cannot be used in atomic context (e.g., interrupt handlers) as it would cause scheduling failure.

  • Example

    1
    2
    3
    4
    // Deleting an RCU-protected linked list node
    list_del_rcu(node); // Remove node from linked list (RCU-safe version)
    synchronize_rcu(); // Wait for grace period to end
    kfree(node); // Free old node

synchronize_rcu()will block waiting for the grace period; atomic context (e.g., interrupts, softirqs) cannot schedule, causing kernel crash. In such cases, usecall_rcu()

call_rcu()

  • Function: Asynchronously waits for the grace period; after it ends, calls the specified callback to free old data (non-blocking).

  • Prototypevoid call_rcu(struct rcu_head *head, void (*func)(struct rcu_head *head));

  • Example

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // Define callback function (free data)
    void free_data(struct rcu_head *head) {
    struct data *d = container_of(head, struct data, rcu);
    kfree(d);
    }

    // Write-side operation: update data and asynchronously free old data
    struct data *new_data = kmalloc(...);
    struct data *old_data = rcu_dereference_protected(global_ptr, ...);
    rcu_assign_pointer(global_ptr, new_data); // Update pointer
    call_rcu(&old_data->rcu, free_data); // Call free_data after grace period
  • Read side must usercu_dereference(): Direct pointer access is prohibited (e.g.,d = global_ptr), otherwise data inconsistency may occur due to compiler optimization or CPU reordering.
  • Write side must usercu_assign_pointer(): Direct assignment is prohibited (e.g.,global_ptr = new_d), ensuring the new pointer is visible to all CPUs.

RCU usage example

RCU-protected pointer update

Scenario: global pointerglobal_datapoints to a struct, read operations access frequently, write operations update occasionally.

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 <linux/rcupdate.h>
#include <linux/sched.h>

// Define shared data structure (including RCU header for call_rcu)
struct my_data {
int value;
struct rcu_head rcu; // Must be included for call_rcu callback
};

// RCU-protected global pointer
static struct my_data __rcu *global_data;

// Reader-side function: access data
void read_data(void) {
struct my_data *d;

rcu_read_lock();
d = rcu_dereference(global_data); // Safely obtain pointer
if (d)
printk("Read value: %d\n", d->value);
rcu_read_unlock();
}

// Writer-side function: update data (asynchronously free using call_rcu)
void update_data(int new_val) {
struct my_data *new_d, *old_d;

new_d = kmalloc(sizeof(*new_d), GFP_KERNEL);
if (!new_d)
return;
new_d->value = new_val;

// Atomically update pointer (visible to reader side)
old_d = rcu_dereference_protected(global_data, 1); // Writer side safely obtains old pointer
rcu_assign_pointer(global_data, new_d);

// Free old data after grace period
if (old_d)
call_rcu(&old_d->rcu, (void (*)(struct rcu_head *))kfree);
}

RCU-protected linked list operations

RCU is commonly used to protect dynamic linked lists (e.g., process list, network connection table). The kernel provideslist_add_rcu()list_del_rcu()and other RCU-safe linked list operation macros.

Scenario: Maintain an RCU-protected doubly linked list, with reader side traversing the list and writer side adding/removing nodes.

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
#include <linux/list.h>
#include <linux/rculist.h> // RCU linked list macros

// Linked list node structure
struct my_node {
int id;
struct list_head list;
struct rcu_head rcu;
};

// RCU-protected list head
static LIST_HEAD(my_list);
static DEFINE_SPINLOCK(list_lock); // Write-side mutex (multiple writers need synchronization)

// Read-side: traverse the list (lock-free)
void traverse_list(void) {
struct my_node *node;

rcu_read_lock();
// list_for_each_entry_rcu: RCU-safe list traversal macro
list_for_each_entry_rcu(node, &my_list, list) {
printk("Node id: %d\n", node->id);
}
rcu_read_unlock();
}

// Write-side: add a node
void add_node(int id) {
struct my_node *node = kmalloc(sizeof(*node), GFP_KERNEL);
if (!node)
return;
node->id = id;
INIT_LIST_HEAD(&node->list);

// Write-side needs locking (to prevent multiple writers from modifying the list simultaneously)
spin_lock(&list_lock);
list_add_rcu(&node->list, &my_list); // RCU-safe addition
spin_unlock(&list_lock);
}

// Write-side: delete a node
void delete_node(int id) {
struct my_node *node, *tmp;

spin_lock(&list_lock);
list_for_each_entry_safe(node, tmp, &my_list, list) {
if (node->id == id) {
list_del_rcu(&node->list); // RCU-safe deletion
spin_unlock(&list_lock);
// Release the node after waiting for a grace period
call_rcu(&node->rcu, (void (*)(struct rcu_head *))kfree);
return;
}
}
spin_unlock(&list_lock);
}

RCU best practices

Minimize the length of read-side critical sections

The longer the read-side critical section, the longer the grace period may be (writers wait longer), causing delayed release of old data and increasing memory pressure.

Principle: Enter the critical section only when accessing RCU data, and avoid time-consuming operations (such as I/O, complex calculations) within the critical section.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Bad example: critical section contains time-consuming operations
rcu_read_lock();
d = rcu_dereference(global_data);
if (d) {
heavy_computation(); // Time-consuming operations should not be inside the critical section
printk("%d\n", d->value);
}
rcu_read_unlock();

// Good example: narrow the critical section
rcu_read_lock();
d = rcu_dereference(global_data);
if (d)
value = d->value; // Only read necessary data
rcu_read_unlock();
heavy_computation(); // Execute outside the critical section
printk("%d\n", value);

RCU variants

The Linux kernel provides multiple RCU variants for different scenarios:

  • Classic RCU(CONFIG_RCU_FANOUT): default variant, does not support read-side preemption, suitable for non-real-time kernels.
  • Preemptible RCU(CONFIG_PREEMPT_RCU): supports read-side preemption, read-side critical sections can be scheduled, suitable for real-time kernels.
  • Sleepable RCU(SRCU,CONFIG_SRCU): read side can block (e.g., callingmsleep()), must usesrcu_read_lock()/srcu_read_unlock(), suitable for scenarios where the read side needs to sleep.

PER_CPU

Example

net/ipv6/af_inet6.c

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
static int __net_init ipv6_init_mibs(struct net *net)
{
int i;

net->mib.udp_stats_in6 = alloc_percpu(struct udp_mib);
if (!net->mib.udp_stats_in6)
return -ENOMEM;
net->mib.udplite_stats_in6 = alloc_percpu(struct udp_mib);
if (!net->mib.udplite_stats_in6)
goto err_udplite_mib;
net->mib.ipv6_statistics = alloc_percpu(struct ipstats_mib);
if (!net->mib.ipv6_statistics)
goto err_ip_mib;

for_each_possible_cpu(i) {
struct ipstats_mib *af_inet6_stats;
af_inet6_stats = per_cpu_ptr(net->mib.ipv6_statistics, i);
u64_stats_init(&af_inet6_stats->syncp);
}


net->mib.icmpv6_statistics = alloc_percpu(struct icmpv6_mib);
if (!net->mib.icmpv6_statistics)
goto err_icmp_mib;
net->mib.icmpv6msg_statistics = kzalloc(sizeof(struct icmpv6msg_mib),
GFP_KERNEL);
if (!net->mib.icmpv6msg_statistics)
goto err_icmpmsg_mib;
return 0;

err_icmpmsg_mib:
free_percpu(net->mib.icmpv6_statistics);
err_icmp_mib:
free_percpu(net->mib.ipv6_statistics);
err_ip_mib:
free_percpu(net->mib.udplite_stats_in6);
err_udplite_mib:
free_percpu(net->mib.udp_stats_in6);
return -ENOMEM;
}

struct netinstruct netns_mib mibmember is defined as follows:

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
struct netns_mib {
DEFINE_SNMP_STAT(struct tcp_mib, tcp_statistics);
DEFINE_SNMP_STAT(struct ipstats_mib, ip_statistics);
DEFINE_SNMP_STAT(struct linux_mib, net_statistics);
DEFINE_SNMP_STAT(struct udp_mib, udp_statistics);
DEFINE_SNMP_STAT(struct udp_mib, udplite_statistics);
DEFINE_SNMP_STAT(struct icmp_mib, icmp_statistics);
DEFINE_SNMP_STAT_ATOMIC(struct icmpmsg_mib, icmpmsg_statistics);

#if IS_ENABLED(CONFIG_IPV6)
struct proc_dir_entry *proc_net_devsnmp6;
DEFINE_SNMP_STAT(struct udp_mib, udp_stats_in6);
DEFINE_SNMP_STAT(struct udp_mib, udplite_stats_in6);
DEFINE_SNMP_STAT(struct ipstats_mib, ipv6_statistics);
DEFINE_SNMP_STAT(struct icmpv6_mib, icmpv6_statistics);
DEFINE_SNMP_STAT_ATOMIC(struct icmpv6msg_mib, icmpv6msg_statistics);
#endif
#ifdef CONFIG_XFRM_STATISTICS
DEFINE_SNMP_STAT(struct linux_xfrm_mib, xfrm_statistics);
#endif
#if IS_ENABLED(CONFIG_TLS)
DEFINE_SNMP_STAT(struct linux_tls_mib, tls_statistics);
#endif
#ifdef CONFIG_MPTCP
DEFINE_SNMP_STAT(struct mptcp_mib, mptcp_statistics);
#endif
};

And macroDEFINE_SNMP_STATDefined as follows:

1
2
#define DEFINE_SNMP_STAT(type, name)	\
__typeof__(type) __percpu *name

References