Linux Device Driver Tutorial Part 30 – Atomic variable in Linux Device Driver

This is the Series on Linux Device Driver. The aim of this series is to provide easy and practical examples that anyone can understand. This is the Linux Device Driver Tutorial Part 30 – atomic variables (atomic operations) in Linux Device Driver.

atomic variables in Linux Device Driver

Prerequisites

In the below mentioned posts, we are using spinlock and mutex for synchronization. I would recommend you to explore that by using below link.

Introduction

Before looking into atomic variables, we will see one example.

I have one integer or long variable named etx_global_variable which is shared between two threads. Two threads are just incrementing the variable like below.

Thread 1:

Thread 2:

Now we will see how it is incrementing internally in each instruction when both threads are running concurrently. Assume the initial value of etx_global_variable is 0.

Thread 1Thread 2
get the value of etx_global_variable from memory (0)get the value of etx_global_variable from memory (0)
increment etx_global_variable (0 -> 1)
increment etx_global_variable (0 -> 1)
write back the etx_global_variable value to memory (1)
write back the etx_global_variable value to memory (1)

Now the value of etx_global_variable is 1 after the two threads are processed. Are we expecting the same value which is 1? Nope. We are expecting the value of etx_global_variable should be 2. Its not running as expected because global variable is sharing between two concurrent threads. So we need to implement synchronization, because both the threads are accessing (writing/reading) the variable. We can implement synchronization like below using locks.

Thread 1:

Thread 2:

After this synchronization, we will see how it is incrementing internally when both threads are running concurrently. Assume the initial value of etx_global_variable is 0.

Thread 1Thread 2
lock()
get the value of etx_global_variable from memory (0)lock() (it will stuck here because the lock is already taken by thread 1)
increment etx_global_variable (0 -> 1)
write back the etx_global_variable value to memory (1)
unlock()
get the value of etx_global_variable from memory (1)
increment etx_global_variable (1 -> 2)
write back the etx_global_variable value to memory (2)
unlock()

This will be the one possibility to run. Another possibility is mentioned below.

Thread 1Thread 2
lock()
lock() (it will stuck here because the lock is already taken by thread 2)get the value of etx_global_variable from memory (0)
increment etx_global_variable (0 -> 1)
write back the etx_global_variable value to memory (1)
unlock()
get the value of etx_global_variable from memory (1)
increment etx_global_variable (1 -> 2)
write back the etx_global_variable value to memory (2)
unlock()

Great. That’s all. Now we are getting 2 in the two methods mentioned above. But have anyone thought anytime that, why this things are required for single variable? Why don’t we have alternate method for single variable? Yes, obviously we have alternate mechanism for integer and long variables. That is atomic operation. If you use mutex/spinlock for just single variable, it will add overhead. In this tutorial we gonna see that atomic variable, atomic operation and its usage.

atomic variables

The read, write and arithmetic operations on the atomic variables will be done in one instruction without interrupt.

So again we will take the same example mentioned above to explain the atomic variable operations. When we use atomic method, that will work like below.

Thread 1Thread 2
get, increment and store etx_global_variable (0 -> 1)
get, increment and store etx_global_variable (1 -> 2)

and another possibility will be,

Thread 1Thread 2
get, increment and store etx_global_variable (0 -> 1)
get, increment and store etx_global_variable (1 -> 2)

So extra locking mechanism is not required when we are using atomic variables, since operation is happening in one machine instruction.

An atomic_t holds an int value and atomic64_t holds the long value on all supported architectures.

In Linux Kernel Version 2.6, atomic variable has defined like below.

Then later, they have removed volatile and defined like below.

You can read here why they have removed volatile.

Types of atomic variables

Two different atomic variables are there.

  • Atomic variables who operates on Integers
  • Atomic variables who operates on Individual Bits

Atomic Integer Operations

When we are doing atomic operations, that variable should be created using atomic_t or atomic64_t. So we have separate special functions for reading, writing and arithmetic operations, and those are explained below.

The declarations are needed to use the atomic integer operations are in <asm/atomic.h>. Some architectures provide additional methods that are unique to that architecture, but all architectures provide at least a minimum set of operations that are used throughout the kernel. When you write kernel code, you can ensure that these operations are correctly implemented on all architectures.

Creating atomic variables

Reading atomic variables

atomic_read

This function atomically reads the value given atomic variable.

int atomic_read(atomic_t *v);

where,

v – pointer of type atomic_t

Return : It returns the integer value.

Other operations on atomic variables

atomic_set

This function atomically sets the value to atomic variable.

void atomic_set(atomic_t *v, int i);

where,
v – pointer of type atomic_t
i – the value to be set to v

atomic_add

This function atomically adds the value to atomic variable.

void atomic_add(int i, atomic_t *v);

where,
i – the value to be added to v
v – pointer of type atomic_t

atomic_sub

This function atomically subtract the value from atomic variable.

void atomic_sub(int i, atomic_t *v);

where,
i – the value to be subtract from v
v – pointer of type atomic_t

atomic_inc

