working of the fuction atomic_add_return

Atomic instructions are useful for executing instructions with out interrupt. Theory of the same is given in the post. Let us look into the Linux kernel, and see how atomic functions are implemented in the kernel. The atomic variables are declared as type atomic_t which is nothing but a structure defined in “include/Linux/types.h” as given below. Atomic structure :

typedef struct { int counter; } atomic_t;

File: include/Linux/types.h The value of the atomic variable is updated in the counter member of the structure. Atomic functions are written in different ways for different architectures. In this post We will be looking at atomic functions for x86 architecture. One of the atomic functions defined in atomic.h is atomic_add_return. The function is defined as follows.

static inline int atomic_add_return(int i, atomic_t *v) { #ifdef CONFIG_M386 int __i; unsigned long flags; if (unlikely(boot_cpu_data.x86 counter, i); #ifdef CONFIG_M386 no_xadd: /* Legacy 386 processor */ raw_local_irq_save(flags); __i = atomic_read(v); atomic_set(v, i + __i); raw_local_irq_restore(flags); return i + __i; #endif }

The first line “#ifdef CONFIG_M386” is to check which CPU is the kernel being executed on. This is because in modern x86 CPU there are special instructions available that can be used for atomic operations.

If the architecture is 386, then we check if the instruction xadd is available in the CPU. If xadd is not available then we have to implement the atomic operation manually and we ump to the label no_xadd.

If xadd instruction is available or if the CPU is a modern 486 or above CPU, which always has the xadd instruction, then we make use of the instruction and pass the numbers for addition to xadd. Note that the first argument is the atomic number and the second is the number to be added to the atomic number.

If the system has i486 processor it will be defined in file: include/generated/autoconf.h as #define CONFIG_M686 1

The function xadd is implemented in the file arch/x86/include/asm/cmpxchg.h

/* * xadd() adds “inc” to “*ptr” and atomically returns the previous * value of “*ptr”. * * xadd() is locked when multiple CPUs are online * xadd_sync() is always locked * xadd_local() is never locked */ #define __xadd(ptr, inc, lock) __xchg_op((ptr), (inc), xadd, lock) #define xadd(ptr, inc) __xadd((ptr), (inc), LOCK_PREFIX)

From the comment above we note that xadd adds the second argument to the first argument and returns the previous value of the first argument. But note that the value of the first argument is updated to the new value.

xadd has been #defined to __xadd, which in turn has been #defined to __xchg_op. To the function __xcgh_op we pass the two numbers to be added, the instruction “xadd” and the keyword lock to signify that the instruction has to be executed in atomic mode.

The __xchg_op is implemented in the same file as

/* * An exchange-type operation, which takes a value and a pointer, and * returns a the old value. */ #define __xchg_op(ptr, arg, op, lock) ({ __typeof__ (*(ptr)) __ret = (arg); switch (sizeof(*(ptr))) { case __X86_CASE_B: asm volatile (lock #op “b %b0, %1\n” : “+q” (__ret), “+m” (*(ptr)) : : “memory”, “cc”); break; case __X86_CASE_W: asm volatile (lock #op “w %w0, %1\n” : “+r” (__ret), “+m” (*(ptr)) : : “memory”, “cc”); break; case __X86_CASE_L: asm volatile (lock #op “l %0, %1\n” : “+r” (__ret), “+m” (*(ptr)) : : “memory”, “cc”); break; case __X86_CASE_Q: asm volatile (lock #op “q %q0, %1\n” : “+r” (__ret), “+m” (*(ptr)) : : “memory”, “cc”); break; default: __ ## op ## _wrong_size(); } __ret; })

The four arguments passed __xcgh_op are

ptr: Pointer to the first argument, for xadd this the atomic variable which needs to be updated. arg: The second argument and for xadd the value to be added to the atomic variable op: The instruction to be executed, in this case “xadd” lock: To signify that the instruction is to be executed atomically.

The four case statements implement the addition for four different sizes of data. Let us take the first case of addition of single byte.

case __X86_CASE_B: asm volatile (lock #op “b %b0, %1\n” : “+q” (__ret), “+m” (*(ptr)) : : “memory”, “cc”); break;

The above code implements the instruction using “asm” feature of gcc.

asm volatile: Instructs gcc not to do any optimizations to the code that follows.

lock: keyword to make the instruction execute atomically

op: In this case the “op”, the assembly instruction, will be equal to xadd

b %b0, %1: b signifies argument is byte sized and %0,%1 are the arguments to the instruction.

“+q” (__ret), “+m” (*(ptr)): Being the first set of variables after the “:” these are the output variables.

“+” signifies the variable is going to read as well as written

q signifies the value is going to be stored in a register

__ret: Is the first argument to the instructions

m: signifies the value will be stored in memory

*(ptr): Is the second argument to instruction.

There are no input arguments thus there are no arguments in the second set of : : .

The last set of arguments are clobbers which are.

memory: The instruction write to some memory and the values in registers should not be cached by gcc

cc: The instruction affects the condition codes.

The working of xadd in i486 is as below.

XADD dest,sorc Description: Temporary

Copyright 2017. All rights reserved.

Posted April 17, 2013 by Tux Think in category "Linux