This function atomically increments the value of the atomic variable by 1.

void atomic_inc (atomic_t *v);

where,
v – pointer of type atomic_t

atomic_dec

This function atomically decrements the value of the atomic variable by 1.

void atomic_dec (atomic_t *v);

where,
v – pointer of type atomic_t

atomic_sub_and_test

This function atomically subtract the value from atomic variable and test the result is zero or not.

void atomic_sub_and_test(int i, atomic_t *v);

where,
i – the value to be subtract from v
v – pointer of type atomic_t

Return : It returns true if the result is zero, or false for all other cases.

atomic_dec_and_test

This function atomically decrements the value of the atomic variable by 1 and test the result is zero or not.

void atomic_dec_and_test(atomic_t *v);

where,
v – pointer of type atomic_t

Return : It returns true if the result is zero, or false for all other cases.

atomic_inc_and_test

This function atomically increments the value of the atomic variable by 1 and test the result is zero or not.

void atomic_inc_and_test(atomic_t *v);

where,
v – pointer of type atomic_t

Return : It returns true if the result is zero, or false for all other cases.

atomic_add_negative

This function atomically adds the value to atomic variable and test the result is negative or not.

void atomic_add_negative(int i, atomic_t *v);

where,
i – the value to be added to v
v – pointer of type atomic_t

Return : It returns true if the result is negative, or false for all other cases.

atomic_add_return

This function atomically adds the value to atomic variable and return the value.

void atomic_add_return(int i, atomic_t *v);

where,
i – the value to be added to v
v – pointer of type atomic_t

Return : It returns true if the result the value (i + v).

Like this other functions also there. Those are,

FunctionDescription
int atomic_sub_return(int i, atomic_t *v)Atomically subtract i from v and return the result
int atomic_inc_return(int i, atomic_t *v)Atomically increments v by one and return the result
int atomic_dec_return(int i, atomic_t *v)Atomically decrements v by one and return the result
atomic_add_unless

This function atomically adds the value to atomic variable unless the number is a given value.

atomic_add_unless (atomic_t *v, int a, int u);

where,
v – pointer of type atomic_t

a – the amount to add to v…

u – …unless v is equal to u.

Return : It returns non-zero if v was not u, and zero otherwise.

There is 64 bit version also available. Unlike atomic_t, that will be operates on 64 bits. This 64 bit version also have similar function like above, the only change is we have to use 64.

Example

But all the operations are same as atomic_t.

Atomic Bitwise Operations

Atomic_t is good when we are working on integer arithmetic. But when it is comes to bitwise atomic operation, it doesn’t work well. So kernel offers separate functions to achieve that. Atomic bit operations are very fast. The functions are architecture dependent and are declared in <asm/bitops.h>.

These bitwise functions operates on generic pointer. So atomic_t / atomic64_t is not required. So we can work with a pointer to whatever data we want.

The below functions are available for atomic bit operations.

FunctionDescription
void set_bit(int nr, void *addr)Atomically set the nr-th bit starting from addr
void clear_bit(int nr, void *addr)Atomically clear the nr-th bit starting from addr
void change_bit(int nr, void *addr)Atomically flip the value of the nr-th bit starting from addr
int test_and_set_bit(int nr, void *addr)Atomically set the nr-th bit starting from addr and return the previous value
int test_and_clear_bit(int nr, void *addr)Atomically clear the nr-th bit starting from addr and return the previous value
int test_and_change_bit(int nr, void *addr)Atomically flip the nr-th bit starting from addr and return the previous value
int test_bit(int nr, void *addr)Atomically return the value of the nr-th bit starting from addr
int find_first_zero_bit(unsigned long *addr, unsigned int size)Atomically returns the bit-number of the first zero bit, not the number of the byte containing a bit
int find_first_bit(unsigned long *addr, unsigned int size)Atomically returns the bit-number of the first set bit, not the number of the byte containing a bit

And also non-atomic bit operations also available. What is the use of that when we have atomic bit operations? When we have code which is already locked by mutex/spinlock then we can go for this non-atomic version. This might be faster in that case. The below functions are available for non-atomic bit operations.

FunctionDescription
void _set_bit(int nr, void *addr)Non-atomically set the nr-th bit starting from addr
void _clear_bit(int nr, void *addr) Non-atomically clear the nr-th bit starting from addr
void _change_bit(int nr, void *addr)Non-atomically flip the value of the nr-th bit starting from addr
int _test_and_set_bit(int nr, void *addr)Non-atomically set the nr-th bit starting from addr and return the previous value
int _test_and_clear_bit(int nr, void *addr)Non-atomically clear the nr-th bit starting from addr and return the previous value
int _test_and_change_bit(int nr, void *addr)Non-atomically flip the nr-th bit starting from addr and return the previous value
int _test_bit(int nr, void *addr)Non-atomically return the value of the nr-th bit starting from addr

Example Programming

In this program, we have two threads called thread_function1 and thread_function2. Both will be accessing the atomic variables.

Driver Source Code

MakeFile

 

%d bloggers like this: